diff --git a/.claude/settings.json b/.claude/settings.json index 34af6a102..ae2a3966b 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -85,8 +85,8 @@ "Bash(pnpm -w run lint:*)", "Bash(pnpm -w run lint:fix)", "Bash(pnpm lint:*)", - "Bash(pnpm biome lint:*)", - "Bash(biome lint:*)", + "Bash(pnpm oxlint:*)", + "Bash(oxlint:*)", "Bash(pnpx wrangler:*)", "Bash(pnpx wrangler versions upload:*)", "Bash(pnpx wrangler delete:*)", diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 36248fb0c..50b895602 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -5,7 +5,11 @@ "sortPackageJson": false, "ignorePatterns": [ "apps/cli-docs/**", - "packages/cli/**", + "packages/cli/plugins/**", + "packages/cli/README.md", + "packages/cli/DEVELOPMENT.md", + "packages/cli/CHANGELOG.md", + "packages/cli/AGENTS.md", "packages/mcp-server-mocks/src/fixtures/**", "**/worker-configuration.d.ts", "**/tsconfig*.json" diff --git a/.oxlintrc.json b/.oxlintrc.json index ca44a1eda..a1c2a6d58 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,12 +1,12 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": ["eslint", "typescript", "unicorn", "oxc", "jsx-a11y"], + "jsPlugins": ["./packages/cli/lint-rules/cli-oxlint-plugin.js"], "categories": { "correctness": "error" }, "ignorePatterns": [ "apps/cli-docs/**", - "packages/cli/**", "**/worker-configuration.d.ts", "**/tsconfig*.json", "packages/mcp-server-mocks/src/fixtures/**" @@ -21,6 +21,88 @@ "rules": { "jsx-a11y/no-static-element-interactions": "off" } + }, + { + "files": [ + "packages/cli/src/**/*.{ts,tsx}", + "packages/cli/test/**/*.{ts,tsx}" + ], + "rules": { + "sentry-cli/no-silent-catch": "error", + "sentry-cli/no-stdout-write-in-commands": "error", + "sentry-cli/no-process-stdout-in-commands": "error", + "sentry-cli/no-stderr-write-in-commands": "error", + "sentry-cli/no-raw-metadata-queries": "error", + "sentry-cli/no-manual-transactions": "error", + "sentry-cli/no-inline-touch-cache": "error", + "sentry-cli/no-args-join-in-release": "error", + "sentry-cli/no-direct-target-resolution": "error", + "sentry-cli/no-generic-is-record": "error", + "sentry-cli/prefer-paginate-helper": "error", + "sentry-cli/no-namespace-import": "error", + "no-restricted-imports": [ + "error", + { + "paths": [ + { + "name": "@stricli/core", + "importNames": ["buildCommand", "buildRouteMap"], + "message": "Import the CLI command and route-map wrappers instead; they provide telemetry and standard aliases." + } + ] + } + ], + "no-bitwise": "error", + "max-params": ["error", 5], + "sentry-cli/no-skipped-tests": "error" + } + }, + { + "files": ["packages/cli/src/**/*.{ts,tsx}"], + "rules": { + "typescript/no-explicit-any": "error", + "typescript/no-non-null-assertion": "error" + } + }, + { + "files": [ + "packages/cli/src/lib/command.ts", + "packages/cli/src/lib/route-map.ts" + ], + "rules": { + "no-restricted-imports": "off" + } + }, + { + "files": ["packages/cli/src/commands/**/*.{ts,tsx}"], + "rules": { + "no-restricted-imports": [ + "error", + { + "paths": [ + { + "name": "@stricli/core", + "importNames": ["buildCommand", "buildRouteMap"], + "message": "Import the CLI command and route-map wrappers instead; they provide telemetry and standard aliases." + }, + { + "name": "chalk", + "message": "Use colorTag() from formatters/markdown.js so plain output works." + } + ] + } + ] + } + }, + { + "files": [ + "packages/cli/src/lib/build/zip-writer.ts", + "packages/cli/src/lib/build/index.ts", + "packages/cli/test/lib/build/zip-writer.test.ts" + ], + "rules": { + "no-bitwise": "off" + } } ] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index bdf5bcbe5..0ef1e247f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,4 +1,4 @@ { - "recommendations": ["oxc.oxc-vscode", "biomejs.biome"], + "recommendations": ["oxc.oxc-vscode"], "unwantedRecommendations": [] } diff --git a/apps/cli-docs/src/content/docs/contributing.md b/apps/cli-docs/src/content/docs/contributing.md index 00de8dc49..f73eca9b6 100644 --- a/apps/cli-docs/src/content/docs/contributing.md +++ b/apps/cli-docs/src/content/docs/contributing.md @@ -137,7 +137,7 @@ pnpm run test -- --coverage ## Code Style -The project uses [Ultracite](https://github.com/getsentry/ultracite) for linting and formatting: +The project uses Oxlint and Oxfmt for linting and formatting: ```bash # Check for issues diff --git a/docs/contributing/coding-guidelines.md b/docs/contributing/coding-guidelines.md index 498833fb6..2c21a8058 100644 --- a/docs/contributing/coding-guidelines.md +++ b/docs/contributing/coding-guidelines.md @@ -25,12 +25,11 @@ Essential patterns and standards for Sentry MCP development. - 2 spaces, double quotes, semicolons - Formatter width: 80 characters - Trailing commas in multiline -- MCP and root files use Oxfmt and Oxlint; CLI files still use their own Biome rules. -- CLI's custom Grit rules and inline suppressions remain active until their - equivalents are available in the new linter. -- VS Code uses Oxc for root and MCP files. Oxfmt ignores `packages/cli/**`; - use `pnpm --filter sentry exec biome format --write ` to format a CLI - file and keep CLI's Biome checks in the pre-commit hook. +- Root, MCP, and CLI files use Oxlint and Oxfmt. The CLI-specific rules live in + `packages/cli/lint-rules/cli-oxlint-plugin.js`, with inline Oxlint suppressions + for intentional exceptions. +- VS Code uses the Oxc extension for formatting and linting. Run + `pnpm exec oxfmt --write ` to format a file. ### Naming Conventions @@ -99,10 +98,10 @@ Key practices: Before committing: ```bash -pnpm -w run lint # Oxlint and ast-grep checks for MCP and root files +pnpm -w run lint # Oxlint and ast-grep checks for root, MCP, and CLI pnpm -w run lint:fix # Fix issues -pnpm -w run format # Format MCP and root files with Oxfmt -pnpm --filter sentry run lint # CLI Biome rules +pnpm -w run format # Format with Oxfmt +pnpm --filter sentry run lint # CLI-specific lint and format checks pnpm tsc --noEmit # Type check pnpm test # Run tests pnpm -w run build # Build all diff --git a/docs/specs/search-events.md b/docs/specs/search-events.md index 2020b3049..93201f494 100644 --- a/docs/specs/search-events.md +++ b/docs/specs/search-events.md @@ -4,14 +4,14 @@ Natural-language event search is exposed as one tool per dataset: -| Tool | Dataset | Seer strategy | -| --- | --- | --- | -| `search_errors` | `errors` | `Errors` | -| `search_logs` | `logs` | `Logs` | -| `search_traces` | `spans` | `Traces` | -| `search_metrics` | `metrics` | `Metrics` | -| `search_profiles` | `profiles` | — | -| `search_replays` | `replays` | — | +| Tool | Dataset | Seer strategy | +| ----------------- | ---------- | ------------- | +| `search_errors` | `errors` | `Errors` | +| `search_logs` | `logs` | `Logs` | +| `search_traces` | `spans` | `Traces` | +| `search_metrics` | `metrics` | `Metrics` | +| `search_profiles` | `profiles` | — | +| `search_replays` | `replays` | — | All six share one handler (`tools/support/search-events/search.ts`). Each tool fixes its dataset, so the caller never chooses a `dataset` parameter and the @@ -39,12 +39,12 @@ direct MCP surface and is excluded from skill definitions. // search_errors / search_logs / search_traces / search_metrics / search_profiles interface DatasetSearchParams { organizationSlug: string; - query?: string; // Natural language (preferred) or Sentry search syntax + query?: string; // Natural language (preferred) or Sentry search syntax projectSlug?: string; fields?: string[]; sort?: string; - period?: string; // e.g. "24h", "7d" - limit?: number; // Default: 10, Max: 100 + period?: string; // e.g. "24h", "7d" + limit?: number; // Default: 10, Max: 100 includeExplanation?: boolean; regionUrl?: string; } @@ -57,24 +57,24 @@ interface DatasetSearchParams { ```typescript search_errors({ organizationSlug: "my-org", - query: "database timeouts in checkout flow from last hour" -}) + query: "database timeouts in checkout flow from last hour", +}); search_traces({ organizationSlug: "my-org", query: "API calls taking over 5 seconds", - projectSlug: "backend" -}) + projectSlug: "backend", +}); search_logs({ organizationSlug: "my-org", - query: "warning logs about memory usage" -}) + query: "warning logs about memory usage", +}); search_metrics({ organizationSlug: "my-org", - query: "p95 request duration by transaction this week" -}) + query: "p95 request duration by transaction this week", +}); ``` ## Architecture @@ -112,7 +112,7 @@ search_metrics({ The AI produces different query patterns based on the selected dataset: - **Spans dataset**: Focus on `span.op`, `span.description`, `span.duration`, `transaction`, supports timestamp filters -- **Errors dataset**: Focus on `message`, `level`, `error.type`, `error.handled`, supports timestamp filters +- **Errors dataset**: Focus on `message`, `level`, `error.type`, `error.handled`, supports timestamp filters - **Logs dataset**: Focus on `message`, `severity`, `severity_number`, **NO timestamp filters** (uses statsPeriod instead) - **Tracemetrics dataset**: Focus on `metric.name`, `metric.type`, `metric.unit`, `value`, and metric-aware aggregates like `p95(value,http.request.duration,distribution,millisecond)` @@ -167,21 +167,21 @@ Requests for a metric over time ("per hour", "per day", "trend", "over time") re find_errors({ organizationSlug: "sentry", filename: "checkout.js", - query: "is:unresolved" -}) + query: "is:unresolved", +}); // After search_errors({ organizationSlug: "sentry", - query: "unresolved errors in checkout.js" -}) + query: "unresolved errors in checkout.js", +}); ``` ## Implementation Status ### Completed Features -1. **Custom attributes API integration**: +1. **Custom attributes API integration**: - ✅ `/organizations/{org}/trace-items/attributes/` for spans/logs/metrics with parallel string/number fetching - ✅ `/organizations/{org}/tags/` for errors (legacy API) diff --git a/package.json b/package.json index 55c307482..f648c2e85 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "scripts": { "docs:check": "node scripts/check-doc-links.mjs", - "test:ci-projects": "node --test scripts/ci-projects.test.mjs scripts/cli-nightly-version.test.mjs scripts/cloudflare-deployment.test.mjs scripts/deploy-workflow.test.mjs scripts/migrate-cloudflare-token.test.mjs scripts/oxc-config.test.mjs", + "test:ci-projects": "node --test scripts/ci-projects.test.mjs scripts/cli-nightly-version.test.mjs scripts/cloudflare-deployment.test.mjs scripts/deploy-workflow.test.mjs scripts/migrate-cloudflare-token.test.mjs scripts/oxc-config.test.mjs scripts/cli-lint-rules.test.mjs", "dev": "pnpm --filter '@sentry/mcp-cloudflare...' --if-present run build && dotenv -e .env -e .env.local -- pnpm --parallel --filter @sentry/mcp-cloudflare --filter @sentry/mcp-core --filter @sentry/mcp-server-mocks --if-present run dev", "dev:stdio": "pnpm --filter '@sentry/mcp-server...' --if-present run build && dotenv -e .env -e .env.local -- pnpm --parallel --filter @sentry/mcp-server --filter @sentry/mcp-core --filter @sentry/mcp-server-mocks --if-present run dev", "build": "dotenv -e .env -e .env.local -- pnpm -r --filter '!sentry' --filter '!sentry-cli-docs' --if-present run build", @@ -33,7 +33,7 @@ "eval": "pnpm --filter '@sentry/mcp-server-evals...' --if-present run build && dotenv -e .env -e .env.local -- pnpm --filter @sentry/mcp-server-evals run eval", "eval:ci": "pnpm --filter '@sentry/mcp-server-evals...' --if-present run build && CI=true dotenv -e .env -e .env.local -- pnpm --stream -r run eval:ci", "format": "oxfmt --write", - "lint": "oxlint . && pnpm run lint:ast-grep", + "lint": "oxlint --report-unused-disable-directives . && pnpm run lint:ast-grep", "lint:ast-grep": "ast-grep scan && ast-grep test --skip-snapshot-tests", "lint:fix": "oxlint --fix .", "pre-commit:generated": "node scripts/pre-commit-generated.mjs", @@ -71,10 +71,6 @@ "*": [ "oxfmt --write --no-error-on-unmatched-pattern", "oxlint --fix --no-error-on-unmatched-pattern" - ], - "packages/cli/**/*": [ - "pnpm --filter sentry exec biome format --write --no-errors-on-unmatched --files-ignore-unknown=true", - "pnpm --filter sentry exec biome lint --fix --no-errors-on-unmatched --files-ignore-unknown=true" ] }, "devDependencies": { diff --git a/packages/cli/.cursor/hooks.json b/packages/cli/.cursor/hooks.json index a2140a12a..7382c49c7 100644 --- a/packages/cli/.cursor/hooks.json +++ b/packages/cli/.cursor/hooks.json @@ -3,7 +3,7 @@ "hooks": { "afterFileEdit": [ { - "command": "npx ultracite fix" + "command": "pnpm --filter sentry run lint:fix" } ] } diff --git a/packages/cli/.cursor/rules/oxc.mdc b/packages/cli/.cursor/rules/oxc.mdc new file mode 100644 index 000000000..ee26feab6 --- /dev/null +++ b/packages/cli/.cursor/rules/oxc.mdc @@ -0,0 +1,23 @@ +--- +description: Oxc formatting and lint rules +globs: "**/*.{ts,tsx,js,jsx,json,jsonc,html,css,md,mdx}" +alwaysApply: false +--- + +# CLI code standards + +Run `pnpm run lint` from `packages/cli` for Oxlint, CLI-specific rules, and +Oxfmt. Run `pnpm run lint:fix` to apply safe fixes and formatting. The root +pre-commit hook uses the same tools for staged files. + +- Prefer strict types and `unknown` to `any`; narrow input before use. +- Use `const`, Node `node:*` APIs, and argument arrays for child processes. +- Await promises and handle errors at the right boundary. Never use an async + Promise executor or silently discard a failed operation. +- Keep command output in `CommandOutput` and diagnostics in the tagged logger. +- Avoid top-level browser/Node APIs that break supported runtimes. +- Keep tests isolated, with assertions in `it()`/`test()` and no disabled cases. +- Use `// oxlint-disable-next-line -- ` only for an intentional + exception. Unused suppressions fail CI; remove them when fixing the code. + +The CLI-specific Oxlint rules live in `lint-rules/cli-oxlint-plugin.js`. diff --git a/packages/cli/.cursor/rules/ultracite.mdc b/packages/cli/.cursor/rules/ultracite.mdc deleted file mode 100644 index ad163e550..000000000 --- a/packages/cli/.cursor/rules/ultracite.mdc +++ /dev/null @@ -1,101 +0,0 @@ ---- -description: Ultracite Rules - AI-Ready Formatter and Linter -globs: "**/*.{ts,tsx,js,jsx,json,jsonc,html,vue,svelte,astro,css,yaml,yml,graphql,gql,md,mdx,grit}" -alwaysApply: false ---- - -# Ultracite Code Standards - -This project uses **Ultracite**, a zero-config Biome preset that enforces strict code quality standards through automated formatting and linting. - -## Quick Reference - -- **Format code**: `npx ultracite fix` -- **Check for issues**: `npx ultracite check` -- **Diagnose setup**: `npx ultracite doctor` - -Biome (the underlying engine) provides extremely fast Rust-based linting and formatting. Most issues are automatically fixable. - ---- - -## Core Principles - -Write code that is **accessible, performant, type-safe, and maintainable**. Focus on clarity and explicit intent over brevity. - -### Type Safety & Explicitness - -- Use explicit types for function parameters and return values when they enhance clarity -- Prefer `unknown` over `any` when the type is genuinely unknown -- Use const assertions (`as const`) for immutable values and literal types -- Leverage TypeScript's type narrowing instead of type assertions -- Use meaningful variable names instead of magic numbers - extract constants with descriptive names - -### Modern JavaScript/TypeScript - -- Use arrow functions for callbacks and short functions -- Prefer `for...of` loops over `.forEach()` and indexed `for` loops -- Use optional chaining (`?.`) and nullish coalescing (`??`) for safer property access -- Prefer template literals over string concatenation -- Use destructuring for object and array assignments -- Use `const` by default, `let` only when reassignment is needed, never `var` - -### Async & Promises - -- Always `await` promises in async functions - don't forget to use the return value -- Use `async/await` syntax instead of promise chains for better readability -- Handle errors appropriately in async code with try-catch blocks -- Don't use async functions as Promise executors - -### Error Handling & Debugging - -- Remove `console.log`, `debugger`, and `alert` statements from production code -- Throw `Error` objects with descriptive messages, not strings or other values -- Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them -- Prefer early returns over nested conditionals for error cases - -### Code Organization - -- Keep functions focused and under reasonable cognitive complexity limits -- Extract complex conditions into well-named boolean variables -- Use early returns to reduce nesting -- Prefer simple conditionals over nested ternary operators -- Group related code together and separate concerns - -### Security - -- Add `rel="noopener"` when using `target="_blank"` on links -- Avoid `dangerouslySetInnerHTML` unless absolutely necessary -- Don't use `eval()` or assign directly to `document.cookie` -- Validate and sanitize user input - -### Performance - -- Avoid spread syntax in accumulators within loops -- Use top-level regex literals instead of creating them in loops -- Prefer specific imports over namespace imports -- Avoid barrel files (index files that re-export everything) -- Use proper image components (e.g., Next.js ``) over `` tags - ---- - -## Testing - -- Write assertions inside `it()` or `test()` blocks -- Avoid done callbacks in async tests - use async/await instead -- Don't use `.only` or `.skip` in committed code -- Keep test suites reasonably flat - avoid excessive `describe` nesting - -## When Biome Can't Help - -Biome's linter will catch most issues automatically. Focus your attention on: - -1. **Business logic correctness** - Biome can't validate your algorithms -2. **Meaningful naming** - Use descriptive names for functions, variables, and types -3. **Architecture decisions** - Component structure, data flow, and API design -4. **Edge cases** - Handle boundary conditions and error states -5. **User experience** - Accessibility, performance, and usability considerations -6. **Documentation** - Add comments for complex logic, but prefer self-documenting code - ---- - -Most formatting and common issues are automatically fixed by Biome. Run `npx ultracite fix` before committing to ensure compliance. diff --git a/packages/cli/.vscode/settings.json b/packages/cli/.vscode/settings.json index 9136365f3..5359e4ecf 100644 --- a/packages/cli/.vscode/settings.json +++ b/packages/cli/.vscode/settings.json @@ -1,36 +1,11 @@ { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "[javascript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[json]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[jsonc]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[css]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[graphql]": { - "editor.defaultFormatter": "biomejs.biome" - }, + "editor.defaultFormatter": "oxc.oxc-vscode", "typescript.tsdk": "node_modules/typescript/lib", "editor.formatOnSave": true, "editor.formatOnPaste": true, "emmet.showExpandedAbbreviation": "never", "editor.codeActionsOnSave": { - "source.fixAll.biome": "explicit", - "source.organizeImports.biome": "explicit" + "source.fixAll.oxc": "explicit" }, "[mdx]": { "editor.defaultFormatter": "unifiedjs.vscode-mdx" diff --git a/packages/cli/AGENTS.md b/packages/cli/AGENTS.md index 155e16c99..3ba9bf22e 100644 --- a/packages/cli/AGENTS.md +++ b/packages/cli/AGENTS.md @@ -28,7 +28,7 @@ Guidelines for AI agents working in this codebase. Before working on this codebase, read the Cursor rules: -- **`.cursor/rules/ultracite.mdc`** - Code style, formatting, linting rules +- **`.cursor/rules/oxc.mdc`** - Code style, formatting, linting rules ## Quick Reference: Commands @@ -49,7 +49,7 @@ pnpm run build:all # Build for all platforms pnpm run typecheck # Check types (tsc) # Linting & Formatting -pnpm run lint # Check for issues (Biome) +pnpm run lint # Check Oxlint and Oxfmt pnpm run lint:fix # Auto-fix issues (run before committing) # Testing (Vitest) @@ -130,7 +130,7 @@ Top-level layout: `*.model-based.test.ts`, `e2e/`, `fixtures/`, `mocks/`). - **`../../apps/cli-docs/`** — documentation site (Astro + Starlight); **`script/`** — build/utility scripts; **`.cursor/rules/`** — Cursor AI rules; - **`biome.jsonc`** — lint config. + **`lint-rules/cli-oxlint-plugin.js`** — CLI-specific lint rules. ## Key Patterns @@ -530,7 +530,7 @@ CliError (base, exitCode=1) - Pass `alternatives: []` when defaults are irrelevant (e.g., for missing Trace ID, Event ID) - Use `" and "` in `resource` for plural grammar: `"Trace ID and span ID"` → "are required" -**CI enforcement:** `pnpm run check:errors` scans for `ContextError` with multiline commands and `CliError` with ad-hoc "Try:" strings. Silent `catch` blocks are enforced separately by the `no-silent-catch` Biome plugin (see below). +**CI enforcement:** `pnpm run check:errors` scans for `ContextError` with multiline commands and `CliError` with ad-hoc "Try:" strings. Silent `catch` blocks are enforced separately by the `no-silent-catch` CLI Oxlint rule (see below). ```typescript // Usage examples @@ -552,7 +552,7 @@ Reference: `resolveDashboardId()` in `src/commands/dashboard/resolve.ts`. ### Catch Block Logging -Silent `catch` blocks are prohibited in `src/` production code. Biome's `noEmptyBlockStatements` catches syntactically empty `catch {}` blocks, but blocks with only a `return` statement and no logging are equally problematic — errors vanish silently, making debugging impossible. +Silent `catch` blocks are prohibited in `src/` production code. Both empty `catch {}` blocks and blocks that only return without logging discard errors and make debugging impossible. Every `catch` block must either: 1. Re-throw the error @@ -574,17 +574,17 @@ catch (error) { Use `logger.withTag("command-name")` for tagged logging in command files. -**CI enforcement:** the `no-silent-catch` Biome plugin -(`lint-rules/no-silent-catch.grit`, registered in `biome.jsonc`) flags `catch` +**CI enforcement:** the `no-silent-catch` CLI Oxlint rule +(`lint-rules/cli-oxlint-plugin.js`, registered in the root `.oxlintrc.json`) flags `catch` blocks — statement and `.catch()` form — that are empty, comment-only, or return-only without surfacing the error. The pre-existing backlog is grandfathered in place with inline -`// biome-ignore lint/plugin: ` comments. Because `pnpm run lint` runs -with `--error-on-warnings`, an *orphaned* suppression (left behind when a -grandfathered catch is fixed) fails as `suppressions/unused` — so the backlog +`// oxlint-disable-next-line sentry-cli/no-silent-catch -- ` comments. +Because `pnpm run lint` reports unused suppressions as errors, an *orphaned* +suppression (left behind when a grandfathered catch is fixed) fails — so the backlog can only shrink, the same ratchet the old JSON baseline provided, with no separate script or baseline file to maintain. Fix a grandfathered catch by -adding logging/re-throwing and deleting its `biome-ignore` line; only add a new +adding logging/re-throwing and deleting its suppression line; only add a new suppression for a genuinely intentional silent catch, with a real reason. ### Auto-Recovery for Wrong Entity Types @@ -678,7 +678,7 @@ command. `sentry init` is the intentional not-found exception: it calls the classifier with fuzzy recovery disabled, then treats `not-found` as a new project name. Issue-short-ID recovery in `commands/issue/utils.ts` is not CLI target resolution and may perform its own project lookup. The -`no-direct-target-resolution` Biome plugin enforces this command-layer +`no-direct-target-resolution` CLI Oxlint rule enforces this command-layer boundary. ### List Command Infrastructure diff --git a/packages/cli/CONTRIBUTING.md b/packages/cli/CONTRIBUTING.md index 4397e3b86..6331464df 100644 --- a/packages/cli/CONTRIBUTING.md +++ b/packages/cli/CONTRIBUTING.md @@ -17,10 +17,11 @@ sentry issue list [/] [--json] ``` **Target syntax**: + - `/` - Explicit organization and project (e.g., `my-org/frontend`) - `/` - All projects in the specified organization - `` - Search for project by name across all accessible organizations -- *(omit)* - Auto-detect from DSN or config +- _(omit)_ - Auto-detect from DSN or config **Rationale**: Positional arguments follow `gh` CLI conventions and are more concise than flags. @@ -49,11 +50,11 @@ Context (org, project) is resolved in this priority order: ## Common Flags -| Flag | Description | Used In | -|------|-------------|---------| -| `--json` | Output as JSON | All view/list commands | -| `-w`, `--web` | Open in browser | All view commands | -| `--limit` | Max items to return | List commands | +| Flag | Description | Used In | +| ------------- | ------------------- | ---------------------- | +| `--json` | Output as JSON | All view/list commands | +| `-w`, `--web` | Open in browser | All view commands | +| `--limit` | Max items to return | List commands | ## Error Handling @@ -64,9 +65,9 @@ import { ContextError } from "../../lib/errors.js"; if (!resolved) { throw new ContextError( - "Organization", // What is required - "sentry org view ", // Primary usage - ["Set SENTRY_DSN for auto-detection"] // Alternatives + "Organization", // What is required + "sentry org view ", // Primary usage + ["Set SENTRY_DSN for auto-detection"], // Alternatives ); } ``` diff --git a/packages/cli/biome.jsonc b/packages/cli/biome.jsonc deleted file mode 100644 index 0457369d8..000000000 --- a/packages/cli/biome.jsonc +++ /dev/null @@ -1,190 +0,0 @@ -{ - "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", - "extends": ["ultracite/core"], - // The .gitignore Biome honors lives at the monorepo root (two levels up). - // Point Biome's VCS integration there so `useIgnoreFile` reads the root file, - // whose patterns are `packages/cli/`-prefixed. (The package-level .gitignore - // is for plain git only; Biome does not read it.) - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true, - "root": "../.." - }, - "plugins": [ - "./lint-rules/no-stdout-write-in-commands.grit", - "./lint-rules/no-process-stdout-in-commands.grit", - "./lint-rules/no-raw-metadata-queries.grit", - "./lint-rules/no-manual-transactions.grit", - "./lint-rules/no-inline-touch-cache.grit", - "./lint-rules/no-stderr-write-in-commands.grit", - "./lint-rules/no-args-join-in-release.grit", - "./lint-rules/no-direct-target-resolution.grit", - "./lint-rules/no-generic-is-record.grit", - "./lint-rules/prefer-paginate-helper.grit" - ], - "files": { - // custom-ca.ts excluded: Biome's type analysis hits the 200k type limit - // on the node:tls module graph — an internal Biome bug that surfaces - // non-deterministically as error vs warning. See biome issue tracker. - "includes": [ - "!test/init-eval/templates", - "!dist-build", - // The codemod package is a self-contained Codemod (jssg) project with - // its own tooling/tests (`codemods/sentry-v3-to-v4`); it uses the - // `codemod:ast-grep` runtime import and intentionally "bad" v3 fixtures, - // so it must not be linted/formatted by the repo's Biome config. - "!codemods", - // custom-ca.ts blows past Biome's type-analysis limit when pulled into - // the module graph (internal Biome bug); force-ignore even via a test. - "!!src/lib/custom-ca.ts", - // output.ts hits the same 200k type-analysis limit once valibot's - // GenericSchema graph is pulled in — the same internal Biome bug, surfacing - // non-deterministically (main passes by a hair; unrelated PRs trip it, - // e.g. #1499, #1501). Force-ignore until Biome raises/fixes the limit. - "!!src/lib/formatters/output.ts" - ] - }, - "javascript": {}, - "linter": { - "rules": { - "style": { - "noRestrictedImports": { - "level": "error", - "options": { - "paths": { - "@stricli/core": { - "importNames": ["buildCommand", "buildRouteMap"], - "message": "Import buildCommand from '../lib/command.js' and buildRouteMap from '../lib/route-map.js' instead. The wrappers inject telemetry and standard subcommand aliases." - } - } - } - } - } - } - }, - "overrides": [ - { - // Silent-catch enforcement is scoped to production code under src/**. - // The grandfathered backlog is pinned with inline `// biome-ignore - // lint/plugin` comments; `pnpm run lint` runs with `--error-on-warnings` - // so an orphaned suppression fails as `suppressions/unused`, keeping the - // backlog shrink-only. Replaces the old script/silent-catch-baseline.json - // ratchet (see #1531). - "includes": ["src/**/*.ts"], - "plugins": ["./lint-rules/no-silent-catch.grit"] - }, - { - // The React-hook lint rules infer "this is a hook" from the - // `use*` naming convention. We have a couple of test helpers - // (`useTestConfigDir`, `useEnvSandbox`) that share the prefix - // by coincidence — they register `beforeEach`/`afterEach` and - // have nothing to do with React. Without these overrides every - // call site lights up `useHookAtTopLevel` since making the - // tsconfig JSX-aware (for `OpenTuiUI`) flipped the rule on. - // The actual React tree lives in `src/lib/init/ui/opentui-app.tsx` - // and keeps the rule active. - "includes": ["test/**/*.ts", "src/**/*.ts", "!src/**/*.tsx"], - "linter": { - "rules": { - "correctness": { - "useHookAtTopLevel": "off", - "useExhaustiveDependencies": "off" - } - } - } - }, - { - "includes": ["test/**/*.ts"], - "linter": { - "rules": { - "performance": { - "useTopLevelRegex": "off", - "noDelete": "off" - }, - "style": { - "useBlockStatements": "off", - "noNonNullAssertion": "off" - }, - "suspicious": { - "useAwait": "off" - } - } - } - }, - { - // A few DB modules still have genuinely async functions (stat() for mtime validation, - // clearResponseCache for auth cleanup). The useAwait rule is off for these specific files. - "includes": [ - "src/lib/db/dsn-cache.ts", - "src/lib/db/project-root-cache.ts", - "src/lib/db/auth.ts" - ], - "linter": { - "rules": { - "suspicious": { - "useAwait": "off" - } - } - } - }, - { - // src/index.ts is the library entry point — re-exports SentryError, SentryOptions, SentrySDK - // db/index.ts exports db connection utilities - not a barrel file but triggers the rule - // api-client.ts is a barrel that re-exports from src/lib/api/ domain modules - // to preserve the existing import path for all consumers - // markdown.ts re-exports isPlainOutput from plain-detect.ts for backward compat - // script/debug-id.ts re-exports from src/lib/sourcemap/debug-id.ts for build scripts - "includes": [ - "src/index.ts", - "src/lib/db/index.ts", - "src/lib/api-client.ts", - "src/lib/formatters/markdown.ts", - "script/debug-id.ts" - ], - "linter": { - "rules": { - "performance": { - "noBarrelFile": "off" - } - } - } - }, - { - // command.ts and route-map.ts are the canonical wrappers — they must import from @stricli/core - "includes": ["src/lib/command.ts", "src/lib/route-map.ts"], - "linter": { - "rules": { - "style": { - "noRestrictedImports": "off" - } - } - } - }, - { - // 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", "buildRouteMap"], - "message": "Import buildCommand from '../lib/command.js' and buildRouteMap from '../lib/route-map.js' instead. The wrappers inject telemetry and standard subcommand aliases." - }, - "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/packages/cli/lint-rules/cli-oxlint-plugin.js b/packages/cli/lint-rules/cli-oxlint-plugin.js new file mode 100644 index 000000000..d1035dd58 --- /dev/null +++ b/packages/cli/lint-rules/cli-oxlint-plugin.js @@ -0,0 +1,269 @@ +const name = "sentry-cli"; + +function member(node, property) { + return ( + node?.type === "MemberExpression" && + !node.computed && + node.property.name === property + ); +} + +function call(node, property) { + return node.type === "CallExpression" && member(node.callee, property); +} + +function rule(message, visitors) { + return { + meta: { type: "problem", docs: { description: message }, schema: [] }, + create(context) { + const filename = context.filename.replaceAll("\\", "/"); + if (!filename.includes("/packages/cli/")) return {}; + return visitors(context, filename, (node) => + context.report({ node, message }), + ); + }, + }; +} + +function containsIdentifier(node, identifier) { + if (!node || typeof node !== "object") return false; + if (node.type === "Identifier" && node.name === identifier) return true; + return Object.entries(node).some( + ([key, value]) => + key !== "parent" && + (Array.isArray(value) + ? value.some((child) => containsIdentifier(child, identifier)) + : value && + typeof value === "object" && + containsIdentifier(value, identifier)), + ); +} + +function silentBody(block, parameter) { + if (block?.type !== "BlockStatement" || block.body.length > 1) return false; + if (block.body.length === 0) return true; + const statement = block.body[0]; + return ( + statement.type === "ReturnStatement" && + (!parameter || + parameter.type !== "Identifier" || + !containsIdentifier(statement.argument, parameter.name)) + ); +} + +const rules = { + "no-silent-catch": rule( + "Handle or report caught errors instead of discarding them.", + (context, file, report) => + file.includes("/src/") + ? { + TryStatement(node) { + if ( + node.handler && + silentBody(node.handler.body, node.handler.param) + ) + report(node); + }, + CallExpression(node) { + if (!call(node, "catch")) return; + const handler = node.arguments[0]; + if ( + handler?.type === "ArrowFunctionExpression" && + silentBody(handler.body, handler.params[0]) + ) + report(node); + }, + } + : {}, + ), + "no-stdout-write-in-commands": rule( + "Yield CommandOutput instead of writing to stdout.", + (_context, file, report) => + file.includes("/src/commands/") + ? { + CallExpression(node) { + if (call(node, "write") && node.callee.object.name === "stdout") + report(node); + }, + } + : {}, + ), + "no-process-stdout-in-commands": rule( + "Use CommandOutput instead of process.stdout in commands.", + (_context, file, report) => + file.includes("/src/commands/") + ? { + MemberExpression(node) { + if (member(node, "stdout") && node.object.name === "process") + report(node); + }, + } + : {}, + ), + "no-stderr-write-in-commands": rule( + "Use the tagged logger instead of writing to stderr.", + (_context, file, report) => + file.includes("/src/commands/") + ? { + CallExpression(node) { + if (call(node, "write") && node.callee.object.name === "stderr") + report(node); + }, + } + : {}, + ), + "no-raw-metadata-queries": rule( + "Use the metadata helpers in db/utils.js.", + (context, file, report) => + file.endsWith("utils.ts") || file.endsWith("migration.ts") + ? {} + : { + CallExpression(node) { + if ( + call(node, "query") && + node.arguments.length > 0 && + /(?:SELECT value FROM|DELETE FROM) metadata/.test( + context.sourceCode.getText(node.arguments[0]), + ) + ) + report(node); + if ( + node.callee.name === "runUpsert" && + node.arguments[1]?.value === "metadata" + ) + report(node); + }, + }, + ), + "no-manual-transactions": rule( + "Use db.transaction()() instead of manual transaction statements.", + (context, file, report) => + !file.includes("/src/") || + file.endsWith("migration.ts") || + file.endsWith("db/sqlite.ts") + ? {} + : { + CallExpression(node) { + if ( + call(node, "exec") && + node.arguments.length > 0 && + /\b(?:BEGIN|COMMIT|ROLLBACK)\b/.test( + context.sourceCode.getText(node.arguments[0]), + ) + ) + report(node); + }, + }, + ), + "no-inline-touch-cache": rule( + "Use touchCacheEntry() instead of an inline last_accessed update.", + (context, file, report) => + file.endsWith("utils.ts") + ? {} + : { + CallExpression(node) { + if ( + call(node, "run") && + call(node.callee.object, "query") && + node.callee.object.arguments.length > 0 && + /UPDATE .* SET last_accessed/.test( + context.sourceCode.getText(node.callee.object.arguments[0]), + ) + ) + report(node); + }, + }, + ), + "no-args-join-in-release": rule( + "Use the Stricli tuple positional instead of joining release args.", + (_context, file, report) => + file.includes("/src/commands/release/") + ? { + CallExpression(node) { + if (call(node, "join") && node.callee.object.name === "args") + report(node); + }, + } + : {}, + ), + "no-direct-target-resolution": rule( + "Use a capability helper from resolve-target.ts.", + (_context, file, report) => + file.includes("/src/commands/") && !file.endsWith("/issue/utils.ts") + ? { + CallExpression(node) { + if ( + node.callee.name === "findProjectsBySlug" || + node.callee.name === "triageProjectNotFound" || + member(node.callee, "findProjectsBySlug") || + member(node.callee, "triageProjectNotFound") + ) + report(node); + }, + } + : {}, + ), + "no-generic-is-record": rule( + "Use a shape-specific guard instead of isRecord().", + (_context, file, report) => + file.includes("/src/") + ? { + FunctionDeclaration(node) { + if (node.id?.name === "isRecord") report(node); + }, + VariableDeclarator(node) { + if ( + node.id.name === "isRecord" && + ["ArrowFunctionExpression", "FunctionExpression"].includes( + node.init?.type, + ) + ) + report(node); + }, + } + : {}, + ), + "prefer-paginate-helper": rule( + "Use paginate() from infrastructure.js instead of a raw limit.", + (_context, file, report) => + file.includes("/src/lib/api/") && !file.endsWith("/infrastructure.ts") + ? { + CallExpression(node) { + if ( + node.callee.name === "autoPaginate" && + node.arguments[1]?.name === "limit" + ) + report(node); + }, + } + : {}, + ), + "no-namespace-import": rule( + "Import only the names used by production code.", + (_context, file, report) => + file.includes("/src/") || file.includes("/test/") + ? { + ImportNamespaceSpecifier(node) { + report(node); + }, + } + : {}, + ), + "no-skipped-tests": rule( + "Do not leave disabled tests in the suite.", + (_context, file, report) => + file.includes("/test/") + ? { + CallExpression(node) { + if ( + member(node.callee, "skip") && + ["test", "it", "describe"].includes(node.callee.object.name) + ) + report(node); + }, + } + : {}, + ), +}; + +export default { meta: { name }, rules }; diff --git a/packages/cli/lint-rules/no-args-join-in-release.grit b/packages/cli/lint-rules/no-args-join-in-release.grit deleted file mode 100644 index ac4c3cd50..000000000 --- a/packages/cli/lint-rules/no-args-join-in-release.grit +++ /dev/null @@ -1,5 +0,0 @@ -file($name, $body) where { - $name <: r".*src/commands/release/.*", - $body <: contains `args.join($sep)` as $call, - register_diagnostic(span=$call, message="Don't join variadic release args with args.join(). Declare positionals as a Stricli tuple and resolve them via resolveReleaseTarget()/parseReleaseArg() so Stricli enforces arity instead of silently swallowing extra arguments. See src/commands/release/parse.ts.") -} diff --git a/packages/cli/lint-rules/no-direct-target-resolution.grit b/packages/cli/lint-rules/no-direct-target-resolution.grit deleted file mode 100644 index 09299b4e8..000000000 --- a/packages/cli/lint-rules/no-direct-target-resolution.grit +++ /dev/null @@ -1,19 +0,0 @@ -language js - -// CLI commands resolve project/org targets through capability helpers in -// resolve-target.ts. Calling the low-level search or miss classifier directly -// lets command-specific precedence drift. -// -// issue/utils.ts is intentionally excluded: it uses project lookup while -// resolving issue IDs, not CLI target syntax. -file($name, $body) where { - $name <: r".*src/commands/.*", - $name <: not r".*src/commands/issue/utils\.ts$", - $body <: contains or { - `findProjectsBySlug($args)` as $match, - `$api.findProjectsBySlug($args)` as $match, - `triageProjectNotFound($args)` as $match, - `$resolver.triageProjectNotFound($args)` as $match - }, - register_diagnostic(span=$match, message="Use a capability helper from resolve-target.ts instead of low-level project target resolution (AGENTS.md).") -} diff --git a/packages/cli/lint-rules/no-generic-is-record.grit b/packages/cli/lint-rules/no-generic-is-record.grit deleted file mode 100644 index b5303c467..000000000 --- a/packages/cli/lint-rules/no-generic-is-record.grit +++ /dev/null @@ -1,22 +0,0 @@ -file($name, $body) where { - $name <: r".*src/.*", - $body <: contains or { - `function isRecord($args): $return_type { $function_body }` as $match, - `function isRecord($args) { $function_body }` as $match, - `async function isRecord($args): $return_type { $function_body }` as $match, - `async function isRecord($args) { $function_body }` as $match, - `const isRecord = ($args): $return_type => $implementation` as $match, - `const isRecord = ($args) => $implementation` as $match, - `const isRecord = $arg => $implementation` as $match, - `const isRecord: $type = ($args) => $implementation` as $match, - `const isRecord = function ($args) { $function_body }` as $match, - `const isRecord: $type = function ($args) { $function_body }` as $match, - `let isRecord = ($args): $return_type => $implementation` as $match, - `let isRecord = ($args) => $implementation` as $match, - `let isRecord = $arg => $implementation` as $match, - `let isRecord: $type = ($args) => $implementation` as $match, - `let isRecord = function ($args) { $function_body }` as $match, - `let isRecord: $type = function ($args) { $function_body }` as $match - }, - register_diagnostic(span=$match, message="Use a local, shape-specific guard (for example, hasFileMap()) instead of the generic isRecord() name.") -} diff --git a/packages/cli/lint-rules/no-inline-touch-cache.grit b/packages/cli/lint-rules/no-inline-touch-cache.grit deleted file mode 100644 index aca49eba9..000000000 --- a/packages/cli/lint-rules/no-inline-touch-cache.grit +++ /dev/null @@ -1,7 +0,0 @@ -file($name, $body) where { - $name <: not r".*utils\.ts$", - $body <: contains `$db.query($sql).run($args)` as $match where { - $sql <: r".*UPDATE .* SET last_accessed.*" - }, - register_diagnostic(span=$match, message="Use touchCacheEntry() from db/utils.js instead of inline last_accessed UPDATE.") -} diff --git a/packages/cli/lint-rules/no-manual-transactions.grit b/packages/cli/lint-rules/no-manual-transactions.grit deleted file mode 100644 index 0e2344b23..000000000 --- a/packages/cli/lint-rules/no-manual-transactions.grit +++ /dev/null @@ -1,12 +0,0 @@ -file($name, $body) where { - $name <: not r".*migration\.ts$", - $name <: not r".*db/sqlite\.ts$", - $body <: contains `$db.exec($sql)` as $match where { - $sql <: or { - r".*BEGIN.*", - r".*COMMIT.*", - r".*ROLLBACK.*" - } - }, - register_diagnostic(span=$match, message="Use db.transaction()() instead of manual BEGIN/COMMIT/ROLLBACK.") -} diff --git a/packages/cli/lint-rules/no-process-stdout-in-commands.grit b/packages/cli/lint-rules/no-process-stdout-in-commands.grit deleted file mode 100644 index a70a27f6b..000000000 --- a/packages/cli/lint-rules/no-process-stdout-in-commands.grit +++ /dev/null @@ -1,5 +0,0 @@ -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/packages/cli/lint-rules/no-raw-metadata-queries.grit b/packages/cli/lint-rules/no-raw-metadata-queries.grit deleted file mode 100644 index ae2ea2faf..000000000 --- a/packages/cli/lint-rules/no-raw-metadata-queries.grit +++ /dev/null @@ -1,14 +0,0 @@ -file($name, $body) where { - $name <: not r".*utils\.ts$", - $name <: not r".*migration\.ts$", - $body <: contains or { - `$db.query($sql)` as $match where { - $sql <: or { - r".*SELECT value FROM metadata.*", - r".*DELETE FROM metadata.*" - } - }, - `runUpsert($db, "metadata", $data, $keys)` as $match - }, - register_diagnostic(span=$match, message="Use getMetadata/setMetadata/clearMetadata from db/utils.js instead of raw metadata queries.") -} diff --git a/packages/cli/lint-rules/no-silent-catch.grit b/packages/cli/lint-rules/no-silent-catch.grit deleted file mode 100644 index 0066c55b1..000000000 --- a/packages/cli/lint-rules/no-silent-catch.grit +++ /dev/null @@ -1,65 +0,0 @@ -language js - -// Flags "silent" catch blocks: a `catch` clause (or a `.catch()` handler) whose -// body is empty, a bare `return;`, or `return ;` that does not reference -// the caught error. Errors must be surfaced via log.debug()/log.warn() or -// re-thrown (see AGENTS.md). Biome's built-in noEmptyBlockStatements only -// covers syntactically empty `catch {}`; this also covers comment-only and -// return-only bodies. -// -// This replaces the hand-rolled silent-catch detector and the -// script/silent-catch-baseline.json ratchet (see #1531). The pre-existing -// backlog is grandfathered with inline `// biome-ignore lint/plugin: ` -// comments. Removing a grandfathered catch orphans its suppression, which Biome -// reports as `suppressions/unused`; `pnpm run lint` runs with -// `--error-on-warnings`, so the backlog can only shrink (the same ratchet the -// old baseline JSON provided). -// -// Registered via an override scoped to `src/**/*.ts` in biome.jsonc (matching -// the old script's scan scope). -or { - `try { $t } catch { }`, - `try { $t } catch { return; }`, - `try { $t } catch { return $r; }`, - `try { $t } catch ($e) { }`, - `try { $t } catch ($e) { return; }`, - `try { $t } catch ($e) { return $r; }` where { $r <: not contains $e }, - // Typed catch bindings. GritQL type-annotation metavariables don't bind, so - // the untyped `catch ($e)` patterns above miss `catch (e: unknown)` / - // `catch (e: any)`. TypeScript only permits `unknown` or `any` as a catch - // binding type, so those two literals cover every typed form. - `try { $t } catch ($e: unknown) { }`, - `try { $t } catch ($e: unknown) { return; }`, - `try { $t } catch ($e: unknown) { return $r; }` where { - $r <: not contains $e - }, - `try { $t } catch ($e: any) { }`, - `try { $t } catch ($e: any) { return; }`, - `try { $t } catch ($e: any) { return $r; }` where { $r <: not contains $e }, - `try { $t } catch { } finally { $f }`, - `try { $t } catch { return; } finally { $f }`, - `try { $t } catch { return $r; } finally { $f }`, - `try { $t } catch ($e) { } finally { $f }`, - `try { $t } catch ($e) { return; } finally { $f }`, - `try { $t } catch ($e) { return $r; } finally { $f }` where { - $r <: not contains $e - }, - `try { $t } catch ($e: unknown) { } finally { $f }`, - `try { $t } catch ($e: unknown) { return; } finally { $f }`, - `try { $t } catch ($e: unknown) { return $r; } finally { $f }` where { - $r <: not contains $e - }, - `try { $t } catch ($e: any) { } finally { $f }`, - `try { $t } catch ($e: any) { return; } finally { $f }`, - `try { $t } catch ($e: any) { return $r; } finally { $f }` where { - $r <: not contains $e - }, - `$p.catch(() => { })`, - `$p.catch(() => { return; })`, - `$p.catch(() => { return $r; })`, - `$p.catch(($e) => { })`, - `$p.catch(($e) => { return; })`, - `$p.catch(($e) => { return $r; })` where { $r <: not contains $e } -} as $m where { - register_diagnostic(span=$m, message="Silent catch block. Add log.debug()/log.warn() or re-throw — errors must not vanish (AGENTS.md). If the suppression is intentional, grandfather it with `// biome-ignore lint/plugin: `.") -} diff --git a/packages/cli/lint-rules/no-stderr-write-in-commands.grit b/packages/cli/lint-rules/no-stderr-write-in-commands.grit deleted file mode 100644 index 86e194806..000000000 --- a/packages/cli/lint-rules/no-stderr-write-in-commands.grit +++ /dev/null @@ -1,5 +0,0 @@ -file($name, $body) where { - $name <: r".*src/commands/.*", - $body <: contains `stderr.write($args)` as $call, - register_diagnostic(span=$call, message="Don't call stderr.write() in commands. Use logger.withTag('command-name').warn() for warnings or log.debug() for diagnostics.") -} diff --git a/packages/cli/lint-rules/no-stdout-write-in-commands.grit b/packages/cli/lint-rules/no-stdout-write-in-commands.grit deleted file mode 100644 index ceb328f87..000000000 --- a/packages/cli/lint-rules/no-stdout-write-in-commands.grit +++ /dev/null @@ -1,5 +0,0 @@ -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/packages/cli/lint-rules/prefer-paginate-helper.grit b/packages/cli/lint-rules/prefer-paginate-helper.grit deleted file mode 100644 index ea8352114..000000000 --- a/packages/cli/lint-rules/prefer-paginate-helper.grit +++ /dev/null @@ -1,11 +0,0 @@ -file($name, $body) where { - $name <: r".*src/lib/api/.*", - $name <: not r".*infrastructure\.ts$", - $body <: contains or { - `autoPaginate($fetch, $bound)` as $match where { $bound <: `limit` }, - `autoPaginate($fetch, $bound, $cursor)` as $match where { - $bound <: `limit` - } - }, - register_diagnostic(span=$match, message="Use paginate() from infrastructure.js instead of wiring autoPaginate() with a raw limit. It centralizes the per_page cap and limit+cursor threading (see #1473).") -} diff --git a/packages/cli/package.json b/packages/cli/package.json index c0a15169b..e3556c168 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -62,8 +62,8 @@ "build:all": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/build.ts", "bundle": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/bundle.ts", "typecheck": "pnpm run generate:docs && pnpm run generate:sdk && tsc --noEmit", - "lint": "biome check --no-errors-on-unmatched --error-on-warnings --max-diagnostics=none ./", - "lint:fix": "biome check --write --no-errors-on-unmatched --max-diagnostics=none ./", + "lint": "pnpm -w exec oxlint --report-unused-disable-directives packages/cli && pnpm -w exec oxfmt --check packages/cli", + "lint:fix": "pnpm -w exec oxlint --fix packages/cli && pnpm -w exec oxfmt --write packages/cli", "test": "pnpm run test:unit", "test:unit": "pnpm run generate:docs && pnpm run generate:sdk && vitest run test/lib test/commands test/types test/script --coverage", "test:changed": "pnpm run generate:docs && pnpm run generate:sdk && vitest run --changed", @@ -93,7 +93,6 @@ }, "devDependencies": { "@anthropic-ai/sdk": "^0.39.0", - "@biomejs/biome": "2.3.8", "@clack/prompts": "0.11.0", "@hono/node-server": "^2.0.10", "@mastra/client-js": "^1.38.0", @@ -145,7 +144,6 @@ "tinyglobby": "^0.2.17", "tsx": "^4.22.4", "typescript": "^5.9.3", - "ultracite": "6.3.10", "uuidv7": "^1.2.1", "valibot": "^1.4.2", "vitest": "^4.1.11", diff --git a/packages/cli/script/bench-sweep.ts b/packages/cli/script/bench-sweep.ts index 63a09f3d6..be6f259cc 100644 --- a/packages/cli/script/bench-sweep.ts +++ b/packages/cli/script/bench-sweep.ts @@ -49,8 +49,8 @@ const DEFAULT_VALUES = [1, 2, 4, 8, 16, 32, 50, 100, 200] as const; const DEFAULT_SIZES: readonly PresetName[] = ["medium", "large"]; /** - * DSN scanner hot regex — reused by the `scan.grepFiles` op. Kept at - * module scope to satisfy Biome's `useTopLevelRegex` rule. + * DSN scanner hot regex — reused by the `scan.grepFiles` op. Kept + * at module scope to avoid recompiling it. */ const DSN_PATTERN = /https?:\/\/[a-z0-9]+(?::[a-z0-9]+)?@[a-z0-9.-]+(?:\.[a-z]+|:[0-9]+)\/\d+/i; @@ -64,7 +64,7 @@ type SweepArgs = { kneeThresholdPct: number; }; -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: CLI flag switch is inherently branchy +// CLI flag switch is inherently branchy function parseArgs(argv: readonly string[]): SweepArgs { const sizes: PresetName[] = [...DEFAULT_SIZES]; let values: number[] = [...DEFAULT_VALUES]; @@ -89,7 +89,7 @@ function parseArgs(argv: readonly string[]): SweepArgs { sizes.push(next as PresetName); } else { throw new Error( - `Unknown size '${next}'. Valid: ${PRESET_NAMES.join(", ")}, all` + `Unknown size '${next}'. Valid: ${PRESET_NAMES.join(", ")}, all`, ); } i += 1; @@ -153,7 +153,6 @@ function printHelp(): void { /** Resolve (or create) a synthetic fixture, same as bench.ts. */ function resolveFixture(name: PresetName): { label: string; rootDir: string } { const preset = PRESETS[name]; - // biome-ignore lint/suspicious/noBitwiseOperators: deterministic seed mix const seed = (0xde_ad_be_ef ^ hashStr(name)) >>> 0; const spec: FixtureSpec = { ...preset, @@ -169,7 +168,6 @@ function resolveFixture(name: PresetName): { label: string; rootDir: string } { } /** Cheap 32-bit FNV-1a over a short string — same as bench.ts. */ -// biome-ignore-start lint/suspicious/noBitwiseOperators: FNV-1a is a bitwise hash function hashStr(s: string): number { let h = 0x81_1c_9d_c5; for (let i = 0; i < s.length; i += 1) { @@ -177,7 +175,6 @@ function hashStr(s: string): number { } return h >>> 0; } -// biome-ignore-end lint/suspicious/noBitwiseOperators: FNV-1a is a bitwise hash /** * Single op × concurrency → p50 in ms. Returns NaN when the op @@ -257,7 +254,7 @@ async function sweepFixture( fx: { label: string; rootDir: string }, ops: Awaited>, args: SweepArgs, - results: SweepResult[] + results: SweepResult[], ): Promise { for (const op of ops) { for (const concurrency of args.values) { @@ -277,7 +274,7 @@ async function sweepFixture( }); if (!args.json) { console.log( - ` ${op.label.padEnd(24)} conc=${String(concurrency).padStart(3)} p50 ${stats.p50.toFixed(1).padStart(6)}ms p95 ${stats.p95.toFixed(1).padStart(6)}ms` + ` ${op.label.padEnd(24)} conc=${String(concurrency).padStart(3)} p50 ${stats.p50.toFixed(1).padStart(6)}ms p95 ${stats.p95.toFixed(1).padStart(6)}ms`, ); } } @@ -291,7 +288,7 @@ async function sweepFixture( */ function findKnee( entries: readonly SweepResult[], - thresholdPct: number + thresholdPct: number, ): number | null { const sorted = [...entries].sort((a, b) => a.concurrency - b.concurrency); let bestP50 = Number.POSITIVE_INFINITY; @@ -315,7 +312,7 @@ function findKnee( /** Render the per-(fixture, op) knee table. */ function printKnees( results: readonly SweepResult[], - thresholdPct: number + thresholdPct: number, ): void { const byKey = new Map(); for (const r of results) { @@ -326,7 +323,7 @@ function printKnees( } console.log(""); console.log( - `Knee analysis (smallest concurrency past which each additional step gains < ${(thresholdPct * 100).toFixed(0)}%)` + `Knee analysis (smallest concurrency past which each additional step gains < ${(thresholdPct * 100).toFixed(0)}%)`, ); console.log("─".repeat(72)); for (const [key, entries] of byKey) { @@ -334,7 +331,7 @@ function printKnees( const knee = findKnee(entries, thresholdPct); const minP50 = Math.min(...entries.map((e) => e.p50)); console.log( - ` ${String(fixture).padEnd(20)} ${String(operation).padEnd(24)} knee = ${knee ?? "?"} (best p50 ${minP50.toFixed(1)}ms)` + ` ${String(fixture).padEnd(20)} ${String(operation).padEnd(24)} knee = ${knee ?? "?"} (best p50 ${minP50.toFixed(1)}ms)`, ); } console.log(""); @@ -359,7 +356,7 @@ async function main(): Promise { process.stdout.write(`${JSON.stringify(report, null, 2)}\n`); } else { console.log( - `\nsystem: ${platform()}/${arch()}, availableParallelism=${availableParallelism()}` + `\nsystem: ${platform()}/${arch()}, availableParallelism=${availableParallelism()}`, ); printKnees(results, args.kneeThresholdPct); } diff --git a/packages/cli/script/bench.ts b/packages/cli/script/bench.ts index db464bd21..f17ff5708 100644 --- a/packages/cli/script/bench.ts +++ b/packages/cli/script/bench.ts @@ -69,8 +69,8 @@ import { } from "../test/fixtures/bench/presets.js"; /** - * The DSN scanner's hot regex. Pinned at module scope per Biome's - * `useTopLevelRegex` rule; reused by the `scan.grepFiles` op. + * The DSN scanner's hot regex. Pinned at module scope and reused + * by the `scan.grepFiles` op. */ const DSN_PATTERN = /https?:\/\/[a-z0-9]+(?::[a-z0-9]+)?@[a-z0-9.-]+(?:\.[a-z]+|:[0-9]+)\/\d+/i; @@ -106,7 +106,7 @@ type ParseState = { function applyFlag( state: ParseState, arg: string, - next: string | undefined + next: string | undefined, ): boolean { switch (arg) { case "--size": { @@ -119,7 +119,7 @@ function applyFlag( state.sizes = [next as PresetName]; } else { throw new Error( - `Unknown size '${next}'. Valid: ${PRESET_NAMES.join(", ")}, all` + `Unknown size '${next}'. Valid: ${PRESET_NAMES.join(", ")}, all`, ); } return true; @@ -209,7 +209,7 @@ function parseArgs(argv: readonly string[]): CliArgs { } if (state.saveBaseline && state.repo) { throw new Error( - "--save-baseline is only valid with synthetic fixtures; remove --repo/BENCH_REPO" + "--save-baseline is only valid with synthetic fixtures; remove --repo/BENCH_REPO", ); } @@ -218,11 +218,11 @@ function parseArgs(argv: readonly string[]): CliArgs { function printHelp(): void { console.log( - "Usage: pnpm run bench [-- --size small|medium|large|all] [--op NAME] [--repo PATH]" + "Usage: pnpm run bench [-- --size small|medium|large|all] [--op NAME] [--repo PATH]", ); console.log(" [--runs N] [--warmup N]"); console.log( - " [--json] [--save-baseline] [--compare] [--regen-fixtures]" + " [--json] [--save-baseline] [--compare] [--regen-fixtures]", ); } @@ -237,7 +237,6 @@ type FixtureHandle = { fileCount: number; }; -// biome-ignore-start lint/suspicious/noBitwiseOperators: FNV-1a is a bitwise hash /** 32-bit FNV-1a hash of a string — supplies a stable per-preset seed. */ function hashToSeed(s: string): number { let h = 0x81_1c_9d_c5; @@ -247,18 +246,16 @@ function hashToSeed(s: string): number { // Keep the result in the 32-bit signed range for determinism across engines. return h >>> 0; } -// biome-ignore-end lint/suspicious/noBitwiseOperators: FNV-1a is a bitwise hash /** Build (or reuse) a synthetic fixture for the given preset. */ function resolveSyntheticFixture( name: PresetName, - forceRegen: boolean + forceRegen: boolean, ): FixtureHandle { const preset = PRESETS[name]; // Deterministic seed per preset so every contributor lands on the same tree. // We XOR an anchor constant with the per-preset name hash so seeds are // spread across the 32-bit space even when preset names are similar. - // biome-ignore lint/suspicious/noBitwiseOperators: deterministic 32-bit seed mix const seed = (0xde_ad_be_ef ^ hashToSeed(name)) >>> 0; const specNoRoot = { ...preset, seed }; const hash = hashSpec(specNoRoot); @@ -324,18 +321,15 @@ type OpEntry = { async function buildOps(): Promise { // Lazy-import production code so unit tests can import the helpers/fixtures // without loading all of @sentry/node-core. - const { detectDsn, detectAllDsns } = await import( - "../src/lib/dsn/detector.js" - ); + const { detectDsn, detectAllDsns } = + await import("../src/lib/dsn/detector.js"); const { findProjectRoot } = await import("../src/lib/dsn/project-root.js"); - const { scanCodeForDsns, scanCodeForFirstDsn } = await import( - "../src/lib/dsn/code-scanner.js" - ); + const { scanCodeForDsns, scanCodeForFirstDsn } = + await import("../src/lib/dsn/code-scanner.js"); // Scan module — not yet wired into DSN detection (PR 3 will do that). // These ops give us standalone baselines so PR 2/PR 3 can compare. - const { walkFiles, IgnoreStack, TEXT_EXTENSIONS, collectGrep } = await import( - "../src/lib/scan/index.js" - ); + const { walkFiles, IgnoreStack, TEXT_EXTENSIONS, collectGrep } = + await import("../src/lib/scan/index.js"); // DSN-parity preset — used by the `scan.walk.dsnParity` op below. const { dsnScanOptions } = await import("../src/lib/dsn/scan-options.js"); @@ -509,7 +503,7 @@ async function resolveFixtures(args: CliArgs): Promise { ]; } return args.sizes.map((size) => - resolveSyntheticFixture(size, args.regenFixtures) + resolveSyntheticFixture(size, args.regenFixtures), ); } @@ -521,7 +515,7 @@ function filterOps(ops: OpEntry[], opFilter: string | undefined): OpEntry[] { const filtered = ops.filter((op) => op.label.includes(opFilter)); if (filtered.length === 0) { throw new Error( - `--op ${opFilter} matched no operations.\n Available: ${ops.map((o) => o.label).join(", ")}` + `--op ${opFilter} matched no operations.\n Available: ${ops.map((o) => o.label).join(", ")}`, ); } return filtered; @@ -531,7 +525,7 @@ function filterOps(ops: OpEntry[], opFilter: string | undefined): OpEntry[] { async function runAll( fixtures: readonly FixtureHandle[], ops: readonly OpEntry[], - args: CliArgs + args: CliArgs, ): Promise { const entries: BenchEntry[] = []; for (const fx of fixtures) { @@ -554,7 +548,7 @@ async function runAll( }); if (!args.json) { console.log( - ` ${op.label.padEnd(24)} p50 ${stats.p50.toFixed(2)}ms p95 ${stats.p95.toFixed(2)}ms (${stats.runs} runs)` + ` ${op.label.padEnd(24)} p50 ${stats.p50.toFixed(2)}ms p95 ${stats.p95.toFixed(2)}ms (${stats.runs} runs)`, ); } } @@ -566,17 +560,17 @@ async function runAll( /** Perform the --compare step. Returns false on regression. */ function compareAgainstBaseline( report: BenchReport, - thresholdPct: number + thresholdPct: number, ): boolean { const baselinePath = ".bench/baseline.json"; if (!existsSync(baselinePath)) { console.error( - `✗ No baseline found at ${baselinePath}. Run with --save-baseline first.` + `✗ No baseline found at ${baselinePath}. Run with --save-baseline first.`, ); return false; } const baseline = JSON.parse( - readFileSync(baselinePath, "utf8") + readFileSync(baselinePath, "utf8"), ) as BenchReport; const rows = compareReports(baseline, report, thresholdPct); const ok = printComparison(rows, thresholdPct); diff --git a/packages/cli/script/build.ts b/packages/cli/script/build.ts index c0d697e89..89b9b960a 100644 --- a/packages/cli/script/build.ts +++ b/packages/cli/script/build.ts @@ -183,7 +183,7 @@ async function bundleJs(): Promise { 1024 ).toFixed(2); const mapSize = ((await stat(SOURCEMAP_FILE)).size / 1024 / 1024).toFixed( - 2 + 2, ); console.log(` -> ${BUNDLE_JS} (${jsSize} MB)`); console.log(` -> ${SOURCEMAP_FILE} (${mapSize} MB, for Sentry upload)`); @@ -191,7 +191,7 @@ async function bundleJs(): Promise { } catch (error) { console.error(" Failed to bundle JS:"); console.error( - ` ${error instanceof Error ? error.message : String(error)}` + ` ${error instanceof Error ? error.message : String(error)}`, ); return false; } @@ -237,12 +237,12 @@ async function injectDebugIds(): Promise { const jsContent = await readFile(BUNDLE_JS, "utf-8"); await writeFile( BUNDLE_JS, - jsContent.split(PLACEHOLDER_DEBUG_ID).join(currentDebugId) + jsContent.split(PLACEHOLDER_DEBUG_ID).join(currentDebugId), ); } catch (error) { const msg = error instanceof Error ? error.message : String(error); console.warn( - ` Warning: Debug ID placeholder replacement failed: ${msg}` + ` Warning: Debug ID placeholder replacement failed: ${msg}`, ); } } @@ -303,7 +303,7 @@ async function uploadSourcemapToSentry(): Promise { * (fossilize parallelizes internally), then post-processes each binary. */ async function compileAllTargets( - targets: BuildTarget[] + targets: BuildTarget[], ): Promise<{ successes: number; failures: number }> { const platforms = targets.map((t) => getFossilizePlatform(t)); @@ -328,16 +328,16 @@ async function compileAllTargets( let DIF_WASM_SRC: string; try { DIF_WASM_SRC = createRequire(import.meta.url).resolve( - "@sentry/symbolic/symbolic_bg.wasm" + "@sentry/symbolic/symbolic_bg.wasm", ); } catch { throw new Error( - "Missing @sentry/symbolic WASM (@sentry/symbolic/symbolic_bg.wasm). Run: pnpm install" + "Missing @sentry/symbolic WASM (@sentry/symbolic/symbolic_bg.wasm). Run: pnpm install", ); } if (!existsSync(DIF_WASM_SRC)) { throw new Error( - `Missing @sentry/symbolic WASM at ${DIF_WASM_SRC}. Run: pnpm install` + `Missing @sentry/symbolic WASM at ${DIF_WASM_SRC}. Run: pnpm install`, ); } const DIF_WASM = `${BUILD_DIR}/symbolic_bg.wasm`; @@ -349,7 +349,7 @@ async function compileAllTargets( const SPLEEN_FONT_SRC = "src/lib/formatters/assets/spleen-8x16.bin"; if (!existsSync(SPLEEN_FONT_SRC)) { throw new Error( - `Missing Spleen dashboard font asset at ${SPLEEN_FONT_SRC}` + `Missing Spleen dashboard font asset at ${SPLEEN_FONT_SRC}`, ); } const SPLEEN_FONT = `${BUILD_DIR}/spleen-8x16.bin`; @@ -357,7 +357,7 @@ async function compileAllTargets( assetArgs.push("--assets", SPLEEN_FONT); console.log( - ` Step 2: Compiling ${platforms.length} target(s) (Node SEA via fossilize)...` + ` Step 2: Compiling ${platforms.length} target(s) (Node SEA via fossilize)...`, ); // Invoke fossilize via `pnpm exec` so it resolves from the workspace bin @@ -388,12 +388,12 @@ async function compileAllTargets( ...assetArgs, BUNDLE_JS, ].join(" "), - { stdio: "inherit" } + { stdio: "inherit" }, ); } catch (error) { console.error(" Fossilize compilation failed:"); console.error( - ` ${error instanceof Error ? error.message : String(error)}` + ` ${error instanceof Error ? error.message : String(error)}`, ); return { successes: 0, failures: targets.length }; } @@ -407,7 +407,7 @@ async function compileAllTargets( successes += 1; } catch (error) { console.error( - ` Post-processing ${getPackageName(target)} failed: ${error}` + ` Post-processing ${getPackageName(target)} failed: ${error}`, ); failures += 1; } @@ -480,7 +480,7 @@ async function build(): Promise { if (!SENTRY_CLIENT_ID) { console.error( - "\nError: SENTRY_CLIENT_ID environment variable is required." + "\nError: SENTRY_CLIENT_ID environment variable is required.", ); console.error(" The CLI requires OAuth to function."); console.error(" Set it via: SENTRY_CLIENT_ID=xxx pnpm run build\n"); @@ -496,7 +496,7 @@ async function build(): Promise { if (!target) { console.error(`Invalid target: ${targetArg}`); console.error( - `Valid targets: ${ALL_TARGETS.map((t) => `${t.os === "win32" ? "windows" : t.os}-${t.arch}`).join(", ")}` + `Valid targets: ${ALL_TARGETS.map((t) => `${t.os === "win32" ? "windows" : t.os}-${t.arch}`).join(", ")}`, ); process.exit(1); } @@ -504,17 +504,17 @@ async function build(): Promise { console.log(`\nBuilding for target: ${getPackageName(target)}`); } else if (singleBuild) { const currentTarget = ALL_TARGETS.find( - (t) => t.os === process.platform && t.arch === process.arch + (t) => t.os === process.platform && t.arch === process.arch, ); if (!currentTarget) { console.error( - `Unsupported platform: ${process.platform}-${process.arch}` + `Unsupported platform: ${process.platform}-${process.arch}`, ); process.exit(1); } targets = [currentTarget]; console.log( - `\nBuilding for current platform: ${getPackageName(currentTarget)}` + `\nBuilding for current platform: ${getPackageName(currentTarget)}`, ); } else { targets = ALL_TARGETS; diff --git a/packages/cli/script/bump-version.ts b/packages/cli/script/bump-version.ts index 2e386415f..9a2fe1ff8 100644 --- a/packages/cli/script/bump-version.ts +++ b/packages/cli/script/bump-version.ts @@ -155,7 +155,7 @@ function postRelease(): void { if (diffResult.status !== 0) { console.log("Committing version bump"); exec( - 'git commit -anm "meta: Bump new development version\n\n#skip-changelog"' + 'git commit -anm "meta: Bump new development version\n\n#skip-changelog"', ); exec("git pull --rebase"); exec("git push"); diff --git a/packages/cli/script/bundle.ts b/packages/cli/script/bundle.ts index 16207c398..ad744b31f 100644 --- a/packages/cli/script/bundle.ts +++ b/packages/cli/script/bundle.ts @@ -37,7 +37,7 @@ async function deleteMapFiles(injected: InjectedFile[]): Promise { /** Inject debug IDs into JS outputs and their companion sourcemaps. */ async function injectDebugIdsForOutputs( - jsFiles: string[] + jsFiles: string[], ): Promise { const injected: InjectedFile[] = []; for (const jsPath of jsFiles) { @@ -51,7 +51,7 @@ async function injectDebugIdsForOutputs( } catch (err) { const msg = err instanceof Error ? err.message : String(err); console.warn( - ` Warning: Debug ID injection failed for ${jsPath}: ${msg}` + ` Warning: Debug ID injection failed for ${jsPath}: ${msg}`, ); } } @@ -64,7 +64,7 @@ async function injectDebugIdsForOutputs( * @returns `true` if upload succeeded, `false` if it failed (non-fatal). */ async function uploadInjectedSourcemaps( - injected: InjectedFile[] + injected: InjectedFile[], ): Promise { try { console.log(" Uploading sourcemaps to Sentry..."); @@ -120,7 +120,7 @@ const sentrySourcemapPlugin: Plugin = { (p) => p.endsWith(".cjs") || p.endsWith(".mjs") || - (p.endsWith(".js") && !p.endsWith(".map")) + (p.endsWith(".js") && !p.endsWith(".map")), ); if (jsFiles.length === 0) { @@ -138,7 +138,7 @@ const sentrySourcemapPlugin: Plugin = { const content = await readFile(jsPath, "utf-8"); await writeFile( jsPath, - content.split(PLACEHOLDER_DEBUG_ID).join(debugId) + content.split(PLACEHOLDER_DEBUG_ID).join(debugId), ); } @@ -224,7 +224,7 @@ const zlibNamespaceShimPlugin: Plugin = { "zstdDecompressSync", "createZstdCompress", "createZstdDecompress", - ].filter((n) => IDENTIFIER_FILTER_RE.test(n) && n !== "default") + ].filter((n) => IDENTIFIER_FILTER_RE.test(n) && n !== "default"), ); // Redirect node:zlib to the shim — EXCEPT the shim's own re-export below, @@ -234,7 +234,7 @@ const zlibNamespaceShimPlugin: Plugin = { b.onResolve({ filter: NODE_ZLIB_RE }, (args) => args.namespace === NS ? { path: "node:zlib", external: true } - : { path: NS, namespace: NS } + : { path: NS, namespace: NS }, ); b.onLoad({ filter: ANY_RE, namespace: NS }, () => ({ @@ -242,7 +242,7 @@ const zlibNamespaceShimPlugin: Plugin = { `import * as zlib from "node:zlib";`, "export default zlib;", ...[...names].map( - (n) => `export const ${n} = zlib[${JSON.stringify(n)}];` + (n) => `export const ${n} = zlib[${JSON.stringify(n)}];`, ), ].join("\n"), loader: "js", @@ -260,7 +260,7 @@ if (process.env.SENTRY_AUTH_TOKEN) { console.log(" Sentry auth token found, source maps will be uploaded"); } else { console.log( - " No SENTRY_AUTH_TOKEN, debug IDs will be injected but source maps will not be uploaded" + " No SENTRY_AUTH_TOKEN, debug IDs will be injected but source maps will not be uploaded", ); } @@ -364,7 +364,7 @@ await writeFile("./dist/bin.cjs", BIN_WRAPPER); // The SentrySDK type is read from sdk.generated.d.cts (produced by generate-sdk.ts), and // SentryOptions from sdk-types.ts so the published type cannot drift from the source. const CORE_DECLARATIONS = buildCoreDeclarations( - await readFile(`./${SDK_TYPES_PATH}`, "utf-8") + await readFile(`./${SDK_TYPES_PATH}`, "utf-8"), ); // Read pre-built SDK type declarations (generated by generate-sdk.ts) @@ -394,7 +394,7 @@ await copyFile( // Resolve via module resolution rather than a fixed node_modules path: // in the pnpm workspace @sentry/symbolic may live in the isolated store. createRequire(import.meta.url).resolve("@sentry/symbolic/symbolic_bg.wasm"), - "./dist/vendor/symbolic_bg.wasm" + "./dist/vendor/symbolic_bg.wasm", ); console.log(" -> dist/vendor/symbolic_bg.wasm (DIF parser)"); @@ -404,7 +404,7 @@ console.log(" -> dist/vendor/symbolic_bg.wasm (DIF parser)"); await mkdir("./dist/assets", { recursive: true }); await copyFile( "./src/lib/formatters/assets/spleen-8x16.bin", - "./dist/assets/spleen-8x16.bin" + "./dist/assets/spleen-8x16.bin", ); console.log(" -> dist/assets/spleen-8x16.bin (dashboard font)"); @@ -417,16 +417,16 @@ console.log(" -> dist/assets/spleen-8x16.bin (dashboard font)"); let sqliteWasmSrc: string; try { sqliteWasmSrc = createRequire(import.meta.url).resolve( - "node-sqlite3-wasm/dist/node-sqlite3-wasm.wasm" + "node-sqlite3-wasm/dist/node-sqlite3-wasm.wasm", ); } catch { throw new Error( - "Missing node-sqlite3-wasm WASM (node-sqlite3-wasm/dist/node-sqlite3-wasm.wasm). Run: pnpm install" + "Missing node-sqlite3-wasm WASM (node-sqlite3-wasm/dist/node-sqlite3-wasm.wasm). Run: pnpm install", ); } if (!existsSync(sqliteWasmSrc)) { throw new Error( - `Missing node-sqlite3-wasm WASM at ${sqliteWasmSrc}. Run: pnpm install` + `Missing node-sqlite3-wasm WASM at ${sqliteWasmSrc}. Run: pnpm install`, ); } await copyFile(sqliteWasmSrc, "./dist/node-sqlite3-wasm.wasm"); diff --git a/packages/cli/script/check-env-coverage.ts b/packages/cli/script/check-env-coverage.ts index dfad66222..01234b6e8 100644 --- a/packages/cli/script/check-env-coverage.ts +++ b/packages/cli/script/check-env-coverage.ts @@ -151,7 +151,7 @@ for (const [name, location] of referenced) { errors.push( `Undocumented env var: ${name} (read at ${location})\n` + " Add it to ENV_VAR_REGISTRY in src/lib/env-registry.ts so it appears in configuration.md,\n" + - " or add it to INTERNAL_ENV_VARS in this script with a reason if it is internal-only." + " or add it to INTERNAL_ENV_VARS in this script with a reason if it is internal-only.", ); } @@ -160,7 +160,7 @@ for (const name of INTERNAL_ENV_VARS.keys()) { if (registryNames.has(name)) { errors.push( `Contradictory entry: ${name} is in both ENV_VAR_REGISTRY and INTERNAL_ENV_VARS. ` + - "Remove it from INTERNAL_ENV_VARS." + "Remove it from INTERNAL_ENV_VARS.", ); } } @@ -169,7 +169,7 @@ for (const name of INTERNAL_ENV_VARS.keys()) { for (const name of INTERNAL_ENV_VARS.keys()) { if (!referenced.has(name)) { errors.push( - `Stale INTERNAL_ENV_VARS entry: ${name} is no longer read in src/. Remove it.` + `Stale INTERNAL_ENV_VARS entry: ${name} is no longer read in src/. Remove it.`, ); } } @@ -184,5 +184,5 @@ if (errors.length > 0) { console.log( `✓ All ${referenced.size} user-facing env var(s) read in src/ are documented ` + - `(${registryNames.size} in registry, ${INTERNAL_ENV_VARS.size} internal).` + `(${registryNames.size} in registry, ${INTERNAL_ENV_VARS.size} internal).`, ); diff --git a/packages/cli/script/check-error-patterns.ts b/packages/cli/script/check-error-patterns.ts index 1691f11cc..cd050994a 100644 --- a/packages/cli/script/check-error-patterns.ts +++ b/packages/cli/script/check-error-patterns.ts @@ -11,8 +11,8 @@ * → Should use ResolutionError with structured hint/suggestions * * Silent catch blocks used to be checked here via a ratchet baseline. That - * check now lives in the Biome plugin `lint-rules/no-silent-catch.grit`, whose - * grandfathered backlog is pinned inline with `// biome-ignore lint/plugin` + * check now lives in the CLI Oxlint plugin `lint-rules/cli-oxlint-plugin.js`, whose + * grandfathered backlog is pinned inline with Oxlint disable directives * comments (an unused suppression is itself reported, so the backlog can only * shrink). See #1531. * @@ -129,7 +129,7 @@ function skipString(content: string, start: number): number { */ function advanceToken( content: string, - i: number + i: number, ): { next: number; ch: string } { const ch = content[i] ?? ""; if (isQuote(ch)) { @@ -145,7 +145,7 @@ function advanceToken( */ function findCallBounds( content: string, - startIdx: number + startIdx: number, ): { commaIdx: number; closingIdx: number } | null { let depth = 1; let commaCount = 0; @@ -212,7 +212,7 @@ function extractSecondArg(content: string, startIdx: number): string | null { */ export function findContextErrorNewlines( content: string, - filePath: string + filePath: string, ): Violation[] { const found: Violation[] = []; let match = CONTEXT_ERROR_RE.exec(content); @@ -240,7 +240,7 @@ export function findContextErrorNewlines( */ export function findAdHocTryPatterns( content: string, - filePath: string + filePath: string, ): Violation[] { const found: Violation[] = []; const lines = content.split("\n"); @@ -285,17 +285,17 @@ async function main(): Promise { if (violations.length > 0) { console.error( - `✗ Found ${violations.length} error class anti-pattern(s):\n` + `✗ Found ${violations.length} error class anti-pattern(s):\n`, ); for (const v of violations) { console.error(` ${v.file}:${v.line}`); console.error(` ${v.message}\n`); } console.error( - "Fix: Use ResolutionError for resolution failures, ValidationError for input errors." + "Fix: Use ResolutionError for resolution failures, ValidationError for input errors.", ); console.error( - "See ContextError JSDoc in src/lib/errors.ts for usage guidance.\n" + "See ContextError JSDoc in src/lib/errors.ts for usage guidance.\n", ); process.exit(1); } diff --git a/packages/cli/script/check-fragments.ts b/packages/cli/script/check-fragments.ts index 36db22e1d..5137b7eae 100644 --- a/packages/cli/script/check-fragments.ts +++ b/packages/cli/script/check-fragments.ts @@ -23,13 +23,13 @@ import { DOCS_FRAGMENTS } from "./paths.js"; const SKILL_CONTENT_PATH = "src/generated/skill-content.ts"; const skillContentExists = await access(SKILL_CONTENT_PATH).then( () => true, - () => false + () => false, ); if (!skillContentExists) { mkdirSync("src/generated", { recursive: true }); await writeFile( SKILL_CONTENT_PATH, - "export const SKILL_FILES: [string, string][] = [];\n" + "export const SKILL_FILES: [string, string][] = [];\n", ); } @@ -48,7 +48,7 @@ const isStrict = process.argv.includes("--strict"); const routeMap = routes as unknown as RouteMap; const allRoutes = extractAllRoutes(routeMap).filter( - (r) => !SKIP_ROUTES.has(r.name) + (r) => !SKIP_ROUTES.has(r.name), ); const routeNames = new Set(allRoutes.map((r) => r.name)); @@ -58,7 +58,7 @@ const expectedFragments = new Set([...routeNames, "index"]); let fragmentFiles: string[]; try { fragmentFiles = readdirSync(FRAGMENTS_DIR).filter((f) => - MD_EXTENSION_RE.test(f) + MD_EXTENSION_RE.test(f), ); } catch { console.error(`ERROR: Fragment directory not found: ${FRAGMENTS_DIR}`); @@ -66,7 +66,7 @@ try { } const actualFragments = new Set( - fragmentFiles.map((f) => f.replace(MD_EXTENSION_RE, "")) + fragmentFiles.map((f) => f.replace(MD_EXTENSION_RE, "")), ); const errors: string[] = []; @@ -75,7 +75,7 @@ const errors: string[] = []; for (const name of expectedFragments) { if (!actualFragments.has(name)) { errors.push( - `Missing fragment: ${FRAGMENTS_DIR}/${name}.md (route "${name}" exists but has no fragment file)` + `Missing fragment: ${FRAGMENTS_DIR}/${name}.md (route "${name}" exists but has no fragment file)`, ); } } @@ -84,7 +84,7 @@ for (const name of expectedFragments) { for (const name of actualFragments) { if (!expectedFragments.has(name)) { errors.push( - `Stale fragment: ${FRAGMENTS_DIR}/${name}.md (no matching route found — delete it or add the route)` + `Stale fragment: ${FRAGMENTS_DIR}/${name}.md (no matching route found — delete it or add the route)`, ); } } @@ -95,13 +95,13 @@ for (const file of fragmentFiles) { if (content.includes("---\ntitle:") || content.startsWith("---\n")) { errors.push( - `Fragment contains frontmatter: ${FRAGMENTS_DIR}/${file} (fragments should only contain custom content, not YAML frontmatter)` + `Fragment contains frontmatter: ${FRAGMENTS_DIR}/${file} (fragments should only contain custom content, not YAML frontmatter)`, ); } if (content.includes(GENERATED_END_MARKER)) { errors.push( - `Fragment contains generated marker: ${FRAGMENTS_DIR}/${file} (the "${GENERATED_END_MARKER}" marker should not appear in fragment files)` + `Fragment contains generated marker: ${FRAGMENTS_DIR}/${file} (the "${GENERATED_END_MARKER}" marker should not appear in fragment files)`, ); } } @@ -119,11 +119,11 @@ for (const name of REQUIRED_TOP_LEVEL_FRAGMENTS) { const path = `${TOP_LEVEL_FRAGMENTS_DIR}/${name}.md`; const exists = await access(path).then( () => true, - () => false + () => false, ); if (!exists) { errors.push( - `Missing top-level fragment: ${path} (required for generated ${name}.md page)` + `Missing top-level fragment: ${path} (required for generated ${name}.md page)`, ); } } @@ -156,7 +156,7 @@ function fragmentMentionsSubcommand( content: string, routeName: string, command: { path: string }, - isDefaultCommand: boolean + isDefaultCommand: boolean, ): boolean { // Extract the subcommand portion after "sentry " const subPath = command.path.slice(`sentry ${routeName} `.length); @@ -175,7 +175,7 @@ function fragmentMentionsSubcommand( if (isDefaultCommand) { const bareRouteRe = new RegExp( `sentry\\s+${routeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s*$|[^\\w-])`, - "im" + "im", ); if (bareRouteRe.test(lower)) { return true; @@ -290,7 +290,7 @@ for (const [routeName, route] of multiCommandRoutes) { if (warnings.length > 0) { console.warn( - `\n${warnings.length} subcommand coverage warning(s)${isStrict ? " (treated as errors with --strict)" : ""}:\n` + `\n${warnings.length} subcommand coverage warning(s)${isStrict ? " (treated as errors with --strict)" : ""}:\n`, ); for (const w of warnings) { console.warn(` ⚠ ${w}`); @@ -306,8 +306,8 @@ if (errors.length > 0) { } console.log( - `\nAll ${actualFragments.size} command fragment files valid (${routeNames.size} routes + index)` + `\nAll ${actualFragments.size} command fragment files valid (${routeNames.size} routes + index)`, ); console.log( - `All ${REQUIRED_TOP_LEVEL_FRAGMENTS.length} top-level fragment(s) valid` + `All ${REQUIRED_TOP_LEVEL_FRAGMENTS.length} top-level fragment(s) valid`, ); diff --git a/packages/cli/script/check-no-deps.ts b/packages/cli/script/check-no-deps.ts index 4a2776aea..05a46074b 100644 --- a/packages/cli/script/check-no-deps.ts +++ b/packages/cli/script/check-no-deps.ts @@ -18,7 +18,7 @@ import { readFile } from "node:fs/promises"; const pkg: { dependencies?: Record } = JSON.parse( - await readFile("package.json", "utf-8") + await readFile("package.json", "utf-8"), ); const deps = Object.keys(pkg.dependencies ?? {}); @@ -35,10 +35,10 @@ for (const dep of deps) { } console.error(""); console.error( - "All packages must be in devDependencies and bundled at build time." + "All packages must be in devDependencies and bundled at build time.", ); console.error( - "Move these to devDependencies: pnpm remove && pnpm add -D " + "Move these to devDependencies: pnpm remove && pnpm add -D ", ); process.exit(1); diff --git a/packages/cli/script/check-patches.ts b/packages/cli/script/check-patches.ts index aa75f071f..627148c57 100644 --- a/packages/cli/script/check-patches.ts +++ b/packages/cli/script/check-patches.ts @@ -72,7 +72,7 @@ function parsePatchedDependencies(source: string): Record { const entry = line.match(PATCHED_DEPENDENCY_ENTRY); if (!entry) { throw new Error( - `Unsupported patchedDependencies entry in ${WORKSPACE_PATH}: ${line}` + `Unsupported patchedDependencies entry in ${WORKSPACE_PATH}: ${line}`, ); } const [, key, patchPath] = entry; @@ -111,7 +111,7 @@ const uiUtilsRequire = createRequire(mastraRequire.resolve("@ai-sdk/ui-utils")); function resolvePackageFile( subpath: string, - resolver: NodeJS.Require = require_ + resolver: NodeJS.Require = require_, ): string | null { const parts = subpath.split("/"); const pkgName = subpath.startsWith("@") @@ -140,7 +140,7 @@ function resolvePackageFile( } const patches = parsePatchedDependencies( - await readFile(WORKSPACE_PATH, "utf-8") + await readFile(WORKSPACE_PATH, "utf-8"), ); const warnings: string[] = []; const errors: string[] = []; @@ -173,7 +173,7 @@ for (const [key, patchPath] of Object.entries(patches)) { const versionMatch = patchPath.match(/@(\d+\.\d+\.\d+[^@]*)\.patch$/); if (!versionMatch) { warnings.push( - ` ? ${name}: could not extract version from patch path "${patchPath}"` + ` ? ${name}: could not extract version from patch path "${patchPath}"`, ); continue; } @@ -188,11 +188,11 @@ for (const [key, patchPath] of Object.entries(patches)) { throw new Error("unresolved"); } const installed: { version: string } = JSON.parse( - await readFile(pkgJsonPath, "utf-8") + await readFile(pkgJsonPath, "utf-8"), ); if (installed.version !== patchVersion) { warnings.push( - ` ${name}: patch targets ${patchVersion}, installed ${installed.version} — regenerate with: pnpm patch ${name}` + ` ${name}: patch targets ${patchVersion}, installed ${installed.version} — regenerate with: pnpm patch ${name}`, ); } } catch { @@ -343,7 +343,7 @@ for (const assertion of CONTENT_ASSERTIONS) { const contents = await readFile(assertionPath, "utf-8"); if (assertion.staleMarker && contents.includes(assertion.staleMarker)) { errors.push( - ` ${assertion.description} — patch not applied to ${assertion.file} (regenerate the patch for the current dependency version)` + ` ${assertion.description} — patch not applied to ${assertion.file} (regenerate the patch for the current dependency version)`, ); } if ( @@ -351,19 +351,19 @@ for (const assertion of CONTENT_ASSERTIONS) { !contents.includes(assertion.requiredMarker) ) { errors.push( - ` ${assertion.description} — patch not applied to ${assertion.file} (regenerate the patch for the current dependency version)` + ` ${assertion.description} — patch not applied to ${assertion.file} (regenerate the patch for the current dependency version)`, ); } } catch { errors.push( - ` ${assertion.description} — could not read ${assertion.file} (run pnpm install)` + ` ${assertion.description} — could not read ${assertion.file} (run pnpm install)`, ); } } try { const { createStatusCodeErrorResponseHandler } = uiUtilsRequire( - "@ai-sdk/provider-utils" + "@ai-sdk/provider-utils", ) as { createStatusCodeErrorResponseHandler: () => (options: { response: Response; @@ -381,7 +381,7 @@ try { { status: 500, headers: { "content-length": String(2 * 1024 * 1024 * 1024 + 1) }, - } + }, ); await createStatusCodeErrorResponseHandler()({ @@ -391,20 +391,20 @@ try { }).then( () => { errors.push( - " @ai-sdk/provider-utils: oversized response was accepted (resource-limit patch not applied)" + " @ai-sdk/provider-utils: oversized response was accepted (resource-limit patch not applied)", ); }, () => { if (!cancelled) { errors.push( - " @ai-sdk/provider-utils: oversized response body was not cancelled" + " @ai-sdk/provider-utils: oversized response body was not cancelled", ); } - } + }, ); } catch (error) { errors.push( - ` @ai-sdk/provider-utils: could not verify response-size limit (${String(error)})` + ` @ai-sdk/provider-utils: could not verify response-size limit (${String(error)})`, ); } @@ -426,10 +426,10 @@ if (errors.length > 0) { } console.error(""); console.error( - "A missing package is fixed by `pnpm install`. A content-assertion failure" + "A missing package is fixed by `pnpm install`. A content-assertion failure", ); console.error( - "means the patch no longer applies (likely a dependency bump) — regenerate" + "means the patch no longer applies (likely a dependency bump) — regenerate", ); console.error("it with `pnpm patch ` and re-commit."); process.exit(1); @@ -439,6 +439,6 @@ if (warnings.length === 0) { console.log("✓ All patched dependency versions match installed versions"); } else { console.log( - `✓ Patches applied (${warnings.length} version mismatch warning(s) — consider regenerating)` + `✓ Patches applied (${warnings.length} version mismatch warning(s) — consider regenerating)`, ); } diff --git a/packages/cli/script/check-stale-references.ts b/packages/cli/script/check-stale-references.ts index a1316ae23..4fda955b2 100644 --- a/packages/cli/script/check-stale-references.ts +++ b/packages/cli/script/check-stale-references.ts @@ -64,7 +64,7 @@ const PATTERNS: StalePattern[] = [ // "requires Bun", "Yarn installed", "Bun runtime" (prerequisite prose) pattern: new RegExp( `\\b(?:requires\\s+(?:${escaped})|(?:${escaped})\\s+(?:installed|runtime))\\b`, - "i" + "i", ), reason: `Project uses ${currentPM}, not these runtimes`, }, @@ -163,7 +163,7 @@ for (const filePath of FILES_TO_SCAN) { */ function findOriginalLineNumber( original: string, - strippedLine: string + strippedLine: string, ): number { const trimmed = strippedLine.trim(); const lines = original.split("\n"); @@ -177,13 +177,13 @@ function findOriginalLineNumber( if (findings.length === 0) { console.log( - `✓ No stale package manager references found (current: ${currentPM}, checked: ${FILES_TO_SCAN.length} files)` + `✓ No stale package manager references found (current: ${currentPM}, checked: ${FILES_TO_SCAN.length} files)`, ); process.exit(0); } console.error( - `✗ Found ${findings.length} stale package manager reference(s) (current PM: ${currentPM}):\n` + `✗ Found ${findings.length} stale package manager reference(s) (current PM: ${currentPM}):\n`, ); for (const f of findings) { console.error(` ${f.file}:${f.line}: ${f.text}`); diff --git a/packages/cli/script/eval-skill.ts b/packages/cli/script/eval-skill.ts index 2d167b978..4be90705e 100644 --- a/packages/cli/script/eval-skill.ts +++ b/packages/cli/script/eval-skill.ts @@ -58,7 +58,7 @@ async function evalModel( client: Awaited>, model: string, skillContent: string, - testCases: TestCase[] + testCases: TestCase[], ): Promise { console.log(`\nEvaluating: ${model}`); console.log("─".repeat(40)); @@ -72,7 +72,7 @@ async function evalModel( client, model, skillContent, - testCase.prompt + testCase.prompt, ); const result = await judgePlan(client, testCase, plan); results.push(result); @@ -97,10 +97,10 @@ async function main(): Promise { const provider = resolveEvalProvider(); if (!provider) { console.error( - "Error: an eval provider credential is required for the skill eval." + "Error: an eval provider credential is required for the skill eval.", ); console.error( - "Set OPENROUTER_API_KEY (preferred) or ANTHROPIC_API_KEY, e.g.:" + "Set OPENROUTER_API_KEY (preferred) or ANTHROPIC_API_KEY, e.g.:", ); console.error(" export OPENROUTER_API_KEY="); process.exit(1); @@ -114,7 +114,7 @@ async function main(): Promise { : DEFAULT_THRESHOLD; console.log( - `Skill eval: ${testCases.length} cases × ${client.agentModels.length} models` + `Skill eval: ${testCases.length} cases × ${client.agentModels.length} models`, ); console.log(`Provider: ${provider.provider}`); console.log(`Agent models: ${client.agentModels.join(", ")}`); diff --git a/packages/cli/script/generate-api-schema.ts b/packages/cli/script/generate-api-schema.ts index da653a4f3..398321d0d 100644 --- a/packages/cli/script/generate-api-schema.ts +++ b/packages/cli/script/generate-api-schema.ts @@ -154,7 +154,7 @@ function extractPathParams(url: string): string[] { const params: string[] = []; const pattern = new RegExp( PATH_PARAM_PATTERN.source, - PATH_PARAM_PATTERN.flags + PATH_PARAM_PATTERN.flags, ); let match = pattern.exec(url); while (match !== null) { @@ -173,7 +173,7 @@ console.log(`Fetching OpenAPI spec from ${openApiUrl}...`); const response = await fetch(openApiUrl); if (!response.ok) { throw new Error( - `Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}` + `Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`, ); } const spec = (await response.json()) as OpenApiSpec; @@ -229,5 +229,5 @@ await mkdir(dirname(OUTPUT_PATH), { recursive: true }); await writeFile(OUTPUT_PATH, JSON.stringify(endpoints, null, 2)); console.log( - `Generated ${OUTPUT_PATH} (${endpoints.length} endpoints, ${Math.round(JSON.stringify(endpoints).length / 1024)}KB)` + `Generated ${OUTPUT_PATH} (${endpoints.length} endpoints, ${Math.round(JSON.stringify(endpoints).length / 1024)}KB)`, ); diff --git a/packages/cli/script/generate-banner-sixel.ts b/packages/cli/script/generate-banner-sixel.ts index a35751e99..435def4c6 100644 --- a/packages/cli/script/generate-banner-sixel.ts +++ b/packages/cli/script/generate-banner-sixel.ts @@ -154,7 +154,7 @@ function encodeSixel(png: PNG): string { bandForRow: (y) => Math.min( COLOR_BANDS - 1, - Math.floor((y / Math.max(1, height - 1)) * COLOR_BANDS) + Math.floor((y / Math.max(1, height - 1)) * COLOR_BANDS), ), }; @@ -208,7 +208,7 @@ export const BANNER_SIXEL = { await writeFile(OUT_PATH, module, "utf8"); process.stdout.write( - `Wrote ${OUT_PATH} (${png.width}x${png.height}, sixel ${sixel.length} bytes)\n` + `Wrote ${OUT_PATH} (${png.width}x${png.height}, sixel ${sixel.length} bytes)\n`, ); } diff --git a/packages/cli/script/generate-command-docs.ts b/packages/cli/script/generate-command-docs.ts index cdc8865be..ad17639e1 100644 --- a/packages/cli/script/generate-command-docs.ts +++ b/packages/cli/script/generate-command-docs.ts @@ -38,7 +38,7 @@ const SKILL_CONTENT_STUB = "export const SKILL_FILES: [string, string][] = [];\n"; const skillContentExists = await access(SKILL_CONTENT_PATH).then( () => true, - () => false + () => false, ); if (!skillContentExists) { mkdirSync("src/generated", { recursive: true }); @@ -107,7 +107,7 @@ function getVisibleFlags(cmd: CommandInfo): FlagInfo[] { */ function formatFlagRow( flag: FlagInfo, - aliases: Record + aliases: Record, ): string { const alias = Object.entries(aliases).find(([, v]) => v === flag.name)?.[0]; @@ -163,7 +163,7 @@ function formatPositionalsTable(positionals: PositionalInfo[]): string { /** Format flags as a markdown options table */ function formatFlagsTable( flags: FlagInfo[], - aliases: Record + aliases: Record, ): string { if (flags.length === 0) { return ""; @@ -255,7 +255,7 @@ function generatePage(route: RouteInfo): string { // Global flags footer lines.push( - "All commands support `--json` for machine-readable output and `--fields` to select specific JSON fields." + "All commands support `--json` for machine-readable output and `--fields` to select specific JSON fields.", ); lines.push(""); @@ -332,14 +332,14 @@ function generateConfigurationPage(registry: readonly EnvVarEntry[]): string { lines.push("---"); lines.push("title: Configuration"); lines.push( - "description: Environment variables, config files, and configuration options for the Sentry CLI" + "description: Environment variables, config files, and configuration options for the Sentry CLI", ); lines.push("---"); lines.push(""); // Intro lines.push( - "The Sentry CLI can be configured through config files, environment variables, and a local database. Most users don't need to set any of these — the CLI auto-detects your project from your codebase and stores credentials locally after `sentry auth login`." + "The Sentry CLI can be configured through config files, environment variables, and a local database. Most users don't need to set any of these — the CLI auto-detects your project from your codebase and stores credentials locally after `sentry auth login`.", ); lines.push(""); @@ -395,7 +395,7 @@ async function readTopLevelFragment(fragmentName: string): Promise { const routeMap = routes as unknown as RouteMap; const routeInfos = extractAllRoutes(routeMap).filter( - (r) => !SKIP_ROUTES.has(r.name) + (r) => !SKIP_ROUTES.has(r.name), ); const generatedFiles: string[] = []; @@ -432,7 +432,7 @@ indexLines.push("description: Available commands in the Sentry CLI"); indexLines.push("---"); indexLines.push(""); indexLines.push( - "The Sentry CLI provides commands for interacting with various Sentry resources." + "The Sentry CLI provides commands for interacting with various Sentry resources.", ); indexLines.push(""); indexLines.push("## Available Commands"); @@ -460,5 +460,5 @@ const configContent = configFragment await writeFile(CONFIG_PATH, configContent); console.log( - `Generated ${generatedFiles.length} command doc pages + ${INDEX_PATH} + ${CONFIG_PATH}` + `Generated ${generatedFiles.length} command doc pages + ${INDEX_PATH} + ${CONFIG_PATH}`, ); diff --git a/packages/cli/script/generate-docs-sections.ts b/packages/cli/script/generate-docs-sections.ts index 2f14670c4..93e9b54cb 100644 --- a/packages/cli/script/generate-docs-sections.ts +++ b/packages/cli/script/generate-docs-sections.ts @@ -30,7 +30,7 @@ const SKILL_CONTENT_STUB = "export const SKILL_FILES: [string, string][] = [];\n"; const skillContentExists = await access(SKILL_CONTENT_PATH).then( () => true, - () => false + () => false, ); if (!skillContentExists) { mkdirSync("src/generated", { recursive: true }); @@ -62,7 +62,7 @@ type MarkerStyle = "html" | "mdx"; function markerTags( sectionName: string, - style: MarkerStyle + style: MarkerStyle, ): { startTag: string; endTag: string } { if (style === "mdx") { return { @@ -91,7 +91,7 @@ function replaceMarkerSection( content: string, sectionName: string, generated: string, - style: MarkerStyle = "html" + style: MarkerStyle = "html", ): string { const { startTag, endTag } = markerTags(sectionName, style); @@ -101,7 +101,7 @@ function replaceMarkerSection( if (startIdx === -1 || endIdx === -1) { throw new Error( `Missing markers for section "${sectionName}": ` + - `start=${startIdx !== -1}, end=${endIdx !== -1}` + `start=${startIdx !== -1}, end=${endIdx !== -1}`, ); } if (startIdx > endIdx) { @@ -189,7 +189,7 @@ function generateProjectStructure(allRoutes: RouteInfo[]): string { for (const route of groups) { const subcmds = getSubcommandLabel(route); lines.push( - `│ │ │ ├── ${`${route.name}/`.padEnd(13)}# ${subcmds}` + `│ │ │ ├── ${`${route.name}/`.padEnd(13)}# ${subcmds}`, ); } @@ -207,22 +207,22 @@ function generateProjectStructure(allRoutes: RouteInfo[]): string { const isLast = i === allStandaloneEntries.length - 1; const prefix = isLast ? "└──" : "├──"; lines.push( - `│ │ │ ${prefix} ${`${entry.name}.ts`.padEnd(13)}# ${entry.brief}` + `│ │ │ ${prefix} ${`${entry.name}.ts`.padEnd(13)}# ${entry.brief}`, ); } lines.push("│ │ ├── lib/ # Shared utilities"); lines.push( - "│ │ └── types/ # TypeScript types and Valibot schemas" + "│ │ └── types/ # TypeScript types and Valibot schemas", ); lines.push( - "│ ├── test/ # Test files (mirrors src/ structure)" + "│ ├── test/ # Test files (mirrors src/ structure)", ); lines.push("│ ├── script/ # Build and utility scripts"); lines.push("│ └── plugins/ # Agent skill files"); lines.push("└── apps/"); lines.push( - " └── cli-docs/ # Documentation site (Astro + Starlight)" + " └── cli-docs/ # Documentation site (Astro + Starlight)", ); lines.push("```"); @@ -238,7 +238,7 @@ function generateProjectStructure(allRoutes: RouteInfo[]): string { * Returns groups in insertion order. */ function groupScopesByResource( - scopes: readonly string[] + scopes: readonly string[], ): Map { const groups = new Map(); for (const scope of scopes) { @@ -294,7 +294,7 @@ function extractPnpmVersion(): string { if (!match) { throw new Error( `Cannot extract pnpm version from packageManager: "${pm}". ` + - "Expected format: pnpm@X.Y.Z" + "Expected format: pnpm@X.Y.Z", ); } return match[1]; @@ -315,7 +315,7 @@ function extractNodeVersion(): string { if (!match) { throw new Error( `Cannot extract Node.js version from engines.node: "${constraint}". ` + - "Expected a semver-like version (e.g., >=22.15)" + "Expected a semver-like version (e.g., >=22.15)", ); } return match[1]; @@ -332,7 +332,6 @@ function extractNodeVersion(): string { */ function extractDevNodeVersion(): string { const constraint: string | undefined = - // biome-ignore lint/suspicious/noExplicitAny: devEngines is not in the pkg type (pkg as any).devEngines?.runtime?.version ?? pkg.engines?.node; if (!constraint) { throw new Error("Missing devEngines.runtime.version and engines.node"); @@ -341,7 +340,7 @@ function extractDevNodeVersion(): string { if (!match) { throw new Error( `Cannot extract dev Node.js version from "${constraint}". ` + - "Expected a semver-like version (e.g., >=22.15)" + "Expected a semver-like version (e.g., >=22.15)", ); } return match[1]; @@ -436,7 +435,7 @@ function generateDevScripts(): string { */ function generateDevEnvVarsTable(): string { const entries = ENV_VAR_REGISTRY.filter( - (e: EnvVarEntry) => e.devGuide !== undefined + (e: EnvVarEntry) => e.devGuide !== undefined, ); const lines: string[] = [ "| Variable | Description | Default |", @@ -487,22 +486,22 @@ const SELF_HOSTED_TABLE_ENTRIES: readonly [string, string][] = [ function generateSelfHostedEnvVarsTable(): string { const selfHostedNames = new Set( ENV_VAR_REGISTRY.filter((e: EnvVarEntry) => e.selfHosted === true).map( - (e: EnvVarEntry) => e.name - ) + (e: EnvVarEntry) => e.name, + ), ); for (const [name] of SELF_HOSTED_TABLE_ENTRIES) { if (!selfHostedNames.has(name)) { throw new Error( - `Self-hosted table entry "${name}" is not tagged selfHosted in env-registry.ts` + `Self-hosted table entry "${name}" is not tagged selfHosted in env-registry.ts`, ); } } if (selfHostedNames.size !== SELF_HOSTED_TABLE_ENTRIES.length) { const missing = [...selfHostedNames].filter( - (n) => !SELF_HOSTED_TABLE_ENTRIES.some(([name]) => name === n) + (n) => !SELF_HOSTED_TABLE_ENTRIES.some(([name]) => name === n), ); throw new Error( - `Registry entries tagged selfHosted but missing from self-hosted table: ${missing.join(", ")}` + `Registry entries tagged selfHosted but missing from self-hosted table: ${missing.join(", ")}`, ); } @@ -659,7 +658,7 @@ for (const section of sections) { original, section.sectionName, generated, - section.markerStyle ?? "html" + section.markerStyle ?? "html", ); if (updated !== original) { @@ -677,7 +676,7 @@ for (const section of sections) { if (isCheck && staleCount > 0) { console.error( - `\n${staleCount} section(s) are stale. Run: pnpm run generate:docs` + `\n${staleCount} section(s) are stale. Run: pnpm run generate:docs`, ); process.exit(1); } diff --git a/packages/cli/script/generate-sdk.ts b/packages/cli/script/generate-sdk.ts index 1cb4c5308..19f953cc6 100644 --- a/packages/cli/script/generate-sdk.ts +++ b/packages/cli/script/generate-sdk.ts @@ -79,7 +79,7 @@ type SdkFlagInfo = { */ function discoverCommands( target: RouteMap | Command, - pathPrefix: string[] + pathPrefix: string[], ): DiscoveredCommand[] { if (isCommand(target)) { return [{ path: pathPrefix, command: target }]; @@ -267,9 +267,8 @@ function formatPropertyName(name: string, opt: string): string { */ function generateReturnType( command: Command, - typeName: string + typeName: string, ): { typeDef: string; typeName: string } | null { - // biome-ignore lint/suspicious/noExplicitAny: __jsonSchema is a non-standard property const schema = (command as any).__jsonSchema; if (!schema) { return null; @@ -321,7 +320,7 @@ function buildTypeName(path: string[]): string { function generateParamsInterface( path: string[], positional: PositionalInfo, - flags: SdkFlagInfo[] + flags: SdkFlagInfo[], ): { name: string; code: string } | null { const lines: string[] = []; @@ -353,7 +352,7 @@ function generateParamsInterface( function buildInvokeArgs( path: string[], positional: PositionalInfo, - flags: SdkFlagInfo[] + flags: SdkFlagInfo[], ): { flagObj: string; positionalExpr: string; pathStr: string } { const flagEntries = flags.map((f) => { const camel = camelCase(f.name); @@ -397,12 +396,12 @@ function generateMethodBody( path: string[], positional: PositionalInfo, flags: SdkFlagInfo[], - returnType: string + returnType: string, ): string { const { flagObj, positionalExpr, pathStr } = buildInvokeArgs( path, positional, - flags + flags, ); return `invoke<${returnType}>(${pathStr}, ${flagObj}, ${positionalExpr}) as Promise<${returnType}>`; } @@ -427,7 +426,7 @@ function generateStreamingMethodBody(opts: StreamingMethodOpts): string { const { flagObj, positionalExpr, pathStr } = buildInvokeArgs( opts.path, opts.positional, - opts.flags + opts.flags, ); // Build the streaming condition: params?.follow !== undefined || params?.refresh !== undefined @@ -471,7 +470,7 @@ function insertMethod( tree: NamespaceNode, path: string[], methodCode: string, - typeDecl: string + typeDecl: string, ): void { let node = tree; const namespaceParts = path.slice(0, -1); diff --git a/packages/cli/script/generate-skill-markdown.ts b/packages/cli/script/generate-skill-markdown.ts index eb19074f4..4b9c24782 100644 --- a/packages/cli/script/generate-skill-markdown.ts +++ b/packages/cli/script/generate-skill-markdown.ts @@ -8,7 +8,7 @@ const COMMAND_HEADING_RE = /** Extract the literal command path from a generated command heading. */ export function extractCommandPathFromHeading( - heading: string + heading: string, ): string | undefined { const match = COMMAND_HEADING_RE.exec(heading); return match?.[1] ? `sentry ${match[1]}` : undefined; @@ -19,7 +19,7 @@ export function matchExampleToCommand( code: string, commandPaths: readonly string[], groupFallback: string, - defaultCommandPath?: string + defaultCommandPath?: string, ): string | undefined { // Prefer the longest path so `sentry auth login` wins over bare `sentry auth` // when both would otherwise match via includes(). diff --git a/packages/cli/script/generate-skill.ts b/packages/cli/script/generate-skill.ts index 7ce1ef1d9..ce5993acd 100644 --- a/packages/cli/script/generate-skill.ts +++ b/packages/cli/script/generate-skill.ts @@ -43,7 +43,7 @@ if (!existsSync(SKILL_CONTENT_PATH)) { mkdirSync("src/generated", { recursive: true }); writeFileSync( SKILL_CONTENT_PATH, - "export const SKILL_FILES: ReadonlyMap = new Map();\n" + "export const SKILL_FILES: ReadonlyMap = new Map();\n", ); } @@ -153,7 +153,7 @@ function stripMdxComponents(markdown: string): string { result = result.replace(/<[A-Z][a-zA-Z]*[^>]*\/>/g, ""); result = result.replace( /<[A-Z][a-zA-Z]*[^>]*>[\s\S]*?<\/[A-Z][a-zA-Z]*>/g, - "" + "", ); result = result.replace(/\n{3,}/g, "\n\n"); return result.trim(); @@ -163,7 +163,7 @@ function stripMdxComponents(markdown: string): string { function extractSection(markdown: string, heading: string): string | null { const headingPattern = new RegExp( `^(#{1,6})\\s+${escapeRegex(heading)}\\s*$`, - "m" + "m", ); const match = markdown.match(headingPattern); if (!match || match.index === undefined) { @@ -183,7 +183,7 @@ function extractSection(markdown: string, heading: string): string | null { /** Extract all code blocks from markdown */ function extractCodeBlocks( markdown: string, - language?: string + language?: string, ): { code: string; lang: string }[] { const blocks: { code: string; lang: string }[] = []; const pattern = new RegExp(CODE_BLOCK_REGEX.source, CODE_BLOCK_REGEX.flags); @@ -213,7 +213,7 @@ async function loadDoc(relativePath: string): Promise { const fullPath = `${DOCS_PATH}/${relativePath}`; const exists = await access(fullPath).then( () => true, - () => false + () => false, ); if (!exists) { return null; @@ -231,7 +231,7 @@ function extractPackageManagerCommand(rawContent: string): string | null { /** Generate installation section from docs content */ function generateInstallSection( installSection: string, - rawContent: string + rawContent: string, ): string[] { const lines: string[] = []; lines.push("### Installation"); @@ -276,7 +276,7 @@ async function loadPrerequisites(): Promise { const fullPath = `${DOCS_PATH}/getting-started.mdx`; const exists = await access(fullPath).then( () => true, - () => false + () => false, ); if (!exists) { return getDefaultPrerequisites(); @@ -334,7 +334,7 @@ sentry auth status function appendExample( map: Map, key: string, - code: string + code: string, ): void { const list = map.get(key) ?? []; list.push(code); @@ -347,7 +347,7 @@ function appendExample( */ function collectCommandPaths( tokens: Token[], - examples: Map + examples: Map, ): string[] { const paths: string[] = []; for (const token of tokens) { @@ -376,7 +376,7 @@ function associateCodeBlocks( commandGroup: string; examples: Map; defaultCommandPath?: string; - } + }, ): void { const { commandPaths, commandGroup, examples, defaultCommandPath } = context; const groupFallback = `sentry ${commandGroup}`; @@ -397,7 +397,7 @@ function associateCodeBlocks( code, commandPaths, groupFallback, - defaultCommandPath + defaultCommandPath, ); if (target) { appendExample(examples, target, code); @@ -417,7 +417,7 @@ function associateCodeBlocks( */ async function loadCommandExamples( commandGroup: string, - defaultCommandName?: string + defaultCommandName?: string, ): Promise> { const docContent = await loadDoc(`commands/${commandGroup}.md`); if (!docContent) { @@ -488,7 +488,7 @@ async function extractRoutes(routeMap: RouteMap): Promise { : undefined; const docExamples = await loadCommandExamples( routeName, - defaultCommandName + defaultCommandName, ); if (isRouteMap(target)) { result.push({ @@ -583,7 +583,7 @@ function generateFullCommandDoc(cmd: CommandInfo): string { if (cmd.jsonFields && cmd.jsonFields.length > 0) { lines.push(""); lines.push( - "**JSON Fields** (use `--json --fields` to select specific fields):" + "**JSON Fields** (use `--json --fields` to select specific fields):", ); lines.push(""); lines.push("| Field | Type | Description |"); @@ -632,7 +632,7 @@ function capitalize(s: string): string { function generateReferenceFile( refName: string, groupRoutes: RouteInfo[], - version: string + version: string, ): string { const route = groupRoutes[0]; const title = `${capitalize(refName)} Commands`; @@ -665,7 +665,7 @@ function generateReferenceFile( // Note about global flags lines.push( - "All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags." + "All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags.", ); lines.push(""); @@ -691,7 +691,7 @@ function generateCompactCommandLine(cmd: CommandInfo): string { */ function generateCompactCommandsSection( routeInfos: RouteInfo[], - referenceFiles: Map + referenceFiles: Map, ): string { const lines: string[] = []; lines.push("## Command Reference"); @@ -748,7 +748,7 @@ async function generateSupplementarySections(): Promise { lines.push(""); } else { lines.push( - "Most commands support `--json` flag for JSON output, making it easy to integrate with other tools." + "Most commands support `--json` flag for JSON output, making it easy to integrate with other tools.", ); lines.push(""); } @@ -759,7 +759,7 @@ async function generateSupplementarySections(): Promise { lines.push(overview.webFlag); } else { lines.push( - "View commands support `-w` or `--web` flag to open the resource in your browser." + "View commands support `-w` or `--web` flag to open the resource in your browser.", ); } @@ -781,7 +781,7 @@ type GeneratedFiles = Map; * need for manual route-to-reference mappings that can go stale. */ function groupRoutesByReference( - routeInfos: RouteInfo[] + routeInfos: RouteInfo[], ): Map { const groups = new Map(); for (const route of routeInfos) { @@ -799,7 +799,7 @@ function groupRoutesByReference( * @returns Map of relative file paths → content */ async function generateAllSkillFiles( - routeMap: RouteMap + routeMap: RouteMap, ): Promise { const files: GeneratedFiles = new Map(); const version = await getPackageVersion(); @@ -847,7 +847,7 @@ async function generateAllSkillFiles( indexSections.push(""); const nestedGuidance = agentGuidance.replace( /^(#{2,6})\s/gm, - (_, hashes: string) => `#${hashes} ` + (_, hashes: string) => `#${hashes} `, ); indexSections.push(nestedGuidance); indexSections.push(""); @@ -859,7 +859,7 @@ async function generateAllSkillFiles( // Compact command reference indexSections.push( - generateCompactCommandsSection(routeInfos, referenceFileNames) + generateCompactCommandsSection(routeInfos, referenceFileNames), ); // Supplementary sections @@ -926,7 +926,7 @@ await writeFile(`${PUBLIC_SKILL_DIR}/SKILL.md`, publicSkill); const indexJson = generateIndexJson(files); const indexJsonDir = INDEX_JSON_PATH.substring( 0, - INDEX_JSON_PATH.lastIndexOf("/") + INDEX_JSON_PATH.lastIndexOf("/"), ); mkdirSync(indexJsonDir, { recursive: true }); await writeFile(INDEX_JSON_PATH, indexJson); @@ -960,8 +960,8 @@ await writeFile(SKILL_CONTENT_PATH, skillContentModule); // Report what was generated const refCount = [...files.keys()].filter((k) => - k.startsWith("references/") + k.startsWith("references/"), ).length; console.log( - `Generated ${OUTPUT_PATH} + ${refCount} reference files + ${INDEX_JSON_PATH} + ${SKILL_CONTENT_PATH}` + `Generated ${OUTPUT_PATH} + ${refCount} reference files + ${INDEX_JSON_PATH} + ${SKILL_CONTENT_PATH}`, ); diff --git a/packages/cli/script/require-shim.mjs b/packages/cli/script/require-shim.mjs index 0d8420ce5..adc552fa4 100644 --- a/packages/cli/script/require-shim.mjs +++ b/packages/cli/script/require-shim.mjs @@ -18,7 +18,7 @@ import { createRequire, registerHooks } from "node:module"; if (typeof globalThis.require === "undefined") { globalThis.require = createRequire( - new URL("../package.json", import.meta.url) + new URL("../package.json", import.meta.url), ); } diff --git a/packages/cli/script/sdk-declarations.ts b/packages/cli/script/sdk-declarations.ts index c32a7772c..903da0870 100644 --- a/packages/cli/script/sdk-declarations.ts +++ b/packages/cli/script/sdk-declarations.ts @@ -22,7 +22,7 @@ export function extractSentryOptions(sdkTypesSource: string): string { const startMatch = sdkTypesSource.match(SENTRY_OPTIONS_START); if (!startMatch || startMatch.index === undefined) { throw new Error( - `Could not find the \`SentryOptions\` declaration in ${SDK_TYPES_PATH}.` + `Could not find the \`SentryOptions\` declaration in ${SDK_TYPES_PATH}.`, ); } @@ -42,7 +42,7 @@ export function extractSentryOptions(sdkTypesSource: string): string { const endMatch = sdkTypesSource.slice(i - 1).match(SENTRY_OPTIONS_END); if (depth !== 0 || !endMatch) { throw new Error( - `Could not find the \`SentryOptions\` declaration in ${SDK_TYPES_PATH}.` + `Could not find the \`SentryOptions\` declaration in ${SDK_TYPES_PATH}.`, ); } diff --git a/packages/cli/script/text-import-plugin.ts b/packages/cli/script/text-import-plugin.ts index 418f6fc0f..95d75613e 100644 --- a/packages/cli/script/text-import-plugin.ts +++ b/packages/cli/script/text-import-plugin.ts @@ -133,7 +133,7 @@ export const textImportPlugin: Plugin = { throw new Error( `text-import-plugin: failed to process ${sourcePath} → ${outPath}: ${ err instanceof Error ? err.message : String(err) - }` + }`, ); } diff --git a/packages/cli/src/app.ts b/packages/cli/src/app.ts index c1a11012b..f0eff58a1 100644 --- a/packages/cli/src/app.ts +++ b/packages/cli/src/app.ts @@ -224,9 +224,9 @@ const routesWithDefaultCommand: ReadonlySet = new Set( .filter( (e) => isRouteMap(e.target as unknown) && - (e.target as unknown as RouteMap).getDefaultCommand?.() + (e.target as unknown as RouteMap).getDefaultCommand?.(), ) - .map((e) => e.name.original) + .map((e) => e.name.original), ); /** @@ -278,7 +278,7 @@ function detectPluralAliasMisuse(ansiColor: boolean): string | undefined { */ function formatSynonymError( exc: unknown, - ansiColor: boolean + ansiColor: boolean, ): string | undefined { if (!(exc instanceof CliError)) { return; @@ -315,7 +315,7 @@ const customText: ApplicationText = { ...text_en, exceptionWhileParsingArguments: ( exc: unknown, - ansiColor: boolean + ansiColor: boolean, ): string => { // Case A: bare route group with no subcommand (e.g., `sentry issue`) if (exc instanceof UnsatisfiedPositionalError) { diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 1b8580abf..b598bddbf 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -51,7 +51,7 @@ async function preloadProjectContext(cwd: string): Promise { // Apply persistent URL default (lower priority than env vars and .sentryclirc). const env = getEnv(); if (!(env.SENTRY_HOST?.trim() || env.SENTRY_URL?.trim())) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { getDefaultUrl } = await import("./lib/db/defaults.js"); const url = getDefaultUrl(); @@ -172,15 +172,14 @@ function isUnknownCommandExit(unknownCode: number): boolean { async function recoverUnknownCommandHelp( cliArgs: string[], executor: (argv: string[]) => Promise, - unknownCode: number + unknownCode: number, ): Promise { if (!isUnknownCommandExit(unknownCode) || cliArgs[0] === "help") { return; } - const { isVersionRequest, rewriteHelpJsonToHelpCommand } = await import( - "./lib/help.js" - ); + const { isVersionRequest, rewriteHelpJsonToHelpCommand } = + await import("./lib/help.js"); if (isVersionRequest(cliArgs)) { process.exitCode = 0; @@ -202,8 +201,8 @@ async function recoverUnknownCommandHelp( const { warning } = await import("./lib/formatters/colors.js"); process.stderr.write( warning( - `Tip: use --help for help (e.g., sentry ${groupArgs.join(" ")} --help)\n` - ) + `Tip: use --help for help (e.g., sentry ${groupArgs.join(" ")} --help)\n`, + ), ); await executor(["help", ...groupArgs]); } @@ -226,7 +225,7 @@ async function recoverUnknownCommandHelp( */ type ErrorMiddleware = ( proceed: (cmdInput: string[]) => Promise, - retryArgs: string[] + retryArgs: string[], ) => Promise; /** @@ -240,18 +239,16 @@ export async function runCli(cliArgs: string[]): Promise { const { ExitCode, run } = await import("@stricli/core"); const { app } = await import("./app.js"); const { buildContext } = await import("./context.js"); - const { AuthError, OutputError, getExitCode } = await import( - "./lib/errors.js" - ); + const { AuthError, OutputError, getExitCode } = + await import("./lib/errors.js"); const { error } = await import("./lib/formatters/colors.js"); const { runInteractiveLogin } = await import("./lib/interactive-login.js"); const { recoverWithAutoLogin } = await import("./lib/auto-auth.js"); const { getEnvLogLevel, setLogLevel } = await import("./lib/logger.js"); const { scheduleForceExit } = await import("./lib/force-exit.js"); const { closeGlobalDispatcher } = await import("./lib/close-dispatcher.js"); - const { isTrialEligible, promptAndStartTrial } = await import( - "./lib/seer-trial.js" - ); + const { isTrialEligible, promptAndStartTrial } = + await import("./lib/seer-trial.js"); const { withTelemetry } = await import("./lib/telemetry.js"); const { startCleanupOldBinary } = await import("./lib/upgrade.js"); const { @@ -285,9 +282,9 @@ export async function runCli(cliArgs: string[]): Promise { } catch (err) { if (isTrialEligible(err)) { const started = await promptAndStartTrial( - // biome-ignore lint/style/noNonNullAssertion: isTrialEligible guarantees orgSlug is defined + // oxlint-disable-next-line typescript/no-non-null-assertion -- isTrialEligible guarantees orgSlug is defined err.orgSlug!, - err.reason + err.reason, ); if (started) { @@ -345,14 +342,13 @@ export async function runCli(cliArgs: string[]): Promise { const source = plan.sources.find((s) => s.token)?.path ?? "~/.sentryclirc"; process.stderr.write( `\nFound auth token in ${source}\n` + - "Import settings to the new CLI? This stores your token with proper host scoping.\n\n" + "Import settings to the new CLI? This stores your token with proper host scoping.\n\n", ); const consent = await promptImportConsent(); if (consent === "declined") { - const { markImportDeclined } = await import( - "./lib/sentryclirc-import.js" - ); + const { markImportDeclined } = + await import("./lib/sentryclirc-import.js"); markImportDeclined(plan.sources); return "declined"; } @@ -503,7 +499,7 @@ export async function runCli(cliArgs: string[]): Promise { return; } // Best-effort: telemetry must never crash the CLI - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await reportUnknownCommand(argv); } catch { @@ -553,7 +549,7 @@ export async function runCli(cliArgs: string[]): Promise { const pathSegments = argv.filter((t) => !t.startsWith("-")); const resolved = resolveCommandPath( routes as unknown as Parameters[0], - pathSegments + pathSegments, ); const unknownToken = resolved?.kind === "unresolved" ? resolved.input : (argv.at(-1) ?? ""); diff --git a/packages/cli/src/commands/agent-conversation/list.ts b/packages/cli/src/commands/agent-conversation/list.ts index 009809093..d122fb385 100644 --- a/packages/cli/src/commands/agent-conversation/list.ts +++ b/packages/cli/src/commands/agent-conversation/list.ts @@ -77,7 +77,7 @@ function formatListHuman(result: ConversationListResult): string { function jsonTransform( result: ConversationListResult, - fields?: string[] + fields?: string[], ): unknown { const items = fields && fields.length > 0 @@ -161,7 +161,7 @@ export const listCommand = buildListCommand("agent-conversation", { const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const timeParams = timeRangeToApiParams(flags.period); @@ -177,7 +177,7 @@ export const listCommand = buildListCommand("agent-conversation", { limit: flags.limit, cursor, ...timeParams, - }) + }), ); advancePaginationState(PAGINATION_KEY, contextKey, direction, nextCursor); diff --git a/packages/cli/src/commands/agent-conversation/view.ts b/packages/cli/src/commands/agent-conversation/view.ts index 9d537aa3a..29423a37f 100644 --- a/packages/cli/src/commands/agent-conversation/view.ts +++ b/packages/cli/src/commands/agent-conversation/view.ts @@ -108,7 +108,7 @@ export const viewCommand = buildCommand({ message: "Fetching conversation spans...", json: flags.json, }, - () => getConversationSpans(org, conversationId) + () => getConversationSpans(org, conversationId), ); const result = buildTranscriptResult(conversationId, org, spans, title); diff --git a/packages/cli/src/commands/alert/issues/create.ts b/packages/cli/src/commands/alert/issues/create.ts index e25911de3..e0025373f 100644 --- a/packages/cli/src/commands/alert/issues/create.ts +++ b/packages/cli/src/commands/alert/issues/create.ts @@ -165,7 +165,7 @@ export const createCommand = buildCommand({ async *func( this: SentryContext, flags: CreateFlags, - arg: string | undefined + arg: string | undefined, ) { const { cwd } = this; if (!flags.name.trim()) { @@ -174,7 +174,7 @@ export const createCommand = buildCommand({ if (flags.frequency <= 0) { throw new ValidationError( "frequency must be greater than 0.", - "frequency" + "frequency", ); } @@ -195,7 +195,7 @@ export const createCommand = buildCommand({ if (targets.length !== 1) { throw new ValidationError( "Provide a target that resolves to exactly one project for create.", - "target" + "target", ); } const target = targets[0] as (typeof targets)[number]; @@ -247,7 +247,7 @@ export const createCommand = buildCommand({ id: String(created.id ?? ""), name: String(created.name ?? flags.name), status: String( - created.status ?? (created.enabled === false ? "disabled" : "active") + created.status ?? (created.enabled === false ? "disabled" : "active"), ), } satisfies CreateResult); }, diff --git a/packages/cli/src/commands/alert/issues/delete.ts b/packages/cli/src/commands/alert/issues/delete.ts index 3937594d6..f31b1449b 100644 --- a/packages/cli/src/commands/alert/issues/delete.ts +++ b/packages/cli/src/commands/alert/issues/delete.ts @@ -96,7 +96,7 @@ export const deleteCommand = buildDeleteCommand({ if (parsed.type !== "explicit") { throw new ValidationError( "Issue alert delete requires an explicit // target.", - "target" + "target", ); } @@ -111,7 +111,7 @@ export const deleteCommand = buildDeleteCommand({ const { target, rule } = await resolveIssueAlertRule( targets, ref, - USAGE_HINT + USAGE_HINT, ); const key = `${target.org}/${target.project}/${rule.id}`; @@ -130,7 +130,7 @@ export const deleteCommand = buildDeleteCommand({ if (!isConfirmationBypassed(flags)) { const ok = await confirmByTyping( key, - `Type '${key}' to permanently delete this issue alert rule:` + `Type '${key}' to permanently delete this issue alert rule:`, ); if (!ok) { logger.info("Delete cancelled."); diff --git a/packages/cli/src/commands/alert/issues/edit.ts b/packages/cli/src/commands/alert/issues/edit.ts index 94ac4bc61..c05c00954 100644 --- a/packages/cli/src/commands/alert/issues/edit.ts +++ b/packages/cli/src/commands/alert/issues/edit.ts @@ -66,7 +66,7 @@ function validateIssueEditFlags(flags: EditFlags): void { if (!hasIssueMutations(flags)) { throw new ValidationError( "Pass at least one editable field (for example --name or --status).", - "name" + "name", ); } if (flags.frequency !== undefined && flags.frequency <= 0) { @@ -74,10 +74,10 @@ function validateIssueEditFlags(flags: EditFlags): void { } } -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: per-flag merge with user-only validation +// per-flag merge with user-only validation function applyIssueEdits( body: Record, - flags: EditFlags + flags: EditFlags, ): Record { const conditions = parseJsonObjectList(flags.condition, "condition"); const actions = parseJsonObjectList(flags.action, "action"); @@ -271,7 +271,7 @@ export const editCommand = buildCommand({ const { target, rule } = await resolveIssueAlertRule( targets, ref, - USAGE_HINT + USAGE_HINT, ); const body = { @@ -288,7 +288,7 @@ export const editCommand = buildCommand({ // The workflows endpoint returns `enabled` rather than `status`; map it // back to a status label so human/JSON output matches the create path. status: String( - updated.status ?? (updated.enabled === false ? "disabled" : "active") + updated.status ?? (updated.enabled === false ? "disabled" : "active"), ), } satisfies EditResult); }, diff --git a/packages/cli/src/commands/alert/issues/list.ts b/packages/cli/src/commands/alert/issues/list.ts index 78070efe4..a7778109e 100644 --- a/packages/cli/src/commands/alert/issues/list.ts +++ b/packages/cli/src/commands/alert/issues/list.ts @@ -134,7 +134,7 @@ const issueAlertListMeta: ListCommandMeta = { */ async function fetchRulesForTarget( target: ResolvedTarget, - options: { limit: number; startCursor?: string } + options: { limit: number; startCursor?: string }, ): Promise { const result = await withAuthGuard(async () => { const rules: IssueAlertRule[] = []; @@ -147,7 +147,7 @@ async function fetchRulesForTarget( { perPage: Math.min(options.limit - rules.length, API_MAX_PER_PAGE), cursor: serverCursor, - } + }, ); for (const rule of data) { @@ -188,7 +188,7 @@ async function fetchRulesForTarget( async function resolveWebUrl( parsed: ReturnType, - cwd: string + cwd: string, ): Promise { if (parsed.type === "explicit") { return buildIssueAlertsUrl(parsed.org, parsed.project); @@ -206,7 +206,7 @@ async function resolveWebUrl( const resolution = await classifyProjectSearchTarget(parsed); if (resolution.kind === "organization") { logger.warn( - `'${parsed.projectSlug}' is an organization, not a project. Opening organization '${resolution.org}'.` + `'${parsed.projectSlug}' is an organization, not a project. Opening organization '${resolution.org}'.`, ); return buildIssueAlertsUrl(resolution.org); } @@ -226,15 +226,15 @@ async function resolveWebUrl( if (orgs.length !== 1) { throw new ValidationError( "--web resolved alert rules in multiple organizations. Specify an explicit / or / target.", - "target" + "target", ); } const projects = [...new Set(targets.map((t) => t.project))]; return buildIssueAlertsUrl( - // biome-ignore lint/style/noNonNullAssertion: orgs length is checked above + // oxlint-disable-next-line typescript/no-non-null-assertion -- orgs length is checked above orgs[0]!, - projects.length === 1 ? projects[0] : undefined + projects.length === 1 ? projects[0] : undefined, ); } @@ -247,15 +247,15 @@ type ResolvedTargetsOptions = { projectSearchResolution?: ProjectSearchTargetResolution; }; -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: inherent multi-target resolution, compound cursor, error handling, and display logic +// inherent multi-target resolution, compound cursor, error handling, and display logic async function handleResolvedTargets( - options: ResolvedTargetsOptions + options: ResolvedTargetsOptions, ): Promise { const { parsed, flags, cwd, projectSearchResolution } = options; const { targets, footer, detectedDsns } = await resolveProjectBoundTargets( parsed, - { cwd, usageHint: USAGE_HINT, projectSearchResolution } + { cwd, usageHint: USAGE_HINT, projectSearchResolution }, ); if (targets.length === 0) { @@ -270,11 +270,11 @@ async function handleResolvedTargets( const { cursor: rawCursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { startCursors, exhausted: exhaustedTargets } = decodeTargetCursors( rawCursor, - sortedTargetKeys + sortedTargetKeys, ); const activeTargets = @@ -298,32 +298,32 @@ async function handleResolvedTargets( fetchGroup: fetchRulesForTarget, onProgress: (fetched) => { setMessage( - `${baseMessage}, ${fetched} and counting (up to ${flags.limit})...` + `${baseMessage}, ${fetched} and counting (up to ${flags.limit})...`, ); }, - }) + }), ); const validResults: AlertRuleListFetchResult[] = []; const failures: { target: ResolvedTarget; error: Error }[] = []; for (let i = 0; i < results.length; i++) { - // biome-ignore lint/style/noNonNullAssertion: index within bounds + // oxlint-disable-next-line typescript/no-non-null-assertion -- index within bounds const result = results[i]!; if (result.success) { validResults.push(result.data); } else { - // biome-ignore lint/style/noNonNullAssertion: index within bounds + // oxlint-disable-next-line typescript/no-non-null-assertion -- index within bounds failures.push({ target: activeTargets[i]!, error: result.error }); } } if (validResults.length === 0 && failures.length > 0) { - // biome-ignore lint/style/noNonNullAssertion: guarded by failures.length > 0 + // oxlint-disable-next-line typescript/no-non-null-assertion -- guarded by failures.length > 0 const { error: first } = failures[0]!; throwAlertListFetchFailure( `Failed to fetch alert rules from ${targets.length} project(s)`, - first + first, ); } @@ -332,7 +332,7 @@ async function handleResolvedTargets( .map(({ target: t }) => `${t.org}/${t.project}`) .join(", "); logger.warn( - `Failed to fetch alert rules from ${failedNames}. Showing results from ${validResults.length} project(s).` + `Failed to fetch alert rules from ${failedNames}. Showing results from ${validResults.length} project(s).`, ); } @@ -353,20 +353,20 @@ async function handleResolvedTargets( // Apply client-side name filter const allRows: AlertRuleRow[] = validResults.flatMap((r) => - r.rules.map((rule) => ({ rule, target: r.target })) + r.rules.map((rule) => ({ rule, target: r.target })), ); const filteredRows = flags.query ? allRows.filter((row) => (row.rule.name ?? "") .toLowerCase() - .includes(flags.query?.toLowerCase() ?? "") + .includes(flags.query?.toLowerCase() ?? ""), ) : allRows; const displayRows = trimWithGroupGuarantee( filteredRows, flags.limit, - (row) => `${row.target.org}/${row.target.project}` + (row) => `${row.target.org}/${row.target.project}`, ); const trimmed = displayRows.length < filteredRows.length; const cursorValues: (string | null)[] = sortedTargetKeys.map((key) => { @@ -397,7 +397,7 @@ async function handleResolvedTargets( PAGINATION_KEY, contextKey, direction, - compoundNextCursor + compoundNextCursor, ); const hasPrev = hasPreviousPage(PAGINATION_KEY, contextKey); @@ -406,7 +406,7 @@ async function handleResolvedTargets( failures, "project", ({ target: t }) => `${t.org}/${t.project}`, - ({ error }) => error + ({ error }) => error, ); const nav = paginationHint({ @@ -465,7 +465,7 @@ function formatIssueAlertListHuman(result: IssueAlertListResult): string { const rows = result.displayRows ?? []; const uniqueProjects = new Set( - rows.map((r) => `${r.target.org}/${r.target.project}`) + rows.map((r) => `${r.target.org}/${r.target.project}`), ); const isMultiProject = uniqueProjects.size > 1; @@ -574,12 +574,12 @@ export const listCommand = buildListCommand("alert issues", { if (flags.web) { await openInBrowser( await resolveWebUrl(parsed, cwd), - "issue alert rules" + "issue alert rules", ); return; } - // biome-ignore lint/suspicious/noExplicitAny: shared handler accepts any mode variant + // oxlint-disable-next-line typescript/no-explicit-any -- shared handler accepts any mode variant const resolveAndHandle: ModeHandler = (ctx) => handleResolvedTargets({ ...ctx, flags }); diff --git a/packages/cli/src/commands/alert/issues/rule-resolve.ts b/packages/cli/src/commands/alert/issues/rule-resolve.ts index a9e38529c..4580d6d8d 100644 --- a/packages/cli/src/commands/alert/issues/rule-resolve.ts +++ b/packages/cli/src/commands/alert/issues/rule-resolve.ts @@ -29,7 +29,7 @@ export type IssueRuleResolution = { */ export function parseIssueRuleArg( arg: string, - usageHint: string + usageHint: string, ): { ref: string; targetArg: string | undefined; @@ -38,7 +38,7 @@ export function parseIssueRuleArg( if (!trimmed) { throw new ValidationError( `Rule id or name is required.\nUse: ${usageHint}`, - "rule" + "rule", ); } @@ -51,7 +51,7 @@ export function parseIssueRuleArg( `Missing rule id or name after the project (got '${trimmed}').\n` + `Use: ${usageHint}\n` + `Example: ${trimmed}/`, - "rule" + "rule", ); } @@ -61,7 +61,7 @@ export function parseIssueRuleArg( if (!ref) { throw new ValidationError( `Invalid rule reference '${arg}'.\nUse: ${usageHint}`, - "rule" + "rule", ); } return { ref, targetArg: targetPart || undefined }; @@ -69,7 +69,7 @@ export function parseIssueRuleArg( /** List all issue alert rules for a project (paginated). */ export async function listAllIssueRulesForTarget( - target: ResolvedTarget + target: ResolvedTarget, ): Promise { const all: IssueAlertRule[] = []; let cursor: string | undefined; @@ -77,7 +77,7 @@ export async function listAllIssueRulesForTarget( const { data, nextCursor } = await listIssueAlertsPaginated( target.org, target.project, - { perPage: API_MAX_PER_PAGE, cursor } + { perPage: API_MAX_PER_PAGE, cursor }, ); all.push(...data); if (!nextCursor) { @@ -86,16 +86,16 @@ export async function listAllIssueRulesForTarget( cursor = nextCursor; } log.warn( - `Pagination limit reached for issue alert rules in ${target.org}/${target.project}. Results may be incomplete.` + `Pagination limit reached for issue alert rules in ${target.org}/${target.project}. Results may be incomplete.`, ); return all; } -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: per-target list + name resolution +// per-target list + name resolution export async function resolveIssueAlertRule( targets: ResolvedTarget[], ref: string, - usageHint: string + usageHint: string, ): Promise { if (isAllDigits(ref)) { const hits: IssueRuleResolution[] = []; @@ -117,13 +117,13 @@ export async function resolveIssueAlertRule( if (hits.length > 1) { throw new ValidationError( `Alert rule ID '${ref}' matched multiple projects.\n` + - "Use an explicit target: sentry alert issues //" + "Use an explicit target: sentry alert issues //", ); } throw new ResolutionError( `Issue alert rule '${ref}'`, "not found", - usageHint + usageHint, ); } @@ -138,7 +138,7 @@ export async function resolveIssueAlertRule( } } const exact = rules.find( - (rule) => rule.name && rule.name.toLowerCase() === ref.toLowerCase() + (rule) => rule.name && rule.name.toLowerCase() === ref.toLowerCase(), ); if (exact) { hits.push({ target, rule: exact }); @@ -151,7 +151,7 @@ export async function resolveIssueAlertRule( if (hits.length > 1) { throw new ValidationError( `Alert rule name '${ref}' matched multiple projects.\n` + - "Use an explicit target: sentry alert issues //" + "Use an explicit target: sentry alert issues //", ); } @@ -162,13 +162,13 @@ export async function resolveIssueAlertRule( throw new ValidationError( `No issue alert rule named '${ref}' in the selected project(s).\n\n` + `Did you mean:\n${lines}`, - "rule" + "rule", ); } throw new ResolutionError( `Issue alert rule '${ref}'`, "not found", - usageHint + usageHint, ); } diff --git a/packages/cli/src/commands/alert/issues/view.ts b/packages/cli/src/commands/alert/issues/view.ts index 077127d05..0144e227f 100644 --- a/packages/cli/src/commands/alert/issues/view.ts +++ b/packages/cli/src/commands/alert/issues/view.ts @@ -85,7 +85,7 @@ export const viewCommand = buildCommand({ if (flags.web && parsed.type === "explicit") { await openInBrowser( buildIssueAlertsUrl(parsed.org, parsed.project), - "issue alert rules" + "issue alert rules", ); return; } @@ -99,11 +99,11 @@ export const viewCommand = buildCommand({ } if (flags.web) { - // biome-ignore lint/style/noNonNullAssertion: guarded by length check above + // oxlint-disable-next-line typescript/no-non-null-assertion -- guarded by length check above const t = targets[0]!; await openInBrowser( buildIssueAlertsUrl(t.org, t.project), - "issue alert rules" + "issue alert rules", ); return; } diff --git a/packages/cli/src/commands/alert/list-utils.ts b/packages/cli/src/commands/alert/list-utils.ts index b040a17c4..c98254ea4 100644 --- a/packages/cli/src/commands/alert/list-utils.ts +++ b/packages/cli/src/commands/alert/list-utils.ts @@ -29,14 +29,14 @@ export function assertAlertListLimit(limit: number): void { throw new ValidationError( `--limit cannot exceed ${LIST_MAX_LIMIT}. ` + "Use --cursor to paginate through larger result sets.", - "limit" + "limit", ); } } export function throwAlertListFetchFailure( prefix: string, - error: Error + error: Error, ): never { if (!(error instanceof ApiError)) { throw new Error(`${prefix}: ${error.message}`); @@ -46,7 +46,7 @@ export function throwAlertListFetchFailure( error.status, error.detail, error.endpoint, - error.enriched403 + error.enriched403, ); } @@ -54,7 +54,7 @@ export function buildAlertListFailureErrors( failures: TFailure[], labelKey: TKey, getLabel: (failure: TFailure) => string, - getError: (failure: TFailure) => Error + getError: (failure: TFailure) => Error, ): (Record & { status?: number; message: string })[] | undefined { if (failures.length === 0) { return; @@ -76,7 +76,7 @@ export function fetchAlertRulesWithBudget< TPage extends AlertRuleFetchPage, >( groups: TGroup[], - options: BudgetOptions + options: BudgetOptions, ): Promise<{ results: FetchResult[]; hasMore: boolean }> { return fetchGroupsWithBudget(groups, { ...options, diff --git a/packages/cli/src/commands/alert/metrics/create.ts b/packages/cli/src/commands/alert/metrics/create.ts index 2498a53fa..79390420a 100644 --- a/packages/cli/src/commands/alert/metrics/create.ts +++ b/packages/cli/src/commands/alert/metrics/create.ts @@ -55,13 +55,13 @@ function formatCreated(result: CreateResult): string { function resolveMetricCreateOrg( arg: string | undefined, - cwd: string + cwd: string, ): Promise { return resolveOrgOnlyFromArg( arg, cwd, "alert metrics create", - "sentry alert metrics create /" + "sentry alert metrics create /", ); } @@ -185,7 +185,7 @@ export const createCommand = buildCommand({ if (!projects || projects.length === 0) { throw new ValidationError( "A project is required to create a metric alert rule (pass --project).", - "project" + "project", ); } diff --git a/packages/cli/src/commands/alert/metrics/delete.ts b/packages/cli/src/commands/alert/metrics/delete.ts index a4db53f65..3a97f0157 100644 --- a/packages/cli/src/commands/alert/metrics/delete.ts +++ b/packages/cli/src/commands/alert/metrics/delete.ts @@ -77,19 +77,19 @@ export const deleteCommand = buildDeleteCommand({ requireExplicitTarget( parseOrgProjectArg(targetArg), "Metric alert target", - USAGE_HINT + USAGE_HINT, ); const org = await resolveOrgOnlyFromArg( targetArg, cwd, - "alert metrics delete" + "alert metrics delete", ); const orgSlugs = [org]; const { orgSlug, rule } = await resolveMetricAlertRule( orgSlugs, ref, - USAGE_HINT + USAGE_HINT, ); const key = `${orgSlug}/${rule.id}`; const name = rule.name; @@ -105,7 +105,7 @@ export const deleteCommand = buildDeleteCommand({ if (!isConfirmationBypassed(flags)) { const ok = await confirmByTyping( key, - `Type '${key}' to permanently delete this metric alert rule:` + `Type '${key}' to permanently delete this metric alert rule:`, ); if (!ok) { logger.info("Delete cancelled."); diff --git a/packages/cli/src/commands/alert/metrics/edit.ts b/packages/cli/src/commands/alert/metrics/edit.ts index 07f87ae65..6bba1666a 100644 --- a/packages/cli/src/commands/alert/metrics/edit.ts +++ b/packages/cli/src/commands/alert/metrics/edit.ts @@ -70,14 +70,14 @@ function validateMetricEditFlags(flags: EditFlags): void { if (!hasMetricMutations(flags)) { throw new ValidationError( "Pass at least one editable field (for example --name or --status).", - "name" + "name", ); } } function applyMetricCoreFields( body: Record, - flags: EditFlags + flags: EditFlags, ): string | undefined { let notice: string | undefined; if (flags.name !== undefined) { @@ -115,7 +115,7 @@ function applyMetricCoreFields( function applyMetricOptionalFields( body: Record, - flags: EditFlags + flags: EditFlags, ): Record { if (flags.trigger !== undefined) { body.triggers = parseJsonObjectList(flags.trigger, "trigger"); @@ -135,11 +135,11 @@ function applyMetricOptionalFields( function validateMetricBody( body: Record, - flags: EditFlags + flags: EditFlags, ): void { if (flags.trigger !== undefined) { validateMetricTriggers( - body.triggers as Record[] | undefined + body.triggers as Record[] | undefined, ); } if (flags.dataset !== undefined) { @@ -157,7 +157,7 @@ function validateMetricBody( ) { throw new ValidationError( "aggregate must be present and non-empty.", - "aggregate" + "aggregate", ); } } @@ -269,13 +269,13 @@ export const editCommand = buildCommand({ const org = await resolveOrgOnlyFromArg( targetArg, cwd, - "alert metrics edit" + "alert metrics edit", ); const orgSlugs = [org]; const { orgSlug, rule } = await resolveMetricAlertRule( orgSlugs, ref, - USAGE_HINT + USAGE_HINT, ); const body = { ...(await getMetricAlertRuleDocument(orgSlug, rule.id)), diff --git a/packages/cli/src/commands/alert/metrics/list.ts b/packages/cli/src/commands/alert/metrics/list.ts index d91f81d00..f5f01fe17 100644 --- a/packages/cli/src/commands/alert/metrics/list.ts +++ b/packages/cli/src/commands/alert/metrics/list.ts @@ -140,7 +140,7 @@ const metricAlertListMeta: ListCommandMeta = { */ async function fetchRulesForOrg( orgSlug: string, - options: { limit: number; startCursor?: string } + options: { limit: number; startCursor?: string }, ): Promise { const result = await withAuthGuard(async () => { const rules: MetricAlertRule[] = []; @@ -208,7 +208,7 @@ type ResolvedOrgsOptions = { async function resolveOrgs( parsed: ReturnType, cwd: string, - projectSearchResolution?: ProjectSearchTargetResolution + projectSearchResolution?: ProjectSearchTargetResolution, ): Promise<{ orgs: string[]; footer?: string }> { if (parsed.type === "explicit" || parsed.type === "org-all") { return { orgs: [parsed.org] }; @@ -226,7 +226,7 @@ async function resolveOrgs( async function resolveWebUrl( parsed: ReturnType, - cwd: string + cwd: string, ): Promise { let projectSearchResolution: ProjectSearchTargetResolution | undefined; if ( @@ -237,7 +237,7 @@ async function resolveWebUrl( const resolution = await classifyProjectSearchTarget(parsed); if (resolution.kind === "organization") { logger.warn( - `'${parsed.projectSlug}' is an organization, not a project. Opening organization '${resolution.org}'.` + `'${parsed.projectSlug}' is an organization, not a project. Opening organization '${resolution.org}'.`, ); return buildMetricAlertsUrl(resolution.org); } @@ -252,10 +252,10 @@ async function resolveWebUrl( if (uniqueOrgs.length !== 1) { throw new ValidationError( "--web resolved metric alert rules in multiple organizations. Specify an explicit / target.", - "target" + "target", ); } - // biome-ignore lint/style/noNonNullAssertion: length checked above + // oxlint-disable-next-line typescript/no-non-null-assertion -- length checked above return buildMetricAlertsUrl(uniqueOrgs[0]!); } @@ -263,16 +263,16 @@ async function resolveWebUrl( * Handle all four modes: resolve orgs → fetch within budget → compound cursor * per org → display. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: inherent multi-org resolution, compound cursor, error handling, and display logic +// inherent multi-org resolution, compound cursor, error handling, and display logic async function handleResolvedOrgs( - options: ResolvedOrgsOptions + options: ResolvedOrgsOptions, ): Promise { const { parsed, flags, cwd, projectSearchResolution } = options; const { orgs: resolved, footer } = await resolveOrgs( parsed, cwd, - projectSearchResolution + projectSearchResolution, ); if (resolved.length === 0) { @@ -287,11 +287,11 @@ async function handleResolvedOrgs( const { cursor: rawCursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { startCursors, exhausted: exhaustedOrgs } = decodeTargetCursors( rawCursor, - sortedOrgKeys + sortedOrgKeys, ); const activeOrgs = @@ -315,39 +315,39 @@ async function handleResolvedOrgs( fetchGroup: fetchRulesForOrg, onProgress: (fetched) => { setMessage( - `${baseMessage}, ${fetched} and counting (up to ${flags.limit})...` + `${baseMessage}, ${fetched} and counting (up to ${flags.limit})...`, ); }, - }) + }), ); const validResults: MetricRuleFetchResult[] = []; const failures: { orgSlug: string; error: Error }[] = []; for (let i = 0; i < results.length; i++) { - // biome-ignore lint/style/noNonNullAssertion: index within bounds + // oxlint-disable-next-line typescript/no-non-null-assertion -- index within bounds const result = results[i]!; if (result.success) { validResults.push(result.data); } else { - // biome-ignore lint/style/noNonNullAssertion: index within bounds + // oxlint-disable-next-line typescript/no-non-null-assertion -- index within bounds failures.push({ orgSlug: activeOrgs[i]!, error: result.error }); } } if (validResults.length === 0 && failures.length > 0) { - // biome-ignore lint/style/noNonNullAssertion: guarded by failures.length > 0 + // oxlint-disable-next-line typescript/no-non-null-assertion -- guarded by failures.length > 0 const { error: first } = failures[0]!; throwAlertListFetchFailure( `Failed to fetch metric alert rules from ${uniqueOrgs.length} organization(s)`, - first + first, ); } if (failures.length > 0) { const failedNames = failures.map(({ orgSlug }) => orgSlug).join(", "); logger.warn( - `Failed to fetch metric alert rules from ${failedNames}. Showing results from ${validResults.length} organization(s).` + `Failed to fetch metric alert rules from ${failedNames}. Showing results from ${validResults.length} organization(s).`, ); } @@ -356,20 +356,20 @@ async function handleResolvedOrgs( // Apply client-side name filter const allRows: MetricAlertRow[] = validResults.flatMap((r) => - r.rules.map((rule) => ({ rule, orgSlug: r.orgSlug })) + r.rules.map((rule) => ({ rule, orgSlug: r.orgSlug })), ); const filteredRows = flags.query ? allRows.filter((row) => (row.rule.name ?? "") .toLowerCase() - .includes(flags.query?.toLowerCase() ?? "") + .includes(flags.query?.toLowerCase() ?? ""), ) : allRows; const displayRows = trimWithGroupGuarantee( filteredRows, flags.limit, - (row) => row.orgSlug + (row) => row.orgSlug, ); const trimmed = displayRows.length < filteredRows.length; const cursorValues: (string | null)[] = sortedOrgKeys.map((key) => { @@ -395,7 +395,7 @@ async function handleResolvedOrgs( PAGINATION_KEY, contextKey, direction, - compoundNextCursor + compoundNextCursor, ); const hasPrev = hasPreviousPage(PAGINATION_KEY, contextKey); @@ -404,7 +404,7 @@ async function handleResolvedOrgs( failures, "org", ({ orgSlug }) => orgSlug, - ({ error }) => error + ({ error }) => error, ); const nav = paginationHint({ @@ -573,12 +573,12 @@ export const listCommand = buildListCommand("alert metrics", { if (flags.web) { await openInBrowser( await resolveWebUrl(parsed, cwd), - "metric alert rules" + "metric alert rules", ); return; } - // biome-ignore lint/suspicious/noExplicitAny: shared handler accepts any mode variant + // oxlint-disable-next-line typescript/no-explicit-any -- shared handler accepts any mode variant const resolveAndHandle: ModeHandler = (ctx) => handleResolvedOrgs({ ...ctx, flags }); diff --git a/packages/cli/src/commands/alert/metrics/rule-resolve.ts b/packages/cli/src/commands/alert/metrics/rule-resolve.ts index 671a31dad..46917305a 100644 --- a/packages/cli/src/commands/alert/metrics/rule-resolve.ts +++ b/packages/cli/src/commands/alert/metrics/rule-resolve.ts @@ -28,7 +28,7 @@ export type MetricRuleResolution = { */ export function parseMetricRuleArg( arg: string, - usageHint: string + usageHint: string, ): { ref: string; targetArg: string | undefined; @@ -37,7 +37,7 @@ export function parseMetricRuleArg( if (!trimmed) { throw new ValidationError( `Rule id or name is required.\nUse: ${usageHint}`, - "rule" + "rule", ); } @@ -49,7 +49,7 @@ export function parseMetricRuleArg( if (slashCount > 1) { throw new ValidationError( `Metric alerts are org-scoped — use '/', not '//'.\nUse: ${usageHint}`, - "rule" + "rule", ); } @@ -59,14 +59,14 @@ export function parseMetricRuleArg( if (!ref) { throw new ValidationError( `Invalid rule reference '${arg}' (missing id or name after org).\nUse: ${usageHint}`, - "rule" + "rule", ); } return { ref, targetArg: targetPart ? `${targetPart}/` : undefined }; } export async function listAllMetricRulesForOrg( - orgSlug: string + orgSlug: string, ): Promise { const all: MetricAlertRule[] = []; let cursor: string | undefined; @@ -82,16 +82,16 @@ export async function listAllMetricRulesForOrg( cursor = nextCursor; } log.warn( - `Pagination limit reached for metric alert rules in ${orgSlug}. Results may be incomplete.` + `Pagination limit reached for metric alert rules in ${orgSlug}. Results may be incomplete.`, ); return all; } -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: per-org list + name resolution +// per-org list + name resolution export async function resolveMetricAlertRule( orgSlugs: string[], ref: string, - usageHint: string + usageHint: string, ): Promise { if (isAllDigits(ref)) { const hits: MetricRuleResolution[] = []; @@ -113,13 +113,13 @@ export async function resolveMetricAlertRule( if (hits.length > 1) { throw new ValidationError( `Alert rule ID '${ref}' matched multiple organizations.\n` + - "Use an explicit target: sentry alert metrics /" + "Use an explicit target: sentry alert metrics /", ); } throw new ResolutionError( `Metric alert rule '${ref}'`, "not found", - usageHint + usageHint, ); } @@ -134,7 +134,7 @@ export async function resolveMetricAlertRule( } } const exact = rules.find( - (rule) => rule.name && rule.name.toLowerCase() === ref.toLowerCase() + (rule) => rule.name && rule.name.toLowerCase() === ref.toLowerCase(), ); if (exact) { hits.push({ orgSlug, rule: exact }); @@ -147,7 +147,7 @@ export async function resolveMetricAlertRule( if (hits.length > 1) { throw new ValidationError( `Alert rule name '${ref}' matched multiple organizations.\n` + - "Use an explicit target: sentry alert metrics /" + "Use an explicit target: sentry alert metrics /", ); } @@ -158,13 +158,13 @@ export async function resolveMetricAlertRule( throw new ValidationError( `No metric alert rule named '${ref}' in the selected organization(s).\n\n` + `Did you mean:\n${lines}`, - "rule" + "rule", ); } throw new ResolutionError( `Metric alert rule '${ref}'`, "not found", - usageHint + usageHint, ); } diff --git a/packages/cli/src/commands/alert/metrics/view.ts b/packages/cli/src/commands/alert/metrics/view.ts index 35ed7aed6..977295d96 100644 --- a/packages/cli/src/commands/alert/metrics/view.ts +++ b/packages/cli/src/commands/alert/metrics/view.ts @@ -86,7 +86,7 @@ export const viewCommand = buildCommand({ if (flags.web && parsed.type === "org-all") { await openInBrowser( buildMetricAlertsUrl(parsed.org), - "metric alert rules" + "metric alert rules", ); return; } @@ -94,7 +94,7 @@ export const viewCommand = buildCommand({ const org = await resolveOrgOnlyFromArg( targetArg, cwd, - "alert metrics view" + "alert metrics view", ); if (flags.web) { diff --git a/packages/cli/src/commands/alert/mutation-utils.ts b/packages/cli/src/commands/alert/mutation-utils.ts index 8f7060339..059bd59b8 100644 --- a/packages/cli/src/commands/alert/mutation-utils.ts +++ b/packages/cli/src/commands/alert/mutation-utils.ts @@ -59,7 +59,7 @@ const METRIC_TIME_WINDOWS = new Set([ /** Parse and validate an "all" | "any" match mode flag. Returns `undefined` when absent. */ export function parseMatchMode( value: string | undefined, - field: "action-match" | "filter-match" + field: "action-match" | "filter-match", ): "all" | "any" | undefined { if (value === undefined || value === "") { return; @@ -70,7 +70,7 @@ export function parseMatchMode( } throw new ValidationError( `${field} must be 'all' or 'any' (got ${JSON.stringify(value)}).`, - field + field, ); } @@ -83,7 +83,7 @@ export function parseMatchMode( * accepts either logic type here. */ export function matchToLogicType( - match: "all" | "any" | undefined + match: "all" | "any" | undefined, ): "all" | "any-short" { return match === "any" ? "any-short" : "all"; } @@ -106,7 +106,7 @@ export function triggerLogicType(): "any-short" { /** Parse and validate an "active" | "disabled" status flag. Returns `undefined` when absent. */ export function parseStatusFlag( - value: string | undefined + value: string | undefined, ): "active" | "disabled" | undefined { if (value === undefined || value === "") { return; @@ -117,7 +117,7 @@ export function parseStatusFlag( } throw new ValidationError( `Status must be 'active' or 'disabled' (got ${JSON.stringify(value)}).`, - "status" + "status", ); } @@ -127,7 +127,7 @@ function parseJsonValue(raw: string, field: string): unknown { } catch { throw new ValidationError( `${field} must be valid JSON (got ${JSON.stringify(raw)}).`, - field + field, ); } } @@ -151,7 +151,7 @@ function toObject(value: unknown, field: string): Record { */ export function parseJsonObjectList( values: readonly string[] | undefined, - field: string + field: string, ): Record[] | undefined { if (!values || values.length === 0) { return; @@ -176,13 +176,13 @@ export function parseJsonObjectList( export function validateIssueRuleArrays( conditions: readonly Record[] | undefined, actions: readonly Record[] | undefined, - field: "conditions" | "actions" + field: "conditions" | "actions", ): void { if (field === "conditions") { if (!conditions || conditions.length === 0) { throw new ValidationError( "Pass at least one --condition JSON object.", - "condition" + "condition", ); } return; @@ -191,14 +191,14 @@ export function validateIssueRuleArrays( if (!actions || actions.length === 0) { throw new ValidationError( "Pass at least one --action JSON object.", - "action" + "action", ); } } /** Split comma-separated project slugs, trim whitespace, and filter empties. */ export function normalizeProjectList( - projects: readonly string[] | undefined + projects: readonly string[] | undefined, ): string[] | undefined { if (!projects || projects.length === 0) { return; @@ -262,7 +262,7 @@ function appendIsTransactionFilter(query: string): string { */ export function resolveMetricDataset( dataset: string, - query: string + query: string, ): ResolvedMetricDataset { const lower = dataset.trim().toLowerCase(); if (TRANSACTION_DATASET_NAMES.has(lower)) { @@ -283,7 +283,7 @@ export function validateMetricDataset(dataset: string): void { } throw new ValidationError( `dataset must be one of: ${[...METRIC_DATASET_VALUES].join(", ")}.`, - "dataset" + "dataset", ); } @@ -294,18 +294,18 @@ export function validateMetricTimeWindow(timeWindow: number): void { } throw new ValidationError( `timeWindow must be one of: ${[...METRIC_TIME_WINDOWS].join(", ")} minutes.`, - "timeWindow" + "timeWindow", ); } /** Validate that each trigger has an `alertThreshold` and a non-empty `actions` array. */ export function validateMetricTriggers( - triggers: readonly Record[] | undefined + triggers: readonly Record[] | undefined, ): void { if (!triggers || triggers.length === 0) { throw new ValidationError( "Pass at least one --trigger JSON object.", - "trigger" + "trigger", ); } @@ -314,14 +314,14 @@ export function validateMetricTriggers( if (typeof threshold !== "number" && typeof threshold !== "string") { throw new ValidationError( "Each trigger must include alertThreshold.", - "trigger" + "trigger", ); } const actions = trigger.actions; if (!Array.isArray(actions) || actions.length === 0) { throw new ValidationError( "Each trigger must include a non-empty actions array.", - "trigger" + "trigger", ); } } diff --git a/packages/cli/src/commands/api.ts b/packages/cli/src/commands/api.ts index e9ba50443..6933557a7 100644 --- a/packages/cli/src/commands/api.ts +++ b/packages/cli/src/commands/api.ts @@ -6,7 +6,7 @@ */ import { access, readFile } from "node:fs/promises"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import type { SentryContext } from "../context.js"; import { appendSearchParams, rawApiRequest } from "../lib/api-client.js"; @@ -61,7 +61,7 @@ const VALID_METHODS: HttpMethod[] = ["GET", "POST", "PUT", "DELETE", "PATCH"]; * @internal Exported for testing */ export async function readStdin( - stdin: NodeJS.ReadStream & { fd: 0 } + stdin: NodeJS.ReadStream & { fd: 0 }, ): Promise { const chunks: Buffer[] = []; @@ -84,7 +84,7 @@ export function parseMethod(value: string): HttpMethod { const upper = value.toUpperCase(); if (!VALID_METHODS.includes(upper as HttpMethod)) { throw new Error( - `Invalid method: ${value}. Must be one of: ${VALID_METHODS.join(", ")}` + `Invalid method: ${value}. Must be one of: ${VALID_METHODS.join(", ")}`, ); } return upper as HttpMethod; @@ -253,7 +253,7 @@ function isApiPathSlug(segment: string | undefined): segment is string { * @internal Exported for testing */ export function parseOrgProjectFromApiPath( - endpoint: string + endpoint: string, ): { org: string; project?: string } | undefined { const path = endpoint.split("?", 1)[0] ?? ""; const segments = path.split("/").filter((segment) => segment.length > 0); @@ -300,7 +300,7 @@ function tagOrgFromApiPath(endpoint: string): void { * @internal Exported for testing */ export function parseFieldValue(value: string): unknown { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(value); } catch { @@ -359,7 +359,7 @@ function validatePathSegments(path: string[]): void { if (DANGEROUS_KEYS.has(segment)) { throw new ValidationError( `Invalid field key: "${segment}" is not allowed`, - "field" + "field", ); } @@ -368,7 +368,7 @@ function validatePathSegments(path: string[]): void { if (segment === "" && i < path.length - 1) { throw new ValidationError( "Invalid field key: empty brackets [] can only appear at the end of a key", - "field" + "field", ); } } @@ -415,21 +415,21 @@ function validateTypeCompatibility( existing: unknown, expectsArray: boolean, path: string[], - index: number + index: number, ): void { const pathStr = formatPathForError(path, index); if (expectsArray && !Array.isArray(existing)) { throw new ValidationError( `expected array type under "${pathStr}", got ${getTypeName(existing)}`, - "field" + "field", ); } if (!(expectsArray || isTraversableObject(existing))) { throw new ValidationError( `expected map type under "${pathStr}", got ${getTypeName(existing)}`, - "field" + "field", ); } } @@ -441,7 +441,7 @@ function validateTypeCompatibility( */ function navigateToParent( obj: Record, - path: string[] + path: string[], ): unknown { let current: unknown = obj; @@ -489,7 +489,7 @@ function navigateToParent( export function setNestedValue( obj: Record, key: string, - value: unknown + value: unknown, ): void { const path = parseFieldKey(key); validatePathSegments(path); @@ -528,7 +528,7 @@ export function setNestedValue( * @internal Exported for testing */ export function normalizeFields( - fields: string[] | undefined + fields: string[] | undefined, ): string[] | undefined { if (!fields || fields.length === 0) { return fields; @@ -555,7 +555,7 @@ export function normalizeFields( const value = field.substring(colonIndex + 1); const corrected = `${key}=${value}`; log.warn( - `field '${field}' looks like it uses ':' instead of '=' — interpreting as '${corrected}'` + `field '${field}' looks like it uses ':' instead of '=' — interpreting as '${corrected}'`, ); return corrected; } @@ -575,7 +575,7 @@ export function normalizeFields( function processField( result: Record, field: string, - raw: boolean + raw: boolean, ): void { const eqIndex = field.indexOf("="); @@ -587,7 +587,7 @@ function processField( } throw new ValidationError( `Invalid field format: ${field}. Expected key=value`, - "field" + "field", ); } @@ -610,7 +610,7 @@ function processField( */ export function parseFields( fields: string[], - raw = false + raw = false, ): Record { const result: Record = {}; @@ -643,7 +643,7 @@ function stringifyValue(value: unknown): string { * @internal Exported for testing */ export function buildQueryParams( - fields: string[] + fields: string[], ): Record { const result: Record = {}; @@ -652,7 +652,7 @@ export function buildQueryParams( if (eqIndex === -1) { throw new ValidationError( `Invalid field format: ${field}. Expected key=value`, - "field" + "field", ); } @@ -689,7 +689,7 @@ export function buildQueryParams( * @internal Exported for testing */ export function buildRawQueryParams( - fields: string[] + fields: string[], ): Record { const result: Record = {}; @@ -698,7 +698,7 @@ export function buildRawQueryParams( if (eqIndex === -1) { throw new ValidationError( `Invalid field format: ${field}. Expected key=value`, - "field" + "field", ); } @@ -706,7 +706,7 @@ export function buildRawQueryParams( if (key === "") { throw new ValidationError( "Invalid field key format: key cannot be empty", - "field" + "field", ); } @@ -740,7 +740,7 @@ export function buildRawQueryParams( */ export function buildQueryParamsFromFields( typedFields?: string[], - rawFields?: string[] + rawFields?: string[], ): Record { const typedParams = typedFields && typedFields.length > 0 ? buildQueryParams(typedFields) : {}; @@ -764,7 +764,7 @@ export function buildQueryParamsFromFields( export function prepareRequestOptions( method: HttpMethod, typedFields?: string[], - rawFields?: string[] + rawFields?: string[], ): { body?: Record; params?: Record; @@ -803,7 +803,7 @@ export function parseHeaders(headers: string[]): Record { if (colonIndex === -1) { throw new ValidationError( `Invalid header format: ${header}. Expected Key: Value`, - "header" + "header", ); } @@ -826,9 +826,9 @@ export function parseHeaders(headers: string[]): Record { * @internal Exported for testing */ export function parseDataBody( - data: string + data: string, ): Record | unknown[] | string { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(data) as Record | unknown[]; } catch { @@ -841,7 +841,7 @@ export function parseDataBody( * Duplicate keys are collected into arrays. */ function parseUrlEncodedParams( - data: string + data: string, ): Record { const params: Record = {}; for (const [key, value] of new URLSearchParams(data)) { @@ -873,7 +873,7 @@ function parseUrlEncodedParams( * @internal Exported for testing */ export function dataToQueryParams( - data: Record | unknown[] | string + data: Record | unknown[] | string, ): Record { if (typeof data === "string") { return parseUrlEncodedParams(data); @@ -886,7 +886,7 @@ export function dataToQueryParams( "Cannot use --data with a JSON primitive or array for GET requests. " + "Only JSON objects and URL-encoded strings can be converted to query parameters. " + "Use --method POST to send this data as a request body.", - "data" + "data", ); } @@ -911,7 +911,7 @@ export function dataToQueryParams( * @internal */ function tryParseJsonField( - field: string + field: string, ): Record | unknown[] | undefined { if (field.includes("=")) { return; @@ -920,7 +920,7 @@ function tryParseJsonField( return; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(field) as Record | unknown[]; } catch { @@ -966,14 +966,14 @@ export function extractJsonBody(fields: string[] | undefined): { throw new ValidationError( "Multiple JSON bodies detected in field arguments. " + "Use --data/-d to pass an inline JSON body explicitly.", - "field" + "field", ); } jsonBody = parsed; const preview = field.length > 60 ? `${field.substring(0, 57)}...` : field; log.info( - `'${preview}' was used as the request body. Use --data/-d to pass inline JSON next time.` + `'${preview}' was used as the request body. Use --data/-d to pass inline JSON next time.`, ); } @@ -990,7 +990,7 @@ export function extractJsonBody(fields: string[] | undefined): { */ export async function buildBodyFromInput( inputPath: string, - stdin: NodeJS.ReadStream & { fd: 0 } + stdin: NodeJS.ReadStream & { fd: 0 }, ): Promise | string> { let content: string; @@ -999,7 +999,7 @@ export async function buildBodyFromInput( } else { const exists = await access(inputPath).then( () => true, - () => false + () => false, ); if (!exists) { throw new ValidationError(`File not found: ${inputPath}`, "input"); @@ -1008,7 +1008,7 @@ export async function buildBodyFromInput( } // Try to parse as JSON for the API client - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(content) as Record; } catch { @@ -1027,7 +1027,7 @@ export async function buildBodyFromInput( */ export function buildBodyFromFields( typedFields: string[] | undefined, - rawFields: string[] | undefined + rawFields: string[] | undefined, ): Record | undefined { const hasTypedFields = typedFields && typedFields.length > 0; const hasRawFields = rawFields && rawFields.length > 0; @@ -1082,7 +1082,7 @@ export function formatApiResponse(data: unknown): string { export function formatBinaryErrorBody( status: number, headers: Headers, - body: Uint8Array + body: Uint8Array, ): string { const contentType = headers.get("content-type") ?? "unknown"; return ( @@ -1100,7 +1100,7 @@ export function formatEmptyErrorBody( status: number, statusText: string | undefined, method: string | undefined, - endpoint: string | undefined + endpoint: string | undefined, ): string { const statusLabel = [status, statusText].filter(Boolean).join(" "); const request = method && endpoint ? ` — ${method} /api/0/${endpoint}` : ""; @@ -1125,14 +1125,14 @@ type ApiResponseOutput = { /** Throw the appropriate output error for a non-successful API response. */ function throwApiResponseError( response: ApiResponseOutput, - options: ApiResponseOutputOptions + options: ApiResponseOutputOptions, ): never { const isBinary = response.body instanceof Uint8Array; const errorBody = isBinary ? formatBinaryErrorBody( response.status, response.headers, - response.body as Uint8Array + response.body as Uint8Array, ) : response.body; @@ -1157,8 +1157,8 @@ function throwApiResponseError( response.status, response.statusText, options.method, - options.endpoint - ) + options.endpoint, + ), ); } throw new OutputError(response.body); @@ -1168,7 +1168,7 @@ function throwApiResponseError( function formatApiResponseOutput( response: ApiResponseOutput, output: unknown, - json: boolean + json: boolean, ): unknown { if (!json || output instanceof Uint8Array) { return output; @@ -1212,7 +1212,7 @@ function formatApiResponseJson(data: unknown, fields?: string[]): unknown { export function resolveRequestUrl( endpoint: string, params?: Record, - baseUrl?: string + baseUrl?: string, ): string { // Use the same SDK config selection as rawApiRequest to ensure // trailing slashes are stripped and the URL matches what would be sent. @@ -1236,14 +1236,15 @@ export function resolveRequestUrl( */ export function resolveEffectiveHeaders( customHeaders: Record | undefined, - body: unknown + body: unknown, ): Record { // Mirror rawApiRequest exactly: auto-add Content-Type for any non-string, // non-undefined body when no Content-Type was explicitly provided. const isStringBody = typeof body === "string"; + // oxlint-disable-next-line unicorn/no-useless-fallback-in-spread -- Retain the existing fallback while migrating lint tools. const headers = { ...(customHeaders ?? {}) }; const hasContentType = Object.keys(headers).some( - (k) => k.toLowerCase() === "content-type" + (k) => k.toLowerCase() === "content-type", ); if (!(isStringBody || hasContentType) && body !== undefined) { headers["Content-Type"] = "application/json"; @@ -1262,7 +1263,7 @@ export function resolveEffectiveHeaders( */ export function buildFromFields( method: HttpMethod, - flags: Pick + flags: Pick, ): { body?: Record | unknown[]; params?: Record; @@ -1288,7 +1289,7 @@ export function buildFromFields( throw new ValidationError( "Cannot combine a JSON array body with field flags (-F/-f). " + "Use --data/-d to pass the array as the full body without extra fields.", - "field" + "field", ); } if (body) { @@ -1296,13 +1297,13 @@ export function buildFromFields( // silently drop nested fields from the JSON body (e.g. statusDetails.ignoreCount // overwritten by statusDetails[minCount]=5). const conflicts = Object.keys(options.body).filter( - (k) => k in (body as Record) + (k) => k in (body as Record), ); if (conflicts.length > 0) { throw new ValidationError( `Field flag(s) conflict with detected JSON body at key(s): ${conflicts.join(", ")}. ` + "Use --data/-d to pass the full JSON body, or use only field flags (-F/-f).", - "field" + "field", ); } } @@ -1327,7 +1328,7 @@ export function buildFromFields( */ export async function resolveBody( flags: Pick, - stdin: NodeJS.ReadStream & { fd: 0 } + stdin: NodeJS.ReadStream & { fd: 0 }, ): Promise<{ body?: Record | unknown[] | string; params?: Record; @@ -1336,7 +1337,7 @@ export async function resolveBody( throw new ValidationError( "Cannot use --data and --input together. " + "Use --data/-d for inline JSON, or --input for file/stdin.", - "data" + "data", ); } @@ -1347,7 +1348,7 @@ export async function resolveBody( throw new ValidationError( "Cannot use --data with --field or --raw-field. " + "Use --data/-d for a full JSON body, or -F/-f for individual fields.", - "data" + "data", ); } @@ -1383,7 +1384,7 @@ export async function resolveBody( function recordApiErrorAttributes( status: number, endpoint: string, - method: string + method: string, ): void { const span = Sentry.getActiveSpan(); if (span) { @@ -1397,7 +1398,7 @@ function recordApiErrorAttributes( function logRequest( method: string, endpoint: string, - headers: Record | undefined + headers: Record | undefined, ): void { log.debug(`> ${method} /api/0/${endpoint}`); if (headers) { @@ -1415,7 +1416,7 @@ function logResponse(response: { headers: Headers; }): void { log.debug( - `< HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}` + `< HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`, ); response.headers.forEach((value, key) => { log.debug(`< ${key}: ${value}`); @@ -1445,7 +1446,7 @@ export function isApiErrorStatus(status: number): boolean { export function resolveApiResponseOutput( response: ApiResponseOutput, - options: ApiResponseOutputOptions + options: ApiResponseOutputOptions, ): unknown { const isError = isApiErrorStatus(response.status); @@ -1485,7 +1486,7 @@ export function resolveApiResponseOutput( export function resolveBinaryTtyOutput( body: Uint8Array, headers: Headers, - allowGraphics = true + allowGraphics = true, ): string | undefined { if (allowGraphics) { // Cap the rendered width to the terminal's pixel budget so a wide image @@ -1509,7 +1510,7 @@ export function resolveBinaryTtyOutput( log.warn( "Binary response written to a TTY — redirect stdout to a file " + - "(e.g. `> file.bin`) to capture raw bytes cleanly." + "(e.g. `> file.bin`) to capture raw bytes cleanly.", ); return; } @@ -1632,7 +1633,7 @@ export const apiCommand = buildCommand({ // only surface the detail at debug level for troubleshooting // (getsentry/cli#785 item #11). log.debug( - "Stripped /api/0/ prefix from endpoint (auto-added by the API client)" + "Stripped /api/0/ prefix from endpoint (auto-added by the API client)", ); } const { body, params } = await resolveBody(flags, stdin); @@ -1682,7 +1683,7 @@ export const apiCommand = buildCommand({ recordApiErrorAttributes( response.status, normalizedEndpoint, - flags.method + flags.method, ); } @@ -1706,7 +1707,7 @@ export const apiCommand = buildCommand({ const sixel = resolveBinaryTtyOutput( output, response.headers, - !flags.json + !flags.json, ); if (sixel !== undefined) { return yield new CommandOutput(sixel); @@ -1717,7 +1718,7 @@ export const apiCommand = buildCommand({ // formatter, no trailing newline). Textual JSON responses expose the HTTP // metadata so callers can distinguish an empty success from an error. return yield new CommandOutput( - formatApiResponseOutput(response, output, flags.json) + formatApiResponseOutput(response, output, flags.json), ); }, }); diff --git a/packages/cli/src/commands/auth/default.ts b/packages/cli/src/commands/auth/default.ts index aba8ed212..1c59f84fa 100644 --- a/packages/cli/src/commands/auth/default.ts +++ b/packages/cli/src/commands/auth/default.ts @@ -48,7 +48,7 @@ const LOGIN_ONLY_FLAGS = [ */ export function resolveAuthDefaultTarget( flags: Readonly>, - authenticated = isAuthenticated() + authenticated = isAuthenticated(), ): "login" | "status" { for (const name of LOGIN_ONLY_FLAGS) { const value = flags[name]; @@ -74,7 +74,7 @@ function getRawFunc(command: Command): RawCommandFunc { const raw = (command as unknown as { __rawFunc?: RawCommandFunc }).__rawFunc; if (!raw) { throw new Error( - "Command is missing __rawFunc; expected buildCommand output" + "Command is missing __rawFunc; expected buildCommand output", ); } return raw; @@ -175,7 +175,7 @@ export const authDefaultCommand = buildCommand({ const raw = getRawFunc(command as Command); const generator = raw.call( this, - flags as unknown as Record + flags as unknown as Record, ); // Re-yield CommandOutput values so this command's wrapper renders once. diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index a9ce8175e..eb358d1d8 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -59,8 +59,8 @@ export function formatLoginResult(result: LoginResult): string { const lines: string[] = []; lines.push( success( - `✔ ${result.method === "token" ? "Authenticated with API token" : "Authentication successful!"}` - ) + `✔ ${result.method === "token" ? "Authenticated with API token" : "Authentication successful!"}`, + ), ); if (result.user) { lines.push(` Logged in as: ${formatUserIdentity(result.user)}`); @@ -72,7 +72,7 @@ export function formatLoginResult(result: LoginResult): string { lines.push(" Automatic refresh: unavailable"); if (result.expiresIn !== undefined) { lines.push( - ` Access token expires in: ${formatDuration(result.expiresIn)}` + ` Access token expires in: ${formatDuration(result.expiresIn)}`, ); } } @@ -120,14 +120,14 @@ function resolveLoginScope(flags: LoginFlags): string | undefined { "", "Or create a scoped User Auth Token in Sentry (Account → Auth Tokens) and pass it without --read-only/--scope.", ].join("\n"), - readOnly ? "read-only" : "scope" + readOnly ? "read-only" : "scope", ); } if (readOnly && hasScope) { throw new ValidationError( "--read-only and --scope cannot be used together. Use --read-only for the read-only subset, or --scope to list exact scopes.", - "scope" + "scope", ); } @@ -190,7 +190,7 @@ export function parseLoginUrl(raw: string): string { function refuseLoginToUntrustedHost( flags: LoginFlags, effectiveHost: string, - rcSource?: string + rcSource?: string, ): void { if (flags.url || isLoginHostTrusted(effectiveHost)) { return; @@ -199,7 +199,7 @@ function refuseLoginToUntrustedHost( buildHostRefusalMessage(effectiveHost, { tokenFlag: !!flags.token, rcSource, - }) + }), ); } @@ -210,7 +210,7 @@ function refuseLoginToUntrustedHost( async function resolveRcContext( flagUrl: string | undefined, cwd: string, - effectiveHost: string + effectiveHost: string, ): Promise<{ rcConfig: SentryCliRcConfig; urlFromRc: string | undefined; @@ -240,7 +240,7 @@ async function resolveRcContext( /** @internal exported for testing */ export function rcTokenHint( rcConfig: SentryCliRcConfig, - effectiveHost: string + effectiveHost: string, ): string | undefined { if (!rcConfig.token) { return; @@ -276,7 +276,7 @@ export function rcTokenHint( */ function persistLoginUrlAsDefault( flagUrl: string | undefined, - effectiveHost: string + effectiveHost: string, ): void { if (!flagUrl || isSaaSTrustOrigin(effectiveHost)) { return; @@ -285,7 +285,7 @@ function persistLoginUrlAsDefault( setDefaultUrl(effectiveHost); } catch { log.debug( - `Could not persist default URL to DB; host is recorded on the stored token. Set SENTRY_HOST or run 'sentry cli defaults url ${effectiveHost}' if subsequent commands route incorrectly.` + `Could not persist default URL to DB; host is recorded on the stored token. Set SENTRY_HOST or run 'sentry cli defaults url ${effectiveHost}' if subsequent commands route incorrectly.`, ); } } @@ -330,7 +330,7 @@ async function handleExistingAuth(force: boolean): Promise { const envVar = getActiveEnvVarName(); log.warn( `${envVar} is set in your environment (likely from build tooling).\n` + - " OAuth credentials will be stored separately and used for CLI commands." + " OAuth credentials will be stored separately and used for CLI commands.", ); // If no stored credential exists, proceed directly to login if (!hasStoredAuthCredentials()) { @@ -343,7 +343,7 @@ async function handleExistingAuth(force: boolean): Promise { // Non-interactive (piped, CI): print message and block if (!isatty(0)) { log.info( - "You are already authenticated. Use '--force' or 'sentry auth logout' first to re-authenticate." + "You are already authenticated. Use '--force' or 'sentry auth logout' first to re-authenticate.", ); return false; } @@ -353,7 +353,7 @@ async function handleExistingAuth(force: boolean): Promise { const identity = userInfo ? formatUserIdentity(userInfo) : "current user"; const confirmed = await log.prompt( `Already authenticated as ${identity}. Re-authenticate?`, - { type: "confirm", initial: false } + { type: "confirm", initial: false }, ); // Symbol(clack:cancel) is truthy — strict equality check @@ -387,7 +387,7 @@ async function handleTokenValidationError(error: unknown): Promise { await clearAuth(); throw new AuthError( "invalid", - "Invalid API token. Please check your token and try again." + "Invalid API token. Please check your token and try again.", ); } throw error; @@ -475,7 +475,7 @@ export const loginCommand = buildCommand({ const { rcConfig, urlFromRc } = await resolveRcContext( flags.url, this.cwd, - effectiveHost + effectiveHost, ); refuseLoginToUntrustedHost(flags, effectiveHost, urlFromRc); @@ -488,7 +488,7 @@ export const loginCommand = buildCommand({ } } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await clearResponseCache(); } catch { @@ -515,7 +515,7 @@ export const loginCommand = buildCommand({ method: "token", configPath: getDbPath(), }; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const user = await getCurrentUser(); setUserInfo({ @@ -563,7 +563,7 @@ export const loginCommand = buildCommand({ * on the next command that needs it. */ function warmOrgCache(): void { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. listOrganizationsUncached().catch(() => { // Best-effort: cache warming failure doesn't affect the login result }); diff --git a/packages/cli/src/commands/auth/logout.ts b/packages/cli/src/commands/auth/logout.ts index e974067ea..59d679cd7 100644 --- a/packages/cli/src/commands/auth/logout.ts +++ b/packages/cli/src/commands/auth/logout.ts @@ -54,7 +54,7 @@ export const logoutCommand = buildCommand({ throw new AuthError( "invalid", `Authentication is provided via ${envVar} environment variable. ` + - `Unset ${envVar} to log out.` + `Unset ${envVar} to log out.`, ); } diff --git a/packages/cli/src/commands/auth/refresh.ts b/packages/cli/src/commands/auth/refresh.ts index 682cc34a5..20c99059b 100644 --- a/packages/cli/src/commands/auth/refresh.ts +++ b/packages/cli/src/commands/auth/refresh.ts @@ -58,7 +58,7 @@ function resolveRefreshScope(flags: RefreshFlags): string { if (flags["read-only"] && flags.scope?.length) { throw new ValidationError( "--read-only and --scope cannot be used together. Use --read-only for the read-only subset, or --scope to list exact scopes.", - "scope" + "scope", ); } if (flags.scope?.length) { @@ -152,7 +152,7 @@ Examples: "invalid", "Cannot refresh an environment variable token.\n" + "Token refresh is only available for OAuth sessions.\n" + - `Update ${envVar} to change your token.` + `Update ${envVar} to change your token.`, ); } @@ -184,7 +184,7 @@ Examples: throw new AuthError( "invalid", "No refresh token available. You may be using a manual API token.\n" + - "Run 'sentry auth login' to authenticate with OAuth and enable auto-refresh." + "Run 'sentry auth login' to authenticate with OAuth and enable auto-refresh.", ); } diff --git a/packages/cli/src/commands/auth/status.ts b/packages/cli/src/commands/auth/status.ts index 0e83f2279..619f6a268 100644 --- a/packages/cli/src/commands/auth/status.ts +++ b/packages/cli/src/commands/auth/status.ts @@ -95,7 +95,7 @@ export type AuthStatusData = { */ function collectTokenInfo( auth: AuthConfig | undefined, - showToken: boolean + showToken: boolean, ): AuthStatusData["token"] | undefined { if (!auth?.token) { return; diff --git a/packages/cli/src/commands/auth/token.ts b/packages/cli/src/commands/auth/token.ts index 04d8090d8..c9c89b3b1 100644 --- a/packages/cli/src/commands/auth/token.ts +++ b/packages/cli/src/commands/auth/token.ts @@ -21,7 +21,7 @@ export const tokenCommand = buildCommand({ }, parameters: {}, output: { human: (token: string) => token }, - // biome-ignore lint/suspicious/useAwait: sync body but async generator required by buildCommand + // sync body but async generator required by buildCommand async *func(this: SentryContext) { const token = getAuthToken(); if (!token) { diff --git a/packages/cli/src/commands/auth/whoami.ts b/packages/cli/src/commands/auth/whoami.ts index 5c707c3f4..91e395e1c 100644 --- a/packages/cli/src/commands/auth/whoami.ts +++ b/packages/cli/src/commands/auth/whoami.ts @@ -40,7 +40,7 @@ type WhoamiFlags = { * formatter. The `type` field is only present on org-token output. */ function formatWhoami( - data: OrgTokenIdentity | Record + data: OrgTokenIdentity | Record, ): string { if ("type" in data && data.type === "org-auth-token") { return formatOrgTokenIdentity(data as OrgTokenIdentity); @@ -82,12 +82,12 @@ export const whoamiCommand = buildCommand({ [ "Use an OAuth token from `sentry auth login` or a personal access token", "Run `sentry org list` to list organizations this token can access", - ] + ], ); } log.warn( - "This is an organization auth token — not tied to a specific user." + "This is an organization auth token — not tied to a specific user.", ); const data: OrgTokenIdentity = { diff --git a/packages/cli/src/commands/bash-hook.ts b/packages/cli/src/commands/bash-hook.ts index ad1aa360e..59cab3058 100644 --- a/packages/cli/src/commands/bash-hook.ts +++ b/packages/cli/src/commands/bash-hook.ts @@ -142,7 +142,7 @@ async function handleSendEvent(flags: BashHookFlags): Promise { if (!flags.traceback) { throw new ValidationError( "--send-event requires --traceback ", - "traceback" + "traceback", ); } if (!flags.log) { diff --git a/packages/cli/src/commands/build/download.ts b/packages/cli/src/commands/build/download.ts index 813167cc9..b69b8ff39 100644 --- a/packages/cli/src/commands/build/download.ts +++ b/packages/cli/src/commands/build/download.ts @@ -44,7 +44,7 @@ function formatDownloadResult(data: BuildDownloadResult): string { ["Build ID", data.buildId], ["Format", data.format], ["Saved to", data.output], - ]) + ]), ); } @@ -90,7 +90,7 @@ export const downloadCommand = buildCommand({ async *func( this: SentryContext, flags: { output?: string }, - buildId: string + buildId: string, ) { const resolved = await resolveOrg({ cwd: this.cwd }); if (!resolved) { @@ -106,7 +106,7 @@ export const downloadCommand = buildCommand({ USAGE_HINT, [ "The build may still be processing, or it has no downloadable artifact.", - ] + ], ); } @@ -114,7 +114,7 @@ export const downloadCommand = buildCommand({ const format = buildFormatFromUrl(url); const output = resolve( this.cwd, - flags.output ?? `preprod_artifact_${buildId}.${format}` + flags.output ?? `preprod_artifact_${buildId}.${format}`, ); const regionUrl = await resolveOrgRegion(org); diff --git a/packages/cli/src/commands/build/upload.ts b/packages/cli/src/commands/build/upload.ts index 1b1fbfab9..bd30ee69f 100644 --- a/packages/cli/src/commands/build/upload.ts +++ b/packages/cli/src/commands/build/upload.ts @@ -105,7 +105,7 @@ async function uploadOne( path: string, org: string, project: string, - metadata: BuildUploadMetadata + metadata: BuildUploadMetadata, ): Promise { let info: Awaited>; try { @@ -139,7 +139,7 @@ async function uploadOne( } else { throw new ValidationError( `Unsupported build format (expected APK, AAB, IPA, or XCArchive): ${path}`, - "path" + "path", ); } } @@ -177,7 +177,8 @@ export const uploadCommand = buildCommand({ positional: { kind: "array", parameter: { - brief: "Path(s) to the build(s) to upload (APK, AAB, IPA, or XCArchive)", + brief: + "Path(s) to the build(s) to upload (APK, AAB, IPA, or XCArchive)", parse: String, placeholder: "path", }, @@ -226,7 +227,8 @@ export const uploadCommand = buildCommand({ "head-repo-name": { kind: "parsed", parse: String, - brief: "Head repository name, e.g. owner/repo (defaults to the current)", + brief: + "Head repository name, e.g. owner/repo (defaults to the current)", optional: true, }, "base-repo-name": { @@ -244,7 +246,8 @@ export const uploadCommand = buildCommand({ "base-ref": { kind: "parsed", parse: String, - brief: "Base branch/reference (defaults to the merge-base tracking ref)", + brief: + "Base branch/reference (defaults to the merge-base tracking ref)", optional: true, }, "pr-number": { @@ -284,19 +287,14 @@ export const uploadCommand = buildCommand({ if (flags["force-git-metadata"] && flags["no-git-metadata"]) { throw new ValidationError( "--force-git-metadata and --no-git-metadata cannot be used together", - "force-git-metadata" + "force-git-metadata", ); } // Collect git metadata automatically in CI (unless disabled), or when forced. const shouldCollectVcs = Boolean(flags["force-git-metadata"]) || (!flags["no-git-metadata"] && isCi(this.env)); - const vcs = collectVcsMetadata( - flags, - this.cwd, - this.env, - shouldCollectVcs - ); + const vcs = collectVcsMetadata(flags, this.cwd, this.env, shouldCollectVcs); const metadata: BuildUploadMetadata = { buildConfiguration: flags["build-configuration"], diff --git a/packages/cli/src/commands/cli/completion.ts b/packages/cli/src/commands/cli/completion.ts index 9399bb7d3..1198594e9 100644 --- a/packages/cli/src/commands/cli/completion.ts +++ b/packages/cli/src/commands/cli/completion.ts @@ -48,11 +48,11 @@ export const completionCommand = buildCommand({ ], }, }, - // biome-ignore lint/suspicious/useAwait: Stricli requires AsyncGenerator but script generation is synchronous + // Stricli requires AsyncGenerator but script generation is synchronous async *func( this: SentryContext, _flags: Record, - shell?: string + shell?: string, ) { const shellType = shell ? detectShellType(shell) @@ -62,7 +62,7 @@ export const completionCommand = buildCommand({ if (script === null) { throw new ValidationError( `Unsupported shell: ${shell || shellType}. Supported shells: bash, zsh, fish`, - "shell" + "shell", ); } diff --git a/packages/cli/src/commands/cli/defaults.ts b/packages/cli/src/commands/cli/defaults.ts index c3cdc55df..38209644d 100644 --- a/packages/cli/src/commands/cli/defaults.ts +++ b/packages/cli/src/commands/cli/defaults.ts @@ -113,7 +113,7 @@ const DEFAULTS_REGISTRY: Record = { if (parsed === null) { throw new ValidationError( `Invalid telemetry value: '${value}'. Use on/off, yes/no, true/false, or 1/0.`, - "telemetry" + "telemetry", ); } setTelemetryPreference(parsed); @@ -136,7 +136,7 @@ const DEFAULTS_REGISTRY: Record = { if (parsed === null) { throw new ValidationError( `Invalid agent-skills value: '${value}'. Use on/off, yes/no, true/false, or 1/0.`, - "agent-skills" + "agent-skills", ); } setAgentSkillsPreference(parsed); @@ -159,7 +159,7 @@ const DEFAULTS_REGISTRY: Record = { if (parsed === null) { throw new ValidationError( `Invalid graphics value: '${value}'. Use on/off, yes/no, true/false, or 1/0.`, - "graphics" + "graphics", ); } setGraphicsPreference(parsed); @@ -178,7 +178,7 @@ const DEFAULTS_REGISTRY: Record = { } catch { throw new ValidationError( `Invalid URL: '${value}'. Provide a valid URL (e.g., https://sentry.example.com).`, - "url" + "url", ); } setDefaultUrl(normalized); @@ -201,7 +201,7 @@ const DEFAULTS_REGISTRY: Record = { if (!trimmed) { throw new ValidationError( "CA certificate path cannot be empty.", - "ca-cert" + "ca-cert", ); } const resolved = resolve(trimmed); @@ -324,7 +324,7 @@ export const defaultsCommand = buildCommand({ }, aliases: { y: "yes", f: "force" }, }, - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: sequential command dispatch + // sequential command dispatch async *func( this: SentryContext, flags: { @@ -340,7 +340,7 @@ export const defaultsCommand = buildCommand({ if (rest.length > 0) { throw new ValidationError( "Too many arguments. Usage: sentry cli defaults [ []]", - "args" + "args", ); } @@ -352,7 +352,7 @@ export const defaultsCommand = buildCommand({ if (!isConfirmationBypassed(flags)) { const confirmed = await log.prompt( "This will clear all defaults (organization, project, telemetry, URL, headers, ca-cert, agent-skills, graphics). Continue?", - { type: "confirm" } + { type: "confirm" }, ); if (confirmed !== true) { return { hint: "Cancelled." }; @@ -384,7 +384,7 @@ export const defaultsCommand = buildCommand({ ]; throw new ValidationError( `Unknown default '${keyArg}'. Valid keys: ${validKeys.join(", ")}`, - "key" + "key", ); } @@ -395,7 +395,7 @@ export const defaultsCommand = buildCommand({ if (valueArg !== undefined) { throw new ValidationError( `Cannot use --clear with a value. Use either 'sentry cli defaults ${keyArg} --clear' or 'sentry cli defaults ${keyArg} '.`, - "args" + "args", ); } const previous = handler.get(); @@ -438,7 +438,7 @@ export const defaultsCommand = buildCommand({ effective.enabled !== (newValue === "on") ) { log.warn( - `Note: ${effective.source.slice("env:".length)} environment variable overrides this preference.` + `Note: ${effective.source.slice("env:".length)} environment variable overrides this preference.`, ); } } diff --git a/packages/cli/src/commands/cli/feedback.ts b/packages/cli/src/commands/cli/feedback.ts index 41de7e9ba..d8a9e5de9 100644 --- a/packages/cli/src/commands/cli/feedback.ts +++ b/packages/cli/src/commands/cli/feedback.ts @@ -9,7 +9,7 @@ */ import { isatty } from "node:tty"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import type { SentryContext } from "../../context.js"; import { buildCommand } from "../../lib/command.js"; @@ -48,7 +48,7 @@ export const feedbackCommand = buildCommand({ }, async *func( this: SentryContext, - // biome-ignore lint/complexity/noBannedTypes: Stricli requires empty object for commands with no flags + // Stricli requires empty object for commands with no flags _flags: {}, ...messageParts: string[] ) { @@ -71,7 +71,7 @@ export const feedbackCommand = buildCommand({ if (!Sentry.isEnabled()) { throw new ConfigError( "Feedback not sent: telemetry is disabled.", - "Unset SENTRY_CLI_NO_TELEMETRY to enable feedback." + "Unset SENTRY_CLI_NO_TELEMETRY to enable feedback.", ); } diff --git a/packages/cli/src/commands/cli/fix.ts b/packages/cli/src/commands/cli/fix.ts index f4554445a..e069fe4df 100644 --- a/packages/cli/src/commands/cli/fix.ts +++ b/packages/cli/src/commands/cli/fix.ts @@ -114,11 +114,11 @@ type OwnershipIssue = { */ async function checkMode( path: string, - expectedMode: number + expectedMode: number, ): Promise<{ actualMode: number } | null> { try { const st = await stat(path); - // biome-ignore lint/suspicious/noBitwiseOperators: extracting permission bits with bitmask + // oxlint-disable-next-line no-bitwise -- extracting permission bits with bitmask const mode = st.mode & 0o777; if (mode !== expectedMode) { return { actualMode: mode }; @@ -182,7 +182,7 @@ async function checkPermissions(dbPath: string): Promise { } satisfies PermissionIssue; } return null; - }) + }), ); return results.filter((r): r is PermissionIssue => r !== null); @@ -202,7 +202,7 @@ async function checkPermissions(dbPath: string): Promise { */ async function checkOwnership( dbPath: string, - comparisonUid: number + comparisonUid: number, ): Promise { const configDir = getConfigDir(); @@ -285,10 +285,10 @@ type RepairOutcome = { async function repairOwnership( issues: OwnershipIssue[], username: string, - targetUid: number + targetUid: number, ): Promise { const results = await Promise.allSettled( - issues.map((issue) => chown(issue.path, targetUid, -1)) + issues.map((issue) => chown(issue.path, targetUid, -1)), ); return results.map((result, i) => { @@ -327,7 +327,7 @@ function ownershipInstructions( currentUid: number, username: string, configDir: string, - dryRun: boolean + dryRun: boolean, ): string { if (dryRun && currentUid === 0) { return `Would transfer ownership of "${configDir}" to ${username}.`; @@ -357,7 +357,7 @@ function ownershipInstructions( async function handleOwnershipIssues( dbPath: string, currentUid: number, - dryRun: boolean + dryRun: boolean, ): Promise { const configDir = getConfigDir(); const username = getRealUsername(); @@ -400,7 +400,7 @@ async function handleOwnershipIssues( currentUid, username, configDir, - true + true, ), repairFailed: false, }; @@ -414,7 +414,7 @@ async function handleOwnershipIssues( currentUid, username, configDir, - false + false, ), repairFailed: true, }; @@ -467,8 +467,9 @@ function formatMode(mode: number): string { * Repairs directories before files to avoid EACCES on child chmod calls. */ async function repairPermissions( - issues: PermissionIssue[] + issues: PermissionIssue[], ): Promise { + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const outcomes = new Array(issues.length); // Build index maps for dirs and files @@ -495,13 +496,13 @@ async function repairPermissions( */ async function collectPermResults( entries: Array<{ idx: number; issue: PermissionIssue }>, - outcomes: RepairOutcome[] + outcomes: RepairOutcome[], ): Promise { const results = await Promise.allSettled( entries.map(async ({ issue }) => { await chmod(issue.path, issue.expectedMode); return `${issue.kind} ${issue.path}: ${formatMode(issue.currentMode)} -> ${formatMode(issue.expectedMode)}`; - }) + }), ); for (let i = 0; i < results.length; i++) { @@ -531,7 +532,7 @@ async function collectPermResults( */ async function handlePermissionIssues( dbPath: string, - dryRun: boolean + dryRun: boolean, ): Promise { const permIssues = await checkPermissions(dbPath); if (permIssues.length === 0) { @@ -630,7 +631,7 @@ function handleSchemaIssues(dbPath: string, dryRun: boolean): HandlerResult { function safeHandleSchemaIssues( dbPath: string, dryRun: boolean, - priorIssuesFound: number + priorIssuesFound: number, ): HandlerResult { try { return handleSchemaIssues(dbPath, dryRun); @@ -710,7 +711,7 @@ export const fixCommand = buildCommand({ const schema = safeHandleSchemaIssues( dbPath, dryRun, - ownership.issues.length + perm.issues.length + ownership.issues.length + perm.issues.length, ); const allIssues = [...ownership.issues, ...perm.issues, ...schema.issues]; diff --git a/packages/cli/src/commands/cli/import.ts b/packages/cli/src/commands/cli/import.ts index fcb93555a..83baf3e12 100644 --- a/packages/cli/src/commands/cli/import.ts +++ b/packages/cli/src/commands/cli/import.ts @@ -222,7 +222,7 @@ function enforceTrustGate(plan: ImportPlan): void { throw new HostScopeError( `Token (from ${tokenSource}) and URL (from ${urlSource}) come from different files.\n` + "To confirm you trust this URL, pass it explicitly:\n" + - ` sentry cli import --url ${plan.effective.url ?? ""}` + ` sentry cli import --url ${plan.effective.url ?? ""}`, ); } @@ -321,7 +321,7 @@ export const importCommand = buildCommand({ if (plan.newFields.length === 0) { markImportCompleted(plan); yield new CommandOutput( - emptyResult(["All settings from .sentryclirc are already configured."]) + emptyResult(["All settings from .sentryclirc are already configured."]), ); return; } @@ -349,7 +349,7 @@ export const importCommand = buildCommand({ yield new CommandOutput( emptyResult([ "Import requires confirmation. Use --yes in non-interactive mode.", - ]) + ]), ); process.exitCode = 1; return; diff --git a/packages/cli/src/commands/cli/setup.ts b/packages/cli/src/commands/cli/setup.ts index d4892f864..fadcaf93b 100644 --- a/packages/cli/src/commands/cli/setup.ts +++ b/packages/cli/src/commands/cli/setup.ts @@ -106,7 +106,7 @@ function formatSetupResult(result: SetupResult): string { async function migrateLegacyConfig( homeDir: string, env: NodeJS.ProcessEnv, - emit: Logger + emit: Logger, ): Promise { const legacyDir = join(homeDir, ".sentry"); // Target the XDG location directly — resolveConfigDir keeps returning the @@ -118,7 +118,7 @@ async function migrateLegacyConfig( const configFiles = ["cli.db", "cli.db-wal", "cli.db-shm", "config.json"]; const hasLegacyConfig = configFiles.some((name) => - existsSync(join(legacyDir, name)) + existsSync(join(legacyDir, name)), ); if (!hasLegacyConfig || existsSync(join(targetConfigDir, "cli.db"))) { return; @@ -145,7 +145,7 @@ async function migrateLegacyConfig( function findMigratableBinary( homeDir: string, targetDir: string, - filename: string + filename: string, ): string | undefined { for (const dir of getLegacyInstallDirs(homeDir)) { if (samePath(dir, targetDir)) { @@ -168,7 +168,7 @@ function findMigratableBinary( async function migrateLegacyBinary( homeDir: string, env: NodeJS.ProcessEnv, - emit: Logger + emit: Logger, ): Promise { const filename = getBinaryFilename(); const targetDir = determineInstallDir(homeDir, env); @@ -218,7 +218,7 @@ async function handleInstall( execPath: string, homeDir: string, env: NodeJS.ProcessEnv, - emit: Logger + emit: Logger, ): Promise<{ binaryPath: string; binaryDir: string; created: boolean }> { const installDir = determineInstallDir(homeDir, env); const targetPath = join(installDir, getBinaryFilename()); @@ -231,7 +231,7 @@ async function handleInstall( // Clean up temp binary (Posix only — the inode stays alive for the running process) if (process.platform !== "win32") { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { unlinkSync(execPath); } catch { @@ -249,7 +249,7 @@ async function handlePathModification( binaryDir: string, shell: ShellInfo, env: NodeJS.ProcessEnv, - emit: Logger + emit: Logger, ) { const alreadyInPath = isInPath(binaryDir, env.PATH); @@ -295,7 +295,7 @@ async function handlePathModification( async function tryBashCompletionFallback( homeDir: string, xdgDataHome: string | undefined, - pathEnv: string | undefined + pathEnv: string | undefined, ): Promise { if (!isBashAvailable(pathEnv)) { return null; @@ -316,7 +316,7 @@ async function tryBashCompletionFallback( async function handleZshFpath( shell: ShellInfo, completionDir: string, - isNewInstall: boolean + isNewInstall: boolean, ): Promise { const lines: string[] = []; @@ -328,7 +328,7 @@ async function handleZshFpath( } else if (result.manualCommand) { lines.push(`Completions: ${result.message}`); lines.push( - ` Add manually to ${shell.configFile}: ${result.manualCommand}` + ` Add manually to ${shell.configFile}: ${result.manualCommand}`, ); } } else if (isNewInstall) { @@ -353,7 +353,7 @@ async function handleCompletions( shell: ShellInfo, homeDir: string, xdgDataHome: string | undefined, - pathEnv: string | undefined + pathEnv: string | undefined, ): Promise { const location = await installCompletions(shell.type, homeDir, xdgDataHome); @@ -363,7 +363,7 @@ async function handleCompletions( if (shell.type === "zsh") { const completionDir = dirname(location.path); lines.push( - ...(await handleZshFpath(shell, completionDir, location.created)) + ...(await handleZshFpath(shell, completionDir, location.created)), ); } @@ -392,7 +392,7 @@ async function handleCompletions( const fallback = await tryBashCompletionFallback( homeDir, xdgDataHome, - pathEnv + pathEnv, ); if (fallback) { @@ -435,7 +435,7 @@ async function handleAgentSkills(homeDir: string, emit: Logger) { function printWelcomeMessage( emit: Logger, version: string, - binaryPath: string + binaryPath: string, ): void { emit(""); emit(`Installed sentry v${version} to ${binaryPath}`); @@ -461,7 +461,7 @@ type WarnLogger = (step: string, error: unknown) => void; async function bestEffort( stepName: string, fn: () => void | Promise, - warn: WarnLogger + warn: WarnLogger, ) { try { await fn(); @@ -510,7 +510,7 @@ async function runConfigurationSteps(opts: ConfigStepOptions) { emit(`Recorded installation method: ${method}`); } }, - warn + warn, ); } @@ -525,7 +525,7 @@ async function runConfigurationSteps(opts: ConfigStepOptions) { emit(`Recorded release channel: ${channel}`); } }, - warn + warn, ); } @@ -534,7 +534,7 @@ async function runConfigurationSteps(opts: ConfigStepOptions) { await bestEffort( "PATH modification", () => handlePathModification(binaryDir, shell, env, emit), - warn + warn, ); } @@ -547,13 +547,13 @@ async function runConfigurationSteps(opts: ConfigStepOptions) { shell, homeDir, env.XDG_DATA_HOME, - env.PATH + env.PATH, ); for (const line of completionLines) { emit(line); } }, - warn + warn, ); } @@ -565,7 +565,7 @@ async function runConfigurationSteps(opts: ConfigStepOptions) { await bestEffort( "Recording agent skills preference", () => setAgentSkillsPreference(false), - warn + warn, ); } const skipAgentSkills = @@ -574,7 +574,7 @@ async function runConfigurationSteps(opts: ConfigStepOptions) { await bestEffort( "Agent skills", () => handleAgentSkills(homeDir, emit), - warn + warn, ); } } @@ -684,7 +684,7 @@ export const setupCommand = buildCommand({ await bestEffort( "Legacy config migration", () => migrateLegacyConfig(homeDir, process.env, emit), - warn + warn, ); await bestEffort( "Legacy binary migration", @@ -692,7 +692,7 @@ export const setupCommand = buildCommand({ const migratedBinary = await migrateLegacyBinary( homeDir, process.env, - emit + emit, ); // Adopt the new location so PATH setup and recorded install info point // at the migrated binary rather than the deleted legacy path. @@ -701,7 +701,7 @@ export const setupCommand = buildCommand({ binaryDir = dirname(migratedBinary); } }, - warn + warn, ); // 1. Install binary from temp location (when --install is set) @@ -710,7 +710,7 @@ export const setupCommand = buildCommand({ process.execPath, homeDir, process.env, - emit + emit, ); binaryPath = result.binaryPath; binaryDir = result.binaryDir; @@ -732,12 +732,11 @@ export const setupCommand = buildCommand({ await bestEffort( "Authorization", async () => { - const { ensureCurrentOAuthScopes } = await import( - "../../lib/scope-recovery.js" - ); + const { ensureCurrentOAuthScopes } = + await import("../../lib/scope-recovery.js"); await ensureCurrentOAuthScopes(runInteractiveLogin); }, - warn + warn, ); } @@ -784,7 +783,7 @@ export const setupCommand = buildCommand({ log.info("Run 'sentry auth login' to authenticate later."); } }, - warn + warn, ); }, }); diff --git a/packages/cli/src/commands/cli/uninstall.ts b/packages/cli/src/commands/cli/uninstall.ts index b285c6ce2..a2cf537ec 100644 --- a/packages/cli/src/commands/cli/uninstall.ts +++ b/packages/cli/src/commands/cli/uninstall.ts @@ -121,7 +121,7 @@ type UninstallResult = { * @internal Exported for testing */ export async function removeSentryLinesFromConfig( - configFile: string + configFile: string, ): Promise { try { const content = await readFile(configFile, "utf-8"); @@ -293,8 +293,8 @@ function gatherArtifacts(home: string): UninstallArtifact[] { remove: () => Promise.reject( new Error( - `Cannot delete running binary on Windows. Please delete manually: ${binaryPath}` - ) + `Cannot delete running binary on Windows. Please delete manually: ${binaryPath}`, + ), ), }); } else { @@ -352,7 +352,7 @@ function formatArtifactList(artifacts: UninstallArtifact[]): string[] { */ async function executeRemovals( existing: UninstallArtifact[], - missing: UninstallArtifact[] + missing: UninstallArtifact[], ): Promise { const result: UninstallResult = { removed: [], @@ -413,7 +413,7 @@ function formatUninstallHuman(result: UninstallResult): string { result.failed.length === 0 ) { lines.push( - "\nSentry CLI has been uninstalled. Restart your shell to apply PATH changes." + "\nSentry CLI has been uninstalled. Restart your shell to apply PATH changes.", ); } @@ -449,7 +449,7 @@ export const uninstallCommand = buildDeleteCommand({ readonly force?: boolean; readonly "dry-run"?: boolean; readonly "keep-config"?: boolean; - } + }, ) { const home = homedir(); @@ -518,12 +518,12 @@ export const uninstallCommand = buildDeleteCommand({ // Confirm unless --yes/--force if (!isConfirmationBypassed(flags)) { log.warn( - `This will remove ${existing.length} item(s):\n${existing.map((a) => ` - ${a.label} (${a.path})`).join("\n")}` + `This will remove ${existing.length} item(s):\n${existing.map((a) => ` - ${a.label} (${a.path})`).join("\n")}`, ); const confirmed = await confirmByTyping( "uninstall", - "Type 'uninstall' to confirm removal:" + "Type 'uninstall' to confirm removal:", ); if (!confirmed) { return { hint: "Uninstall cancelled." }; diff --git a/packages/cli/src/commands/cli/upgrade.ts b/packages/cli/src/commands/cli/upgrade.ts index b3039d86b..812cd0aeb 100644 --- a/packages/cli/src/commands/cli/upgrade.ts +++ b/packages/cli/src/commands/cli/upgrade.ts @@ -151,7 +151,7 @@ function resolveOfflineTarget(versionArg: string | undefined): string { if (!latestVersion) { throw new UpgradeError( "network_error", - "No cached version available. Run any command to trigger a background version check, then retry." + "No cached version available. Run any command to trigger a background version check, then retry.", ); } return latestVersion; @@ -193,7 +193,7 @@ async function resolveTargetWithFallback(opts: { throw new UpgradeError( "unsupported_operation", "Cannot switch channels in offline mode — the cached version belongs to the current channel. " + - "Run 'sentry cli upgrade' with network access to switch channels." + "Run 'sentry cli upgrade' with network access to switch channels.", ); } // Read the cached version BEFORE persisting the channel — setReleaseChannel @@ -244,7 +244,7 @@ async function resolveTargetWithFallback(opts: { function validateMethod( method: InstallationMethod, versionArg: string | undefined, - offline: boolean + offline: boolean, ): void { if (method === "unknown") { throw new UpgradeError("unknown_method"); @@ -257,7 +257,7 @@ function validateMethod( if (method === "brew" && pinnedVersion && !isNightlyVersion(pinnedVersion)) { throw new UpgradeError( "unsupported_operation", - "Homebrew does not support installing a specific version. Run 'brew upgrade getsentry/tools/sentry' to upgrade to the latest formula version." + "Homebrew does not support installing a specific version. Run 'brew upgrade getsentry/tools/sentry' to upgrade to the latest formula version.", ); } // Offline mode is only supported for curl-installed binaries — package @@ -265,7 +265,7 @@ function validateMethod( if (offline && method !== "curl") { throw new UpgradeError( "unsupported_operation", - "Offline upgrade is only supported for curl-installed binaries." + "Offline upgrade is only supported for curl-installed binaries.", ); } } @@ -295,13 +295,13 @@ type ResolveResult = async function resolvePinnedVersion( lookupMethod: InstallationMethod, - target: string + target: string, ): Promise { if (lookupMethod !== "curl") { if (!(await versionExists(lookupMethod, target))) { throw new UpgradeError( "version_not_found", - `Version ${target} not found` + `Version ${target} not found`, ); } return; @@ -320,7 +320,7 @@ async function resolvePinnedVersion( * or return a completed result immediately. */ async function resolveTargetVersion( - opts: ResolveTargetOptions + opts: ResolveTargetOptions, ): Promise { const { method, channel, versionArg, channelChanged, flags } = opts; const standalone = @@ -479,7 +479,7 @@ export function isEnoentSpawnError(error: unknown): boolean { async function spawnWithRetry( binaryPath: string, args: string[], - env: NodeJS.ProcessEnv | undefined + env: NodeJS.ProcessEnv | undefined, ): Promise { for (let attempt = 1; attempt <= SPAWN_MAX_ATTEMPTS; attempt++) { try { @@ -498,8 +498,8 @@ async function spawnWithRetry( "execution_failed", "Downloaded binary was killed by the operating system (SIGKILL). " + "This usually means the binary has an invalid code signature. " + - "Try reinstalling: curl -sL https://sentry.io/get-cli/ | bash" - ) + "Try reinstalling: curl -sL https://sentry.io/get-cli/ | bash", + ), ); return; } @@ -520,7 +520,7 @@ async function spawnWithRetry( throw new UpgradeError( "execution_failed", `Downloaded binary not found at ${binaryPath}. ` + - "The download may have been interrupted — rerun `sentry cli upgrade`." + "The download may have been interrupted — rerun `sentry cli upgrade`.", ); } if (!isEbusyError(error) || attempt === SPAWN_MAX_ATTEMPTS) { @@ -528,7 +528,7 @@ async function spawnWithRetry( } const delay = attempt * SPAWN_RETRY_BASE_MS; log.warn( - `Binary is locked (antivirus scan?), retrying in ${delay}ms... (attempt ${attempt}/${SPAWN_MAX_ATTEMPTS})` + `Binary is locked (antivirus scan?), retrying in ${delay}ms... (attempt ${attempt}/${SPAWN_MAX_ATTEMPTS})`, ); await setTimeout(delay); } @@ -605,7 +605,7 @@ async function runSetupOnNewBinary(opts: SetupOptions): Promise { if (exitCode !== 0) { throw new UpgradeError( "execution_failed", - `Setup failed with exit code ${exitCode}` + `Setup failed with exit code ${exitCode}`, ); } } @@ -613,7 +613,7 @@ async function runSetupOnNewBinary(opts: SetupOptions): Promise { function resolveUpdatedCliPath( execPath: string, entryPath: string | undefined, - pathEnv: string | undefined + pathEnv: string | undefined, ): string { return whichSync("sentry", { PATH: pathEnv }) ?? entryPath ?? execPath; } @@ -634,7 +634,7 @@ function resolveUpdatedCliPath( */ export function resolveUpgradeInstallDir( currentInstallDir: string, - pathEnv: string | undefined + pathEnv: string | undefined, ): string { const legacyBinDir = join(homedir(), LEGACY_INSTALL_SUBDIR); if (!samePath(currentInstallDir, legacyBinDir)) { @@ -692,12 +692,12 @@ async function executeStandardUpgrade(opts: { const downloadResult = await withProgress( { message: `Downloading ${target}...`, json }, async (setMessage) => - executeUpgrade(method, target, downloadTag, offline, setMessage, source) + executeUpgrade(method, target, downloadTag, offline, setMessage, source), ); if (downloadResult?.patchBytes) { log.info( - `Applied delta patch (${formatBytes(downloadResult.patchBytes)} downloaded)` + `Applied delta patch (${formatBytes(downloadResult.patchBytes)} downloaded)`, ); } @@ -780,19 +780,26 @@ async function migrateToStandaloneForNightly(opts: { const downloadResult = await withProgress( { message: `Downloading ${target}...`, json }, async (setMessage) => - executeUpgrade("curl", target, downloadTag, undefined, setMessage, source) + executeUpgrade( + "curl", + target, + downloadTag, + undefined, + setMessage, + source, + ), ); if (downloadResult?.patchBytes) { log.info( - `Applied delta patch (${formatBytes(downloadResult.patchBytes)} downloaded)` + `Applied delta patch (${formatBytes(downloadResult.patchBytes)} downloaded)`, ); } if (!downloadResult) { throw new UpgradeError( "execution_failed", - "Failed to download nightly binary" + "Failed to download nightly binary", ); } @@ -824,7 +831,7 @@ async function migrateToStandaloneForNightly(opts: { }; const warnings: string[] = []; warnings.push( - `Your ${method}-installed sentry may still appear earlier in PATH.` + `Your ${method}-installed sentry may still appear earlier in PATH.`, ); const hint = uninstallHints[method]; if (hint) { @@ -836,11 +843,11 @@ async function migrateToStandaloneForNightly(opts: { /** * Resolve the channel, version arg, method, and channel-changed flag from * the positional version argument and flags. Extracted to keep `func()` - * complexity under the biome limit. + * complexity manageable. */ async function resolveContext( version: string | undefined, - flags: UpgradeFlags + flags: UpgradeFlags, ): Promise<{ channel: ReleaseChannel; versionArg: string | undefined; @@ -865,7 +872,7 @@ async function resolveContext( function persistChannel( channel: ReleaseChannel, channelChanged: boolean, - version: string | undefined + version: string | undefined, ): void { if (channelChanged || CHANNEL_VERSIONS.has(version ?? "")) { setReleaseChannel(channel); @@ -1004,7 +1011,7 @@ export const upgradeCommand = buildCommand({ method, persistChannelFn: () => persistChannel(channel, channelChanged, version), - }) + }), ); // Early exit for check-only (online) and up-to-date results. if (resolved.kind === "done") { @@ -1048,7 +1055,7 @@ export const upgradeCommand = buildCommand({ flags, offline, changelogPromise, - }) + }), ); } diff --git a/packages/cli/src/commands/code-mappings/upload.ts b/packages/cli/src/commands/code-mappings/upload.ts index 42abf55ee..1df1bcc7e 100644 --- a/packages/cli/src/commands/code-mappings/upload.ts +++ b/packages/cli/src/commands/code-mappings/upload.ts @@ -89,7 +89,7 @@ function formatUploadResult(data: CodeMappingsUploadResult): string { * Read and validate the code mappings JSON file. */ async function readAndValidateMappings( - path: string + path: string, ): Promise> { let content: string; try { @@ -99,19 +99,19 @@ async function readAndValidateMappings( if (code === "ENOENT") { throw new ValidationError( `Code mappings file '${path}' does not exist.`, - "path" + "path", ); } if (code === "EISDIR") { throw new ValidationError( `Path '${path}' is a directory, not a code mappings file.`, - "path" + "path", ); } const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `Cannot read code mappings file '${path}': ${msg}`, - "path" + "path", ); } @@ -121,21 +121,21 @@ async function readAndValidateMappings( } catch { throw new ValidationError( `Invalid code mappings file '${path}': not valid JSON`, - "path" + "path", ); } if (!Array.isArray(parsed)) { throw new ValidationError( `Invalid code mappings file '${path}': expected a JSON array`, - "path" + "path", ); } if (parsed.length === 0) { throw new ValidationError( `Code mappings file '${path}' contains no mappings`, - "path" + "path", ); } @@ -152,7 +152,7 @@ async function readAndValidateMappings( .join(", "); throw new ValidationError( `Invalid code mapping at index ${i}: ${issues}`, - "path" + "path", ); } mappings.push(result.output); @@ -218,7 +218,7 @@ export const uploadCommand = buildCommand({ repo?: string; "default-branch"?: string; }, - mappingPath: string + mappingPath: string, ) { // 1. Read and validate the mappings file const mappings = await readAndValidateMappings(mappingPath); @@ -243,7 +243,7 @@ export const uploadCommand = buildCommand({ [ "Could not auto-detect repository from git remotes", "Provide --repo explicitly", - ] + ], ); } @@ -253,7 +253,7 @@ export const uploadCommand = buildCommand({ inferDefaultBranch(repoInfo?.remote ?? "origin", this.cwd); log.info( - `Uploading ${mappings.length} code mapping(s) for ${org}/${project} → ${repository}` + `Uploading ${mappings.length} code mapping(s) for ${org}/${project} → ${repository}`, ); // 5. Upload diff --git a/packages/cli/src/commands/dart-symbol-map/upload.ts b/packages/cli/src/commands/dart-symbol-map/upload.ts index a4b37e850..c86ad33dc 100644 --- a/packages/cli/src/commands/dart-symbol-map/upload.ts +++ b/packages/cli/src/commands/dart-symbol-map/upload.ts @@ -73,7 +73,7 @@ function validateDebugId(debugId: string): void { if (!UUID_RE.test(debugId)) { throw new ValidationError( `Invalid debug ID format: '${debugId}'. Expected UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, - "debug-id" + "debug-id", ); } } @@ -92,14 +92,14 @@ function validateDartSymbolMap(content: Buffer, path: string): void { } catch { throw new ValidationError( `Invalid dart symbol map '${path}': not valid JSON`, - "path" + "path", ); } if (!Array.isArray(parsed)) { throw new ValidationError( `Invalid dart symbol map '${path}': expected a JSON array, got ${typeof parsed}`, - "path" + "path", ); } @@ -107,7 +107,7 @@ function validateDartSymbolMap(content: Buffer, path: string): void { if (typeof parsed[i] !== "string") { throw new ValidationError( `Invalid dart symbol map '${path}': entry at index ${i} is ${typeof parsed[i]}, expected string`, - "path" + "path", ); } } @@ -115,7 +115,7 @@ function validateDartSymbolMap(content: Buffer, path: string): void { if (parsed.length % 2 !== 0) { throw new ValidationError( `Invalid dart symbol map '${path}': expected an even number of entries (pairs), got ${parsed.length}`, - "path" + "path", ); } } @@ -135,19 +135,19 @@ async function readMappingFile(path: string): Promise { if (code === "ENOENT") { throw new ValidationError( `Dart symbol map '${path}' does not exist.`, - "path" + "path", ); } if (code === "EISDIR") { throw new ValidationError( `Path '${path}' is a directory, not a dart symbol map file.`, - "path" + "path", ); } const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `Cannot read dart symbol map '${path}': ${msg}`, - "path" + "path", ); } } @@ -210,7 +210,7 @@ export const uploadCommand = buildCommand({ "debug-id": string; "no-upload"?: boolean; }, - mappingPath: string + mappingPath: string, ) { // 1. Validate debug ID format validateDebugId(flags["debug-id"]); @@ -220,7 +220,7 @@ export const uploadCommand = buildCommand({ if (content.length === 0) { throw new ValidationError( `Dart symbol map '${mappingPath}' is empty.`, - "path" + "path", ); } validateDartSymbolMap(content, mappingPath); @@ -249,7 +249,7 @@ export const uploadCommand = buildCommand({ // 5. Upload log.debug( - `Uploading dart symbol map '${mappingPath}' (debug ID: ${flags["debug-id"]}) to ${org}/${project}` + `Uploading dart symbol map '${mappingPath}' (debug ID: ${flags["debug-id"]}) to ${org}/${project}`, ); await uploadDartSymbolMap({ org, diff --git a/packages/cli/src/commands/dashboard/create.ts b/packages/cli/src/commands/dashboard/create.ts index 674016021..556ba46ec 100644 --- a/packages/cli/src/commands/dashboard/create.ts +++ b/packages/cli/src/commands/dashboard/create.ts @@ -62,21 +62,21 @@ type ResolvedDashboardTarget = { /** Enrich targets that lack a projectId by calling the project API */ async function enrichTargetProjectIds( - targets: { org: string; project: string; projectId?: number }[] + targets: { org: string; project: string; projectId?: number }[], ): Promise { const enriched = await Promise.all( targets.map(async (t) => { if (t.projectId !== undefined) { return t.projectId; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const info = await getProject(t.org, t.project); return toNumericId(info.id); } catch { return; } - }) + }), ); return enriched.filter((id): id is number => id !== undefined); } @@ -84,7 +84,7 @@ async function enrichTargetProjectIds( /** Resolve org and project IDs from the parsed target argument */ async function resolveDashboardTarget( parsed: ParsedOrgProject, - cwd: string + cwd: string, ): Promise { switch (parsed.type) { case "explicit": { @@ -104,7 +104,7 @@ async function resolveDashboardTarget( parsed.projectSlug, "sentry dashboard create / ", undefined, - parsed.originalSlug + parsed.originalSlug, ); const pid = toNumericId(found.projectData.id); return { @@ -119,7 +119,7 @@ async function resolveDashboardTarget( if (!resolved) { throw new ContextError( "Organization", - "sentry dashboard create <org>/ <title>" + "sentry dashboard create <org>/ <title>", ); } return { orgSlug: resolved.org, projectIds: [] }; @@ -131,7 +131,7 @@ async function resolveDashboardTarget( default: { const _exhaustive: never = parsed; throw new Error( - `Unexpected parsed type: ${(_exhaustive as { type: string }).type}` + `Unexpected parsed type: ${(_exhaustive as { type: string }).type}`, ); } } @@ -175,7 +175,7 @@ export const createCommand = buildCommand({ widgets: [], projects: projectIds.length > 0 ? projectIds : undefined, }).catch(async (error: unknown) => - enrichDashboardError(error, { orgSlug, operation: "create" }) + enrichDashboardError(error, { orgSlug, operation: "create" }), ); const url = buildDashboardUrl(orgSlug, dashboard.id); diff --git a/packages/cli/src/commands/dashboard/list.ts b/packages/cli/src/commands/dashboard/list.ts index d3ffff11f..a3722ce82 100644 --- a/packages/cli/src/commands/dashboard/list.ts +++ b/packages/cli/src/commands/dashboard/list.ts @@ -82,7 +82,7 @@ type DashboardListResult = { */ export function encodeCursor( serverCursor: string | undefined, - afterId?: string + afterId?: string, ): string | undefined { if (afterId) { return `${serverCursor ?? ""}|${afterId}`; @@ -134,7 +134,7 @@ function formatDashboardListHuman(result: DashboardListResult): string { result.allTitles, { maxResults: 5, - } + }, ); if (similar.length > 0) { return `No dashboards matching '${result.titleFilter}'. Did you mean:\n${similar.map((t) => ` • ${t}`).join("\n")}`; @@ -182,7 +182,7 @@ function formatDashboardListHuman(result: DashboardListResult): string { */ function jsonTransformDashboardList( result: DashboardListResult, - fields?: string[] + fields?: string[], ): unknown { const items = fields && fields.length > 0 @@ -235,7 +235,7 @@ function processPage( serverCursor: string | undefined; afterId: string | undefined; glob: ((input: string) => boolean) | undefined; - } + }, ): PageResult { // When resuming mid-page, find the afterId and skip everything up to and // including it. If the afterId was deleted between requests, fall through @@ -285,7 +285,7 @@ async function fetchDashboards( serverCursor: string | undefined; afterId: string | undefined; glob: ((input: string) => boolean) | undefined; - } + }, ): Promise<FetchResult> { let { serverCursor, afterId } = opts; const results: DashboardListItem[] = []; @@ -341,7 +341,7 @@ async function fetchDashboards( */ function buildHint( result: DashboardListResult, - orgSlug: string + orgSlug: string, ): string | undefined { const filterArg = result.titleFilter ? ` '${result.titleFilter}'` : ""; const navRaw = paginationHint({ @@ -413,7 +413,7 @@ export const listCommand = buildListCommand("dashboard", { const orgSlug = await resolveOrgFromTarget( parsed, cwd, - "sentry dashboard list <org>/" + "sentry dashboard list <org>/", ); if (flags.web) { @@ -430,7 +430,7 @@ export const listCommand = buildListCommand("dashboard", { const { cursor: rawCursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { serverCursor, afterId } = decodeCursor(rawCursor ?? ""); @@ -460,9 +460,9 @@ export const listCommand = buildListCommand("dashboard", { serverCursor, afterId, glob, - }) + }), ).catch(async (error: unknown) => - enrichDashboardError(error, { orgSlug, operation: "list" }) + enrichDashboardError(error, { orgSlug, operation: "list" }), ); // Advance the pagination cursor stack @@ -470,7 +470,7 @@ export const listCommand = buildListCommand("dashboard", { PAGINATION_KEY, contextKey, direction, - cursorToStore + cursorToStore, ); const hasMore = !!cursorToStore; diff --git a/packages/cli/src/commands/dashboard/resolve.ts b/packages/cli/src/commands/dashboard/resolve.ts index bb3f6b6ef..c64123161 100644 --- a/packages/cli/src/commands/dashboard/resolve.ts +++ b/packages/cli/src/commands/dashboard/resolve.ts @@ -61,7 +61,7 @@ export type WidgetQueryFlags = { export function resolveOrgFromTarget( parsed: ReturnType<typeof parseOrgProjectArg>, cwd: string, - usageHint: string + usageHint: string, ): Promise<string> { return resolveOrgOnlyTarget(parsed, cwd, "dashboard", usageHint); } @@ -80,7 +80,7 @@ type DashboardArgResult = { */ function tryExtractDashboardUrl( first: string, - args: string[] + args: string[], ): DashboardArgResult | null { const urlParsed = parseSentryUrl(first); if (!urlParsed) { @@ -90,7 +90,7 @@ function tryExtractDashboardUrl( if (urlParsed.dashboardId) { log.warn( `Extracted dashboard ID ${urlParsed.dashboardId} from URL` + - (urlParsed.org ? ` (org: ${urlParsed.org})` : "") + (urlParsed.org ? ` (org: ${urlParsed.org})` : ""), ); return { dashboardRef: urlParsed.dashboardId, @@ -110,7 +110,7 @@ function tryExtractDashboardUrl( "Dashboard ID or title is required.\n\n" + "The URL provided contains an org but no dashboard ID.\n" + `Try: sentry dashboard <command> ${urlParsed.org}/ <id-or-title>`, - "dashboard" + "dashboard", ); } return null; @@ -132,12 +132,12 @@ function tryExtractDashboardUrl( * @returns Dashboard reference string and optional target arg */ export function parseDashboardPositionalArgs( - args: string[] + args: string[], ): DashboardArgResult { if (args.length === 0) { throw new ValidationError( "Dashboard ID or title is required.", - "dashboard" + "dashboard", ); } @@ -198,7 +198,7 @@ type ListArgResult = { */ function tryExtractListUrl( first: string, - remaining: string[] + remaining: string[], ): ListArgResult | null { const urlParsed = parseSentryUrl(first); if (!urlParsed) { @@ -214,7 +214,7 @@ function tryExtractListUrl( ` sentry dashboard view ${orgPrefix}${urlParsed.dashboardId}\n\n` + `To list dashboards${orgNote}:\n\n` + ` sentry dashboard list${orgSuffix}`, - "dashboard" + "dashboard", ); } if (urlParsed.org) { @@ -230,7 +230,7 @@ export function parseDashboardListArgs(args: string[]): ListArgResult { // when the first positional matches a subcommand name (e.g. "view", "create"). // Filter those out so we don't crash on .includes("/"). const filtered = args.filter( - (a): a is string => a !== null && a !== undefined && a !== "" + (a): a is string => a !== null && a !== undefined && a !== "", ); if (filtered.length === 0) { return { targetArg: undefined, titleFilter: undefined }; @@ -307,7 +307,7 @@ function splitOrgProjectName(arg: string): { */ export async function resolveDashboardId( orgSlug: string, - ref: string + ref: string, ): Promise<string> { if (isAllDigits(ref)) { return ref; @@ -323,7 +323,7 @@ export async function resolveDashboardId( perPage: API_MAX_PER_PAGE, cursor, }).catch(async (error: unknown) => - enrichDashboardError(error, { orgSlug, operation: "list" }) + enrichDashboardError(error, { orgSlug, operation: "list" }), ); // Match by ID/slug first (e.g. "default-overview"), then fall back to title const match = @@ -359,7 +359,7 @@ export async function resolveDashboardId( } throw new ValidationError( - `No dashboard with title '${ref}' found in '${orgSlug}'.${hint}` + `No dashboard with title '${ref}' found in '${orgSlug}'.${hint}`, ); } @@ -374,25 +374,25 @@ export async function resolveDashboardId( export function resolveWidgetIndex( widgets: DashboardWidget[], index: number | undefined, - title: string | undefined + title: string | undefined, ): number { if (index !== undefined) { if (index < 0 || index >= widgets.length) { throw new ValidationError( `Widget index ${index} out of range (dashboard has ${widgets.length} widgets).`, - "index" + "index", ); } return index; } const lowerTitle = (title ?? "").toLowerCase(); const matchIndex = widgets.findIndex( - (w) => (w.title ?? "").toLowerCase() === lowerTitle + (w) => (w.title ?? "").toLowerCase() === lowerTitle, ); if (matchIndex === -1) { throw new ValidationError( `No widget with title '${title}' found in dashboard.`, - "title" + "title", ); } return matchIndex; @@ -407,7 +407,7 @@ export function resolveWidgetIndex( */ export function validateSortReferencesAggregate( orderby: string, - aggregates: string[] + aggregates: string[], ): void { // Strip leading "-" for descending sorts const sortAgg = orderby.startsWith("-") ? orderby.slice(1) : orderby; @@ -417,7 +417,7 @@ export function validateSortReferencesAggregate( "The --sort field must be one of the aggregate expressions in --query.\n" + `Current aggregates: ${aggregates.join(", ")}\n\n` + `Either add "${sortAgg}" to --query or sort by an existing aggregate.`, - "sort" + "sort", ); } } @@ -447,7 +447,7 @@ export const DEFAULT_GROUP_BY_LIMIT = 5; */ export function autoDefaultGroupLimit( columns: string[], - limit: number | null | undefined + limit: number | null | undefined, ): number | undefined { if (limit !== undefined && limit !== null) { return limit; @@ -476,7 +476,7 @@ const log = logger.withTag("dashboard"); export function applyGroupLimitAutoDefault( userGroupBy: string[] | undefined, columns: string[], - limit: number | null | undefined + limit: number | null | undefined, ): number | null | undefined { if (!userGroupBy || userGroupBy.length === 0) { return limit; @@ -484,7 +484,7 @@ export function applyGroupLimitAutoDefault( const effective = autoDefaultGroupLimit(columns, limit); if (effective !== limit && effective !== undefined) { log.info( - `Auto-defaulting --limit to ${DEFAULT_GROUP_BY_LIMIT} for grouped widget. Pass --limit <n> to override.` + `Auto-defaulting --limit to ${DEFAULT_GROUP_BY_LIMIT} for grouped widget. Pass --limit <n> to override.`, ); return effective; } @@ -538,7 +538,7 @@ const NUMERIC_AGGREGATE_FUNCTIONS = new Set([ */ function warnUnknownAggregateFields( aggregates: string[], - dataset: string | undefined + dataset: string | undefined, ): void { if (dataset && dataset !== "spans") { return; @@ -562,7 +562,7 @@ function warnUnknownAggregateFields( `Aggregate field "${inner}" in "${agg}" is not a known aggregatable span field. ` + "Span attributes (custom tags) cannot be used with numeric aggregates — " + "use them in --where or --group-by instead. " + - `Known numeric fields: ${[...KNOWN_SPAN_AGGREGATE_FIELDS].join(", ")}` + `Known numeric fields: ${[...KNOWN_SPAN_AGGREGATE_FIELDS].join(", ")}`, ); } } @@ -650,7 +650,7 @@ const MAX_404_SUGGESTIONS = 5; */ async function build404Error( ctx: DashboardErrorContext, - org: string + org: string, ): Promise<never> { if (ctx.operation === "list") { throw new ResolutionError( @@ -660,7 +660,7 @@ async function build404Error( [ "Verify the organization slug with: sentry org list", "Check that you have access to the organization", - ] + ], ); } const listHint = `sentry dashboard list ${ctx.orgSlug ?? "<org>"}/`; @@ -677,7 +677,7 @@ async function build404Error( }); if (data.length > 0) { const lines = data.map( - (d) => ` ${d.id} ${d.title ?? "(untitled)"}` + (d) => ` ${d.id} ${d.title ?? "(untitled)"}`, ); alternatives.push(`Available dashboards:\n${lines.join("\n")}`); } @@ -689,7 +689,7 @@ async function build404Error( `Dashboard ${ctx.dashboardId} in ${org}`, "not found", listHint, - alternatives + alternatives, ); } // Generic 404 for create or other operations @@ -697,7 +697,7 @@ async function build404Error( `Organization ${org}`, "not found", "sentry org list", - ["Verify the organization slug and your access"] + ["Verify the organization slug and your access"], ); } @@ -705,7 +705,7 @@ async function build404Error( function build403Error( ctx: DashboardErrorContext, org: string, - detail: string | undefined + detail: string | undefined, ): never { const message = detail ?? "You do not have permission."; if (ctx.dashboardId) { @@ -713,14 +713,14 @@ function build403Error( `Dashboard ${ctx.dashboardId} in ${org}`, "access denied", `sentry dashboard list ${ctx.orgSlug ?? "<org>"}/`, - [message, "Check your organization membership and role"] + [message, "Check your organization membership and role"], ); } throw new ResolutionError( `Dashboards in ${org}`, "access denied", "sentry org list", - [message, "Check your organization membership and role"] + [message, "Check your organization membership and role"], ); } @@ -736,7 +736,7 @@ function build403Error( */ export async function enrichDashboardError( error: unknown, - ctx: DashboardErrorContext + ctx: DashboardErrorContext, ): Promise<never> { if (!(error instanceof ApiError)) { throw error; @@ -772,7 +772,7 @@ export async function enrichDashboardError( error.status, error.detail ?? "The API rejected the request. Check widget configuration.", - error.endpoint + error.endpoint, ); } @@ -839,7 +839,7 @@ export function validateWidgetEnums(display?: string, dataset?: string): void { ) { throw new ValidationError( `Invalid --display value "${display}".\nValid display types: ${DISPLAY_TYPES.join(", ")}`, - "display" + "display", ); } if ( @@ -848,7 +848,7 @@ export function validateWidgetEnums(display?: string, dataset?: string): void { ) { throw new ValidationError( `Invalid --dataset value "${dataset}".\nValid datasets: ${WIDGET_TYPES.join(", ")}`, - "dataset" + "dataset", ); } // The Sentry backend validates displayType and widgetType as independent enums — diff --git a/packages/cli/src/commands/dashboard/restore.ts b/packages/cli/src/commands/dashboard/restore.ts index 92637738e..1e4d0d8cd 100644 --- a/packages/cli/src/commands/dashboard/restore.ts +++ b/packages/cli/src/commands/dashboard/restore.ts @@ -84,7 +84,7 @@ export const restoreCommand = buildCommand({ if (!revision) { throw new ValidationError( "--revision must be a non-empty revision ID.", - "revision" + "revision", ); } return revision; @@ -102,19 +102,19 @@ export const restoreCommand = buildCommand({ const orgSlug = await resolveOrgFromTarget( parsed, cwd, - "sentry dashboard restore <org>/ <id> --revision <rev>" + "sentry dashboard restore <org>/ <id> --revision <rev>", ); const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); const dashboard = await withProgress( { message: `Restoring revision ${flags.revision}...`, json: flags.json }, - () => restoreDashboardRevision(orgSlug, dashboardId, flags.revision) + () => restoreDashboardRevision(orgSlug, dashboardId, flags.revision), ).catch(async (error: unknown) => enrichDashboardError(error, { orgSlug, dashboardId, operation: "update", - }) + }), ); const outputData: RestoreResult = { diff --git a/packages/cli/src/commands/dashboard/revisions.ts b/packages/cli/src/commands/dashboard/revisions.ts index b3ae2e99b..3bce65b31 100644 --- a/packages/cli/src/commands/dashboard/revisions.ts +++ b/packages/cli/src/commands/dashboard/revisions.ts @@ -73,7 +73,7 @@ function formatRevisionsHuman(result: RevisionsResult): string { id: r.id, title: escapeMarkdownCell(r.title), author: escapeMarkdownCell( - r.createdBy?.name ?? r.createdBy?.email ?? r.createdBy?.id ?? "—" + r.createdBy?.name ?? r.createdBy?.email ?? r.createdBy?.id ?? "—", ), created: `${escapeMarkdownCell(formatRelativeTime(r.dateCreated))}\n${colorTag("muted", r.dateCreated)}`, })); @@ -94,7 +94,7 @@ function formatRevisionsHuman(result: RevisionsResult): string { function jsonTransformRevisions( result: RevisionsResult, - fields?: string[] + fields?: string[], ): unknown { const items = fields && fields.length > 0 @@ -153,19 +153,19 @@ export const revisionsCommand = buildCommand({ const orgSlug = await resolveOrgFromTarget( parsed, cwd, - "sentry dashboard revisions <org>/ <id>" + "sentry dashboard revisions <org>/ <id>", ); const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); const contextKey = buildPaginationContextKey( "dashboard-revisions", `${orgSlug}/${dashboardId}`, - {} + {}, ); const { cursor: rawCursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const perPage = Math.min(flags.limit, API_MAX_PER_PAGE); @@ -189,13 +189,13 @@ export const revisionsCommand = buildCommand({ } cursor = nc; } - } + }, ).catch(async (error: unknown) => enrichDashboardError(error, { orgSlug, dashboardId, operation: "view", - }) + }), ); const trimmed = results.slice(0, flags.limit); @@ -206,7 +206,7 @@ export const revisionsCommand = buildCommand({ PAGINATION_KEY, contextKey, direction, - cursorToStore + cursorToStore, ); const hasPrev = hasPreviousPage(PAGINATION_KEY, contextKey); diff --git a/packages/cli/src/commands/dashboard/view.ts b/packages/cli/src/commands/dashboard/view.ts index d9f6fc3c5..edf3c1891 100644 --- a/packages/cli/src/commands/dashboard/view.ts +++ b/packages/cli/src/commands/dashboard/view.ts @@ -70,7 +70,7 @@ function parseRefresh(value: string): number { const num = Number.parseInt(value, 10); if (Number.isNaN(num) || num < MIN_REFRESH_INTERVAL) { throw new Error( - `--refresh interval must be at least ${MIN_REFRESH_INTERVAL} seconds` + `--refresh interval must be at least ${MIN_REFRESH_INTERVAL} seconds`, ); } return num; @@ -115,7 +115,7 @@ function buildViewData( rendererPreference: GraphicsRendererPreference; graphicsCap: boolean; url: string; - } + }, ): DashboardViewData { return { id: dashboard.id, @@ -152,7 +152,7 @@ function buildViewData( */ function resolveViewTimeRange( flagPeriod: TimeRange | undefined, - dashboardPeriod: string | null | undefined + dashboardPeriod: string | null | undefined, ): TimeRange { if (flagPeriod) { return flagPeriod; @@ -244,7 +244,7 @@ export const viewCommand = buildCommand({ const orgSlug = await resolveOrgFromTarget( parsed, cwd, - "sentry dashboard view <org>/ <id>" + "sentry dashboard view <org>/ <id>", ); const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); const url = buildDashboardUrl(orgSlug, dashboardId); @@ -257,13 +257,13 @@ export const viewCommand = buildCommand({ // Fetch the dashboard definition (widget structure) const dashboard = await withProgress( { message: "Fetching dashboard...", json: flags.json }, - () => getDashboard(orgSlug, dashboardId) + () => getDashboard(orgSlug, dashboardId), ).catch(async (error: unknown) => enrichDashboardError(error, { orgSlug, dashboardId, operation: "view", - }) + }), ); const regionUrl = await resolveOrgRegion(orgSlug); @@ -281,7 +281,7 @@ export const viewCommand = buildCommand({ const interval = flags.refresh; if (!flags.json) { logger.info( - `Auto-refreshing dashboard every ${interval}s. Press Ctrl+C to stop.` + `Auto-refreshing dashboard every ${interval}s. Press Ctrl+C to stop.`, ); } @@ -304,7 +304,7 @@ export const viewCommand = buildCommand({ regionUrl, orgSlug, dashboard, - { ...widgetTimeOpts, periodSeconds } + { ...widgetTimeOpts, periodSeconds }, ); // Build output data before clearing so clear→render is instantaneous @@ -337,7 +337,7 @@ export const viewCommand = buildCommand({ queryAllWidgets(regionUrl, orgSlug, dashboard, { ...widgetTimeOpts, periodSeconds, - }) + }), ); yield new CommandOutput( @@ -346,7 +346,7 @@ export const viewCommand = buildCommand({ rendererPreference: flags.renderer, graphicsCap: !flags["no-graphics-cap"], url, - }) + }), ); return { hint: `Dashboard: ${url}` }; }, diff --git a/packages/cli/src/commands/dashboard/widget/add.ts b/packages/cli/src/commands/dashboard/widget/add.ts index b50062b18..300e6db8d 100644 --- a/packages/cli/src/commands/dashboard/widget/add.ts +++ b/packages/cli/src/commands/dashboard/widget/add.ts @@ -60,14 +60,14 @@ function parseAddPositionalArgs(args: string[]): { "Widget title is required as a positional argument.\n\n" + "Example:\n" + ' sentry dashboard widget add <dashboard> "My Widget" --display line --query count', - "title" + "title", ); } if (args.length > 3) { throw new ValidationError( `Too many positional arguments (got ${args.length}, expected at most 3).\n\n` + "Usage: sentry dashboard widget add [<org/project>] <dashboard> <title>", - "positional" + "positional", ); } @@ -221,7 +221,7 @@ export const addCommand = buildCommand({ const orgSlug = await resolveOrgFromTarget( parsed, cwd, - "sentry dashboard widget add <org>/ <dashboard> <title> --display <type>" + "sentry dashboard widget add <org>/ <dashboard> <title> --display <type>", ); const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); @@ -244,7 +244,7 @@ export const addCommand = buildCommand({ ) { throw new ValidationError( `Invalid --layout mode "${flags.layout}". Valid values: sequential, dense`, - "layout" + "layout", ); } const layoutMode: WidgetLayoutMode = @@ -257,7 +257,11 @@ export const addCommand = buildCommand({ // GET current dashboard → append widget with layout → PUT const current = await getDashboard(orgSlug, dashboardId).catch( async (error: unknown) => - enrichDashboardError(error, { orgSlug, dashboardId, operation: "view" }) + enrichDashboardError(error, { + orgSlug, + dashboardId, + operation: "view", + }), ); const updateBody = prepareDashboardForUpdate(current); @@ -288,7 +292,7 @@ export const addCommand = buildCommand({ const finalLayout = newWidget.layout ?? baseLayout; validateWidgetLayout( { col: finalLayout.x, width: finalLayout.w }, - finalLayout + finalLayout, ); } @@ -297,13 +301,13 @@ export const addCommand = buildCommand({ const updated = await updateDashboard( orgSlug, dashboardId, - updateBody + updateBody, ).catch(async (error: unknown) => enrichDashboardError(error, { orgSlug, dashboardId, operation: "update", - }) + }), ); const url = buildDashboardUrl(orgSlug, dashboardId); diff --git a/packages/cli/src/commands/dashboard/widget/delete.ts b/packages/cli/src/commands/dashboard/widget/delete.ts index d1f78b98f..6d337d15c 100644 --- a/packages/cli/src/commands/dashboard/widget/delete.ts +++ b/packages/cli/src/commands/dashboard/widget/delete.ts @@ -111,7 +111,7 @@ export const deleteCommand = buildDeleteCommand( if (flags.index === undefined && !flags.title) { throw new ValidationError( "Specify --index or --title to identify the widget to delete.", - "index" + "index", ); } @@ -120,7 +120,7 @@ export const deleteCommand = buildDeleteCommand( const orgSlug = await resolveOrgFromTarget( parsed, cwd, - "sentry dashboard widget delete <org>/ <id> (--index <n> | --title <name>)" + "sentry dashboard widget delete <org>/ <id> (--index <n> | --title <name>)", ); const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); @@ -131,7 +131,7 @@ export const deleteCommand = buildDeleteCommand( orgSlug, dashboardId, operation: "view", - }) + }), ); const widgets = current.widgets ?? []; @@ -157,13 +157,13 @@ export const deleteCommand = buildDeleteCommand( const updated = await updateDashboard( orgSlug, dashboardId, - updateBody + updateBody, ).catch(async (error: unknown) => enrichDashboardError(error, { orgSlug, dashboardId, operation: "update", - }) + }), ); yield new CommandOutput({ @@ -174,5 +174,5 @@ export const deleteCommand = buildDeleteCommand( return { hint: `Dashboard: ${url}` }; }, }, - { noNonInteractiveGuard: true } + { noNonInteractiveGuard: true }, ); diff --git a/packages/cli/src/commands/dashboard/widget/edit.ts b/packages/cli/src/commands/dashboard/widget/edit.ts index 301d319ba..f9a5d5750 100644 --- a/packages/cli/src/commands/dashboard/widget/edit.ts +++ b/packages/cli/src/commands/dashboard/widget/edit.ts @@ -57,7 +57,7 @@ type EditResult = { /** Merge query-level flags over existing widget query */ function mergeQueries( flags: EditFlags, - existingQuery: DashboardWidgetQuery | undefined + existingQuery: DashboardWidgetQuery | undefined, ): DashboardWidgetQuery[] | undefined { const hasChanges = flags.query || flags.where !== undefined || flags["group-by"] || flags.sort; @@ -83,7 +83,7 @@ function mergeQueries( /** Merge layout flags over existing layout, returning the result or the existing layout unchanged */ function mergeLayout( flags: WidgetLayoutFlags, - existing: DashboardWidget + existing: DashboardWidget, ): DashboardWidget["layout"] { const hasChange = flags.col !== undefined || @@ -106,12 +106,12 @@ function mergeLayout( /** * Validate enum and aggregate constraints on the effective (merged) widget state. - * Extracted from buildReplacement to stay under Biome's complexity limit. + * Extracted from buildReplacement to keep the validation readable. */ function validateEnumsAndAggregates( flags: EditFlags, existing: DashboardWidget, - mergedQueries: DashboardWidgetQuery[] | undefined + mergedQueries: DashboardWidgetQuery[] | undefined, ): void { const newDataset = flags.dataset ?? existing.widgetType; const aggregatesToValidate = @@ -137,7 +137,7 @@ function validateEnumsAndAggregates( function validateQueryConstraints( flags: EditFlags, existing: DashboardWidget, - mergedQueries: DashboardWidgetQuery[] | undefined + mergedQueries: DashboardWidgetQuery[] | undefined, ): void { if (!flags.sort) { return; @@ -153,7 +153,7 @@ function validateQueryConstraints( /** Build the replacement widget object by merging flags over existing */ function buildReplacement( flags: EditFlags, - existing: DashboardWidget + existing: DashboardWidget, ): DashboardWidget { const mergedQueries = mergeQueries(flags, existing.queries?.[0]); const baseLimit = flags.limit !== undefined ? flags.limit : existing.limit; @@ -162,7 +162,7 @@ function buildReplacement( const limit = applyGroupLimitAutoDefault( flags["group-by"], columns, - baseLimit + baseLimit, ); validateEnumsAndAggregates(flags, existing, mergedQueries); @@ -327,7 +327,7 @@ export const editCommand = buildCommand({ "Specify --index or --title to identify the widget to edit.\n\n" + "Example:\n" + " sentry dashboard widget edit <dashboard> --title 'My Widget' --display bar", - "index" + "index", ); } @@ -350,20 +350,24 @@ export const editCommand = buildCommand({ const orgSlug = await resolveOrgFromTarget( parsed, cwd, - "sentry dashboard widget edit <org>/ <dashboard> --title <name> --display <type>" + "sentry dashboard widget edit <org>/ <dashboard> --title <name> --display <type>", ); const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); // GET current dashboard → find widget → merge changes → PUT const current = await getDashboard(orgSlug, dashboardId).catch( async (error: unknown) => - enrichDashboardError(error, { orgSlug, dashboardId, operation: "view" }) + enrichDashboardError(error, { + orgSlug, + dashboardId, + operation: "view", + }), ); const widgets = current.widgets ?? []; const widgetIndex = resolveWidgetIndex( widgets, normalizedFlags.index, - normalizedFlags.title + normalizedFlags.title, ); const updateBody = prepareDashboardForUpdate(current); @@ -380,7 +384,7 @@ export const editCommand = buildCommand({ if (replacement.layout && !existing.layout) { validateWidgetLayout( { col: replacement.layout.x, width: replacement.layout.w }, - replacement.layout + replacement.layout, ); } @@ -389,13 +393,13 @@ export const editCommand = buildCommand({ const updated = await updateDashboard( orgSlug, dashboardId, - updateBody + updateBody, ).catch(async (error: unknown) => enrichDashboardError(error, { orgSlug, dashboardId, operation: "update", - }) + }), ); const url = buildDashboardUrl(orgSlug, dashboardId); diff --git a/packages/cli/src/commands/debug-files/bundle-jvm.ts b/packages/cli/src/commands/debug-files/bundle-jvm.ts index c2a13ff04..8efc8e3f5 100644 --- a/packages/cli/src/commands/debug-files/bundle-jvm.ts +++ b/packages/cli/src/commands/debug-files/bundle-jvm.ts @@ -103,14 +103,14 @@ export const bundleJvmCommand = buildCommand({ "debug-id": string; exclude?: string[]; }, - sourcePath: string + sourcePath: string, ) { // 1. Validate debug ID format if (!UUID_RE.test(flags["debug-id"])) { throw new ValidationError( `Invalid debug ID format: '${flags["debug-id"]}'. ` + "Expected UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "debug-id" + "debug-id", ); } @@ -119,13 +119,13 @@ export const bundleJvmCommand = buildCommand({ const srcStat = await stat(resolvedSource).catch(() => { throw new ValidationError( `Source path '${sourcePath}' does not exist.`, - "path" + "path", ); }); if (!srcStat.isDirectory()) { throw new ValidationError( `Source path '${sourcePath}' is not a directory.`, - "path" + "path", ); } diff --git a/packages/cli/src/commands/debug-files/bundle-sources.ts b/packages/cli/src/commands/debug-files/bundle-sources.ts index 232567746..117921c51 100644 --- a/packages/cli/src/commands/debug-files/bundle-sources.ts +++ b/packages/cli/src/commands/debug-files/bundle-sources.ts @@ -52,8 +52,8 @@ function formatBundleResult(data: BundleSourcesResult): string { return renderMarkdown( colorTag( "warning", - "No source files referenced by this debug file were found on disk; nothing was bundled." - ) + "No source files referenced by this debug file were found on disk; nothing was bundled.", + ), ); } const rows: [string, string][] = [ @@ -125,17 +125,17 @@ export const bundleSourcesCommand = buildCommand({ } catch (err) { log.debug( `Source file not available, skipping: ${sourcePath}`, - err + err, ); return null; } - } + }, ); } catch (err) { const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `'${path}' is not a recognized debug information file: ${msg}`, - "path" + "path", ); } @@ -156,7 +156,7 @@ export const bundleSourcesCommand = buildCommand({ if (result.objectCount > 1) { log.warn( - `'${path}' contains ${result.objectCount} objects; bundled sources for ${result.debugId} only. Other slices are not included.` + `'${path}' contains ${result.objectCount} objects; bundled sources for ${result.debugId} only. Other slices are not included.`, ); } diff --git a/packages/cli/src/commands/debug-files/check.ts b/packages/cli/src/commands/debug-files/check.ts index c5bb470d7..29822086e 100644 --- a/packages/cli/src/commands/debug-files/check.ts +++ b/packages/cli/src/commands/debug-files/check.ts @@ -73,8 +73,8 @@ function formatCheckResult(data: DebugFilesCheckResult): string { out += `\n${renderMarkdown( colorTag( "warning", - "This file is not usable for symbolication (missing debug id or features)." - ) + "This file is not usable for symbolication (missing debug id or features).", + ), )}`; } return out; @@ -145,7 +145,7 @@ export const checkCommand = buildCommand({ async *func( this: SentryContext, _flags: Record<string, never>, - path: string + path: string, ) { const content = await readDebugFile(path); @@ -156,7 +156,7 @@ export const checkCommand = buildCommand({ const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `'${path}' is not a recognized debug information file: ${msg}`, - "path" + "path", ); } diff --git a/packages/cli/src/commands/debug-files/find.ts b/packages/cli/src/commands/debug-files/find.ts index 2c0c75598..16a7c6785 100644 --- a/packages/cli/src/commands/debug-files/find.ts +++ b/packages/cli/src/commands/debug-files/find.ts @@ -47,10 +47,10 @@ function formatFindResult(data: FindResult): string { (m) => `${colorTag("muted", m.id)} \`${m.path}\` [${colorTag( "yellow", - m.type - )}]` + m.type, + )}]`, ) - .join("\n") + .join("\n"), ); } else { sections.push(colorTag("muted", "No debug information files found.")); @@ -58,7 +58,7 @@ function formatFindResult(data: FindResult): string { if (data.missing.length > 0) { const lines = data.missing.map((m) => ` ${m.id} (${m.hint})`).join("\n"); sections.push( - `${colorTag("yellow", "Missing debug information files:")}\n${lines}` + `${colorTag("yellow", "Missing debug information files:")}\n${lines}`, ); } return renderMarkdown(sections.join("\n\n")); @@ -74,9 +74,9 @@ function resolveTypes(flags: FindFlags): string[] { if (!FIND_DIF_TYPES.includes(type)) { throw new ValidationError( `Unknown debug file type '${type}'. Valid types: ${FIND_DIF_TYPES.join( - ", " + ", ", )}`, - "type" + "type", ); } } @@ -87,7 +87,7 @@ function resolveTypes(flags: FindFlags): string[] { async function resolveSearchPaths( flags: FindFlags, types: string[], - cwd: string + cwd: string, ): Promise<string[]> { const paths: string[] = []; // dSYMs live in Xcode DerivedData; only search it when dSYMs are wanted. @@ -178,7 +178,7 @@ export const findCommand = buildCommand({ const result = await findDebugFiles({ ids, types, paths }); log.debug( - `Located ${result.matches.length} file(s); ${result.missing.length} id(s) missing` + `Located ${result.matches.length} file(s); ${result.missing.length} id(s) missing`, ); yield new CommandOutput(result); diff --git a/packages/cli/src/commands/debug-files/print-sources.ts b/packages/cli/src/commands/debug-files/print-sources.ts index 60c574433..1c9c83ff1 100644 --- a/packages/cli/src/commands/debug-files/print-sources.ts +++ b/packages/cli/src/commands/debug-files/print-sources.ts @@ -89,7 +89,7 @@ function formatPrintSources(data: PrintSourcesResult): string { return `${header} — ${colorTag("muted", "no referenced sources")}`; } const lines = object.files.map( - (file) => `- ${safeCodeSpan(file.path)} — ${describeSource(file)}` + (file) => `- ${safeCodeSpan(file.path)} — ${describeSource(file)}`, ); const count = object.files.length; return `${header} — ${count} source file${count === 1 ? "" : "s"}:\n\n${lines.join("\n")}`; @@ -134,7 +134,7 @@ export const printSourcesCommand = buildCommand({ async *func( this: SentryContext, _flags: Record<string, never>, - path: string + path: string, ) { const content = await readDebugFile(path); @@ -145,7 +145,7 @@ export const printSourcesCommand = buildCommand({ const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `'${path}' is not a recognized debug information file: ${msg}`, - "path" + "path", ); } @@ -167,7 +167,7 @@ export const printSourcesCommand = buildCommand({ const failed = objects.filter((object) => object.enumerationError !== null); for (const object of failed) { log.warn( - `Could not enumerate sources for ${object.debugId} in '${path}': ${object.enumerationError}` + `Could not enumerate sources for ${object.debugId} in '${path}': ${object.enumerationError}`, ); } @@ -179,7 +179,7 @@ export const printSourcesCommand = buildCommand({ log.warn( `'${path}' contains ${info.objects.length} objects; ` + `\`bundle-sources\` would bundle sources for ${bundled.debugId} only. ` + - "Other slices are not included." + "Other slices are not included.", ); } diff --git a/packages/cli/src/commands/debug-files/read-file.ts b/packages/cli/src/commands/debug-files/read-file.ts index ac54407b6..9501fe131 100644 --- a/packages/cli/src/commands/debug-files/read-file.ts +++ b/packages/cli/src/commands/debug-files/read-file.ts @@ -23,7 +23,7 @@ export async function readDebugFile(path: string): Promise<Buffer> { if (code === "EISDIR") { throw new ValidationError( `Path '${path}' is a directory, not a debug information file.`, - "path" + "path", ); } const msg = err instanceof Error ? err.message : String(err); diff --git a/packages/cli/src/commands/debug-files/upload.ts b/packages/cli/src/commands/debug-files/upload.ts index afc8b6dcf..2477063e6 100644 --- a/packages/cli/src/commands/debug-files/upload.ts +++ b/packages/cli/src/commands/debug-files/upload.ts @@ -89,7 +89,7 @@ function collectScanPaths(paths: string[], derivedData: boolean): string[] { const derivedDataPath = join(homedir(), DERIVED_DATA_SUBPATH); if (!existsSync(derivedDataPath)) { log.warn( - `Xcode DerivedData folder not found at ${derivedDataPath}; ignoring --derived-data.` + `Xcode DerivedData folder not found at ${derivedDataPath}; ignoring --derived-data.`, ); return paths; } @@ -204,7 +204,7 @@ function appendIl2cppMapping(difs: DebugFileUpload[], file: PreparedDif): void { result = createIl2cppLineMapping( new Uint8Array(file.content), readSourceFile, - file.debugId + file.debugId, ); } catch (err) { log.debug(`Could not compute IL2CPP line mapping for ${file.path}`, err); @@ -239,7 +239,7 @@ function appendIl2cppMapping(difs: DebugFileUpload[], file: PreparedDif): void { */ function buildDifList( prepared: PreparedDif[], - options: { includeSources: boolean; il2cppMapping: boolean } + options: { includeSources: boolean; il2cppMapping: boolean }, ): DebugFileUpload[] { const { includeSources, il2cppMapping } = options; const difs: DebugFileUpload[] = []; @@ -264,7 +264,7 @@ function buildDifList( new Uint8Array(file.content), basename(file.path), readSourceFile, - { collectIl2cppSources: il2cppMapping } + { collectIl2cppSources: il2cppMapping }, ); } catch (err) { log.debug(`Could not build source bundle for ${file.path}`, err); @@ -307,14 +307,14 @@ function dedupeDifs(difs: DebugFileUpload[]): DebugFileUpload[] { */ function missingRequestedIds( requestedIds: string[] | undefined, - prepared: PreparedDif[] + prepared: PreparedDif[], ): string[] { if (!requestedIds || requestedIds.length === 0) { return []; } const foundIds = prepared.flatMap((p) => p.objects.map((o) => o.debugId)); return requestedIds.filter( - (requested) => !foundIds.some((found) => debugIdMatches(requested, found)) + (requested) => !foundIds.some((found) => debugIdMatches(requested, found)), ); } @@ -331,14 +331,14 @@ function resolveWaitMode(flags: UploadFlags): { if (flags.wait && waitFor !== undefined) { throw new ValidationError( "--wait and --wait-for cannot be combined", - "wait" + "wait", ); } if (waitFor !== undefined) { if (!Number.isFinite(waitFor) || waitFor <= 0) { throw new ValidationError( "--wait-for must be a positive number of seconds", - "wait-for" + "wait-for", ); } return { wait: true, maxWaitMs: Math.round(waitFor * 1000) }; @@ -359,7 +359,7 @@ function* doDryRun( missingIds: string[]; requireAll: boolean; oversizedCount: number; - } + }, ) { const { difs, missingIds, requireAll, oversizedCount } = params; yield new CommandOutput<DebugFilesUploadResult>({ @@ -399,7 +399,7 @@ function* doNothingToUpload( requireAll: boolean; oversizedCount: number; maxFileSize: number; - } + }, ) { const { missingIds, requireAll, oversizedCount, maxFileSize } = params; yield new CommandOutput<DebugFilesUploadResult>({ @@ -448,14 +448,14 @@ async function* doUpload( oversizedCount: number; maxFileSize: number; serverOptions?: ChunkServerOptions; - } + }, ) { const results = await uploadDebugFiles(params); // Files the server (or the local size gate) rejected come back as // error/not_found results; they must not be counted as uploaded. const failures = results.filter( - (r) => r.state === "error" || r.state === "not_found" + (r) => r.state === "error" || r.state === "not_found", ); yield new CommandOutput<DebugFilesUploadResult>({ @@ -491,7 +491,7 @@ async function* doUpload( const details = failures .map( (r) => - `${r.debugId ?? r.name}: ${r.state}${r.detail ? ` (${r.detail})` : ""}` + `${r.debugId ?? r.name}: ${r.state}${r.detail ? ` (${r.detail})` : ""}`, ) .join("; "); return { @@ -709,7 +709,7 @@ export const uploadCommand = buildCommand({ buildDifList(prepared, { includeSources: Boolean(flags["include-sources"]), il2cppMapping: Boolean(flags["il2cpp-mapping"]), - }) + }), ); const missingIds = missingRequestedIds(flags.id, prepared); diff --git a/packages/cli/src/commands/docs/list.ts b/packages/cli/src/commands/docs/list.ts index 0ad485524..216d772a7 100644 --- a/packages/cli/src/commands/docs/list.ts +++ b/packages/cli/src/commands/docs/list.ts @@ -64,7 +64,7 @@ export const listCommand = buildCommand({ if (!query) { throw new ValidationError( "Provide documentation keywords to search for.", - "keywords" + "keywords", ); } yield new CommandOutput(await listDocs(query, flags.limit)); diff --git a/packages/cli/src/commands/docs/query.ts b/packages/cli/src/commands/docs/query.ts index d517c327b..5da26c6ab 100644 --- a/packages/cli/src/commands/docs/query.ts +++ b/packages/cli/src/commands/docs/query.ts @@ -50,7 +50,7 @@ export const queryCommand = buildCommand({ if (!query) { throw new ValidationError( "Provide a documentation question.", - "question" + "question", ); } const detectedContext = await detectDocsContext(this.cwd); @@ -62,7 +62,7 @@ export const queryCommand = buildCommand({ rotatingMessages: DOCS_QUERY_PROGRESS_MESSAGES, rotationIntervalMs: 4000, }, - async () => queryDocs(query, detectedContext) + async () => queryDocs(query, detectedContext), ); yield new CommandOutput<QueryOutput>({ ...result, detectedContext }); }, diff --git a/packages/cli/src/commands/dsn/list.ts b/packages/cli/src/commands/dsn/list.ts index c2876957d..10809fd5c 100644 --- a/packages/cli/src/commands/dsn/list.ts +++ b/packages/cli/src/commands/dsn/list.ts @@ -65,7 +65,7 @@ const listConfig: ListCommandMeta = { /** List an organization's keys directly, preserving its cursor history. */ async function listForOrganization( org: string, - flags: BaseListFlags + flags: BaseListFlags, ): Promise<ListResult<DsnListItem>> { const target = `${org}/`; const contextKey = buildPaginationContextKey("org", target, { @@ -74,11 +74,11 @@ async function listForOrganization( const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const response = await withProgress( { message: "Fetching DSNs...", json: flags.json }, - () => listOrganizationDsns(org, { limit: flags.limit, cursor }) + () => listOrganizationDsns(org, { limit: flags.limit, cursor }), ); const { data, nextCursor } = response; advancePaginationState(PAGINATION_KEY, contextKey, direction, nextCursor); @@ -115,7 +115,7 @@ type DsnProjectPage = { /** Fetch a bounded project page, allowing callers to report partial failures. */ async function fetchProjectPage( target: ResolvedTarget, - options: GroupFetchOptions + options: GroupFetchOptions, ): Promise<FetchResult<DsnProjectPage>> { const result = await withAuthGuard(async () => { const page = await listProjectDsns(target.org, target.project, { @@ -151,7 +151,7 @@ const targetKey = (target: Pick<ResolvedTarget, "org" | "project">) => /** Collect successful pages, reporting partial failures and throwing if all fail. */ function collectProjectResults( results: FetchResult<DsnProjectPage>[], - targets: ResolvedTarget[] + targets: ResolvedTarget[], ) { const pages = new Map<string, DsnProjectPage>(); const failures: { project: string; error: Error }[] = []; @@ -170,7 +170,7 @@ function collectProjectResults( } if (failures.length > 0) { logger.warn( - `Failed to fetch DSNs from ${failures.map((failure) => failure.project).join(", ")}. Showing results from ${pages.size} project(s).` + `Failed to fetch DSNs from ${failures.map((failure) => failure.project).join(", ")}. Showing results from ${pages.size} project(s).`, ); } return { pages, failures }; @@ -192,7 +192,7 @@ async function listForResolvedProjects< undefined, resolution.skippedSelfHosted ? `Found ${resolution.skippedSelfHosted} DSN(s) that could not be resolved — you may not have access to these projects` - : undefined + : undefined, ); } @@ -200,8 +200,8 @@ async function listForResolvedProjects< // Canonical slugs keep both output and cursor identity stable as caches warm. const targets = await Promise.all( resolution.targets.map((target) => - limitRequests(() => resolveTargetSlugs(target)) - ) + limitRequests(() => resolveTargetSlugs(target)), + ), ); const { flags } = ctx; const firstTarget = targets[0]; @@ -210,18 +210,18 @@ async function listForResolvedProjects< ? buildPaginationContextKey( "project", `${firstTarget.org}/${firstTarget.project}`, - { limit: String(flags.limit) } + { limit: String(flags.limit) }, ) : buildMultiTargetContextKey(targets, { limit: flags.limit }); const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const sortedKeys = targets.map(targetKey).sort(); const { startCursors, exhausted } = decodeTargetCursors(cursor, sortedKeys); const activeTargets = targets.filter( - (target) => !exhausted.has(targetKey(target)) + (target) => !exhausted.has(targetKey(target)), ); const { results, hasMore } = await withProgress( { message: "Fetching DSNs...", json: flags.json }, @@ -235,9 +235,9 @@ async function listForResolvedProjects< limitRequests(() => fetchProjectPage(target, options)), onProgress: (count) => setMessage( - `Fetching DSNs, ${count} and counting (up to ${flags.limit})...` + `Fetching DSNs, ${count} and counting (up to ${flags.limit})...`, ), - }) + }), ); const { pages, failures } = collectProjectResults(results, activeTargets); const allItems = [...pages.values()].flatMap((page) => page.items); diff --git a/packages/cli/src/commands/event/list.ts b/packages/cli/src/commands/event/list.ts index d05a50607..52adad8bb 100644 --- a/packages/cli/src/commands/event/list.ts +++ b/packages/cli/src/commands/event/list.ts @@ -58,7 +58,7 @@ const PAGINATION_KEY = "event-list"; /** Build the CLI hint for fetching the next page, preserving active flags. */ function nextPageHint( issueArg: string, - flags: Pick<EventsFlags, "query" | "full" | "period"> + flags: Pick<EventsFlags, "query" | "full" | "period">, ): string { return appendEventsFlags(`sentry event list ${issueArg} -c next`, flags); } @@ -66,7 +66,7 @@ function nextPageHint( /** Build the CLI hint for fetching the previous page, preserving active flags. */ function prevPageHint( issueArg: string, - flags: Pick<EventsFlags, "query" | "full" | "period"> + flags: Pick<EventsFlags, "query" | "full" | "period">, ): string { return appendEventsFlags(`sentry event list ${issueArg} -c prev`, flags); } @@ -127,7 +127,7 @@ export const listCommand = buildListCommand("event", { if (!org) { throw new ContextError( "Organization", - buildCommandHint(COMMAND_NAME, issueArg, COMMAND_BASE) + buildCommandHint(COMMAND_NAME, issueArg, COMMAND_BASE), ); } @@ -135,12 +135,12 @@ export const listCommand = buildListCommand("event", { const contextKey = buildPaginationContextKey( "event-list", `${org}/${issue.id}`, - { q: flags.query, period: serializeTimeRange(timeRange) } + { q: flags.query, period: serializeTimeRange(timeRange) }, ); const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { data: events, nextCursor } = await withProgress( @@ -158,7 +158,7 @@ export const listCommand = buildListCommand("event", { }).catch((error: unknown): never => { // An unparseable user --query is a user input mistake, not a CLI bug. throw toSearchQueryError(error, flags.query); - }) + }), ); // Update pagination state (handles both advance and truncation) diff --git a/packages/cli/src/commands/event/send.ts b/packages/cli/src/commands/event/send.ts index 52a039822..a7de06fd6 100644 --- a/packages/cli/src/commands/event/send.ts +++ b/packages/cli/src/commands/event/send.ts @@ -52,7 +52,7 @@ function formatSendEventHuman(result: SendEventResult): string { async function buildFilePayload( file: string, raw: boolean, - dsnComponents: DsnComponents + dsnComponents: DsnComponents, ): Promise<{ body: string | Uint8Array; eventId: string }> { const bytes = await readFileBytes(file); @@ -75,7 +75,7 @@ async function buildFilePayload( } catch (err) { throw new ValidationError( `Failed to parse JSON from ${file}: ${(err as Error).message}`, - "path" + "path", ); } @@ -86,7 +86,7 @@ async function buildFilePayload( } catch (err) { throw new ValidationError( `Failed to create envelope from ${file}: ${(err as Error).message}`, - "path" + "path", ); } return { body, eventId: event.event_id ?? "" }; @@ -303,7 +303,7 @@ built entirely from the file contents. const { body, eventId } = await buildFilePayload( file, flags.raw ?? false, - dsnComponents + dsnComponents, ); await sendEnvelopeRequest(dsn, body); yield new CommandOutput<SendEventResult>({ eventId, file }); @@ -312,13 +312,13 @@ built entirely from the file contents. if (flags.raw) { throw new ValidationError( "--raw requires a file argument (raw bytes cannot be built from inline flags)", - "raw" + "raw", ); } if (!flags.message?.length) { throw new ConfigError( "Provide a message via -m/--message or a JSON event file as a positional argument.", - "sentry event send -m 'My message'" + "sentry event send -m 'My message'", ); } const event = await buildEventFromFlags(flags); @@ -329,7 +329,7 @@ built entirely from the file contents. } catch (err) { throw new ValidationError( `Failed to create event envelope: ${(err as Error).message}`, - "event" + "event", ); } await sendEnvelopeRequest(dsn, body); diff --git a/packages/cli/src/commands/event/shared-events.ts b/packages/cli/src/commands/event/shared-events.ts index ce53c8b78..0eb282e40 100644 --- a/packages/cli/src/commands/event/shared-events.ts +++ b/packages/cli/src/commands/event/shared-events.ts @@ -150,7 +150,7 @@ export function formatEventsHuman(result: EventsResult): string { */ export function jsonTransformEvents( result: EventsResult, - fields?: string[] + fields?: string[], ): unknown { const items = fields && fields.length > 0 @@ -179,7 +179,7 @@ export function jsonTransformEvents( /** Append active non-default flags to a base command string. */ export function appendEventsFlags( base: string, - flags: Pick<EventsFlags, "query" | "full" | "period"> + flags: Pick<EventsFlags, "query" | "full" | "period">, ): string { const parts: string[] = []; if (flags.query) { diff --git a/packages/cli/src/commands/event/view.ts b/packages/cli/src/commands/event/view.ts index 7ab74d398..9cbd084af 100644 --- a/packages/cli/src/commands/event/view.ts +++ b/packages/cli/src/commands/event/view.ts @@ -148,7 +148,7 @@ export function formatEventView(data: EventViewData): string { */ export function jsonTransformEventView( data: EventViewData, - fields?: string[] + fields?: string[], ): unknown { const transform = (entry: SingleEventViewData): Record<string, unknown> => { const result: Record<string, unknown> = { @@ -191,7 +191,7 @@ function joinHintParts(parts: Array<string | undefined>): string | undefined { function viewOutputHint( org: string, data: SingleEventViewData | undefined, - extra?: string + extra?: string, ): string | undefined { if (!data) { return extra; @@ -247,7 +247,7 @@ function parseSingleArg(arg: string): ParsedPositionalArgs { if (slashIdx !== -1 && arg.indexOf("/", slashIdx + 1) === -1) { const singleSlash = parseSingleSlashArg( arg.slice(0, slashIdx), - arg.slice(slashIdx + 1) + arg.slice(slashIdx + 1), ); if (singleSlash) { return singleSlash; @@ -257,7 +257,7 @@ function parseSingleArg(arg: string): ParsedPositionalArgs { const { id: eventId, targetArg } = parseSlashSeparatedArg( arg, "Event ID", - USAGE_HINT + USAGE_HINT, ); // Detect bare issue short ID passed as event ID (e.g., "BRUNCHIE-APP-29"). @@ -302,7 +302,7 @@ function parseSingleArg(arg: string): ParsedPositionalArgs { */ function parseSingleSlashArg( beforeSlash: string, - afterSlash: string + afterSlash: string, ): ParsedPositionalArgs | null { // "org/SHORT-ID" → auto-redirect to that issue's latest event. // e.g., "figma/FULLSCREEN-2RN". Use "org/" (trailing slash) to signal @@ -387,7 +387,7 @@ type ParsedPositionalArgs = { * * @returns Parsed event ID and optional target arg */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: positional arg parsing has many format branches by design +// positional arg parsing has many format branches by design export function parsePositionalArgs(args: string[]): ParsedPositionalArgs { if (args.length === 0) { throw new ContextError("Event ID", USAGE_HINT, []); @@ -524,7 +524,7 @@ export function parsePositionalArgs(args: string[]): ParsedPositionalArgs { async function validateAndRecoverEventId( rawEventId: string, parsed: ReturnType<typeof parseOrgProjectArg>, - skipValidation: boolean + skipValidation: boolean, ): Promise<string> { if (skipValidation) { return rawEventId; @@ -578,7 +578,7 @@ type ResolveTargetOptions = { * @internal Exported for testing */ export async function resolveEventTarget( - options: ResolveTargetOptions + options: ResolveTargetOptions, ): Promise<ResolvedEventTarget | null> { const { parsed, eventId, cwd } = options; @@ -600,7 +600,7 @@ export async function resolveEventTarget( parsed.projectSlug, USAGE_HINT, `sentry event view <org>/${parsed.projectSlug} ${eventId}`, - parsed.originalSlug + parsed.originalSlug, ); target = { org: resolved.org, @@ -647,14 +647,14 @@ export async function resolveEventTarget( export async function resolveOrgAllTarget( org: string, eventId: string, - _cwd: string + _cwd: string, ): Promise<ResolvedEventTarget> { const resolved = await resolveEventInOrg(org, eventId); if (!resolved) { throw new ResolutionError( `Event ${eventId} in organization "${org}"`, "not found", - `sentry event view ${org}/<project> ${eventId}` + `sentry event view ${org}/<project> ${eventId}`, ); } return { @@ -674,7 +674,7 @@ export async function resolveOrgAllTarget( */ export async function resolveAutoDetectTarget( eventId: string, - cwd: string + cwd: string, ): Promise<ResolvedEventTarget | null> { const autoTarget = await resolveOrgAndProject({ cwd, usageHint: USAGE_HINT }); if (autoTarget) { @@ -687,7 +687,7 @@ export async function resolveAutoDetectTarget( .withTag("event.view") .warn( `Found event in ${resolved.org}/${resolved.project}. ` + - `Use: sentry event view ${resolved.org}/${resolved.project} ${eventId}` + `Use: sentry event view ${resolved.org}/${resolved.project} ${eventId}`, ); return { org: resolved.org, @@ -714,7 +714,7 @@ async function buildSingleEventViewData( org: string, event: SentryEvent, spans: number, - project?: string + project?: string, ): Promise<SingleEventViewData> { const projectSlug = project ?? eventProjectSlug(event); const [spanTreeResult, attachments] = await Promise.all([ @@ -744,7 +744,7 @@ async function fetchLatestEventData( org: string, issueId: string, spans: number, - project?: string + project?: string, ): Promise<SingleEventViewData> { const event = await getLatestEvent(org, issueId); return buildSingleEventViewData(org, event, spans, project); @@ -762,7 +762,7 @@ async function fetchLatestEventData( async function tryEventFallbacks( org: string, project: string, - eventId: string + eventId: string, ): Promise<ResolvedEvent | null> { // Same-org fallback: try cross-project lookup within the specified org. // Handles wrong-project resolution from DSN auto-detect or config defaults. @@ -778,7 +778,7 @@ async function tryEventFallbacks( sameOrgSearched = true; if (resolved) { logger.warn( - `Event not found in ${org}/${project}, but found in ${resolved.org}/${resolved.project}.` + `Event not found in ${org}/${project}, but found in ${resolved.org}/${resolved.project}.`, ); return resolved; } @@ -845,7 +845,7 @@ export async function fetchEventWithContext( prefetchedEvent: SentryEvent | null, org: string, project: string, - eventId: string + eventId: string, ): Promise<ResolvedEvent> { if (prefetchedEvent) { return { org, project, event: prefetchedEvent }; @@ -869,7 +869,7 @@ export async function fetchEventWithContext( // Nudge the user when the event ID looks like an issue short ID if (looksLikeIssueShortId(eventId)) { suggestions.unshift( - `This looks like an issue short ID. Try: sentry issue view ${eventId}` + `This looks like an issue short ID. Try: sentry issue view ${eventId}`, ); } @@ -877,7 +877,7 @@ export async function fetchEventWithContext( `Event '${eventId}'`, `not found in ${org}/${project}`, `sentry event view ${org}/<project> ${eventId}`, - suggestions + suggestions, ); } throw error; @@ -899,7 +899,7 @@ export async function fetchEventWithContext( async function resolveIssueShortIdEvent( issueShortId: string, org: string, - spans: number + spans: number, ): Promise<SingleEventViewData> { const issue = await getIssueByShortId(org, issueShortId); return fetchLatestEventData(org, issue.id, spans, issue.project?.slug); @@ -927,7 +927,7 @@ type IssueShortcutOptions = { */ async function tryResolveIssueProject( org: string, - issueId: string + issueId: string, ): Promise<string | undefined> { try { const issue = await getIssueInOrg(org, issueId, { @@ -953,7 +953,7 @@ async function resolveIssueIdShortcut( parsed: ReturnType<typeof parseOrgProjectArg>, issueId: string, cwd: string, - spans: number + spans: number, ): Promise<IssueShortcutResult> { const log = logger.withTag("event.view"); const explicitOrg = @@ -990,7 +990,7 @@ async function resolveIssueIdShortcut( * @returns Result with org, data, and hint — or null if not an issue shortcut */ async function resolveIssueShortcut( - options: IssueShortcutOptions + options: IssueShortcutOptions, ): Promise<IssueShortcutResult | null> { const { parsed, eventId, issueId, issueShortId, cwd, spans } = options; const log = logger.withTag("event.view"); @@ -1017,7 +1017,7 @@ async function resolveIssueShortcut( if (!resolved) { throw new ContextError( "Organization", - `sentry issue view ${issueShortId}` + `sentry issue view ${issueShortId}`, ); } @@ -1031,7 +1031,7 @@ async function resolveIssueShortcut( `Issue '${issueShortId}'`, "has no associated project", `sentry event view <org>/<project> ${eventId}`, - ["Specify the project explicitly to view this event"] + ["Specify the project explicitly to view this event"], ); } const event = await getEvent(resolved.org, issueProject, eventId); @@ -1039,7 +1039,7 @@ async function resolveIssueShortcut( resolved.org, event, spans, - issueProject + issueProject, ); return { org: resolved.org, @@ -1049,12 +1049,12 @@ async function resolveIssueShortcut( } log.warn( - `'${issueShortId}' is an issue short ID, not an event ID. Showing the latest event.` + `'${issueShortId}' is an issue short ID, not an event ID. Showing the latest event.`, ); const data = await resolveIssueShortIdEvent( issueShortId, resolved.org, - spans + spans, ); return { org: resolved.org, @@ -1078,7 +1078,7 @@ async function resolveIssueShortcut( */ export function collectEventIds( primaryId: string, - extraIds: string[] | undefined + extraIds: string[] | undefined, ): string[] { const seen = new Set<string>([primaryId]); const allIds = [primaryId]; @@ -1124,7 +1124,7 @@ type FetchMultipleOptions = { * When all fetches fail, re-throws the error from the primary (first) event. */ export async function fetchMultipleEvents( - options: FetchMultipleOptions + options: FetchMultipleOptions, ): Promise<ResolvedEvent[]> { const { eventIds, org, project, prefetchedEvent, primaryId } = options; const log = logger.withTag("event.view"); @@ -1137,10 +1137,10 @@ export async function fetchMultipleEvents( id === primaryId ? prefetchedEvent : null, org, project, - id - ) - ) - ) + id, + ), + ), + ), ); const events: ResolvedEvent[] = []; @@ -1240,9 +1240,9 @@ export const viewCommand = buildCommand({ await openInBrowser( buildEventSearchUrl( issueShortcut.org, - issueShortcut.data.event.eventID + issueShortcut.data.event.eventID, ), - "event" + "event", ); return; } @@ -1254,7 +1254,7 @@ export const viewCommand = buildCommand({ hint: viewOutputHint( issueShortcut.org, issueShortcut.data, - issueShortcut.hint + issueShortcut.hint, ), }; } @@ -1281,7 +1281,7 @@ export const viewCommand = buildCommand({ if (flags.web) { if (extraEventIds && extraEventIds.length > 0) { log.warn( - "--web only opens the first event; extra event IDs are ignored." + "--web only opens the first event; extra event IDs are ignored.", ); } await openInBrowser(buildEventSearchUrl(target.org, eventId), "event"); @@ -1307,9 +1307,9 @@ export const viewCommand = buildCommand({ resolved.org, resolved.event, flags.spans, - resolved.project - ) - ) + resolved.project, + ), + ), ); yield new CommandOutput({ @@ -1320,7 +1320,9 @@ export const viewCommand = buildCommand({ hint: viewOutputHint( viewDataEntries[0]?.org ?? target.org, viewDataEntries[0], - target.detectedFrom ? `Detected from ${target.detectedFrom}` : undefined + target.detectedFrom + ? `Detected from ${target.detectedFrom}` + : undefined, ), }; }, diff --git a/packages/cli/src/commands/explore.ts b/packages/cli/src/commands/explore.ts index 066fa209e..4d0252ba4 100644 --- a/packages/cli/src/commands/explore.ts +++ b/packages/cli/src/commands/explore.ts @@ -114,7 +114,7 @@ const VALID_DATASETS = new Set([ * Used by pagination hints so they emit `--dataset metrics` not `--dataset tracemetrics`. */ const API_TO_USER_DATASET = new Map( - Array.from(VALID_DATASETS, (name) => [DATASET_ALIASES[name] ?? name, name]) + Array.from(VALID_DATASETS, (name) => [DATASET_ALIASES[name] ?? name, name]), ); // --------------------------------------------------------------------------- @@ -171,7 +171,7 @@ function parseDataset(value: string): string { } throw new ValidationError( `Invalid dataset "${value}". Must be one of: ${Array.from(VALID_DATASETS).join(", ")}`, - "dataset" + "dataset", ); } @@ -196,20 +196,20 @@ function inferReplayFieldType(field: string): string { function buildReplayExploreResponse( fields: string[], - replays: Awaited<ReturnType<typeof listReplays>>["data"] + replays: Awaited<ReturnType<typeof listReplays>>["data"], ): { data: Record<string, unknown>[]; meta: NonNullable<ExploreData["meta"]> } { return { data: replays.map((replay) => Object.fromEntries( - fields.map((field) => [field, getReplayFieldValue(replay, field)]) - ) + fields.map((field) => [field, getReplayFieldValue(replay, field)]), + ), ), meta: { fields: Object.fromEntries( - fields.map((field) => [field, inferReplayFieldType(field)]) + fields.map((field) => [field, inferReplayFieldType(field)]), ), units: Object.fromEntries( - fields.map((field) => [field, field === "duration" ? "s" : null]) + fields.map((field) => [field, field === "duration" ? "s" : null]), ), }, }; @@ -229,7 +229,7 @@ function buildReplayExploreResponse( */ function orderFieldNames( requestedFields: string[], - data: ExploreData + data: ExploreData, ): string[] { const apiFields = data.meta?.fields ? Object.keys(data.meta.fields) @@ -264,7 +264,7 @@ function formatExploreHuman(data: ExploreData): string { const columns = buildMetaColumns( fieldNames, data.meta?.fields, - data.meta?.units + data.meta?.units, ); const scope = data.project ? `${data.org}/${data.project}` : data.org; @@ -318,7 +318,7 @@ function defaultFieldsForDataset(dataset: string): readonly string[] { function appendMetricHints( parts: string[], metric: string | undefined, - agg: string + agg: string, ): void { if (metric) { parts.push(`-m "${metric}"`); @@ -333,7 +333,7 @@ function appendFieldHints( parts: string[], rawFields: string[] | undefined, dataset: string, - metricActive: boolean + metricActive: boolean, ): void { const fields = rawFields ?? []; const fieldList = metricActive @@ -361,7 +361,7 @@ function appendFlagHints( | "limit" | "metric" | "agg" - > + >, ): string { const parts: string[] = []; const defaultSort = @@ -422,7 +422,7 @@ function isTracemetricsAggregate(aggregate: string): boolean { */ function validateMetricsFields(fieldList: string[]): void { const badAggs = fieldList.filter( - (f) => isAggregate(f) && !isTracemetricsAggregate(f) + (f) => isAggregate(f) && !isTracemetricsAggregate(f), ); if (badAggs.length === 0) { return; @@ -439,7 +439,7 @@ function validateMetricsFields(fieldList: string[]): void { " - metric_name: the metric name emitted by the SDK (e.g., llm.token_usage)\n" + " - metric_type: distribution, gauge, counter, or set\n" + " - unit: none, byte, second, millisecond, etc.", - "field" + "field", ); } @@ -496,7 +496,7 @@ type DatasetConfig = { * syntax so they are ORed rather than ANDed. */ function buildEnvironmentQuery( - environment: string[] | undefined + environment: string[] | undefined, ): string | undefined { if (!environment || environment.length === 0) { return; @@ -541,12 +541,12 @@ function resolveDatasetConfig(params: { if (dataset === "replays") { const unsupportedField = fieldList.find( - (field) => !isSupportedReplayField(field) + (field) => !isSupportedReplayField(field), ); if (unsupportedField) { throw new ValidationError( `Unsupported replay field "${unsupportedField}". Supported fields include: ${listSupportedReplayFields().slice(0, 12).join(", ")}...`, - "field" + "field", ); } @@ -554,7 +554,7 @@ function resolveDatasetConfig(params: { if (!isReplaySortValue(sort)) { throw new ValidationError( `Invalid replay sort "${sort}". Use a replay sort like ${DEFAULT_REPLAY_SORT} or -count_errors.`, - "sort" + "sort", ); } @@ -603,7 +603,7 @@ function resolveDatasetConfig(params: { if (rawSort && flags.sort) { const displayDataset = API_TO_USER_DATASET.get(dataset) ?? dataset; log.warn( - `--sort is not supported on the ${displayDataset} dataset. Ignoring sort.` + `--sort is not supported on the ${displayDataset} dataset. Ignoring sort.`, ); } sort = undefined; @@ -767,7 +767,7 @@ export const exploreCommand = buildListCommand("explore", { const { org, project } = await resolveOrgOptionalFromArg( target, cwd, - "explore" + "explore", ); let dataset = flags.dataset; @@ -797,7 +797,7 @@ export const exploreCommand = buildListCommand("explore", { queryMetricsMeta(org, { ...metaParams, project, - }) + }), ); const aggField = resolveMetricField(flags.metric, flags.agg, metrics); @@ -815,7 +815,7 @@ export const exploreCommand = buildListCommand("explore", { " sentry explore my-org/ -m llm.token_usage --agg avg --dataset metrics\n\n" + "Manual mode (tracemetrics format):\n" + ' sentry explore my-org/ -F "sum(value,llm.token_usage,distribution,none)" --dataset metrics', - "field" + "field", ); } validateMetricsFields(fieldList); @@ -840,12 +840,12 @@ export const exploreCommand = buildListCommand("explore", { q: flags.query, sort: config.sort, period: serializeTimeRange(timeRange), - } + }, ); const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { data: response, nextCursor } = await withProgress( @@ -859,7 +859,7 @@ export const exploreCommand = buildListCommand("explore", { .catch((error: unknown): never => { // An unparseable user --query is a user input mistake, not a CLI bug. throw toSearchQueryError(error, flags.query); - }) + }), ); advancePaginationState(PAGINATION_KEY, contextKey, direction, nextCursor); @@ -873,11 +873,11 @@ export const exploreCommand = buildListCommand("explore", { hasMore, prevHint: appendFlagHints( `sentry explore ${baseTarget} -c prev`, - hintFlags + hintFlags, ), nextHint: appendFlagHints( `sentry explore ${baseTarget} -c next`, - hintFlags + hintFlags, ), }); diff --git a/packages/cli/src/commands/feedback/list.ts b/packages/cli/src/commands/feedback/list.ts index 55031b512..887bbfbd9 100644 --- a/packages/cli/src/commands/feedback/list.ts +++ b/packages/cli/src/commands/feedback/list.ts @@ -76,7 +76,7 @@ function formatScope(org: string, project?: string): string { } async function projectIdFor( - resolved: ResolvedOrgOptionalTarget + resolved: ResolvedOrgOptionalTarget, ): Promise<number | undefined> { if (!resolved.project) { return; @@ -88,7 +88,7 @@ async function projectIdFor( function appendFeedbackFlags( base: string, - flags: Pick<ListFlags, "status" | "limit" | "query" | "period"> + flags: Pick<ListFlags, "status" | "limit" | "query" | "period">, ): string { const parts: string[] = []; if (flags.status !== DEFAULT_STATUS) { @@ -106,17 +106,17 @@ function pageHint( direction: "next" | "prev", org: string, project: string | undefined, - flags: Pick<ListFlags, "status" | "limit" | "query" | "period"> + flags: Pick<ListFlags, "status" | "limit" | "query" | "period">, ): string { return appendFeedbackFlags( `sentry feedback list ${formatScope(org, project)} -c ${direction}`, - flags + flags, ); } function jsonTransformFeedbackList( result: FeedbackListResult, - fields?: string[] + fields?: string[], ): unknown { return jsonTransformListResult( { @@ -125,7 +125,7 @@ function jsonTransformFeedbackList( hasPrev: result.hasPrev, nextCursor: result.nextCursor, }, - fields + fields, ); } @@ -199,7 +199,7 @@ export const listCommand = buildListCommand("feedback", { const resolved = await resolveOrgOptionalFromArg( target, this.cwd, - COMMAND_NAME + COMMAND_NAME, ); const projectId = await projectIdFor(resolved); const contextKey = buildPaginationContextKey( @@ -210,12 +210,12 @@ export const listCommand = buildListCommand("feedback", { limit: String(flags.limit), q: flags.query, period: serializeTimeRange(flags.period), - } + }, ); const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { feedback, nextCursor } = await withProgress( @@ -233,7 +233,7 @@ export const listCommand = buildListCommand("feedback", { ...timeRangeToApiParams(flags.period), }).catch((error: unknown): never => { throw toSearchQueryError(error, flags.query); - }) + }), ); advancePaginationState(PAGINATION_KEY, contextKey, direction, nextCursor); diff --git a/packages/cli/src/commands/feedback/resolve.ts b/packages/cli/src/commands/feedback/resolve.ts index 876610fe9..0a14702aa 100644 --- a/packages/cli/src/commands/feedback/resolve.ts +++ b/packages/cli/src/commands/feedback/resolve.ts @@ -36,7 +36,7 @@ export const resolveCommand = buildCommand({ const { org, feedback } = await resolveFeedback( feedbackArg, this.cwd, - "resolve" + "resolve", ); const updated = await updateIssueStatus(feedback.id, "resolved", { orgSlug: org, diff --git a/packages/cli/src/commands/feedback/spam.ts b/packages/cli/src/commands/feedback/spam.ts index b0adbf7c0..055044723 100644 --- a/packages/cli/src/commands/feedback/spam.ts +++ b/packages/cli/src/commands/feedback/spam.ts @@ -36,7 +36,7 @@ export const spamCommand = buildCommand({ const { org, feedback } = await resolveFeedback( feedbackArg, this.cwd, - "spam" + "spam", ); const updated = await updateIssueStatus(feedback.id, "ignored", { orgSlug: org, diff --git a/packages/cli/src/commands/feedback/unresolve.ts b/packages/cli/src/commands/feedback/unresolve.ts index dd707ebd2..4a2754e07 100644 --- a/packages/cli/src/commands/feedback/unresolve.ts +++ b/packages/cli/src/commands/feedback/unresolve.ts @@ -35,7 +35,7 @@ export const unresolveCommand = buildCommand({ const { org, feedback } = await resolveFeedback( feedbackArg, this.cwd, - "unresolve" + "unresolve", ); const updated = await updateIssueStatus(feedback.id, "unresolved", { orgSlug: org, diff --git a/packages/cli/src/commands/feedback/utils.ts b/packages/cli/src/commands/feedback/utils.ts index aba59362e..986dc4361 100644 --- a/packages/cli/src/commands/feedback/utils.ts +++ b/packages/cli/src/commands/feedback/utils.ts @@ -42,12 +42,12 @@ async function resolveFeedbackSelector( selector: IssueSelector, explicitOrg: string | undefined, cwd: string, - command: string + command: string, ): Promise<ResolvedFeedback> { if (selector !== "@latest") { throw new ValidationError( "Feedback only supports @latest; @most_frequent is not meaningful for Feedback.", - "feedback selector" + "feedback selector", ); } @@ -57,7 +57,7 @@ async function resolveFeedbackSelector( if (!org) { throw new ContextError( "Organization", - `sentry feedback ${command} <org>/@latest` + `sentry feedback ${command} <org>/@latest`, ); } @@ -71,13 +71,13 @@ async function resolveFeedbackSelector( "Selector '@latest'", "found no unresolved User Feedback", `sentry feedback list ${org}/ --status all`, - ["The @latest selector only matches unresolved Feedback."] + ["The @latest selector only matches unresolved Feedback."], ); } setOrgProjectContext( [org], - latest.project?.slug ? [latest.project.slug] : [] + latest.project?.slug ? [latest.project.slug] : [], ); return { org, feedback: latest }; } @@ -90,7 +90,7 @@ async function resolveFeedbackSelector( export async function resolveFeedback( feedbackArg: string, cwd: string, - command = "view" + command = "view", ): Promise<ResolvedFeedback> { const parsed = parseIssueArg(feedbackArg); if (parsed.type === "selector") { @@ -111,7 +111,7 @@ export async function resolveFeedback( `Feedback '${feedbackArg}'`, "not found", buildCommandHint(command, feedbackArg, "sentry feedback"), - ["List available Feedback: sentry feedback list"] + ["List available Feedback: sentry feedback list"], ); } throw error; @@ -123,7 +123,7 @@ export async function resolveFeedback( `Issue '${issue.shortId}'`, "is not User Feedback", `sentry issue view ${org ? `${org}/` : ""}${issue.shortId}`, - ["Use a Feedback ID from: sentry feedback list"] + ["Use a Feedback ID from: sentry feedback list"], ); } diff --git a/packages/cli/src/commands/feedback/view.ts b/packages/cli/src/commands/feedback/view.ts index bb8823f5c..58eed6362 100644 --- a/packages/cli/src/commands/feedback/view.ts +++ b/packages/cli/src/commands/feedback/view.ts @@ -41,7 +41,7 @@ type ViewFlags = { async function tryGetLatestEvent( orgSlug: string, - feedbackId: string + feedbackId: string, ): Promise<SentryEvent | undefined> { try { return await getLatestEvent(orgSlug, feedbackId); @@ -53,7 +53,7 @@ async function tryGetLatestEvent( async function tryListReplayIds( orgSlug: string, - feedbackId: string + feedbackId: string, ): Promise<string[]> { try { return await listReplayIdsForIssue(orgSlug, feedbackId); @@ -66,7 +66,7 @@ async function tryListReplayIds( async function tryListAttachments( orgSlug: string, projectSlug: string | undefined, - event: SentryEvent | undefined + event: SentryEvent | undefined, ): Promise<EventAttachmentDetailsResponse[]> { if (!(projectSlug && event)) { return []; @@ -81,7 +81,7 @@ async function tryListAttachments( function jsonTransformFeedbackView( data: FeedbackViewResult, - fields?: string[] + fields?: string[], ): unknown { const result: Record<string, unknown> = { ...data.feedback, diff --git a/packages/cli/src/commands/help.ts b/packages/cli/src/commands/help.ts index 3283eb817..2d4df56ee 100644 --- a/packages/cli/src/commands/help.ts +++ b/packages/cli/src/commands/help.ts @@ -41,8 +41,8 @@ export const helpCommand = buildCommand({ }, }, }, - // biome-ignore lint/complexity/noBannedTypes: Stricli requires empty object for commands with no flags - // biome-ignore lint/suspicious/useAwait: async generator required by Stricli buildCommand pattern + // Stricli requires empty object for commands with no flags + // async generator required by Stricli buildCommand pattern async *func(this: SentryContext, _flags: {}, ...commandPath: string[]) { if (commandPath.length === 0) { // Yield the full command tree. The branded banner + help is rendered by diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index c0ed5e426..eb26d2b35 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -104,7 +104,7 @@ type InitFlags = { */ function classifyArgs( first?: string, - second?: string + second?: string, ): { target: string | undefined; directory: string | undefined } { // No args — auto-detect everything if (!first) { @@ -130,7 +130,7 @@ function classifyArgs( "", "Provide a single directory:", ` sentry init [<org>/<project>] ${first}`, - ].join("\n") + ].join("\n"), ); } @@ -142,7 +142,7 @@ function classifyArgs( "", "Pair a target with a directory path:", ` sentry init ${first} ./my-project`, - ].join("\n") + ].join("\n"), ); } @@ -157,7 +157,7 @@ function classifyArgs( } function parseFeatures( - features: readonly string[] | undefined + features: readonly string[] | undefined, ): string[] | undefined { const requested = features ?.flatMap((feature) => feature.split(FEATURE_DELIMITER)) @@ -176,7 +176,7 @@ function normalizeFeature(feature: string): string { if (!normalized) { throw new ValidationError( `Unknown init feature "${feature}". Supported features: ${SUPPORTED_FEATURE_TEXT}`, - "features" + "features", ); } return normalized; @@ -193,7 +193,7 @@ function isNonInteractiveContext(context: unknown): boolean { function validateNonInteractiveInit( context: unknown, flags: InitFlags, - features: readonly string[] | undefined + features: readonly string[] | undefined, ): void { if (!isNonInteractiveContext(context)) { return; @@ -255,7 +255,7 @@ async function resolveTarget(targetArg: string | undefined): Promise<{ throw new ValidationError( `Project "${parsed.projectSlug}" exists in multiple organizations.\n\n` + `Specify the organization:\n${orgList}\n\n` + - `Example: sentry init ${first.orgSlug}/${parsed.projectSlug}` + `Example: sentry init ${first.orgSlug}/${parsed.projectSlug}`, ); } @@ -273,7 +273,7 @@ async function resolveTarget(targetArg: string | undefined): Promise<{ // Truly not found — treat as the name for a new project to create. // Org will be resolved later by init preflight before the workflow starts. log.info( - `No existing project "${parsed.projectSlug}" found — will create a new project with this name.` + `No existing project "${parsed.projectSlug}" found — will create a new project with this name.`, ); return { org: undefined, project: parsed.projectSlug }; } @@ -367,17 +367,17 @@ export const initCommand = buildCommand< t: "team", }, }, - // biome-ignore lint/correctness/useYield: init renders through WizardUI instead of command output + // oxlint-disable-next-line require-yield -- init renders through WizardUI instead of command output async *func( this: SentryContext, flags: InitFlags, first?: string, - second?: string + second?: string, ) { // 1. Classify positionals into target vs directory const { target: targetArg, directory: dirArg } = classifyArgs( first, - second + second, ); // 2. Resolve directory diff --git a/packages/cli/src/commands/issue/archive.ts b/packages/cli/src/commands/issue/archive.ts index a8c0c36df..276cdb939 100644 --- a/packages/cli/src/commands/issue/archive.ts +++ b/packages/cli/src/commands/issue/archive.ts @@ -98,7 +98,7 @@ function tryAbsoluteDate(raw: string): number | undefined { const deltaMinutes = Math.ceil((ts - Date.now()) / 60_000); if (deltaMinutes < 1) { throw new ValidationError( - `--until date must be in the future, got '${raw}'` + `--until date must be in the future, got '${raw}'`, ); } return deltaMinutes; @@ -118,7 +118,7 @@ function parseDurationMinutes(raw: string): number | undefined { /** Try parsing a single-char count suffix: "10x", "10u". */ function tryShortCount( - raw: string + raw: string, ): { type: "x" | "u"; value: number } | undefined { const lastChar = raw.at(-1)?.toLowerCase(); if (lastChar !== "x" && lastChar !== "u") { @@ -133,7 +133,7 @@ function tryShortCount( /** Try parsing a verbose count: "10events", "10users". */ function tryVerboseCount( - raw: string + raw: string, ): { type: "x" | "u"; value: number } | undefined { const m = NUMERIC_WORD_RE.exec(raw); if (!m) { @@ -152,7 +152,7 @@ function tryVerboseCount( * Parse a single atom: count (`10x`), user count (`10u`), or duration. */ function parseAtom( - raw: string + raw: string, ): | { type: "x" | "u"; value: number } | { type: "duration"; minutes: number } @@ -175,7 +175,7 @@ function parseSlashPair(raw: string, trimmed: string): UntilSpec { const right = trimmed.slice(slashIdx + 1).trim(); if (!(left && right)) { throw new ValidationError( - `invalid --until format: '${raw}' (expected '<count>/<window>', e.g., '10x/5m')` + `invalid --until format: '${raw}' (expected '<count>/<window>', e.g., '10x/5m')`, ); } @@ -184,18 +184,18 @@ function parseSlashPair(raw: string, trimmed: string): UntilSpec { if (!(leftAtom && rightAtom)) { throw new ValidationError( - `invalid --until format: '${raw}' (expected '<count>/<window>', e.g., '10x/5m')` + `invalid --until format: '${raw}' (expected '<count>/<window>', e.g., '10x/5m')`, ); } if (leftAtom.type === "duration") { throw new ValidationError( - `invalid --until format: '${raw}' (left of '/' must be a count like '10x' or '10u', not a duration)` + `invalid --until format: '${raw}' (left of '/' must be a count like '10x' or '10u', not a duration)`, ); } if (rightAtom.type !== "duration") { throw new ValidationError( - `invalid --until format: '${raw}' (right of '/' must be a duration like '5m' or '2h', not a count)` + `invalid --until format: '${raw}' (right of '/' must be a duration like '5m' or '2h', not a count)`, ); } @@ -225,7 +225,7 @@ function throwUnrecognizedUntil(raw: string): never { " 10x Archive until 10 more events\n" + " 10u Archive until 10 more users\n" + " 10x/5m 10 events within 5 minutes\n" + - " 10events/2hours Same, verbose form" + " 10events/2hours Same, verbose form", ); } diff --git a/packages/cli/src/commands/issue/events.ts b/packages/cli/src/commands/issue/events.ts index 3b9159d0f..f17c15ce9 100644 --- a/packages/cli/src/commands/issue/events.ts +++ b/packages/cli/src/commands/issue/events.ts @@ -48,7 +48,7 @@ export const PAGINATION_KEY = "issue-events"; /** Build the CLI hint for fetching the next page, preserving active flags. */ function nextPageHint( issueArg: string, - flags: Pick<EventsFlags, "query" | "full" | "period"> + flags: Pick<EventsFlags, "query" | "full" | "period">, ): string { return appendEventsFlags(`sentry issue events ${issueArg} -c next`, flags); } @@ -56,7 +56,7 @@ function nextPageHint( /** Build the CLI hint for fetching the previous page, preserving active flags. */ function prevPageHint( issueArg: string, - flags: Pick<EventsFlags, "query" | "full" | "period"> + flags: Pick<EventsFlags, "query" | "full" | "period">, ): string { return appendEventsFlags(`sentry issue events ${issueArg} -c prev`, flags); } @@ -109,7 +109,7 @@ export const eventsCommand = buildListCommand("issue", { if (!org) { throw new ContextError( "Organization", - buildCommandHint(COMMAND_NAME, issueArg) + buildCommandHint(COMMAND_NAME, issueArg), ); } @@ -117,12 +117,12 @@ export const eventsCommand = buildListCommand("issue", { const contextKey = buildPaginationContextKey( "issue-events", `${org}/${issue.id}`, - { q: flags.query, period: serializeTimeRange(timeRange) } + { q: flags.query, period: serializeTimeRange(timeRange) }, ); const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { data: events, nextCursor } = await withProgress( @@ -140,7 +140,7 @@ export const eventsCommand = buildListCommand("issue", { }).catch((error: unknown): never => { // An unparseable user --query is a user input mistake, not a CLI bug. throw toSearchQueryError(error, flags.query); - }) + }), ); // Update pagination state (handles both advance and truncation) diff --git a/packages/cli/src/commands/issue/explain.ts b/packages/cli/src/commands/issue/explain.ts index 4591510d4..bc91da295 100644 --- a/packages/cli/src/commands/issue/explain.ts +++ b/packages/cli/src/commands/issue/explain.ts @@ -43,7 +43,7 @@ async function analyzeIssue( issueArg: string, cwd: string, flags: ExplainFlags, - suppressProgress: boolean + suppressProgress: boolean, ): Promise<IssueExplainResult> { let resolvedOrg: string | undefined; @@ -65,7 +65,7 @@ async function analyzeIssue( if (rootCauses.length === 0) { throw new Error( "Analysis completed but no root causes found. " + - "The issue may not have enough context for root cause analysis." + "The issue may not have enough context for root cause analysis.", ); } @@ -146,7 +146,7 @@ export const explainCommand = buildCommand({ (issueArg) => analyzeIssue(issueArg, cwd, flags, flags.json || isBatch), (issueArg, reason) => { log.warn(`Failed to analyze issue ${issueArg}: ${reason}`); - } + }, ); yield new CommandOutput({ diff --git a/packages/cli/src/commands/issue/link-utils.ts b/packages/cli/src/commands/issue/link-utils.ts index 107bc71ee..186ad7f42 100644 --- a/packages/cli/src/commands/issue/link-utils.ts +++ b/packages/cli/src/commands/issue/link-utils.ts @@ -34,7 +34,7 @@ export const EXTERNAL_ISSUE_FLAGS = { /** Parse repeated App form fields while rejecting ambiguous duplicate keys. */ export function parseIssueLinkFields( - fields: readonly string[] | undefined + fields: readonly string[] | undefined, ): Record<string, string> | undefined { if (!fields?.length) { return; @@ -49,7 +49,7 @@ export function parseIssueLinkFields( Object.hasOwn(result, key) ) { throw new ValidationError( - "Each --field must be a unique name=value pair." + "Each --field must be a unique name=value pair.", ); } result[key] = field.slice(separator + 1); diff --git a/packages/cli/src/commands/issue/link.ts b/packages/cli/src/commands/issue/link.ts index 5f5b753c4..9b6a7fa1e 100644 --- a/packages/cli/src/commands/issue/link.ts +++ b/packages/cli/src/commands/issue/link.ts @@ -56,7 +56,7 @@ export const linkCommand = buildCommand({ this: SentryContext, flags: LinkFlags, issueArg: string, - url: string + url: string, ) { const fields = parseIssueLinkFields(flags.field); const { org, issueId, projectId } = await resolveOrgAndIssueId({ diff --git a/packages/cli/src/commands/issue/list.ts b/packages/cli/src/commands/issue/list.ts index 189276dbc..4485fd199 100644 --- a/packages/cli/src/commands/issue/list.ts +++ b/packages/cli/src/commands/issue/list.ts @@ -216,7 +216,7 @@ const SEEN_STATS_FIELDS = new Set([...LIFETIME_FIELDS, "stats"]); */ function shouldCollapseForFields( fields: string[] | undefined, - dependentFields: ReadonlySet<string> + dependentFields: ReadonlySet<string>, ): boolean { return ( fields !== undefined && @@ -275,7 +275,7 @@ function resolveCompact(flag: boolean | undefined, rowCount: number): boolean { function parseSort(value: string): SortValue { if (!VALID_SORT_VALUES.includes(value as SortValue)) { throw new Error( - `Invalid sort value. Must be one of: ${VALID_SORT_VALUES.join(", ")}` + `Invalid sort value. Must be one of: ${VALID_SORT_VALUES.join(", ")}`, ); } return value as SortValue; @@ -328,7 +328,7 @@ function formatListFooter(mode: "single" | "multi" | "none"): string { function attachFormatOptions( results: IssueListFetchResult[], aliasMap: Map<string, string>, - isMultiProject: boolean + isMultiProject: boolean, ): IssueTableRow[] { return results.flatMap((result) => result.issues.map((issue) => { @@ -343,7 +343,7 @@ function attachFormatOptions( isMultiProject, }, }; - }) + }), ); } @@ -363,7 +363,7 @@ function compareDates(a: string | undefined, b: string | undefined): number { * @returns Comparator function for Array.sort() */ function getComparator( - sort: SortValue + sort: SortValue, ): (a: SentryIssue, b: SentryIssue) => number { switch (sort) { case "date": @@ -419,7 +419,7 @@ async function fetchIssuesForTarget( collapse?: IssueCollapseField[]; /** Stats period resolution — undefined when stats are collapsed. */ groupStatsPeriod?: "" | "14d" | "24h" | "auto"; - } + }, ): Promise<FetchResult> { const result = await withAuthGuard(async () => { const { issues, nextCursor } = await listIssuesAllPages( @@ -431,7 +431,7 @@ async function fetchIssuesForTarget( groupStatsPeriod: options.groupStatsPeriod, start: options.start, end: options.end, - } + }, ); return { target, issues, hasMore: !!nextCursor, nextCursor }; }); @@ -457,7 +457,7 @@ async function runPhase2( context: { surplus: number; options: Omit<BudgetFetchOptions, "limit" | "startCursors">; - } + }, ): Promise<void> { const { surplus, options } = context; const extraQuotas = distributeFetchBudget(surplus, expandableIndices.length); @@ -475,24 +475,24 @@ async function runPhase2( const phase2 = await Promise.all( requests.map(({ targetIndex, limit }) => { // expandableIndices only contains indices where r.success && r.data.nextCursor - // biome-ignore lint/style/noNonNullAssertion: guaranteed by expandableIndices filter + // oxlint-disable-next-line typescript/no-non-null-assertion -- guaranteed by expandableIndices filter const target = targets[targetIndex]!; const r = phase1[targetIndex] as { success: true; data: IssueListFetchResult; }; - // biome-ignore lint/style/noNonNullAssertion: same guarantee + // oxlint-disable-next-line typescript/no-non-null-assertion -- same guarantee const cursor = r.data.nextCursor!; return fetchIssuesForTarget(target, { ...options, limit, startCursor: cursor, }); - }) + }), ); for (let j = 0; j < requests.length; j++) { - // biome-ignore lint/style/noNonNullAssertion: j is within requests bounds + // oxlint-disable-next-line typescript/no-non-null-assertion -- j is within requests bounds const i = requests[j]!.targetIndex; const p2 = phase2[j]; const p1 = phase1[i]; @@ -543,7 +543,7 @@ type BudgetFetchOptions = { async function fetchWithBudget( targets: ResolvedTarget[], options: BudgetFetchOptions, - onProgress: (fetched: number) => void + onProgress: (fetched: number) => void, ): Promise<{ results: FetchResult[]; hasMore: boolean }> { const { limit, startCursors } = options; const quotas = distributeFetchBudget(limit, targets.length, { @@ -557,8 +557,8 @@ async function fetchWithBudget( ...options, limit: quotas[i] ?? 1, startCursor: startCursors?.get(`${t.org}/${t.project}`), - }) - ) + }), + ), ); let totalFetched = 0; @@ -619,12 +619,12 @@ async function fetchWithBudget( */ function trimWithProjectGuarantee( issues: IssueTableRow[], - limit: number + limit: number, ): IssueTableRow[] { return trimWithGroupGuarantee( issues, limit, - (r) => `${r.orgSlug}/${r.formatOptions.projectSlug ?? ""}` + (r) => `${r.orgSlug}/${r.formatOptions.projectSlug ?? ""}`, ); } @@ -663,7 +663,7 @@ async function fetchOrgAllIssues( options: { cursor?: string; onPage?: (fetched: number, limit: number) => void; - } + }, ): Promise<IssuesPage> { const apiOpts = buildListApiOptions(flags.json, flags.fields); const timeParams = timeRangeToApiParams(timeRange); @@ -712,7 +712,7 @@ type OrgAllIssuesOptions = { * is responsible for rendering (JSON or human output). */ async function handleOrgAllIssues( - options: OrgAllIssuesOptions + options: OrgAllIssuesOptions, ): Promise<IssueListResult> { const { org, flags, timeRange } = options; // Encode sort + query in context key so cursors from different searches don't collide. @@ -724,7 +724,7 @@ async function handleOrgAllIssues( const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); let issuesResult: IssuesPage; @@ -739,9 +739,9 @@ async function handleOrgAllIssues( cursor, onPage: (fetched, limit) => setMessage( - `Fetching issues, ${fetched} and counting (up to ${limit})...` + `Fetching issues, ${fetched} and counting (up to ${limit})...`, ), - }) + }), ); } catch (error) { throw enrichIssueListError(error, flags); @@ -839,7 +839,7 @@ const UNSUPPORTED_SORT_RE = /sort key/i; */ function build400Detail( originalDetail: string | undefined, - flags: Pick<ListFlags, "query" | "period" | "sort"> + flags: Pick<ListFlags, "query" | "period" | "sort">, ): string { const lines: string[] = []; @@ -856,14 +856,14 @@ function build400Detail( // query/time-range/access suggestions, so short-circuit on it. if (originalDetail && UNSUPPORTED_SORT_RE.test(originalDetail)) { suggestions.push( - `This Sentry instance does not support the '${flags.sort}' sort. Use a widely-supported sort such as --sort date (the 'recommended' sort requires a recent Sentry version).` + `This Sentry instance does not support the '${flags.sort}' sort. Use a widely-supported sort such as --sort date (the 'recommended' sort requires a recent Sentry version).`, ); return formatDetailWithSuggestions(lines, suggestions); } if (flags.query) { suggestions.push( - "Check your --query syntax (Sentry search reference: https://docs.sentry.io/concepts/search/)" + "Check your --query syntax (Sentry search reference: https://docs.sentry.io/concepts/search/)", ); } @@ -872,7 +872,7 @@ function build400Detail( } suggestions.push( - "Verify you have access to the target project: sentry project list <org>/" + "Verify you have access to the target project: sentry project list <org>/", ); return formatDetailWithSuggestions(lines, suggestions); @@ -888,7 +888,7 @@ function build400Detail( */ function formatDetailWithSuggestions( detailLines: string[], - suggestions: string[] + suggestions: string[], ): string { const lines = [...detailLines]; if (lines.length > 0) { @@ -909,7 +909,7 @@ function formatDetailWithSuggestions( */ function enrichIssueListError( error: unknown, - flags: Pick<ListFlags, "query" | "period" | "sort"> + flags: Pick<ListFlags, "query" | "period" | "sort">, ): never { // A user-supplied --query the server cannot parse is a user input mistake, // not a CLI bug: surface it as a ValidationError. A 400 with no user --query @@ -924,7 +924,7 @@ function enrichIssueListError( error.message, error.status, build400Detail(error.detail, flags), - error.endpoint + error.endpoint, ); } if (error.status === 403) { @@ -938,7 +938,7 @@ function enrichIssueListError( error.status, detail, error.endpoint, - true + true, ); } } @@ -992,7 +992,7 @@ function build403Detail(originalDetail: unknown): string { : `Your ${getActiveEnvVarName()} token may lack the required scopes`; lines.push( ` • ${leader} (${scopeList})`, - " • Check token scopes at: https://sentry.io/settings/account/api/auth-tokens/" + " • Check token scopes at: https://sentry.io/settings/account/api/auth-tokens/", ); } else { lines.push(" • Re-authenticate with: sentry auth login"); @@ -1022,9 +1022,9 @@ function appendProjectMembershipHint(detail: string | undefined): string { * Cursor pagination uses a compound cursor (one cursor per project, encoded * as a pipe-separated string) so `-c next` / `-c prev` works across multi-target results. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: inherent multi-target resolution, compound cursor, error handling, and display logic +// inherent multi-target resolution, compound cursor, error handling, and display logic async function handleResolvedTargets( - options: ResolvedTargetsOptions + options: ResolvedTargetsOptions, ): Promise<IssueListResult> { const { parsed, flags, cwd, timeRange, projectSearchResolution } = options; @@ -1043,7 +1043,7 @@ async function handleResolvedTargets( "Organization and project", USAGE_HINT, undefined, - `Found ${skippedSelfHosted} DSN(s) that could not be resolved — you may not have access to these projects` + `Found ${skippedSelfHosted} DSN(s) that could not be resolved — you may not have access to these projects`, ); } throw new ContextError("Organization and project", USAGE_HINT); @@ -1063,11 +1063,11 @@ async function handleResolvedTargets( const { cursor: rawCursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { startCursors, exhausted: exhaustedTargets } = decodeTargetCursors( rawCursor, - sortedTargetKeys + sortedTargetKeys, ); const activeTargets = @@ -1099,28 +1099,28 @@ async function handleResolvedTargets( }, (fetched) => { setMessage( - `${baseMessage}, ${fetched} and counting (up to ${flags.limit})...` + `${baseMessage}, ${fetched} and counting (up to ${flags.limit})...`, ); - } - ) + }, + ), ); const validResults: IssueListFetchResult[] = []; const failures: { target: ResolvedTarget; error: Error }[] = []; for (let i = 0; i < results.length; i++) { - // biome-ignore lint/style/noNonNullAssertion: index within bounds + // oxlint-disable-next-line typescript/no-non-null-assertion -- index within bounds const result = results[i]!; if (result.success) { validResults.push(result.data); } else { - // biome-ignore lint/style/noNonNullAssertion: index within bounds + // oxlint-disable-next-line typescript/no-non-null-assertion -- index within bounds failures.push({ target: activeTargets[i]!, error: result.error }); } } if (validResults.length === 0 && failures.length > 0) { - // biome-ignore lint/style/noNonNullAssertion: guarded by failures.length > 0 + // oxlint-disable-next-line typescript/no-non-null-assertion -- guarded by failures.length > 0 const { error: first } = failures[0]!; const prefix = `Failed to fetch issues from ${targets.length} project(s)`; @@ -1150,7 +1150,7 @@ async function handleResolvedTargets( first.status, detail, first.endpoint, - first.enriched403 || first.status === 403 + first.enriched403 || first.status === 403, ); } @@ -1175,7 +1175,7 @@ async function handleResolvedTargets( const allIssuesWithOptions = attachFormatOptions( validResults, aliasMap, - isMultiProject + isMultiProject, ); // Only re-sort when merging results from multiple separately-fetched @@ -1187,14 +1187,14 @@ async function handleResolvedTargets( // silently replace the server's ranking with a `lastSeen` fallback. if (isMultiProject) { allIssuesWithOptions.sort((a, b) => - getComparator(flags.sort)(a.issue, b.issue) + getComparator(flags.sort)(a.issue, b.issue), ); } // Trim to the global limit with project representation guarantee const issuesWithOptions = trimWithProjectGuarantee( allIssuesWithOptions, - flags.limit + flags.limit, ); const trimmed = issuesWithOptions.length < allIssuesWithOptions.length; // Store compound cursor only after display trimming is known. If rows were @@ -1230,7 +1230,7 @@ async function handleResolvedTargets( PAGINATION_KEY, contextKey, direction, - compoundNextCursor + compoundNextCursor, ); const hasPrev = hasPreviousPage(PAGINATION_KEY, contextKey); @@ -1245,7 +1245,7 @@ async function handleResolvedTargets( status: e.status, message: e.message, } - : { project: `${t.org}/${t.project}`, message: e.message } + : { project: `${t.org}/${t.project}`, message: e.message }, ) : undefined; @@ -1255,7 +1255,7 @@ async function handleResolvedTargets( .map(({ target: t }) => `${t.org}/${t.project}`) .join(", "); logger.warn( - `Failed to fetch issues from ${failedNames}. Showing results from ${validResults.length} project(s).` + `Failed to fetch issues from ${failedNames}. Showing results from ${validResults.length} project(s).`, ); } @@ -1407,7 +1407,7 @@ function formatIssueListHuman(result: IssueListResult): string { */ function jsonTransformIssueList( result: IssueListResult, - fields?: string[] + fields?: string[], ): unknown { const transformed = jsonTransformListResult(result, fields); // Only inject into empty paginated envelopes — helps agents discover @@ -1540,7 +1540,7 @@ export const listCommand = buildListCommand("issue", { ) { const shortId = parsed.projectSlug; log.warn( - `'${shortId}' is an issue short ID, not a project slug. Showing the issue.` + `'${shortId}' is an issue short ID, not a project slug. Showing the issue.`, ); const { org, issue } = await resolveIssue({ issueArg: shortId, @@ -1579,13 +1579,13 @@ export const listCommand = buildListCommand("issue", { throw new ValidationError( `--limit cannot exceed ${LIST_MAX_LIMIT}. ` + "Use --cursor to paginate through larger result sets.", - "limit" + "limit", ); } const timeRange = flags.period; - // biome-ignore lint/suspicious/noExplicitAny: shared handler accepts any mode variant + // oxlint-disable-next-line typescript/no-explicit-any -- shared handler accepts any mode variant const resolveAndHandle: ModeHandler<any> = (ctx) => handleResolvedTargets({ ...ctx, diff --git a/packages/cli/src/commands/issue/merge.ts b/packages/cli/src/commands/issue/merge.ts index d7fb60610..8e3eab029 100644 --- a/packages/cli/src/commands/issue/merge.ts +++ b/packages/cli/src/commands/issue/merge.ts @@ -87,7 +87,7 @@ function jsonTransform(result: MergeCommandResult): unknown { */ async function resolveAllIssues( args: readonly string[], - cwd: string + cwd: string, ): Promise<{ org: string; issues: SentryIssue[] }> { const resolved = await Promise.all( args.map((arg) => @@ -95,8 +95,8 @@ async function resolveAllIssues( issueArg: arg, cwd, command: COMMAND, - }) - ) + }), + ), ); // Every resolved issue must have a concrete org slug — otherwise we @@ -109,7 +109,7 @@ async function resolveAllIssues( throw new ValidationError( `Could not determine the organization for: ${badIds}.\n\n` + "Provide the org explicitly (e.g. <org>/<issue>) so the merge\n" + - "can verify all issues belong to the same organization." + "can verify all issues belong to the same organization.", ); } @@ -118,7 +118,7 @@ async function resolveAllIssues( if (orgs.size > 1) { throw new ValidationError( `Cannot merge issues across organizations (${Array.from(orgs).join(", ")}).\n\n` + - "All issues must belong to the same organization." + "All issues must belong to the same organization.", ); } @@ -152,7 +152,7 @@ async function resolveAllIssues( throw new ValidationError( `Merge needs at least 2 distinct issues (all inputs resolved to ${issues[0]?.shortId ?? "the same issue"}).\n\n` + "Check your argument list — you may have passed the same issue in\n" + - "multiple forms (short ID + org-qualified + numeric all count as one)." + "multiple forms (short ID + org-qualified + numeric all count as one).", ); } @@ -181,7 +181,7 @@ async function resolveAllIssues( async function orderForMerge( issues: SentryIssue[], into: string | undefined, - cwd: string + cwd: string, ): Promise<SentryIssue[]> { if (!into) { return issues; @@ -201,7 +201,7 @@ async function orderForMerge( i.shortId === bareUpper || i.id === bare || i.shortId === normalizedUpper || - i.id === normalized + i.id === normalized, ); if (direct) { return [direct, ...issues.filter((i) => i !== direct)]; @@ -245,7 +245,7 @@ async function orderForMerge( throw new ValidationError( `--into '${into}' did not match any of the provided issues.\n\n` + `Provided: ${issues.map((i) => i.shortId).join(", ")}`, - "into" + "into", ); } @@ -310,7 +310,7 @@ export const mergeCommand = buildCommand({ : "Example: sentry issue merge CLI-K9 CLI-15H CLI-15N"; throw new ValidationError( `'sentry ${COMMAND_PATH}' needs at least 2 issue IDs (got ${args.length}).\n\n` + - hint + hint, ); } @@ -323,7 +323,7 @@ export const mergeCommand = buildCommand({ const requestedParentId = flags.into ? groupIds[0] : undefined; log.debug( - `Merging ${groupIds.length} issues in ${org}: ${ordered.map((i) => i.shortId).join(", ")}` + `Merging ${groupIds.length} issues in ${org}: ${ordered.map((i) => i.shortId).join(", ")}`, ); const raw = await mergeIssues(org, groupIds); @@ -337,7 +337,7 @@ export const mergeCommand = buildCommand({ const requestedShortId = idToShort.get(requestedParentId) ?? flags.into; log.warn( `--into '${requestedShortId}' was a preference, not a guarantee. ` + - `Sentry selected ${parentShortId} as the canonical parent based on event count.` + `Sentry selected ${parentShortId} as the canonical parent based on event count.`, ); } diff --git a/packages/cli/src/commands/issue/plan.ts b/packages/cli/src/commands/issue/plan.ts index a5e12ec40..9fd648967 100644 --- a/packages/cli/src/commands/issue/plan.ts +++ b/packages/cli/src/commands/issue/plan.ts @@ -82,7 +82,7 @@ function formatPlanOutput(data: PlanData): string { lines.push(`No solution found: ${ctx.reason}`); } else { lines.push( - "No solution found. Seer completed analysis but could not identify a code fix." + "No solution found. Seer completed analysis but could not identify a code fix.", ); if (ctx?.root_cause) { lines.push(""); @@ -107,7 +107,7 @@ function formatPlanOutput(data: PlanData): string { * Returns undefined when there's nothing useful to report. */ function buildNoSolutionContext( - state: AutofixState + state: AutofixState, ): NoSolutionContext | undefined { const reason = extractNoSolutionReason(state); const cause = extractRootCauses(state)[0]; @@ -170,7 +170,7 @@ function buildPlanData(state: AutofixState): PlanData { */ async function confirmRootCauseAndContinue( state: AutofixState, - json: boolean + json: boolean, ): Promise<void> { const causes = json ? [] : extractRootCauses(state); const primaryCause = causes[0]; @@ -193,7 +193,7 @@ async function confirmRootCauseAndContinue( throw new CliError( "Root cause analysis requires your input before a plan can be generated.\n" + - "Open the issue in Sentry to select a root cause, then re-run this command." + "Open the issue in Sentry to select a root cause, then re-run this command.", ); } @@ -305,7 +305,7 @@ export const planCommand = buildCommand({ if (finalState.status === "ERROR") { throw new Error( - "Plan creation failed. Check the Sentry web UI for details." + "Plan creation failed. Check the Sentry web UI for details.", ); } @@ -316,7 +316,7 @@ export const planCommand = buildCommand({ if (finalState.status === "WAITING_FOR_USER_RESPONSE") { throw new CliError( "Plan creation requires your input in the Sentry UI.\n" + - "Open the issue in Sentry to provide the requested information, then re-run this command." + "Open the issue in Sentry to provide the requested information, then re-run this command.", ); } diff --git a/packages/cli/src/commands/issue/resolve-commit-spec.ts b/packages/cli/src/commands/issue/resolve-commit-spec.ts index f53533f25..b221d287b 100644 --- a/packages/cli/src/commands/issue/resolve-commit-spec.ts +++ b/packages/cli/src/commands/issue/resolve-commit-spec.ts @@ -29,7 +29,7 @@ import type { SentryRepository } from "../../types/index.js"; /** Fetch the git origin URL without throwing when it's missing. */ function getGitOriginUrl(cwd: string): string | undefined { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return execFileSync("git", ["remote", "get-url", "origin"], { cwd, @@ -50,7 +50,7 @@ function getGitOriginUrl(cwd: string): string | undefined { */ function findSentryRepoMatchingOrigin( originOwnerRepo: string, - sentryRepos: SentryRepository[] + sentryRepos: SentryRepository[], ): SentryRepository | null { // Prefer externalSlug (canonical `owner/repo` from the integration) // then fall back to `name` for repos without that field populated. @@ -67,7 +67,7 @@ function findSentryRepoMatchingOrigin( */ function findSentryRepoByName( repoName: string, - sentryRepos: SentryRepository[] + sentryRepos: SentryRepository[], ): SentryRepository | null { return ( sentryRepos.find((r) => r.name === repoName) ?? @@ -104,13 +104,13 @@ function formatAvailableRepos(repos: SentryRepository[]): string { export async function resolveCommitSpec( spec: ResolveCommitSpec, orgSlug: string, - cwd: string + cwd: string, ): Promise<{ commit: string; repository: string }> { if (spec.kind === "auto") { if (!isInsideGitWorkTree(cwd)) { throw new ValidationError( "--in @commit requires a git repository. Run from inside a checkout, or use --in @next / --in <version> / --in @commit:<repo>@<sha>.", - "in" + "in", ); } @@ -120,7 +120,7 @@ export async function resolveCommitSpec( } catch { throw new ValidationError( "--in @commit could not read HEAD (is this a fresh repo with no commits?). Make a commit first, or pass --in @commit:<repo>@<sha> explicitly.", - "in" + "in", ); } @@ -128,7 +128,7 @@ export async function resolveCommitSpec( if (!originUrl) { throw new ValidationError( "--in @commit could not determine the git 'origin' remote. Add an origin remote, or pass --in @commit:<repo>@<sha> explicitly.", - "in" + "in", ); } @@ -136,7 +136,7 @@ export async function resolveCommitSpec( if (!originOwnerRepo) { throw new ValidationError( `--in @commit could not parse the origin URL ('${originUrl}') as 'owner/repo'. Use --in @commit:<repo>@<sha> explicitly.`, - "in" + "in", ); } @@ -147,7 +147,7 @@ export async function resolveCommitSpec( `--in @commit: no Sentry repository matches local origin '${originOwnerRepo}' in organization '${orgSlug}'.\n\n` + `${formatAvailableRepos(sentryRepos)}\n\n` + "Register the repo in Sentry, or pass --in @commit:<repo>@<sha> with a registered name.", - "in" + "in", ); } @@ -161,7 +161,7 @@ export async function resolveCommitSpec( throw new ValidationError( `--in @commit:${spec.repository}@${spec.commit}: no Sentry repository named '${spec.repository}' in organization '${orgSlug}'.\n\n` + `${formatAvailableRepos(sentryRepos)}`, - "in" + "in", ); } return { commit: spec.commit, repository: match.name }; diff --git a/packages/cli/src/commands/issue/resolve.ts b/packages/cli/src/commands/issue/resolve.ts index 161b94dec..cf59c01bf 100644 --- a/packages/cli/src/commands/issue/resolve.ts +++ b/packages/cli/src/commands/issue/resolve.ts @@ -155,7 +155,7 @@ export const resolveCommand = buildCommand({ "Organization", "sentry issue resolve <org>/<issue> --in @commit", [], - "--in @commit needs an organization context to look up the Sentry repo registry." + "--in @commit needs an organization context to look up the Sentry repo registry.", ); } const resolved = await resolveCommitSpec(parsed.spec, org, cwd); @@ -168,7 +168,7 @@ export const resolveCommand = buildCommand({ }); log.debug( - `Resolved ${updated.shortId} ${describeSpec(statusDetails ?? null)}` + `Resolved ${updated.shortId} ${describeSpec(statusDetails ?? null)}`, ); yield new CommandOutput<ResolveResult>({ issue: updated, diff --git a/packages/cli/src/commands/issue/unlink.ts b/packages/cli/src/commands/issue/unlink.ts index decf8a996..ee84dcd16 100644 --- a/packages/cli/src/commands/issue/unlink.ts +++ b/packages/cli/src/commands/issue/unlink.ts @@ -46,7 +46,7 @@ export const unlinkCommand = buildDeleteCommand({ this: SentryContext, flags: UnlinkFlags, issueArg: string, - url: string + url: string, ) { const { org, issueId } = await resolveOrgAndIssueId({ issueArg, @@ -56,7 +56,7 @@ export const unlinkCommand = buildDeleteCommand({ if (!(flags["dry-run"] || isConfirmationBypassed(flags))) { const confirmed = await confirmByTyping( issueArg, - `Type '${issueArg}' to unlink ${url}:` + `Type '${issueArg}' to unlink ${url}:`, ); if (!confirmed) { return { hint: "Cancelled." }; diff --git a/packages/cli/src/commands/issue/utils.ts b/packages/cli/src/commands/issue/utils.ts index 715639e7b..a29bc876e 100644 --- a/packages/cli/src/commands/issue/utils.ts +++ b/packages/cli/src/commands/issue/utils.ts @@ -114,11 +114,11 @@ export function collectIssueArgs(args: readonly string[]): string[] { export async function mapIssueArgsConcurrently<T>( issueArgs: readonly string[], operation: (issueArg: string) => Promise<T>, - onError: (issueArg: string, reason: unknown) => void + onError: (issueArg: string, reason: unknown) => void, ): Promise<T[]> { const limit = pLimit(ORG_FANOUT_CONCURRENCY); const settled = await Promise.allSettled( - issueArgs.map((issueArg) => limit(() => operation(issueArg))) + issueArgs.map((issueArg) => limit(() => operation(issueArg))), ); const values: T[] = []; @@ -162,7 +162,7 @@ export async function mapIssueArgsConcurrently<T>( export function buildCommandHint( command: string, issueId: string, - base = "sentry issue" + base = "sentry issue", ): string { // URLs are self-contained — no enrichment needed if (issueId.startsWith("http://") || issueId.startsWith("https://")) { @@ -253,7 +253,7 @@ type IssueCommandContext = { async function tryResolveFromAlias( alias: string, suffix: string, - cwd: string + cwd: string, ): Promise<StrictResolvedIssue | null> { // Detect DSNs to get fingerprint for validation const detection = await detectAllDsns(cwd); @@ -282,7 +282,7 @@ async function tryResolveFromAlias( async function resolveProjectSearchFallback( projectSlug: string, suffix: string, - commandContext: IssueCommandContext + commandContext: IssueCommandContext, ): Promise<StrictResolvedIssue> { const { command, commandBase, commandHint } = commandContext; const { projects } = await findProjectsBySlug(projectSlug.toLowerCase()); @@ -297,7 +297,7 @@ async function resolveProjectSearchFallback( "No issue with this short ID found in any accessible organization", "Check the project prefix and suffix, or use a numeric issue ID", `Specify the org: ${commandBase} ${command} <org>/${fullShortId}`, - ] + ], ); } @@ -310,7 +310,7 @@ async function resolveProjectSearchFallback( [ `Found in: ${orgList}`, `Specify the org: ${commandBase} ${command} <org>/${projectSlug}-${suffix}`, - ] + ], ); } @@ -330,14 +330,14 @@ async function resolveProjectSearchFallback( throw new ResolutionError( `Project '${projectSlug}'`, "not found", - commandHint + commandHint, ); } /** Search the supplied orgs and preserve failures when none could be queried. */ async function findIssuesByShortId( orgSlugs: readonly string[], - fullShortId: string + fullShortId: string, ): Promise<StrictResolvedIssue[]> { const limit = pLimit(ORG_FANOUT_CONCURRENCY); const results = await Promise.all( @@ -348,9 +348,9 @@ async function findIssuesByShortId( collapse: ISSUE_DETAIL_COLLAPSE, }); return issue ? { org, issue } : null; - }) - ) - ) + }), + ), + ), ); // If every org failed with a real error (403, 5xx, network timeout), @@ -359,7 +359,7 @@ async function findIssuesByShortId( // 404s ({ok: true, value: null}), fall through to the fallback for a // precise error message. const realErrors = results.filter( - (r): r is AuthGuardFailure => r !== undefined && !r.ok + (r): r is AuthGuardFailure => r !== undefined && !r.ok, ); if (realErrors.length === results.length && realErrors.length > 0) { const firstError = realErrors[0]?.error; @@ -369,7 +369,7 @@ async function findIssuesByShortId( } return results.flatMap((result) => - result.ok && result.value ? [result.value] : [] + result.ok && result.value ? [result.value] : [], ); } @@ -392,14 +392,14 @@ async function resolveProjectSearch( projectSlug: string, suffix: string, cwd: string, - commandContext: IssueCommandContext + commandContext: IssueCommandContext, ): Promise<StrictResolvedIssue> { const { command, commandBase, commandHint } = commandContext; // 1. Try alias cache first (fast, local lookup) const aliasResult = await tryResolveFromAlias( projectSlug.toLowerCase(), suffix, - cwd + cwd, ); if (aliasResult) { return aliasResult; @@ -446,7 +446,7 @@ async function resolveProjectSearch( let successes = await findIssuesByShortId( orgs.map((org) => org.slug), - fullShortId + fullShortId, ); // A nonempty cache need not include every accessible org. Refresh once on @@ -473,7 +473,7 @@ async function resolveProjectSearch( [ `Found in: ${orgList}`, `Specify the org: ${commandBase} ${command} <org>/${projectSlug}-${suffix}`, - ] + ], ); } @@ -492,14 +492,14 @@ async function resolveProjectSearch( async function resolveSuffixOnly( suffix: string, cwd: string, - commandHint: string + commandHint: string, ): Promise<StrictResolvedIssue> { const target = await resolveOrgAndProject({ cwd }); if (!target) { throw new ResolutionError( `Issue suffix '${suffix}'`, "could not be resolved without project context", - commandHint + commandHint, ); } const fullShortId = expandToFullShortId(suffix, target.project); @@ -524,7 +524,7 @@ async function resolveSuffixOnly( function resolveExplicitOrgSuffix( org: string, suffix: string, - commandContext: IssueCommandContext + commandContext: IssueCommandContext, ): never { const { command, commandBase, commandHint } = commandContext; throw new ResolutionError( @@ -534,7 +534,7 @@ function resolveExplicitOrgSuffix( [ `The format '${org}/${suffix}' requires a project to build the full issue ID.`, `Use: ${commandBase} ${command} ${org}/<project>-${suffix}`, - ] + ], ); } @@ -575,7 +575,7 @@ async function resolveSelector( selector: IssueSelector, explicitOrg: string | undefined, cwd: string, - commandContext: IssueCommandContext + commandContext: IssueCommandContext, ): Promise<StrictResolvedIssue> { const { commandHint } = commandContext; // Resolve org: explicit from `org/@latest` or auto-detected from DSN/defaults @@ -608,7 +608,7 @@ async function resolveSelector( `Selector '${selector}'`, "no unresolved issues found", `sentry issue list ${orgSlug}/ -q "is:resolved"`, - [`The ${label} issue selector only matches unresolved issues.`] + [`The ${label} issue selector only matches unresolved issues.`], ); } @@ -630,7 +630,7 @@ async function resolveSelector( async function resolveShareIssue( share: Extract<ParsedIssueArg, { type: "share" }>, cwd: string, - commandHint: string + commandHint: string, ): Promise<StrictResolvedIssue> { const { shareId, org, baseUrl } = share; const resolvedOrg = org @@ -671,7 +671,7 @@ export type ResolveIssueOptions = { * @param permalink - Issue permalink URL from the Sentry API response */ function extractOrgFromPermalink( - permalink: string | undefined + permalink: string | undefined, ): string | undefined { if (!permalink) { return; @@ -705,7 +705,7 @@ type FetchIssueByNumericIdResult = { async function fetchIssueByNumericId( id: string, explicitOrg: string | undefined, - cachedOrg: string | null | undefined + cachedOrg: string | null | undefined, ): Promise<FetchIssueByNumericIdResult> { if (explicitOrg) { const issue = await getIssueInOrg(explicitOrg, id, { @@ -754,7 +754,7 @@ async function resolveNumericIssue( id: string, cwd: string, command: string, - commandBase = "sentry issue" + commandBase = "sentry issue", ): Promise<ResolvedIssueResult> { const resolvedOrg = await resolveOrg({ cwd }); // Prefer explicit context over the cache — `resolveOrg()` already factors @@ -764,7 +764,7 @@ async function resolveNumericIssue( const { issue, cacheEvicted } = await fetchIssueByNumericId( id, resolvedOrg?.org, - cachedOrg + cachedOrg, ); // When `cacheEvicted` is true, the cached org slug was stale (404'd) and // the helper fell through to the unscoped endpoint. Do NOT let the stale @@ -787,7 +787,7 @@ async function resolveNumericIssue( setCachedIssueOrg(id, org); } catch (cacheErr) { log.debug( - `Failed to cache issue-org mapping for ${id}: ${String(cacheErr)}` + `Failed to cache issue-org mapping for ${id}: ${String(cacheErr)}`, ); } } @@ -832,7 +832,7 @@ async function resolveNumericIssue( * @throws {ResolutionError} When an issue or project could not be found or resolved */ export async function resolveIssue( - options: ResolveIssueOptions + options: ResolveIssueOptions, ): Promise<ResolvedIssueResult> { const { issueArg, cwd, command, commandBase } = options; const effectiveCommandBase = commandBase ?? "sentry issue"; @@ -879,7 +879,7 @@ export async function resolveIssue( [ `No issue with numeric ID ${parsed.numericId} found in org '${org}' — you may not have access, or it may have been deleted.`, `If this is a short ID suffix, try: ${effectiveCommandBase} ${command} <project>-${parsed.numericId}`, - ] + ], ); } throw err; @@ -900,7 +900,7 @@ export async function resolveIssue( parsed.projectSlug, parsed.suffix, cwd, - commandContext + commandContext, ); break; @@ -915,7 +915,7 @@ export async function resolveIssue( parsed.selector, parsed.org, cwd, - commandContext + commandContext, ); break; @@ -928,7 +928,7 @@ export async function resolveIssue( // Exhaustive check - this should never be reached const _exhaustive: never = parsed; throw new Error( - `Unexpected issue arg type: ${JSON.stringify(_exhaustive)}` + `Unexpected issue arg type: ${JSON.stringify(_exhaustive)}`, ); } } @@ -937,7 +937,7 @@ export async function resolveIssue( if (result.org) { setOrgProjectContext( [result.org], - result.issue.project?.slug ? [result.issue.project.slug] : [] + result.issue.project?.slug ? [result.issue.project.slug] : [], ); } @@ -954,7 +954,7 @@ export async function resolveIssue( * @throws {ContextError} When organization cannot be resolved */ export async function resolveOrgAndIssueId( - options: ResolveIssueOptions + options: ResolveIssueOptions, ): Promise<{ org: string; issueId: string; projectId?: string }> { const result = await resolveIssue(options); if (!result.org) { @@ -1010,7 +1010,7 @@ type EnsureRootCauseOptions = { * @returns The completed autofix state with root causes */ export async function ensureRootCauseAnalysis( - options: EnsureRootCauseOptions + options: EnsureRootCauseOptions, ): Promise<AutofixState> { const { org, issueId, json, force = false } = options; @@ -1065,7 +1065,7 @@ export async function ensureRootCauseAnalysis( */ function shouldStopPolling( state: AutofixState, - stopOnWaitingForUser: boolean + stopOnWaitingForUser: boolean, ): boolean { if (isTerminalStatus(state.status)) { return true; @@ -1089,7 +1089,7 @@ function shouldStopPolling( * @throws {Error} On timeout */ export async function pollAutofixState( - options: PollAutofixOptions + options: PollAutofixOptions, ): Promise<AutofixState> { const { orgSlug, diff --git a/packages/cli/src/commands/issue/view.ts b/packages/cli/src/commands/issue/view.ts index 9e8f7ac83..2bdaed703 100644 --- a/packages/cli/src/commands/issue/view.ts +++ b/packages/cli/src/commands/issue/view.ts @@ -63,7 +63,7 @@ type ViewFlags = { */ async function tryGetLatestEvent( orgSlug: string, - issueId: string + issueId: string, ): Promise<SentryEvent | undefined> { try { return await getLatestEvent(orgSlug, issueId); @@ -79,7 +79,7 @@ async function tryGetLatestEvent( */ async function tryListReplayIdsForIssue( orgSlug: string, - issueId: string + issueId: string, ): Promise<string[]> { try { return await listReplayIdsForIssue(orgSlug, issueId); @@ -92,7 +92,7 @@ async function tryListReplayIdsForIssue( async function buildIssueSpanData( orgSlug: string | undefined, event: SentryEvent | undefined, - spans: number + spans: number, ): Promise<Pick<SingleIssueViewData, "spanTreeLines" | "trace">> { const spanTreeResult = orgSlug && event && spans > 0 @@ -131,7 +131,7 @@ async function buildIssueSpanData( async function buildSingleIssueViewData( issueArg: string, cwd: string, - spans: number + spans: number, ): Promise<SingleIssueViewData> { const { org: orgSlug, issue } = await resolveIssue({ issueArg, @@ -180,7 +180,7 @@ type FetchMultipleIssueViewsOptions = { * When all fetches fail, re-throws the error from the primary (first) issue. */ export function fetchMultipleIssueViews( - options: FetchMultipleIssueViewsOptions + options: FetchMultipleIssueViewsOptions, ): Promise<SingleIssueViewData[]> { const { issueArgs, cwd, spans } = options; return mapIssueArgsConcurrently( @@ -188,7 +188,7 @@ export function fetchMultipleIssueViews( (issueArg) => buildSingleIssueViewData(issueArg, cwd, spans), (issueArg, reason) => { log.warn(`Failed to fetch issue ${issueArg}: ${reason}`); - } + }, ); } @@ -202,12 +202,12 @@ export function fetchMultipleIssueViews( async function openIssuesInBrowser( issueArgs: readonly string[], cwd: string, - force: boolean + force: boolean, ): Promise<void> { const argsToOpen = force ? issueArgs : issueArgs.slice(0, MAX_WEB_ISSUES); if (argsToOpen.length < issueArgs.length) { log.warn( - `Opening the first ${MAX_WEB_ISSUES} of ${issueArgs.length} issues. Use --force to open all.` + `Opening the first ${MAX_WEB_ISSUES} of ${issueArgs.length} issues. Use --force to open all.`, ); } @@ -223,7 +223,7 @@ async function openIssuesInBrowser( }, (issueArg, reason) => { log.warn(`Failed to open issue ${issueArg}: ${reason}`); - } + }, ); for (const issue of issues) { diff --git a/packages/cli/src/commands/local/run.ts b/packages/cli/src/commands/local/run.ts index a9aff9197..6f37ca41f 100644 --- a/packages/cli/src/commands/local/run.ts +++ b/packages/cli/src/commands/local/run.ts @@ -105,7 +105,7 @@ function parseTimeout(value: string): number { if (!Number.isFinite(n) || n < 0) { throw new ValidationError( `Invalid timeout: ${value}. Must be a non-negative number.`, - "timeout" + "timeout", ); } return n; @@ -164,7 +164,7 @@ function attachToExistingServer( url: string, activeFilters: ReadonlySet<FilterValue>, useJson: boolean, - showAttributes: boolean + showAttributes: boolean, ): EventTail { const ac = new AbortController(); const tail = consumeSSE({ @@ -176,7 +176,7 @@ function attachToExistingServer( }).catch((err: unknown) => { if (!ac.signal.aborted) { logger.debug( - `Event tail stopped: ${err instanceof Error ? err.message : String(err)}` + `Event tail stopped: ${err instanceof Error ? err.message : String(err)}`, ); } }); @@ -220,7 +220,7 @@ async function startBackgroundServer({ } } catch (err) { logger.debug( - `Failed to format envelope: ${err instanceof Error ? err.message : String(err)}` + `Failed to format envelope: ${err instanceof Error ? err.message : String(err)}`, ); } }); @@ -280,7 +280,7 @@ async function openEventTail({ /** Augment PATH with `./node_modules/.bin` for Node project scripts. */ function augmentPathForNode( env: Record<string, string | undefined>, - cwd: string + cwd: string, ): Record<string, string | undefined> { const binDir = resolve(cwd, "node_modules", ".bin"); const sep = process.platform === "win32" ? ";" : ":"; @@ -306,13 +306,13 @@ const AUTO_DETECT_ERROR_MESSAGE = [ function buildChildEnv( spotlightUrl: string, commandSource: string, - cwd: string + cwd: string, ): Record<string, string | undefined> { const clientSpotlightVars = Object.fromEntries( CLIENT_SPOTLIGHT_PREFIXES.map((prefix) => [ `${prefix}SENTRY_SPOTLIGHT`, spotlightUrl, - ]) + ]), ); let env: Record<string, string | undefined> = { ...process.env, @@ -330,7 +330,7 @@ function buildChildEnv( /** Resolve args and source — auto-detect from filesystem when no args provided. */ async function resolveArgs( stripped: string[], - cwd: string + cwd: string, ): Promise<{ args: string[]; commandSource: string }> { if (stripped.length > 0) { return { args: stripped, commandSource: "" }; @@ -464,7 +464,7 @@ export const runCommand = buildCommand({ const wrangler = await injectWranglerSpotlightBinding( args, spotlightUrl, - this.cwd + this.cwd, ); if (wrangler.injected) { logger.info("Injected SENTRY_SPOTLIGHT as a Wrangler Worker binding"); @@ -487,7 +487,7 @@ export const runCommand = buildCommand({ await tail.cleanup(); throw new CliError( `Failed to start "${wrangler.args[0]}": ${err instanceof Error ? err.message : String(err)}`, - EXIT.GENERAL + EXIT.GENERAL, ); } @@ -582,18 +582,19 @@ async function gracefulKill(child: ChildProcess): Promise<void> { * for the first envelope, and race between envelope arrival, timeout, * and child exit. */ +// oxlint-disable-next-line require-yield -- The command contract requires an async generator even when it yields no output. async function* runWithVerify( args: string[], flags: RunFlags, cwd: string, - commandSource: string + commandSource: string, ): AsyncGenerator<never, void, unknown> { const buffer = createSpotlightBuffer(BUFFER_SIZE); const app = buildApp(buffer, { uiActions: isLoopbackHost(flags.host) }); const { server, port: boundPort } = await tryListen( app, flags.port, - flags.host + flags.host, ); const url = formatLocalServerUrl(flags.host, boundPort); logger.info(`Verify server listening on ${bold(url)}`); @@ -611,7 +612,7 @@ async function* runWithVerify( const wrangler = await injectWranglerSpotlightBinding( args, spotlightUrl, - cwd + cwd, ); if (wrangler.injected) { logger.info("Injected SENTRY_SPOTLIGHT as a Wrangler Worker binding"); @@ -631,7 +632,7 @@ async function* runWithVerify( await shutdownServer(server); throw new CliError( `Failed to start "${wrangler.args[0]}": ${err instanceof Error ? err.message : String(err)}`, - EXIT.GENERAL + EXIT.GENERAL, ); } @@ -649,7 +650,7 @@ async function* runWithVerify( const childExited = new Promise<{ kind: "exited"; code: number }>((r) => { child.on("close", (code) => - r({ kind: "exited" as const, code: code ?? 1 }) + r({ kind: "exited" as const, code: code ?? 1 }), ); }); @@ -671,9 +672,9 @@ async function* runWithVerify( new Promise<{ kind: "timeout" }>((r) => { timeoutHandle = setTimeout( () => r({ kind: "timeout" as const }), - verifyTimeout * 1000 + verifyTimeout * 1000, ); - }) + }), ); } @@ -715,11 +716,11 @@ async function* runWithVerify( } case "timeout": { logger.warn( - `Verification timed out after ${verifyTimeout}s — no events received from the SDK` + `Verification timed out after ${verifyTimeout}s — no events received from the SDK`, ); throw new CliError( `Verification timed out after ${verifyTimeout}s`, - EXIT.WIZARD_VERIFY + EXIT.WIZARD_VERIFY, ); } case "exited": { @@ -727,13 +728,13 @@ async function* runWithVerify( logger.warn("Process exited before sending any events"); throw new CliError( "Process exited before sending any events", - EXIT.WIZARD_VERIFY + EXIT.WIZARD_VERIFY, ); } logger.warn(`Process crashed with code ${outcome.code}`); throw new CliError( `Process crashed with code ${outcome.code}`, - outcome.code + outcome.code, ); } default: { diff --git a/packages/cli/src/commands/local/server.ts b/packages/cli/src/commands/local/server.ts index 4d5ba8b20..32f9bd7dd 100644 --- a/packages/cli/src/commands/local/server.ts +++ b/packages/cli/src/commands/local/server.ts @@ -76,7 +76,7 @@ export function parseFormat(value: string): FormatValue { if (!FORMAT_VALUES.includes(lower as FormatValue)) { throw new ValidationError( `Invalid format "${value}". Valid values: ${FORMAT_VALUES.join(", ")}`, - "format" + "format", ); } return lower as FormatValue; @@ -91,7 +91,7 @@ export function parseFilter(value: string): FilterValue { if (!FILTER_VALUES.includes(lower as FilterValue)) { throw new ValidationError( `Invalid filter "${value}". Valid values: ${FILTER_VALUES.join(", ")}`, - "filter" + "filter", ); } return lower as FilterValue; @@ -118,7 +118,7 @@ export function parsePort(value: string): number { if (!Number.isInteger(port) || port < 0 || port > 65_535) { throw new ValidationError( `Invalid port: ${value}. Must be an integer between 0 and 65535.`, - "port" + "port", ); } return port; @@ -205,7 +205,7 @@ function buildSSEHandler( data: string; }) => Promise<void>; }, - useBase64 = false + useBase64 = false, ) { const base64Indicator = useBase64 ? ";base64" : ""; return (container: { @@ -231,12 +231,12 @@ function buildSSEHandler( logger.debug( `SSE write failed (client likely disconnected): ${ err instanceof Error ? err.message : String(err) - }` + }`, ); }); } catch (err) { logger.debug( - `SSE serialize failed: ${err instanceof Error ? err.message : String(err)}` + `SSE serialize failed: ${err instanceof Error ? err.message : String(err)}`, ); } }; @@ -244,7 +244,7 @@ function buildSSEHandler( export function buildApp( spotlightBuffer: ReturnType<typeof createSpotlightBuffer>, - { uiActions = false }: LocalReceiverOptions = {} + { uiActions = false }: LocalReceiverOptions = {}, ): Hono { const app = new Hono(); @@ -301,7 +301,7 @@ export function buildApp( ...(isPrivateNetworkRequest ? ["Access-Control-Request-Private-Network"] : []), - ].join(", ") + ].join(", "), ); return c.body(null, 204); } @@ -320,7 +320,7 @@ export function buildApp( actions: { clear: true, envelope: true }, retention: "session", }) - : c.body(null, 403) + : c.body(null, 403), ); app.delete("/clear", (c) => { @@ -347,7 +347,7 @@ export function buildApp( }); } catch (err) { logger.debug( - `Envelope lookup failed: ${err instanceof Error ? err.message : String(err)}` + `Envelope lookup failed: ${err instanceof Error ? err.message : String(err)}`, ); return c.body(null, 404); } @@ -480,7 +480,7 @@ const PORT_RETRY_DELAY_MS = 5000; export function tryListen( app: Hono, port: number, - hostname: string + hostname: string, ): Promise<{ server: Server; port: number }> { let attempts = 0; @@ -505,13 +505,13 @@ export function tryListen( reject( new ValidationError( `Port ${port} is in use after ${MAX_PORT_RETRIES} retries`, - "port" - ) + "port", + ), ); return; } logger.warn( - `Port ${port} is in use, retrying in ${PORT_RETRY_DELAY_MS / 1000}s (attempt ${attempts}/${MAX_PORT_RETRIES})...` + `Port ${port} is in use, retrying in ${PORT_RETRY_DELAY_MS / 1000}s (attempt ${attempts}/${MAX_PORT_RETRIES})...`, ); await sleep(PORT_RETRY_DELAY_MS); resolve(attempt()); @@ -545,14 +545,14 @@ export async function isServerRunning(url: string): Promise<boolean> { }); if (!res.ok) { logger.debug( - `Server at ${url} answered /health with HTTP ${res.status}; treating the port as occupied` + `Server at ${url} answered /health with HTTP ${res.status}; treating the port as occupied`, ); } return true; } catch (err) { logger.debug( `No existing server at ${url}`, - err instanceof Error ? err.message : String(err) + err instanceof Error ? err.message : String(err), ); return false; } @@ -569,7 +569,7 @@ type SSEParserState = { export function feedSSELine( line: string, state: SSEParserState, - onEvent: (type: string, data: string, id: string) => void + onEvent: (type: string, data: string, id: string) => void, ): void { if (line.startsWith("event:")) { const value = line.slice(6); @@ -618,7 +618,7 @@ function isAbortError(err: unknown): boolean { /** Sleep with abort support, suppressing abort errors. */ async function sleepUnlessAborted( ms: number, - signal: AbortSignal + signal: AbortSignal, ): Promise<void> { try { await sleep(ms, undefined, { signal }); @@ -687,12 +687,12 @@ export async function consumeSSE(opts: ConsumeSSEOptions): Promise<void> { retries += 1; if (retries > SSE_MAX_RECONNECTS) { logger.warn( - `SSE connection lost after ${SSE_MAX_RECONNECTS} reconnection attempts` + `SSE connection lost after ${SSE_MAX_RECONNECTS} reconnection attempts`, ); return; } logger.info( - `SSE connection lost, reconnecting in ${retryDelay / 1000}s...` + `SSE connection lost, reconnecting in ${retryDelay / 1000}s...`, ); await sleepUnlessAborted(retryDelay, signal); retryDelay = Math.min(retryDelay * 2, SSE_MAX_RETRY_MS); @@ -706,7 +706,7 @@ export async function consumeSSE(opts: ConsumeSSEOptions): Promise<void> { * but the stream ended or errored — eligible for reconnection */ async function attemptSSEConnection( - opts: ConsumeSSEOnceOptions + opts: ConsumeSSEOnceOptions, ): Promise<"no-connection" | "connected-then-lost"> { let wasConnected = false; const augmented = { @@ -723,7 +723,7 @@ async function attemptSSEConnection( return "no-connection"; } logger.debug( - `SSE error: ${err instanceof Error ? err.message : String(err)}` + `SSE error: ${err instanceof Error ? err.message : String(err)}`, ); // If we got a 200 response before the error, the connection existed // and is worth retrying. Otherwise, the server is unreachable. @@ -820,7 +820,7 @@ function processSSEEvent( data: string, activeFilters: ReadonlySet<FilterValue>, useJson = false, - showAttributes = false + showAttributes = false, ): void { try { const envelope = JSON.parse(data) as [ @@ -840,7 +840,7 @@ function processSSEEvent( payload, header, itemHeader.type ?? "envelope", - showAttributes + showAttributes, ); for (const line of lines) { printLocalEventLine(line, useJson); @@ -848,7 +848,7 @@ function processSSEEvent( } } catch (err) { logger.debug( - `Failed to parse SSE event: ${err instanceof Error ? err.message : String(err)}` + `Failed to parse SSE event: ${err instanceof Error ? err.message : String(err)}`, ); } } @@ -928,6 +928,7 @@ export const serverCommand = buildCommand({ }, }, auth: false, + // oxlint-disable-next-line require-yield -- The command contract requires an async generator even when it yields no output. async *func(this: SentryContext, flags: LocalFlags) { validateOpenHost(flags.open, flags.host); const activeFilters = new Set(flags.filter); @@ -977,13 +978,13 @@ export const serverCommand = buildCommand({ for (const line of formatFn( container, activeFilters, - flags.attributes + flags.attributes, )) { printLocalEventLine(line, useJson); } } catch (err) { logger.debug( - `Failed to format envelope: ${err instanceof Error ? err.message : String(err)}` + `Failed to format envelope: ${err instanceof Error ? err.message : String(err)}`, ); } }); @@ -994,7 +995,7 @@ export const serverCommand = buildCommand({ const { server, port: boundPort } = await tryListen( app, flags.port, - flags.host + flags.host, ); const listenUrl = formatLocalServerUrl(flags.host, boundPort); @@ -1003,10 +1004,10 @@ export const serverCommand = buildCommand({ logger.info(` Events: ${bold(`${listenUrl}/stream`)} (SSE)`); logger.info(""); logger.info( - ` Set ${bold("SENTRY_SPOTLIGHT")}=${listenUrl}/stream in your app` + ` Set ${bold("SENTRY_SPOTLIGHT")}=${listenUrl}/stream in your app`, ); logger.info( - ` Or run: ${bold(`sentry local run -p ${boundPort} -- <your-command>`)}` + ` Or run: ${bold(`sentry local run -p ${boundPort} -- <your-command>`)}`, ); if (activeFilters.size > 0) { logger.info(` Filtering: ${[...activeFilters].join(", ")}`); diff --git a/packages/cli/src/commands/local/ui.ts b/packages/cli/src/commands/local/ui.ts index dc36b5817..80bc7f4a6 100644 --- a/packages/cli/src/commands/local/ui.ts +++ b/packages/cli/src/commands/local/ui.ts @@ -20,7 +20,7 @@ export function assertLoopbackHostForUi(host: string): void { if (!isLoopbackHost(host)) { throw new ValidationError( "--open requires a loopback --host (localhost, 127.0.0.1, or ::1).", - "host" + "host", ); } } @@ -41,7 +41,7 @@ export function formatLocalServerUrl(host: string, port: number): string { /** Pick the independently served local UI URL for this CLI build. */ export function getLocalUiBaseUrl( - environment: string = getCliEnvironment() + environment: string = getCliEnvironment(), ): string { return environment === "development" ? DEVELOPMENT_UI_URL : PRODUCTION_UI_URL; } @@ -76,7 +76,7 @@ export async function openLocalUi(receiverUrl: string): Promise<void> { /** Launch the UI only when the caller explicitly requested it. */ export async function openLocalUiIfRequested( requested: boolean, - receiverUrl: string + receiverUrl: string, ): Promise<void> { if (requested) { await openLocalUi(receiverUrl); diff --git a/packages/cli/src/commands/log/list.ts b/packages/cli/src/commands/log/list.ts index 59a28c603..4b9fa4a65 100644 --- a/packages/cli/src/commands/log/list.ts +++ b/packages/cli/src/commands/log/list.ts @@ -6,7 +6,7 @@ * Supports trace ID as a positional argument to filter logs by trace. */ -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import type { SentryContext } from "../../context.js"; import { @@ -161,7 +161,7 @@ type FetchResult = { * trace-mode usage hint for log list. */ function parseLogListArgs( - args: string[] + args: string[], ): ReturnType<typeof parseDualModeArgs> { return parseDualModeArgs(args, TRACE_USAGE_HINT); } @@ -176,7 +176,7 @@ async function executeSingleFetch( org: string, project: string, flags: ListFlags, - options: { timeRange: TimeRange; projectId?: number } + options: { timeRange: TimeRange; projectId?: number }, ): Promise<FetchResult> { const { timeRange, projectId } = options; const logs = await listLogs(org, project, { @@ -305,15 +305,15 @@ function renderLogRows( logs: LogLike[], includeTrace: boolean, table?: StreamingTable, - extraFields?: string[] + extraFields?: string[], ): string { let text = ""; for (const log of logs) { if (table) { text += table.row( buildLogRowCells(log, true, includeTrace, extraFields).map( - renderInlineMarkdown - ) + renderInlineMarkdown, + ), ); } else { text += formatLogRow(log, includeTrace, extraFields); @@ -331,7 +331,7 @@ function renderLogRows( */ async function fetchPoll<T extends LogLike>( config: FollowGeneratorConfig<T>, - lastTimestamp: number + lastTimestamp: number, ): Promise<T[] | undefined> { try { const rawLogs = await config.fetch("10m", lastTimestamp); @@ -367,7 +367,7 @@ async function fetchPoll<T extends LogLike>( * @throws {AuthError} if the API returns an authentication error */ async function* generateFollowLogs<T extends LogLike>( - config: FollowGeneratorConfig<T> + config: FollowGeneratorConfig<T>, ): AsyncGenerator<T[], void, undefined> { const { flags } = config; const pollInterval = flags.follow ?? DEFAULT_POLL_INTERVAL; @@ -423,7 +423,7 @@ async function* generateFollowLogs<T extends LogLike>( */ async function* yieldFollowItems<T extends LogLike>( generator: AsyncGenerator<T[], void, undefined>, - extraFields?: string[] + extraFields?: string[], ): AsyncGenerator<CommandOutput<LogOutput>, void, undefined> { let contextSent = !extraFields?.length; for await (const batch of generator) { @@ -451,7 +451,7 @@ async function* yieldFollowItems<T extends LogLike>( */ async function* yieldTraceFollowItems<T extends LogLike>( generator: AsyncGenerator<T[], void, undefined>, - traceId: string + traceId: string, ): AsyncGenerator<CommandOutput<LogOutput>, void, undefined> { let contextSent = false; for await (const batch of generator) { @@ -492,7 +492,7 @@ type TraceFetchOptions = { async function executeTraceSingleFetch( org: string, traceId: string, - options: TraceFetchOptions + options: TraceFetchOptions, ): Promise<FetchResult> { const { flags, timeRange, projectFilter } = options; const query = buildProjectQuery(flags.query, projectFilter); @@ -545,7 +545,7 @@ async function executeTraceSingleFetch( function writeFollowBanner( pollInterval: number, bannerText: string, - json: boolean + json: boolean, ): void { if (json) { return; @@ -593,7 +593,7 @@ type LogRendererState = { function initFirstRender( state: LogRendererState, data: LogOutput, - plain: boolean + plain: boolean, ): void { if (isLogListResult(data)) { if (data.traceId) { @@ -632,7 +632,7 @@ function createLogRenderer(): HumanRenderer<LogOutput> { logs, state.includeTrace, state.table, - state.extraFields + state.extraFields, ); return text.trimEnd(); } @@ -641,7 +641,7 @@ function createLogRenderer(): HumanRenderer<LogOutput> { logs, state.includeTrace, state.table, - state.extraFields + state.extraFields, ).trimEnd(); }, @@ -691,7 +691,7 @@ function validateFollowFlags(flags: ListFlags): void { if (flags.follow && flags.sort === "oldest") { throw new ValidationError( '--sort "oldest" cannot be used with --follow. Follow mode streams new logs as they arrive.', - "sort" + "sort", ); } } @@ -795,7 +795,7 @@ export const listCommand = buildListCommand( throw new ValidationError( "--follow cannot be used with an absolute date range. " + "Use a relative duration (e.g., --period 1h) or omit --period.", - "period" + "period", ); } @@ -805,14 +805,14 @@ export const listCommand = buildListCommand( const { traceId, org } = await resolveTraceOrg( parsed.parsed, cwd, - TRACE_USAGE_HINT + TRACE_USAGE_HINT, ); // Warn if --fields was passed — the trace-logs endpoint has a fixed // field set and doesn't support arbitrary extra fields. if (flags.fields?.length) { logger.warn( - "--fields is not supported for trace-scoped log queries. Use project-scoped mode instead." + "--fields is not supported for trace-scoped log queries. Use project-scoped mode instead.", ); } @@ -828,7 +828,7 @@ export const listCommand = buildListCommand( writeFollowBanner( flags.follow ?? DEFAULT_POLL_INTERVAL, `Streaming logs for trace ${traceId}...`, - flags.json + flags.json, ); // Track IDs of logs seen without timestamp_precise so they are @@ -887,7 +887,7 @@ export const listCommand = buildListCommand( flags, timeRange, projectFilter, - }) + }), ); yield new CommandOutput(result); return { hint }; @@ -898,7 +898,7 @@ export const listCommand = buildListCommand( const { org, project } = await resolveProjectBoundFromArg( parsed.target, cwd, - COMMAND_NAME + COMMAND_NAME, ); // Resolve the slug to a numeric project ID so the Events query scopes // via the `project` param. The `project:<slug>` filter only matches @@ -908,7 +908,7 @@ export const listCommand = buildListCommand( writeFollowBanner( flags.follow ?? DEFAULT_POLL_INTERVAL, "Streaming logs...", - flags.json + flags.json, ); const generator = generateFollowLogs({ @@ -942,7 +942,7 @@ export const listCommand = buildListCommand( json: flags.json, }, () => - executeSingleFetch(org, project, flags, { timeRange, projectId }) + executeSingleFetch(org, project, flags, { timeRange, projectId }), ); yield new CommandOutput(result); return { hint }; @@ -952,5 +952,5 @@ export const listCommand = buildListCommand( { noCursorFlag: true, noFreshAlias: true, - } + }, ); diff --git a/packages/cli/src/commands/log/view.ts b/packages/cli/src/commands/log/view.ts index ac232be53..8cb59dbc2 100644 --- a/packages/cli/src/commands/log/view.ts +++ b/packages/cli/src/commands/log/view.ts @@ -80,7 +80,7 @@ const USAGE_HINT = "sentry log view <org>/<project> <log-id> [<log-id>...]"; */ function parseSingleSlashLogArg( beforeSlash: string, - afterSlash: string + afterSlash: string, ): { rawLogIds: string[]; targetArg: string | undefined } | null { // "org/LOG-ID" or "project/LOG-ID" → treat beforeSlash as target, afterSlash as log ID. if (afterSlash && HEX_ID_RE.test(normalizeHexId(afterSlash))) { @@ -112,7 +112,7 @@ function parseSingleSlashLogArg( * @returns Parsed raw log IDs and optional target arg * @throws {ContextError} If no arguments provided */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: positional arg parsing has many format branches by design +// positional arg parsing has many format branches by design export function parsePositionalArgs(args: string[]): { rawLogIds: string[]; targetArg: string | undefined; @@ -138,7 +138,7 @@ export function parsePositionalArgs(args: string[]): { if (slashIdx !== -1 && trimmedFirst.indexOf("/", slashIdx + 1) === -1) { const singleSlash = parseSingleSlashLogArg( trimmedFirst.slice(0, slashIdx), - trimmedFirst.slice(slashIdx + 1) + trimmedFirst.slice(slashIdx + 1), ); if (singleSlash) { if (singleSlash.rawLogIds.length === 0) { @@ -151,7 +151,7 @@ export function parsePositionalArgs(args: string[]): { const { id, targetArg } = parseSlashSeparatedArg( first, "Log ID", - USAGE_HINT + USAGE_HINT, ); const rawLogIds = splitNewlineArg(id); if (rawLogIds.length === 0) { @@ -179,7 +179,7 @@ export function parsePositionalArgs(args: string[]): { }; } - // biome-ignore lint/style/noNonNullAssertion: length >= 2 guarantees index 1 exists + // oxlint-disable-next-line typescript/no-non-null-assertion -- length >= 2 guarantees index 1 exists const second = args[1]!; // Detect swapped args: exactly two args where first has no "/" and second @@ -213,7 +213,7 @@ export function parsePositionalArgs(args: string[]): { */ async function validateAndRecoverLogId( rawId: string, - target: ResolvedLogTarget + target: ResolvedLogTarget, ): Promise<string> { try { return validateHexId(rawId, "log ID"); @@ -255,7 +255,7 @@ export type ResolvedLogTarget = { async function resolveTarget( parsed: ReturnType<typeof parseOrgProjectArg>, rawLogIds: string[], - cwd: string + cwd: string, ): Promise<ResolvedLogTarget | null> { switch (parsed.type) { case "explicit": @@ -267,7 +267,7 @@ async function resolveTarget( parsed.projectSlug, USAGE_HINT, `sentry log view <org>/${parsed.projectSlug} ${rawLogIds.join(" ")}`, - parsed.originalSlug + parsed.originalSlug, ); if ( isAllDigits(parsed.projectSlug) && @@ -275,7 +275,7 @@ async function resolveTarget( ) { log.info( `Tip: Resolved project ID ${parsed.projectSlug} to ${result.org}/${result.project}. ` + - "Use the slug form for faster lookups." + "Use the slug form for faster lookups.", ); } return result; @@ -290,7 +290,7 @@ async function resolveTarget( default: { const _exhaustiveCheck: never = parsed; throw new ValidationError( - `Invalid target specification: ${_exhaustiveCheck}` + `Invalid target specification: ${_exhaustiveCheck}`, ); } } @@ -321,7 +321,7 @@ function warnMissingIds(logIds: string[], logs: DetailedSentryLog[]): void { const missing = logIds.filter((id) => !foundIds.has(id)); if (missing.length > 0) { log.warn( - `${missing.length} of ${logIds.length} log(s) not found:\n${formatIdList(missing)}` + `${missing.length} of ${logIds.length} log(s) not found:\n${formatIdList(missing)}`, ); } } @@ -339,7 +339,7 @@ async function handleWebOpen(orgSlug: string, logIds: string[]): Promise<void> { if (!isatty(0)) { log.warn( `Refusing to open ${logIds.length} browser tabs in non-interactive mode. ` + - "Pass a single log ID or run interactively." + "Pass a single log ID or run interactively.", ); return; } @@ -392,7 +392,7 @@ function retentionSuffix(logId: string): string { function throwNotFoundError( logIds: string[], org: string, - project: string + project: string, ): never { // Generic fallback wording references `RETENTION_DAYS.log` so a single // edit in `retention.ts` keeps this message in sync with the @@ -416,7 +416,7 @@ function throwNotFoundError( `Log '${id}'`, `not found in ${org}/${project}`, `sentry log view ${org}/${project}/${id}`, - suggestions + suggestions, ); } @@ -438,7 +438,7 @@ function throwNotFoundError( `${idList.length} log(s)`, `not found in ${org}/${project}`, hint, - idList.map((id) => `ID: ${id}`) + idList.map((id) => `ID: ${id}`), ); } @@ -474,12 +474,12 @@ function formatLogViewHuman(data: LogViewData): string { } parts.push( formatLogDetails( - // biome-ignore lint/style/noNonNullAssertion: index is bounded by data.logs.length + // oxlint-disable-next-line typescript/no-non-null-assertion -- index is bounded by data.logs.length data.logs[i]!, data.orgSlug, data.details?.[i]?.attributes, - data.extraFields - ) + data.extraFields, + ), ); } return parts.join("\n"); @@ -551,7 +551,7 @@ export const viewCommand = buildCommand({ // (multi-match ResolutionError) exits via `Promise.all` with the // first-thrown error — same UX as the old sequential loop. const logIds = await Promise.all( - rawLogIds.map((raw) => validateAndRecoverLogId(raw, target)) + rawLogIds.map((raw) => validateAndRecoverLogId(raw, target)), ); if (flags.web) { @@ -591,7 +591,7 @@ export const viewCommand = buildCommand({ target.org, target.project, entry["sentry.item_id"], - entry.trace + entry.trace, ); } catch (error) { cmdLog.debug("Failed to fetch log item detail", error); diff --git a/packages/cli/src/commands/monitor/list.ts b/packages/cli/src/commands/monitor/list.ts index 36648a992..17d8cad70 100644 --- a/packages/cli/src/commands/monitor/list.ts +++ b/packages/cli/src/commands/monitor/list.ts @@ -94,5 +94,5 @@ const docs: OrgListCommandDocs = { export const listCommand = buildOrgListCommand( monitorListConfig, docs, - "monitor" + "monitor", ); diff --git a/packages/cli/src/commands/monitor/run.ts b/packages/cli/src/commands/monitor/run.ts index fb3f67a4f..502c8fc63 100644 --- a/packages/cli/src/commands/monitor/run.ts +++ b/packages/cli/src/commands/monitor/run.ts @@ -53,7 +53,7 @@ function parsePositiveInt(value: string): number { if (!Number.isInteger(num) || num < 1) { throw new ValidationError( `Invalid value: ${value}. Must be a positive integer.`, - "monitor-config" + "monitor-config", ); } return num; @@ -67,7 +67,7 @@ function parsePositiveInt(value: string): number { */ async function resolveCheckInDsn( flags: RunFlags, - cwd: string + cwd: string, ): Promise<string> { const dsn = await resolveIngestDsn(flags, cwd); if (dsn) { @@ -76,7 +76,7 @@ async function resolveCheckInDsn( throw new ConfigError( "No DSN found. Provide one via --dsn <dsn>, set the SENTRY_DSN environment variable, or run from a project where a DSN can be detected.", - USAGE_HINT + USAGE_HINT, ); } @@ -95,7 +95,7 @@ async function sendCheckInSafely( dsn: string, dsnComponents: ReturnType<typeof makeDsn>, checkIn: ReturnType<typeof buildCheckIn>, - phase: "in-progress" | "final" + phase: "in-progress" | "final", ): Promise<void> { try { const envelope = createCheckInEnvelope( @@ -103,13 +103,13 @@ async function sendCheckInSafely( undefined, undefined, undefined, - dsnComponents + dsnComponents, ); const body = serializeEnvelope(envelope); const send = sendEnvelopeRequest(dsn, body); // Prevent unhandled rejection if the timeout wins the race but the // fetch later rejects (Node 15+ terminates on unhandled rejections). - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. send.catch(() => { // Intentionally empty — prevents unhandled rejection if timeout wins. }); @@ -118,7 +118,7 @@ async function sendCheckInSafely( timer = setTimeout( reject, CHECKIN_SEND_TIMEOUT_MS, - new Error("Check-in send timed out") + new Error("Check-in send timed out"), ); }); try { @@ -128,7 +128,7 @@ async function sendCheckInSafely( } } catch (err) { log.warn( - `Failed to send ${phase} check-in: ${err instanceof Error ? err.message : String(err)}` + `Failed to send ${phase} check-in: ${err instanceof Error ? err.message : String(err)}`, ); log.debug("Continuing despite check-in failure..."); } @@ -245,6 +245,7 @@ The wrapped command receives the \`SENTRY_MONITOR_SLUG\` environment variable.`, s: "schedule", }, }, + // oxlint-disable-next-line require-yield -- The command contract requires an async generator even when it yields no output. async *func(this: SentryContext, flags: RunFlags, ...rawArgs: string[]) { const { cwd } = this; @@ -258,13 +259,13 @@ The wrapped command receives the \`SENTRY_MONITOR_SLUG\` environment variable.`, if (!monitorSlug) { throw new ValidationError( `No monitor slug provided. Usage: ${USAGE_HINT}`, - "monitor-slug" + "monitor-slug", ); } if (command.length === 0) { throw new ValidationError( `No command provided. Usage: ${USAGE_HINT}`, - "command" + "command", ); } @@ -297,7 +298,7 @@ The wrapped command receives the \`SENTRY_MONITOR_SLUG\` environment variable.`, environment, monitorConfig, }), - "in-progress" + "in-progress", ); const startedAt = Date.now(); @@ -372,7 +373,7 @@ The wrapped command receives the \`SENTRY_MONITOR_SLUG\` environment variable.`, environment, duration: durationSeconds, }), - "final" + "final", ); if (spawnError) { diff --git a/packages/cli/src/commands/org/list.ts b/packages/cli/src/commands/org/list.ts index f16e2dfff..5a6a43eb5 100644 --- a/packages/cli/src/commands/org/list.ts +++ b/packages/cli/src/commands/org/list.ts @@ -140,7 +140,7 @@ export const listCommand = buildCommand({ message: `Fetching organizations (up to ${flags.limit})...`, json: flags.json, }, - () => listOrganizationsUncached() + () => listOrganizationsUncached(), ); const limitedOrgs = orgs.slice(0, flags.limit); diff --git a/packages/cli/src/commands/platform/list.ts b/packages/cli/src/commands/platform/list.ts index d724de98b..eb8f35dcf 100644 --- a/packages/cli/src/commands/platform/list.ts +++ b/packages/cli/src/commands/platform/list.ts @@ -58,7 +58,7 @@ export const listCommand = buildCommand({ }, aliases: { q: "search" }, }, - // biome-ignore lint/suspicious/useAwait: Stricli requires AsyncGenerator but this is synchronous (in-memory data) + // Stricli requires AsyncGenerator but this is synchronous (in-memory data) async *func(this: SentryContext, flags: PlatformsFlags) { if (!flags.search) { return yield new CommandOutput<readonly string[]>(VALID_PLATFORMS); @@ -66,7 +66,7 @@ export const listCommand = buildCommand({ const term = flags.search.toLowerCase(); const matches = VALID_PLATFORMS.filter((p) => - p.toLowerCase().includes(term) + p.toLowerCase().includes(term), ); if (matches.length === 0) { // No matches genuinely means nothing found for this search — exit non-zero, diff --git a/packages/cli/src/commands/proguard/upload.ts b/packages/cli/src/commands/proguard/upload.ts index b3d93a303..97632cc2f 100644 --- a/packages/cli/src/commands/proguard/upload.ts +++ b/packages/cli/src/commands/proguard/upload.ts @@ -78,7 +78,7 @@ function validateUuidFormat(uuid: string): void { if (!UUID_RE.test(uuid)) { throw new ValidationError( `Invalid UUID format: '${uuid}'. Expected format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, - "uuid" + "uuid", ); } } @@ -99,25 +99,25 @@ async function readMappingFile(path: string): Promise<Buffer> { if (code === "ENOENT") { throw new ValidationError( `ProGuard mapping file '${path}' does not exist.`, - "path" + "path", ); } if (code === "EISDIR") { throw new ValidationError( `Path '${path}' is a directory, not a ProGuard mapping file.`, - "path" + "path", ); } const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `Cannot read ProGuard mapping file '${path}': ${msg}`, - "path" + "path", ); } if (content.length === 0) { throw new ValidationError( `ProGuard mapping file '${path}' is empty.`, - "path" + "path", ); } return content; @@ -134,7 +134,7 @@ function deduplicateMappings(mappings: ProguardMapping[]): ProguardMapping[] { const existing = seen.get(m.uuid); if (existing) { log.warn( - `Skipping '${m.path}': identical content as '${existing}' (UUID ${m.uuid})` + `Skipping '${m.path}': identical content as '${existing}' (UUID ${m.uuid})`, ); continue; } @@ -213,7 +213,7 @@ export const uploadCommand = buildCommand({ if (flags["require-one"]) { throw new ValidationError( "No mapping files provided (--require-one is set)", - "path" + "path", ); } throw new ContextError("Mapping file path(s)", USAGE_HINT, []); @@ -223,7 +223,7 @@ export const uploadCommand = buildCommand({ if (flags.uuid && paths.length > 1) { throw new ValidationError( "--uuid cannot be used with multiple files (each file needs a unique UUID)", - "uuid" + "uuid", ); } diff --git a/packages/cli/src/commands/proguard/uuid.ts b/packages/cli/src/commands/proguard/uuid.ts index b45dc3226..de9de27a8 100644 --- a/packages/cli/src/commands/proguard/uuid.ts +++ b/packages/cli/src/commands/proguard/uuid.ts @@ -56,13 +56,13 @@ export const uuidCommand = buildCommand({ async *func( this: SentryContext, _flags: Record<string, never>, - path: string + path: string, ) { if (!path?.trim()) { throw new ContextError( "Mapping file path", "sentry proguard uuid <path>", - [] + [], ); } @@ -74,19 +74,19 @@ export const uuidCommand = buildCommand({ if (code === "ENOENT") { throw new ValidationError( `ProGuard mapping file '${path}' does not exist.`, - "path" + "path", ); } if (code === "EISDIR") { throw new ValidationError( `Path '${path}' is a directory, not a ProGuard mapping file.`, - "path" + "path", ); } const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `Cannot read ProGuard mapping file '${path}': ${msg}`, - "path" + "path", ); } diff --git a/packages/cli/src/commands/project/create.ts b/packages/cli/src/commands/project/create.ts index 3faf44c3f..a16a6419a 100644 --- a/packages/cli/src/commands/project/create.ts +++ b/packages/cli/src/commands/project/create.ts @@ -88,7 +88,7 @@ function normalizePlatform(platform: string): string { } const corrected = platform.replace(/\./g, "-"); log.warn( - `Platform '${platform}' uses '.' instead of '-' — interpreting as '${corrected}'` + `Platform '${platform}' uses '.' instead of '-' — interpreting as '${corrected}'`, ); return corrected; } @@ -165,7 +165,7 @@ async function handleCreateProject404(opts: { throw new CliError( `Failed to create project '${name}' in ${orgSlug}.\n\n` + `Team '${teamSlug}' exists but the request was rejected. ` + - "You may lack permission to create projects in this team." + "You may lack permission to create projects in this team.", ); } @@ -174,12 +174,12 @@ async function handleCreateProject404(opts: { `Team '${teamSlug}'`, `not found in ${orgSlug}`, `sentry project create ${orgSlug}/${name}:${platform} --team <team-slug>`, - [`Available teams: ${teams.map((t) => t.slug).join(", ")}`] + [`Available teams: ${teams.map((t) => t.slug).join(", ")}`], ); } throw new CliError( `No teams found in ${orgSlug}.\n\n` + - "Create a team first, then try again." + "Create a team first, then try again.", ); } @@ -197,7 +197,7 @@ async function handleCreateProject404(opts: { [ "The organization or team may not exist, or you may lack access", `List teams: sentry team list ${orgSlug}/`, - ] + ], ); } @@ -214,7 +214,7 @@ async function resolveDryRunTeam( team?: string; detectedFrom?: string; autoCreateSlug: string; - } + }, ): Promise<ResolvedConcreteTeam> { try { return await resolveOrCreateTeam(orgSlug, { @@ -231,7 +231,7 @@ async function resolveDryRunTeam( throw error; } log.debug( - "403 on listTeams in dry-run — previewing org-scoped fallback outcome" + "403 on listTeams in dry-run — previewing org-scoped fallback outcome", ); return { slug: "team-<username>", source: "auto-created" }; } @@ -264,7 +264,7 @@ type CreateProjectOpts = CreateProjectBaseOpts & { * member project creation entirely. */ async function createProjectWithAutoTeamFallback( - opts: CreateProjectBaseOpts + opts: CreateProjectBaseOpts, ): Promise< CreatedProjectDetails & { teamSlug: string; @@ -290,7 +290,7 @@ async function createProjectWithAutoTeamFallback( "or ask them to create the project and add you to it.", 403, error.detail, - error.endpoint + error.endpoint, ); } return handleCreateApiError(error, opts); @@ -313,7 +313,7 @@ function projectExistsError(orgSlug: string, name: string): CliError { const slug = slugify(name); return new CliError( `A project named '${name}' already exists in ${orgSlug}.\n\n` + - `View it: sentry project view ${orgSlug}/${slug}` + `View it: sentry project view ${orgSlug}/${slug}`, ); } @@ -323,7 +323,7 @@ function projectExistsError(orgSlug: string, name: string): CliError { */ function handleCreateApiError( error: ApiError, - opts: CreateProjectBaseOpts + opts: CreateProjectBaseOpts, ): never { const { orgSlug, name, platform } = opts; if (error.status === 409) { @@ -340,7 +340,7 @@ function handleCreateApiError( `Failed to create project '${name}' in ${orgSlug} (HTTP ${error.status}).`, error.status, error.detail, - error.endpoint + error.endpoint, ); } @@ -349,7 +349,7 @@ function handleCreateApiError( * Wraps API errors with actionable messages instead of raw HTTP status codes. */ async function createProjectWithErrors( - opts: CreateProjectOpts + opts: CreateProjectOpts, ): Promise<CreatedProjectDetails> { const { orgSlug, teamSlug, name, platform } = opts; try { @@ -381,7 +381,7 @@ type ParsedProjectSpec = { */ function parseProjectName( rawName: string, - platform: string + platform: string, ): ParsedProjectSpec { if (rawName.trim() === "") { throw new ValidationError("Project name cannot be empty.", "name"); @@ -389,7 +389,7 @@ function parseProjectName( if (WHITESPACE_RE.test(rawName)) { throw new ValidationError( `Project name '${rawName}' cannot contain whitespace.`, - "name" + "name", ); } @@ -428,7 +428,7 @@ function parseProjectPlatform(rawName: string, rawPlatform: string): string { if (!isValidPlatform(platform)) { throw new ValidationError( buildPlatformError(rawName, platform), - "platform" + "platform", ); } return platform; @@ -445,14 +445,14 @@ function parsePairedProjectSpec(rawSpec: string): ParsedProjectSpec { if (separatorIndex === -1) { throw new ValidationError( `Project '${rawSpec}' must use <name>:<platform> syntax.`, - "project" + "project", ); } const rawName = rawSpec.slice(0, separatorIndex); const platform = parseProjectPlatform( rawName, - rawSpec.slice(separatorIndex + 1) + rawSpec.slice(separatorIndex + 1), ); return parseProjectName(rawName, platform); } @@ -473,19 +473,19 @@ function parseProjectSpecs(rawSpecs: readonly string[]): { if (rawSpecs.length === 1 && !rawSpecs[0]?.includes(":")) { throw new ValidationError( buildPlatformError(rawSpecs[0] ?? ""), - "platform" + "platform", ); } const parsed = rawSpecs.map(parsePairedProjectSpec); const orgs = new Set( - parsed.map((p) => p.org).filter((o): o is string => Boolean(o)) + parsed.map((p) => p.org).filter((o): o is string => Boolean(o)), ); if (orgs.size > 1) { throw new ValidationError( `Cannot create projects across multiple organizations (${[...orgs].join(", ")}).\n\n` + "All names must belong to the same org.", - "organization" + "organization", ); } const [explicitOrg] = orgs; @@ -498,7 +498,7 @@ function parseProjectSpecs(rawSpecs: readonly string[]): { */ function buildProjectCreateOutput( results: ProjectCreatedResult[], - requestedCount: number + requestedCount: number, ): ProjectCreateOutput { const [singleResult] = results; return requestedCount === 1 && singleResult ? singleResult : results; @@ -690,13 +690,13 @@ export const createCommand = buildCommand({ flags, detectedFrom: resolved.detectedFrom, teamAutoCreateSlug, - }) + }), ); } } catch (error) { if (results.length > 0) { yield new CommandOutput( - buildProjectCreateOutput(results, parsed.length) + buildProjectCreateOutput(results, parsed.length), ); } throw error; diff --git a/packages/cli/src/commands/project/delete.ts b/packages/cli/src/commands/project/delete.ts index 62323260c..0af682ecb 100644 --- a/packages/cli/src/commands/project/delete.ts +++ b/packages/cli/src/commands/project/delete.ts @@ -60,7 +60,7 @@ const COMMAND_NAME = "project delete"; */ async function buildPermissionError( orgSlug: string, - projectSlug: string + projectSlug: string, ): Promise<ApiError> { const label = `'${orgSlug}/${projectSlug}'`; const rolesWithAccess = "Manager, Owner, or Admin"; @@ -70,7 +70,7 @@ async function buildPermissionError( // org listing has already been fetched during this session. let orgRole = getCachedOrgRole(orgSlug); if (!orgRole) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const org = await getOrganization(orgSlug); orgRole = (org as Record<string, unknown>).orgRole as string | undefined; @@ -85,7 +85,7 @@ async function buildPermissionError( `Your organization role is '${orgRole}'. ` + `Project deletion requires a ${rolesWithAccess} role.\n` + " Contact an org admin to change your role or delete the project for you.", - 403 + 403, ); } @@ -94,7 +94,7 @@ async function buildPermissionError( `Permission denied: cannot delete ${label}.\n\n` + `Your org role ('${orgRole}') should have permission. ` + "If using a custom auth token, ensure it includes the 'project:admin' scope.", - 403 + 403, ); } @@ -102,7 +102,7 @@ async function buildPermissionError( `Permission denied: cannot delete ${label}.\n\n` + `This requires a ${rolesWithAccess} role, or a token with the 'project:admin' scope.\n` + ` Check your role: sentry org view ${orgSlug}`, - 403 + 403, ); } @@ -110,7 +110,7 @@ async function buildPermissionError( function buildResult( orgSlug: string, project: { slug: string; name: string }, - dryRun?: boolean + dryRun?: boolean, ): ProjectDeleteResult { return { orgSlug, @@ -181,7 +181,7 @@ export const deleteCommand = buildDeleteCommand({ requireExplicitTarget( parsed, "Project target", - `sentry ${COMMAND_NAME} <org>/<project>` + `sentry ${COMMAND_NAME} <org>/<project>`, ); const resolved = await resolveProjectBoundTarget(parsed, cwd, COMMAND_NAME); @@ -203,7 +203,7 @@ export const deleteCommand = buildDeleteCommand({ const expected = `${orgSlug}/${project.slug}`; const confirmed = await confirmByTyping( expected, - `Type '${expected}' to permanently delete project '${project.name}':` + `Type '${expected}' to permanently delete project '${project.name}':`, ); if (!confirmed) { log.info("Cancelled."); diff --git a/packages/cli/src/commands/project/list.ts b/packages/cli/src/commands/project/list.ts index 586c3eba7..95dc5f681 100644 --- a/packages/cli/src/commands/project/list.ts +++ b/packages/cli/src/commands/project/list.ts @@ -93,7 +93,7 @@ type ProjectWithOrg = SentryProject & { orgSlug?: string }; * @returns Projects with org context attached */ export async function fetchOrgProjects( - orgSlug: string + orgSlug: string, ): Promise<ProjectWithOrg[]> { const projects = await listProjects(orgSlug); return projects.map((p) => ({ ...p, orgSlug })); @@ -104,7 +104,7 @@ export async function fetchOrgProjects( * Auth errors propagate so user sees "please log in" message. */ export async function fetchOrgProjectsSafe( - orgSlug: string + orgSlug: string, ): Promise<ProjectWithOrg[]> { const result = await withAuthGuard(() => fetchOrgProjects(orgSlug)); return result.ok ? result.value : []; @@ -122,7 +122,7 @@ export async function fetchAllOrgProjects(): Promise<ProjectWithOrg[]> { for (const org of orgs) { const projectsResult = await withAuthGuard(() => - fetchOrgProjects(org.slug) + fetchOrgProjects(org.slug), ); if (projectsResult.ok) { results.push(...projectsResult.value); @@ -141,14 +141,14 @@ export async function fetchAllOrgProjects(): Promise<ProjectWithOrg[]> { */ export function filterByPlatform( projects: ProjectWithOrg[], - platform?: string + platform?: string, ): ProjectWithOrg[] { if (!platform) { return projects; } const lowerPlatform = platform.toLowerCase(); return projects.filter((p) => - p.platform?.toLowerCase().includes(lowerPlatform) + p.platform?.toLowerCase().includes(lowerPlatform), ); } @@ -165,7 +165,7 @@ export function filterByPlatform( export function buildContextKey( parsed: ParsedOrgProject, flags: { platform?: string }, - host: string + host: string, ): string { const parts: string[] = [`host:${host}`]; switch (parsed.type) { @@ -189,7 +189,7 @@ export function buildContextKey( if (flags.platform) { // Normalize to lowercase since platform filtering is case-insensitive. parts.push( - `platform:${escapeContextKeyValue(flags.platform.toLowerCase())}` + `platform:${escapeContextKeyValue(flags.platform.toLowerCase())}`, ); } return parts.join("|"); @@ -251,7 +251,7 @@ type PaginatedResult = { projects: ProjectWithOrg[]; nextCursor?: string }; async function fetchPaginatedSafe( org: string, - limit: number + limit: number, ): Promise<PaginatedResult> { const result = await withAuthGuard(async () => { const response = await listProjectsAllPages(org, { limit }); @@ -273,7 +273,7 @@ async function fetchPaginatedSafe( */ async function fetchAutoDetectProjects( orgs: string[], - flags: ListFlags + flags: ListFlags, ): Promise<{ projects: ProjectWithOrg[]; nextCursor?: string }> { if (orgs.length === 1 && !flags.platform) { return fetchPaginatedSafe(orgs[0] as string, flags.limit); @@ -297,7 +297,7 @@ function autoDetectHeader( count: number, hasMore: boolean, nextCursor: string | undefined, - orgs: string[] + orgs: string[], ): string | undefined { if (!hasMore) { return; @@ -314,7 +314,7 @@ function autoDetectHeader( /** Build self-hosted DSN warning text, or undefined if none skipped. */ function selfHostedWarning( - skippedSelfHosted: number | undefined + skippedSelfHosted: number | undefined, ): string | undefined { if (!skippedSelfHosted) { return; @@ -331,7 +331,7 @@ function selfHostedWarning( */ export async function handleAutoDetect( cwd: string, - flags: ListFlags + flags: ListFlags, ): Promise<ListResult<ProjectWithOrg>> { const { orgs: orgsToFetch, @@ -344,7 +344,7 @@ export async function handleAutoDetect( message: `Fetching projects (up to ${flags.limit})...`, json: flags.json, }, - () => fetchAutoDetectProjects(orgsToFetch, flags) + () => fetchAutoDetectProjects(orgsToFetch, flags), ); const filtered = filterByPlatform(allProjects, flags.platform); @@ -357,7 +357,7 @@ export async function handleAutoDetect( limited.length, hasMore, nextCursor, - orgsToFetch + orgsToFetch, ); const hintParts: string[] = []; @@ -370,7 +370,7 @@ export async function handleAutoDetect( hintParts.push(footer); } hintParts.push( - "Tip: Use 'sentry project view <org>/<project>' for details" + "Tip: Use 'sentry project view <org>/<project>' for details", ); } @@ -397,11 +397,11 @@ export async function handleAutoDetect( export async function handleExplicit( org: string, projectSlug: string, - flags: ListFlags + flags: ListFlags, ): Promise<ListResult<ProjectWithOrg>> { const projectResult = await withProgress( { message: "Fetching project...", json: flags.json }, - () => withAuthGuard(() => getProject(org, projectSlug)) + () => withAuthGuard(() => getProject(org, projectSlug)), ); if (!projectResult.ok) { return { @@ -443,7 +443,7 @@ export type OrgAllOptions = { * {@link listProjectsAllPages} instead of sending an oversized `per_page`. */ export async function handleOrgAll( - options: OrgAllOptions + options: OrgAllOptions, ): Promise<ListResult<ProjectWithOrg>> { const { org, flags, contextKey, cursor, direction } = options; const response: PaginatedResponse<SentryProject[]> = await withProgress( @@ -451,7 +451,7 @@ export async function handleOrgAll( message: `Fetching projects (up to ${flags.limit})...`, json: flags.json, }, - () => listProjectsAllPages(org, { limit: flags.limit, cursor }) + () => listProjectsAllPages(org, { limit: flags.limit, cursor }), ); const projects: ProjectWithOrg[] = response.data.map((p) => ({ @@ -468,7 +468,7 @@ export async function handleOrgAll( PAGINATION_KEY, contextKey, direction, - response.nextCursor ?? undefined + response.nextCursor ?? undefined, ); const hasPrev = hasPreviousPage(PAGINATION_KEY, contextKey); @@ -531,7 +531,7 @@ export async function handleProjectSearch( scopedOrg?: string; /** Classification supplied by the dispatcher after its target pre-check. */ projectSearchResolution?: ProjectSearchTargetResolution; - } + }, ): Promise<ListResult<ProjectWithOrg>> { const { originalSlug, scopedOrg, projectSearchResolution } = options ?? {}; const parsed = { @@ -547,14 +547,14 @@ export async function handleProjectSearch( message: `Fetching projects (up to ${flags.limit})...`, json: flags.json, }, - () => classifyProjectSearchTarget(parsed) + () => classifyProjectSearchTarget(parsed), )); if (resolution.kind === "organization") { const contextKey = buildContextKey( { type: "org-all", org: resolution.org }, flags, - getApiBaseUrl() + getApiBaseUrl(), ); const result = await handleOrgAll({ org: resolution.org, @@ -606,7 +606,7 @@ export async function handleProjectSearch( function handleProjectSearchNotFound( resolution: Extract<ProjectSearchTargetResolution, { kind: "not-found" }>, projectSlug: string, - json: boolean + json: boolean, ): ListResult<ProjectWithOrg> { if (json) { return { items: [] }; @@ -615,7 +615,7 @@ function handleProjectSearchNotFound( `Project '${resolution.displaySlug}'`, "not found", `sentry project list <org>/${projectSlug}`, - projectSearchNotFoundSuggestions(resolution) + projectSearchNotFoundSuggestions(resolution), ); } @@ -698,12 +698,12 @@ export const listCommand = buildListCommand("project", { const contextKey = buildContextKey( ctx.parsed, flags, - getApiBaseUrl() + getApiBaseUrl(), ); const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); return handleOrgAll({ org: ctx.parsed.org, diff --git a/packages/cli/src/commands/project/view.ts b/packages/cli/src/commands/project/view.ts index 4c4b86d40..db99b0b08 100644 --- a/packages/cli/src/commands/project/view.ts +++ b/packages/cli/src/commands/project/view.ts @@ -54,7 +54,7 @@ function buildContextError(skippedSelfHosted?: number): ContextError { "Organization and project", USAGE_HINT, undefined, - `Found ${skippedSelfHosted} DSN(s) that could not be resolved — you may not have access to these projects` + `Found ${skippedSelfHosted} DSN(s) that could not be resolved — you may not have access to these projects`, ); } @@ -75,7 +75,7 @@ async function handleWebView(resolvedTargets: ResolvedTarget[]): Promise<void> { const target = resolvedTargets[0]; await openInBrowser( target ? buildProjectUrl(target.org, target.project) : undefined, - "project" + "project", ); } @@ -104,7 +104,7 @@ type ProjectWithDsn = { * (auto-detect) or surface (explicit/search) them. */ async function fetchProjectAndDsn( - target: ResolvedTarget + target: ResolvedTarget, ): Promise<ProjectWithDsn> { const result = await withAuthGuard(async () => { const [project, dsn] = await Promise.all([ @@ -126,7 +126,7 @@ async function fetchProjectAndDsn( * `AuthError` still propagates for the auto-login middleware. */ async function fetchProjectDetails( - target: ResolvedTarget + target: ResolvedTarget, ): Promise<ProjectWithDsn | null> { try { return await fetchProjectAndDsn(target); @@ -146,7 +146,7 @@ async function fetchProjectDetails( * generic "Could not auto-detect" fallback (getsentry/cli#785 #8). */ function fetchProjectDetailsOrThrow( - target: ResolvedTarget + target: ResolvedTarget, ): Promise<ProjectWithDsn> { return fetchProjectAndDsn(target); } @@ -163,7 +163,7 @@ type FetchResult = { * out failures while preserving target association. */ async function fetchAllProjectDetails( - targets: ResolvedTarget[] + targets: ResolvedTarget[], ): Promise<FetchResult> { const results = await Promise.all(targets.map(fetchProjectDetails)); @@ -215,7 +215,7 @@ function hydrateOrganizationName(entry: ProjectViewEntry): ProjectViewEntry { */ function jsonTransformProjectView( entries: ProjectViewEntry[], - fields?: string[] + fields?: string[], ): unknown { const hydrated = entries.map((entry) => { const { detectedFrom: _detectedFrom, ...rest } = @@ -322,7 +322,7 @@ export const viewCommand = buildCommand({ parsed.projectSlug, USAGE_HINT, `sentry project view <org>/${parsed.projectSlug}`, - parsed.originalSlug + parsed.originalSlug, ); resolvedTargets = [ { @@ -340,7 +340,7 @@ export const viewCommand = buildCommand({ throw new ContextError( "Specific project", `sentry project view ${parsed.org}/<project>`, - ["Specify the full org/project target, not just the organization"] + ["Specify the full org/project target, not just the organization"], ); case ProjectSpecificationType.AutoDetect: { diff --git a/packages/cli/src/commands/react-native/gradle.ts b/packages/cli/src/commands/react-native/gradle.ts index 559ea2542..c19c848cb 100644 --- a/packages/cli/src/commands/react-native/gradle.ts +++ b/packages/cli/src/commands/react-native/gradle.ts @@ -87,7 +87,7 @@ async function assertNotRamBundle(bundlePath: string): Promise<void> { if (isRamBundle) { throw new ValidationError( "Indexed RAM bundles are not supported. Use a plain or Hermes bundle.", - "bundle" + "bundle", ); } } @@ -96,7 +96,7 @@ async function assertNotRamBundle(bundlePath: string): Promise<void> { function buildArtifacts( bundlePath: string, sourcemapPath: string, - debugId: string + debugId: string, ): ArtifactFile[] { const bundleName = basename(bundlePath); const sourcemapName = basename(sourcemapPath); @@ -198,7 +198,7 @@ export const gradleCommand = buildCommand({ if (flags.release && dists.length > 0) { for (const dist of dists) { log.info( - `Uploading sourcemaps for release ${flags.release} distribution ${dist}` + `Uploading sourcemaps for release ${flags.release} distribution ${dist}`, ); await uploadSourcemaps({ org, diff --git a/packages/cli/src/commands/react-native/xcode.ts b/packages/cli/src/commands/react-native/xcode.ts index 697b29627..05dd18bcd 100644 --- a/packages/cli/src/commands/react-native/xcode.ts +++ b/packages/cli/src/commands/react-native/xcode.ts @@ -108,7 +108,7 @@ function shouldWrap(flags: XcodeFlags, env: NodeJS.ProcessEnv): boolean { if (configuration === undefined) { throw new ValidationError( "Need to run this from Xcode (CONFIGURATION is not set).", - "CONFIGURATION" + "CONFIGURATION", ); } return !configuration.includes("Debug"); @@ -120,7 +120,7 @@ function resolveScript(flags: XcodeFlags, cwd: string): string { if (!existsSync(script)) { throw new ValidationError( `React Native build script not found: ${script}`, - "build-script" + "build-script", ); } return script; @@ -130,7 +130,7 @@ function resolveScript(flags: XcodeFlags, cwd: string): string { function runScript( script: string, args: string[], - env: NodeJS.ProcessEnv + env: NodeJS.ProcessEnv, ): number { const rv = spawnSync(script, args, { stdio: "inherit", env }); // A signal-killed child has `status === null`; treat that as a failure. @@ -140,7 +140,7 @@ function runScript( /** Poll a URL until it responds or the timeout elapses. */ async function waitUntilAvailable( url: string, - timeoutMs: number + timeoutMs: number, ): Promise<boolean> { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { @@ -160,24 +160,24 @@ async function waitUntilAvailable( /** Fetch the bundle + sourcemap from the running RN packager. */ async function fetchFromPackager( fetchUrl: string, - tempDir: string + tempDir: string, ): Promise<BundlePair> { const url = fetchUrl.replace(TRAILING_SLASHES, ""); if (!(await waitUntilAvailable(url, 10_000))) { throw new ValidationError( "React Native packager did not respond in time.", - "fetch-from" + "fetch-from", ); } const bundle = join(tempDir, "index.ios.bundle"); const sourcemap = join(tempDir, "index.ios.map"); const bundleRes = await fetch( - `${url}/index.ios.bundle?platform=ios&dev=true` + `${url}/index.ios.bundle?platform=ios&dev=true`, ); if (!bundleRes.ok) { throw new ValidationError( `Packager returned ${bundleRes.status} for the bundle.`, - "fetch-from" + "fetch-from", ); } writeFileSync(bundle, Buffer.from(await bundleRes.arrayBuffer())); @@ -185,7 +185,7 @@ async function fetchFromPackager( if (!mapRes.ok) { throw new ValidationError( `Packager returned ${mapRes.status} for the sourcemap.`, - "fetch-from" + "fetch-from", ); } writeFileSync(sourcemap, Buffer.from(await mapRes.arrayBuffer())); @@ -218,7 +218,7 @@ function runWrappedBuild( script: string, scriptArgs: string[], ctx: SentryContext, - tempDir: string + tempDir: string, ): { status: number; pair: BundlePair | null } { const reportPath = join(tempDir, "sourcemap-report.json"); writeFileSync(reportPath, "{}"); @@ -273,7 +273,7 @@ function runWrappedBuild( /** Read the debug id already present in a sourcemap (from the Metro plugin). */ async function readSourcemapDebugId( - mapPath: string + mapPath: string, ): Promise<string | undefined> { try { const map = JSON.parse(await readFile(mapPath, "utf-8")) as { @@ -291,7 +291,7 @@ async function readSourcemapDebugId( async function uploadPair( pair: BundlePair, ctx: SentryContext, - flags: XcodeFlags + flags: XcodeFlags, ): Promise<{ debugId?: string; release?: string; @@ -315,7 +315,7 @@ async function uploadPair( if (!debugId) { log.warn( "No debug id found in the sourcemap; uploading without one. Ensure the " + - "Sentry React Native Metro plugin is configured." + "Sentry React Native Metro plugin is configured.", ); } const debugIdField = debugId ? { debugId } : {}; @@ -339,7 +339,7 @@ async function uploadPair( ctx.env, ctx.cwd, flags["no-auto-release"] ?? false, - flags["allow-xcode-infoplist-preprocessing"] ?? false + flags["allow-xcode-infoplist-preprocessing"] ?? false, ); // Explicit --dist overrides the environment/plist-derived distribution. let dists: string[] = []; @@ -386,7 +386,7 @@ async function preparePair( ctx: SentryContext, script: string, scriptArgs: string[], - fetchUrl: string | undefined + fetchUrl: string | undefined, ): Promise<PrepareResult> { const tempDir = mkdtempSync(join(tmpdir(), "sentry-rn-xcode-")); if (fetchUrl) { @@ -535,7 +535,7 @@ export const xcodeCommand = buildCommand({ const { debugId, release, dist, uploads } = await uploadPair( pair, this, - flags + flags, ); yield new CommandOutput<XcodeResult>({ diff --git a/packages/cli/src/commands/release/archive.ts b/packages/cli/src/commands/release/archive.ts index a3ba3e771..af5cc5d48 100644 --- a/packages/cli/src/commands/release/archive.ts +++ b/packages/cli/src/commands/release/archive.ts @@ -31,7 +31,7 @@ const USAGE_HINT = "sentry release archive [<org>/]<version>"; function formatReleaseArchived(data: Record<string, unknown>): string { if (data.dryRun) { return renderMarkdown( - `Would archive release ${safeCodeSpan(String(data.version))} (dry run)` + `Would archive release ${safeCodeSpan(String(data.version))} (dry run)`, ); } const release = data as unknown as SentryRelease; @@ -83,14 +83,14 @@ export const archiveCommand = buildCommand({ readonly json: boolean; readonly fields?: string[]; }, - target: string + target: string, ) { const { cwd } = this; const { version, org, detectedFrom } = await resolveReleaseTarget( target, USAGE_HINT, - cwd + cwd, ); if (flags["dry-run"]) { diff --git a/packages/cli/src/commands/release/create.ts b/packages/cli/src/commands/release/create.ts index 3b5de88b4..f98874576 100644 --- a/packages/cli/src/commands/release/create.ts +++ b/packages/cli/src/commands/release/create.ts @@ -68,7 +68,7 @@ export const createCommand = buildCommand({ ? ` in projects: ${projects.join(", ")}` : "") + (data.finalize ? " (finalized)" : "") + - " (dry run)" + " (dry run)", ); } return formatReleaseCreated(data as SentryRelease); @@ -124,14 +124,14 @@ export const createCommand = buildCommand({ readonly json: boolean; readonly fields?: string[]; }, - target: string + target: string, ) { const { cwd } = this; const { version, org } = await resolveReleaseTarget( target, USAGE_HINT, - cwd + cwd, ); const body: Parameters<typeof createRelease>[1] = { version }; diff --git a/packages/cli/src/commands/release/delete.ts b/packages/cli/src/commands/release/delete.ts index bbfb19f97..e61b003a3 100644 --- a/packages/cli/src/commands/release/delete.ts +++ b/packages/cli/src/commands/release/delete.ts @@ -32,14 +32,14 @@ type DeleteResult = { function formatReleaseDeleted(result: DeleteResult): string { if (result.dryRun) { return renderMarkdown( - `Would delete release ${safeCodeSpan(result.version)} from **${result.org}**. (dry run)` + `Would delete release ${safeCodeSpan(result.version)} from **${result.org}**. (dry run)`, ); } if (!result.deleted) { return "Cancelled."; } return renderMarkdown( - `Release ${safeCodeSpan(result.version)} deleted from **${result.org}**.` + `Release ${safeCodeSpan(result.version)} deleted from **${result.org}**.`, ); } @@ -50,7 +50,7 @@ function formatReleaseDeleted(result: DeleteResult): string { function enrichDeleteError( error: unknown, orgSlug: string, - version: string + version: string, ): unknown { if ( error instanceof ApiError && @@ -64,7 +64,7 @@ function enrichDeleteError( "Releases with active session or crash-free data are protected by Sentry " + "and cannot be removed via the API. The health data must age out before " + `the release can be deleted.\n Release: ${url}`, - error.endpoint + error.endpoint, ); } return error; @@ -110,7 +110,7 @@ export const deleteCommand = buildDeleteCommand({ const { version, org } = await resolveReleaseTarget( target, USAGE_HINT, - cwd + cwd, ); // Verify the release exists before prompting for confirmation @@ -135,7 +135,7 @@ export const deleteCommand = buildDeleteCommand({ : ""; const confirmed = await confirmByTyping( version, - `Type '${version}' to permanently delete this release${deployInfo}:` + `Type '${version}' to permanently delete this release${deployInfo}:`, ); if (!confirmed) { yield new CommandOutput({ diff --git a/packages/cli/src/commands/release/deploy.ts b/packages/cli/src/commands/release/deploy.ts index 90c74abba..b1f16a115 100644 --- a/packages/cli/src/commands/release/deploy.ts +++ b/packages/cli/src/commands/release/deploy.ts @@ -27,7 +27,7 @@ const USAGE_HINT = function formatDeployCreated(data: Record<string, unknown>): string { if (data.dryRun) { return renderMarkdown( - `Would create deploy for ${safeCodeSpan(String(data.environment))} environment (dry run)` + `Would create deploy for ${safeCodeSpan(String(data.environment))} environment (dry run)`, ); } const deploy = data as unknown as SentryDeploy; @@ -66,7 +66,7 @@ function formatDeployCreated(data: Record<string, unknown>): string { function parseDeployArgs( target: string, environment: string, - name?: string + name?: string, ): { version: string; orgSlug?: string; @@ -150,7 +150,6 @@ export const deployCommand = buildCommand({ }, aliases: { ...DRY_RUN_ALIASES, t: "time" }, }, - // biome-ignore lint/nursery/useMaxParams: Stricli maps each `kind: "tuple"` positional to a named func param; deploy legitimately takes version + environment + optional name. async *func( this: SentryContext, flags: { @@ -164,14 +163,14 @@ export const deployCommand = buildCommand({ }, target: string, environmentArg: string, - nameArg?: string + nameArg?: string, ) { const { cwd } = this; const { version, orgSlug, environment, name } = parseDeployArgs( target, environmentArg, - nameArg + nameArg, ); const resolved = await resolveOrg({ org: orgSlug, cwd }); if (!resolved) { @@ -189,7 +188,7 @@ export const deployCommand = buildCommand({ throw new ValidationError( "--time cannot be used with --started or --finished. " + "Use either --time for duration-based timing, or --started/--finished for explicit timestamps.", - "time" + "time", ); } diff --git a/packages/cli/src/commands/release/deploys.ts b/packages/cli/src/commands/release/deploys.ts index ba21cc6e9..8158d5ecd 100644 --- a/packages/cli/src/commands/release/deploys.ts +++ b/packages/cli/src/commands/release/deploys.ts @@ -59,14 +59,14 @@ export const deploysCommand = buildCommand({ async *func( this: SentryContext, _flags: { readonly json: boolean; readonly fields?: string[] }, - target: string + target: string, ) { const { cwd } = this; const { version, org } = await resolveReleaseTarget( target, USAGE_HINT, - cwd + cwd, ); const deploys = await listReleaseDeploys(org, version); diff --git a/packages/cli/src/commands/release/finalize.ts b/packages/cli/src/commands/release/finalize.ts index 7ef2a3a26..1e3d5aafe 100644 --- a/packages/cli/src/commands/release/finalize.ts +++ b/packages/cli/src/commands/release/finalize.ts @@ -24,7 +24,7 @@ const USAGE_HINT = "sentry release finalize [<org>/]<version>"; function formatReleaseFinalized(data: Record<string, unknown>): string { if (data.dryRun) { return renderMarkdown( - `Would finalize release ${safeCodeSpan(String(data.version))} (dry run)` + `Would finalize release ${safeCodeSpan(String(data.version))} (dry run)`, ); } const release = data as unknown as SentryRelease; @@ -90,14 +90,14 @@ export const finalizeCommand = buildCommand({ readonly json: boolean; readonly fields?: string[]; }, - target: string + target: string, ) { const { cwd } = this; const { version, org, detectedFrom } = await resolveReleaseTarget( target, USAGE_HINT, - cwd + cwd, ); if (flags["dry-run"]) { diff --git a/packages/cli/src/commands/release/list.ts b/packages/cli/src/commands/release/list.ts index b63013ca9..76ea32f4a 100644 --- a/packages/cli/src/commands/release/list.ts +++ b/packages/cli/src/commands/release/list.ts @@ -88,7 +88,7 @@ function parseSortFlag(value: string): ReleaseSortValue { return alias; } const allAccepted = [...VALID_SORT_VALUES, ...Object.keys(SORT_ALIASES)].join( - ", " + ", ", ); throw new Error(`Invalid sort value. Must be one of: ${allAccepted}`); } @@ -116,7 +116,7 @@ function extractSessionPoints(stats?: Record<string, unknown>): number[] { return []; } return buckets.map((b: unknown) => - Array.isArray(b) && b.length >= 2 ? Number(b[1]) || 0 : 0 + Array.isArray(b) && b.length >= 2 ? Number(b[1]) || 0 : 0, ); } @@ -165,7 +165,7 @@ function formatSessionSparkline(r: SentryRelease): string { return ""; } const points = extractSessionPoints( - health.stats as Record<string, unknown> | undefined + health.stats as Record<string, unknown> | undefined, ); if (points.length === 0) { return ""; @@ -228,7 +228,7 @@ function buildColumns(multiProject: boolean): Column<ReleaseWithOrg>[] { header: "NEW ISSUES", value: (r) => String(r.newGroups ?? 0), align: "right", - } + }, ); return cols; } @@ -250,7 +250,7 @@ type ExtraApiOptions = Pick< >; function buildReleaseListConfig( - extra: ExtraApiOptions + extra: ExtraApiOptions, ): OrgListConfig<SentryRelease, ReleaseWithOrg> { return { paginationKey: PAGINATION_KEY, @@ -353,7 +353,7 @@ function deduplicateTargets(targets: ResolvedTarget[]): ResolvedTarget[] { */ async function resolveProjectIds( org: string, - project: string + project: string, ): Promise<number[]> { try { const { getProject } = await import("../../lib/api-client.js"); @@ -361,7 +361,7 @@ async function resolveProjectIds( const id = toNumericId(info.id); if (!id) { log.debug( - `Project ${org}/${project} returned non-numeric ID: ${info.id}` + `Project ${org}/${project} returned non-numeric ID: ${info.id}`, ); return []; } @@ -381,7 +381,7 @@ async function resolveProjectIds( async function fetchReleasesForTarget( t: ResolvedTarget, extra: ExtraApiOptions, - perPage: number + perPage: number, ): Promise<ReleaseWithOrg[]> { const ids = t.projectId ? [t.projectId] @@ -406,7 +406,7 @@ async function fetchReleasesForTarget( * comparators sort descending (highest/newest first). */ function buildMergeSorter( - sort: ReleaseSortValue + sort: ReleaseSortValue, ): (a: ReleaseWithOrg, b: ReleaseWithOrg) => number { return (a, b) => { const diff = getSortValue(b, sort) - getSortValue(a, sort); @@ -447,7 +447,7 @@ function getSortValue(r: ReleaseWithOrg, sort: ReleaseSortValue): number { function trimWithProjectGuarantee( items: ReleaseWithOrg[], limit: number, - comparator: (a: ReleaseWithOrg, b: ReleaseWithOrg) => number + comparator: (a: ReleaseWithOrg, b: ReleaseWithOrg) => number, ): ReleaseWithOrg[] { if (items.length <= limit) { return items; @@ -489,7 +489,7 @@ function trimWithProjectGuarantee( */ async function handleAutoDetectWithProject( extra: ExtraApiOptions, - ctx: HandlerContext<"auto-detect"> + ctx: HandlerContext<"auto-detect">, ): Promise<ListResult<ReleaseWithOrg>> { const { cwd, flags } = ctx; const resolved = await resolveAllTargets({ cwd }); @@ -509,13 +509,13 @@ async function handleAutoDetectWithProject( // Fetch from ALL targets in parallel, each scoped by project ID const perTarget = Math.min(Math.ceil(flags.limit / unique.length), 100); const results = await Promise.all( - unique.map((t) => fetchReleasesForTarget(t, effectiveExtra, perTarget)) + unique.map((t) => fetchReleasesForTarget(t, effectiveExtra, perTarget)), ); const merged = results.flat(); // Client-side sort matching the --sort flag, then trim with project guarantee const comparator = buildMergeSorter( - (effectiveExtra.sort as ReleaseSortValue) ?? "date" + (effectiveExtra.sort as ReleaseSortValue) ?? "date", ); merged.sort(comparator); const limited = isMultiProject @@ -533,7 +533,7 @@ async function handleAutoDetectWithProject( // Only show env hint when it was auto-applied (not when user passed -e) if (effectiveExtra.environment && !extra.environment) { hintParts.push( - `Environment: ${effectiveExtra.environment.join(", ")} (use -e to change)` + `Environment: ${effectiveExtra.environment.join(", ")} (use -e to change)`, ); } @@ -546,7 +546,7 @@ async function handleAutoDetectWithProject( /** Apply smart production env default from a resolved target. */ async function applySmartEnvDefault( extra: ExtraApiOptions, - primary?: ResolvedTarget + primary?: ResolvedTarget, ): Promise<ExtraApiOptions> { if (extra.environment || !primary) { return extra; @@ -564,7 +564,7 @@ async function applySmartEnvDefault( */ async function resolveEnvForParsedTarget( extra: ExtraApiOptions, - parsed: ParsedOrgProject + parsed: ParsedOrgProject, ): Promise<ExtraApiOptions> { if (extra.environment) { return extra; @@ -595,7 +595,7 @@ const PRODUCTION_ENV_NAMES = ["production", "prod"]; */ async function resolveDefaultEnvironment( org: string, - project: string + project: string, ): Promise<string[] | undefined> { try { const envs = await listProjectEnvironments(org, project); diff --git a/packages/cli/src/commands/release/parse.ts b/packages/cli/src/commands/release/parse.ts index a6fad93e8..f6ccfd852 100644 --- a/packages/cli/src/commands/release/parse.ts +++ b/packages/cli/src/commands/release/parse.ts @@ -30,7 +30,7 @@ const SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/; */ export function parseReleaseArg( arg: string, - usageHint: string + usageHint: string, ): { version: string; orgSlug?: string } { const firstSlash = arg.indexOf("/"); @@ -48,7 +48,7 @@ export function parseReleaseArg( if (!arg) { throw new ValidationError( `Release version is required.\n\n Usage: ${usageHint}`, - "version" + "version", ); } @@ -86,7 +86,7 @@ export type ResolvedReleaseTarget = { export async function resolveReleaseTarget( target: string | undefined, usageHint: string, - cwd: string + cwd: string, ): Promise<ResolvedReleaseTarget> { const trimmed = target?.trim(); if (!trimmed) { diff --git a/packages/cli/src/commands/release/propose-version.ts b/packages/cli/src/commands/release/propose-version.ts index 9c6b68296..86af35174 100644 --- a/packages/cli/src/commands/release/propose-version.ts +++ b/packages/cli/src/commands/release/propose-version.ts @@ -94,7 +94,7 @@ export const proposeVersionCommand = buildCommand({ parameters: {}, async *func( this: SentryContext, - _flags: { readonly json: boolean; readonly fields?: string[] } + _flags: { readonly json: boolean; readonly fields?: string[] }, ) { const { cwd, env } = this; diff --git a/packages/cli/src/commands/release/restore.ts b/packages/cli/src/commands/release/restore.ts index 354e1310d..bc9affe20 100644 --- a/packages/cli/src/commands/release/restore.ts +++ b/packages/cli/src/commands/release/restore.ts @@ -30,7 +30,7 @@ const USAGE_HINT = "sentry release restore [<org>/]<version>"; function formatReleaseRestored(data: Record<string, unknown>): string { if (data.dryRun) { return renderMarkdown( - `Would restore release ${safeCodeSpan(String(data.version))} (dry run)` + `Would restore release ${safeCodeSpan(String(data.version))} (dry run)`, ); } const release = data as unknown as SentryRelease; @@ -81,14 +81,14 @@ export const restoreCommand = buildCommand({ readonly json: boolean; readonly fields?: string[]; }, - target: string + target: string, ) { const { cwd } = this; const { version, org, detectedFrom } = await resolveReleaseTarget( target, USAGE_HINT, - cwd + cwd, ); if (flags["dry-run"]) { diff --git a/packages/cli/src/commands/release/set-commits.ts b/packages/cli/src/commands/release/set-commits.ts index 0cfd647d0..d89ac603f 100644 --- a/packages/cli/src/commands/release/set-commits.ts +++ b/packages/cli/src/commands/release/set-commits.ts @@ -58,14 +58,14 @@ function setCommitsFromLocal( org: string, version: string, cwd: string, - options: { depth?: number; paths?: string[]; from?: string } + options: { depth?: number; paths?: string[]; from?: string }, ): Promise<SentryRelease> { const { depth, paths, from } = options; const shallow = isShallowRepository(cwd); if (shallow) { log.warn( "Repository is a shallow clone. Commit history may be incomplete. " + - "Consider running `git fetch --unshallow` or increasing --initial-depth." + "Consider running `git fetch --unshallow` or increasing --initial-depth.", ); } @@ -108,7 +108,7 @@ const REPO_CACHE_TTL_MS = 60 * 60 * 1000; /** Check if we've cached that this org has no repo integration */ function hasNoRepoIntegration(orgSlug: string): boolean { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const db = getDatabase(); const key = `repos_configured.${orgSlug}`; @@ -128,7 +128,7 @@ function hasNoRepoIntegration(orgSlug: string): boolean { /** Cache that this org has no repo integration */ function cacheNoRepoIntegration(orgSlug: string): void { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const db = getDatabase(); const key = `repos_configured.${orgSlug}`; @@ -143,7 +143,7 @@ function cacheNoRepoIntegration(orgSlug: string): void { /** Clear the negative cache (e.g., when auto succeeds) */ function clearRepoIntegrationCache(orgSlug: string): void { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const db = getDatabase(); const key = `repos_configured.${orgSlug}`; @@ -163,7 +163,7 @@ async function setCommitsDefault( org: string, version: string, cwd: string, - depth: number + depth: number, ): Promise<SentryRelease> { // Fast path: cached "no repos" — skip the API call entirely if (hasNoRepoIntegration(org)) { @@ -189,14 +189,14 @@ async function setCommitsDefault( cacheNoRepoIntegration(org); log.warn( "Could not auto-discover commits (no repository integration). " + - "Falling back to local git history." + "Falling back to local git history.", ); return setCommitsFromLocal(org, version, cwd, { depth }); } if (error instanceof ValidationError && error.field === "repository") { log.warn( `Auto-discovery failed: ${error.message}. ` + - "Falling back to local git history." + "Falling back to local git history.", ); return setCommitsFromLocal(org, version, cwd, { depth }); } @@ -213,7 +213,7 @@ async function setCommitsDefault( * @throws {ValidationError} When an entry is missing the `@` separator. */ function parseCommitRefs( - commitFlag: string + commitFlag: string, ): Array<{ repository: string; commit: string; previousCommit?: string }> { return commitFlag.split(",").map((pair) => { const trimmed = pair.trim(); @@ -225,7 +225,7 @@ function parseCommitRefs( "sentry release set-commits 1.0.0 --commit owner/repo@abc123", "sentry release set-commits 1.0.0 --commit owner/repo@prev..abc123", ], - "commit" + "commit", ); } const repository = trimmed.slice(0, atIdx); @@ -274,7 +274,7 @@ function parseLocalScope(flags: { throw validationError( "--path requires at least one non-empty path.", ["sentry release set-commits 1.0.0 --path apps/mobile,packages/shared"], - "path" + "path", ); } if (paths.length > 0 && serverExpanded) { @@ -289,7 +289,7 @@ function parseLocalScope(flags: { "sentry release set-commits 1.0.0 --local --path apps/mobile", "sentry release set-commits 1.0.0 --from v0.9.0 --path apps/mobile", ], - "path" + "path", ); } @@ -299,7 +299,7 @@ function parseLocalScope(flags: { "--from requires a non-empty git ref (tag, branch, or commit).", ["sentry release set-commits 1.0.0 --from v0.9.0"], "from", - FROM_RANGE_NOTE + FROM_RANGE_NOTE, ); } // Reject option-like refs. Otherwise `--from=--format=x` would become the @@ -313,7 +313,7 @@ function parseLocalScope(flags: { "sentry release set-commits 1.0.0 --from v0.9.0 --path apps/mobile", ], "from", - FROM_DASHED_REF_NOTE + FROM_DASHED_REF_NOTE, ); } if (from && serverExpanded) { @@ -324,7 +324,7 @@ function parseLocalScope(flags: { "sentry release set-commits 1.0.0 --local --initial-depth 50", ], "from", - FROM_RANGE_NOTE + FROM_RANGE_NOTE, ); } @@ -443,14 +443,14 @@ export const setCommitsCommand = buildCommand({ readonly json: boolean; readonly fields?: string[]; }, - target: string + target: string, ) { const { cwd } = this; const { version, org } = await resolveReleaseTarget( target, USAGE_HINT, - cwd + cwd, ); // Clear mode: remove all commits regardless of other flags. @@ -474,7 +474,7 @@ export const setCommitsCommand = buildCommand({ "sentry release set-commits 1.0.0 --from v0.9.0 --path apps/mobile", "sentry release set-commits 1.0.0 --commit owner/repo@abc123..def456", ], - "commit" + "commit", ); } @@ -510,7 +510,7 @@ export const setCommitsCommand = buildCommand({ org, version, cwd, - flags["initial-depth"] + flags["initial-depth"], ); } diff --git a/packages/cli/src/commands/release/view.ts b/packages/cli/src/commands/release/view.ts index 7307b0a14..daeb5cb90 100644 --- a/packages/cli/src/commands/release/view.ts +++ b/packages/cli/src/commands/release/view.ts @@ -75,7 +75,7 @@ function formatProjectHealthTable(release: SentryRelease): string { "CRASH-FREE SESSIONS:", "USERS (24h):", "SESSIONS (24h):", - ]) + ]), ); for (const project of projects) { @@ -98,7 +98,7 @@ function formatReleaseDetails(release: SentryRelease): string { const lines: string[] = []; lines.push( - `## Release ${escapeMarkdownInline(release.shortVersion || release.version)}` + `## Release ${escapeMarkdownInline(release.shortVersion || release.version)}`, ); lines.push(""); @@ -194,7 +194,7 @@ export const viewCommand = buildCommand({ readonly json: boolean; readonly fields?: string[]; }, - target: string + target: string, ) { applyFreshFlag(flags); const { cwd } = this; @@ -202,7 +202,7 @@ export const viewCommand = buildCommand({ const { version, org, detectedFrom } = await resolveReleaseTarget( target, USAGE_HINT, - cwd + cwd, ); const release = await getRelease(org, version, { diff --git a/packages/cli/src/commands/replay/download.ts b/packages/cli/src/commands/replay/download.ts index 51e32ec50..d26a44780 100644 --- a/packages/cli/src/commands/replay/download.ts +++ b/packages/cli/src/commands/replay/download.ts @@ -101,7 +101,7 @@ export const downloadCommand = buildCommand({ const resolved = await resolveOrgOptionalFromArg( parsedArgs.targetArg, cwd, - "replay download" + "replay download", ); let replay: ReplayDetails; @@ -118,7 +118,7 @@ export const downloadCommand = buildCommand({ [ "Check that you are querying the right organization", "The replay may be past your retention window", - ] + ], ); } throw error; @@ -142,7 +142,7 @@ export const downloadCommand = buildCommand({ const segments = await getReplayRecordingSegments( resolved.org, String(replay.project_id), - replay.id + replay.id, ); const events = toRRWebEvents(segments); if (events.length === 0) { @@ -150,7 +150,7 @@ export const downloadCommand = buildCommand({ } if (!hasFullSnapshot(events)) { log.warn( - "This recording has no full DOM snapshot (e.g. a mobile replay), so rrweb players cannot render it." + "This recording has no full DOM snapshot (e.g. a mobile replay), so rrweb players cannot render it.", ); } @@ -176,6 +176,6 @@ function noRecordingError(org: string, replayId: string): ResolutionError { `Replay '${replayId}'`, "has no recording to download", `sentry replay view ${org}/${replayId}`, - ["The replay may be archived or past your retention window"] + ["The replay may be archived or past your retention window"], ); } diff --git a/packages/cli/src/commands/replay/list.ts b/packages/cli/src/commands/replay/list.ts index 08fba28cf..0be9c5422 100644 --- a/packages/cli/src/commands/replay/list.ts +++ b/packages/cli/src/commands/replay/list.ts @@ -114,7 +114,7 @@ export function parseSort(value: string): ReplaySortValue { } throw new Error( - `Invalid sort value. Must be one of: ${Object.keys(SORT_MAP).join(", ")} or a replay sort like -count_rage_clicks` + `Invalid sort value. Must be one of: ${Object.keys(SORT_MAP).join(", ")} or a replay sort like -count_rage_clicks`, ); } @@ -177,7 +177,7 @@ function formatScope(org: string, project?: string): string { function appendReplayFlags( base: string, - flags: Pick<ListFlags, "environment" | "query" | "sort" | "period"> + flags: Pick<ListFlags, "environment" | "query" | "sort" | "period">, ): string { const parts: string[] = []; appendQueryHint(parts, flags.query); @@ -194,22 +194,22 @@ function appendReplayFlags( function nextPageHint( org: string, project: string | undefined, - flags: Pick<ListFlags, "environment" | "query" | "sort" | "period"> + flags: Pick<ListFlags, "environment" | "query" | "sort" | "period">, ): string { return appendReplayFlags( `sentry replay list ${formatScope(org, project)} -c next`, - flags + flags, ); } function prevPageHint( org: string, project: string | undefined, - flags: Pick<ListFlags, "environment" | "query" | "sort" | "period"> + flags: Pick<ListFlags, "environment" | "query" | "sort" | "period">, ): string { return appendReplayFlags( `sentry replay list ${formatScope(org, project)} -c prev`, - flags + flags, ); } @@ -225,7 +225,7 @@ function formatReplayListHuman(result: ReplayListResult): string { function jsonTransformReplayList( result: ReplayListResult, - fields?: string[] + fields?: string[], ): unknown { const items = fields && fields.length > 0 @@ -338,12 +338,12 @@ export const listCommand = buildListCommand("replay", { sort: flags.sort, q: query, period: serializeTimeRange(timeRange), - } + }, ); const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { data: replays, nextCursor } = await withProgress( @@ -364,7 +364,7 @@ export const listCommand = buildListCommand("replay", { }).catch((error: unknown): never => { // An unparseable user --query is a user input mistake, not a CLI bug. throw toSearchQueryError(error, flags.query); - }) + }), ); advancePaginationState(PAGINATION_KEY, contextKey, direction, nextCursor); diff --git a/packages/cli/src/commands/replay/view.ts b/packages/cli/src/commands/replay/view.ts index 97aa3e49d..cea98352c 100644 --- a/packages/cli/src/commands/replay/view.ts +++ b/packages/cli/src/commands/replay/view.ts @@ -107,7 +107,7 @@ function parseSingleArg(arg: string, usageHint: string): ParsedPositionalArgs { const { id: replayId, targetArg } = parseSlashSeparatedArg( trimmed, "Replay ID", - usageHint + usageHint, ); return { replayId, targetArg }; } @@ -126,7 +126,7 @@ function parseSingleArg(arg: string, usageHint: string): ParsedPositionalArgs { */ export function parsePositionalArgs( args: string[], - usageHint = USAGE_HINT + usageHint = USAGE_HINT, ): ParsedPositionalArgs { if (args.length === 0) { throw new ContextError("Replay ID", usageHint, []); @@ -134,7 +134,7 @@ export function parsePositionalArgs( if (args.length > 2) { throw new ValidationError( `Too many positional arguments (got ${args.length}, expected at most 2).\n\nUsage: ${usageHint}`, - "positional" + "positional", ); } @@ -194,7 +194,7 @@ type ReplayProjectScope = { * has to be checked against the replay's own project. */ export async function validateReplayProjectScope( - scope: ReplayProjectScope + scope: ReplayProjectScope, ): Promise<void> { const { expectedProjectId, org, project, replay, replayId } = scope; const command = scope.command ?? "view"; @@ -213,7 +213,7 @@ export async function validateReplayProjectScope( `sentry replay ${command} ${org}/${project}/${replayId}`, [ `Use the org-scoped replay instead: sentry replay ${command} ${org}/${replayId}`, - ] + ], ); } @@ -225,14 +225,14 @@ export async function validateReplayProjectScope( `sentry replay ${command} ${org}/${project}/${replayId}`, [ `Use the org-scoped replay instead: sentry replay ${command} ${org}/${replayId}`, - ] + ], ); } } async function fetchReplayActivity( org: string, - replay: ReplayDetails + replay: ReplayDetails, ): Promise<ReplayActivityEvent[]> { if ( replay.is_archived || @@ -247,7 +247,7 @@ async function fetchReplayActivity( org, String(replay.project_id), replay.id, - { expectedSegments: replay.count_segments } + { expectedSegments: replay.count_segments }, ); return extractReplayActivityEvents(segments, MAX_ACTIVITY_EVENTS); } catch (error) { @@ -264,7 +264,7 @@ async function fetchReplayActivity( */ function fetchRelatedReplayIssues( org: string, - replay: ReplayDetails + replay: ReplayDetails, ): Promise<ReplayRelatedIssue[]> { const eventIds = replay.error_ids.slice(0, MAX_RELATED_ERRORS); @@ -286,13 +286,13 @@ function fetchRelatedReplayIssues( log.debug(`Failed to resolve issue for event ${eventId}`, error); return { eventId, issueId: null, shortId: null, title: null }; } - }) + }), ); } function fetchRelatedReplayTraces( org: string, - replay: ReplayDetails + replay: ReplayDetails, ): Promise<ReplayRelatedTrace[]> { const traceIds = replay.trace_ids.slice(0, MAX_RELATED_TRACES); @@ -317,13 +317,13 @@ function fetchRelatedReplayTraces( spanCount: null, }; } - }) + }), ); } async function enrichReplayView( org: string, - replay: ReplayDetails + replay: ReplayDetails, ): Promise< Pick<ReplayViewData, "activity" | "relatedIssues" | "relatedTraces"> > { @@ -402,7 +402,7 @@ export const viewCommand = buildCommand({ const resolved = await resolveOrgOptionalFromArg( parsedArgs.targetArg, cwd, - "replay view" + "replay view", ); let replay: ReplayDetails; @@ -419,7 +419,7 @@ export const viewCommand = buildCommand({ [ "Check that you are querying the right organization", "The replay may be past your retention window", - ] + ], ); } throw error; diff --git a/packages/cli/src/commands/schema.ts b/packages/cli/src/commands/schema.ts index aa2adeb52..ce0301861 100644 --- a/packages/cli/src/commands/schema.ts +++ b/packages/cli/src/commands/schema.ts @@ -78,7 +78,7 @@ function formatResourceList(resources: ResourceSummary[]): string { const maxName = Math.max(...resources.map((r) => r.name.length)); const maxCount = Math.max( - ...resources.map((r) => String(r.endpointCount).length) + ...resources.map((r) => String(r.endpointCount).length), ); const padding = 4; @@ -222,7 +222,7 @@ function noResourceMatchError(resource: string): ResolutionError { `Resource "${resource}"`, "does not exist in the schema", primaryHint, - suggestions + suggestions, ); } @@ -242,7 +242,7 @@ function queryLabel(resource: string, operation?: string): string { /** Detect `GET /api/0/...` (quoted or as two positionals) and bare `/api/...` paths. */ function resolvePathQuery( resource: string, - operation?: string + operation?: string, ): { method?: string; path: string } | undefined { const raw = operation ? `${resource} ${operation}` : resource; const parsed = parseEndpointQuery(raw); @@ -265,7 +265,7 @@ function lastConcreteSegment(path: string): string | undefined { function pathNotFoundError( label: string, - query: { method?: string; path: string } + query: { method?: string; path: string }, ): never { const samePath = findEndpointsByPath(query.path); if (query.method && samePath.length > 0) { @@ -280,7 +280,7 @@ function pathNotFoundError( example ? `sentry schema "${example.method} ${example.path}"` : "sentry schema", - [`Available methods at this path: ${methods}`] + [`Available methods at this path: ${methods}`], ); } @@ -296,21 +296,21 @@ function pathNotFoundError( const suggestions = ["sentry schema Browse all resources"]; if (resourceExists && segment) { suggestions.unshift( - `sentry schema --search ${segment} Search endpoints by keyword` + `sentry schema --search ${segment} Search endpoints by keyword`, ); } throw new ResolutionError( `Endpoint '${label}'`, "does not exist in the schema", hint, - suggestions + suggestions, ); } function resolvePathLookup( query: { method?: string; path: string }, resource: string, - operation?: string + operation?: string, ): SchemaResult { const matches = findEndpointsByPath(query.path, query.method); const [single] = matches; @@ -345,7 +345,7 @@ function resolveIdentifierLookup(identifier: string): SchemaResult | undefined { */ export function resolveResourceQuery( resource: string, - operation?: string + operation?: string, ): SchemaResult { const pathQuery = resolvePathQuery(resource, operation); if (pathQuery) { @@ -363,14 +363,14 @@ export function resolveResourceQuery( const pattern = new RegExp(`^${escaped}$`); const allResources = getResourceSummaries(); const matched = allResources.filter((r) => - pattern.test(r.name.toLowerCase()) + pattern.test(r.name.toLowerCase()), ); if (matched.length === 0) { throw new ResolutionError( `Pattern "${resource}"`, "matched no resources", "sentry schema", - ["sentry schema --all List every endpoint in a flat table"] + ["sentry schema --all List every endpoint in a flat table"], ); } const endpoints = matched.flatMap((r) => getEndpointsByResource(r.name)); @@ -461,7 +461,7 @@ export const schemaCommand = buildCommand({ }, }, }, - // biome-ignore lint/suspicious/useAwait: Stricli requires AsyncGenerator but schema queries are synchronous (in-memory JSON) + // Stricli requires AsyncGenerator but schema queries are synchronous (in-memory JSON) async *func(this: SentryContext, flags: SchemaFlags, ...args: string[]) { const [resource, operation] = args; diff --git a/packages/cli/src/commands/send-envelope.ts b/packages/cli/src/commands/send-envelope.ts index fa652d4b5..123aa2628 100644 --- a/packages/cli/src/commands/send-envelope.ts +++ b/packages/cli/src/commands/send-envelope.ts @@ -45,7 +45,7 @@ export const sendEnvelopeCommand = buildCommand({ }, }, }, - // biome-ignore lint/correctness/useYield lint/suspicious/useAwait: deprecation shim — throws before yielding + // oxlint-disable-next-line require-yield -- deprecation shim — throws before yielding async *func( this: SentryContext, _flags: { dsn?: string; raw?: boolean }, @@ -55,7 +55,7 @@ export const sendEnvelopeCommand = buildCommand({ throw new CliError( "`sentry send-envelope` has been removed.\n" + `Use: sentry event send --raw${fileArgs}`, - EXIT.GENERAL + EXIT.GENERAL, ); }, }); diff --git a/packages/cli/src/commands/send-event.ts b/packages/cli/src/commands/send-event.ts index 5adbaf16d..2c2a0a54c 100644 --- a/packages/cli/src/commands/send-event.ts +++ b/packages/cli/src/commands/send-event.ts @@ -5,5 +5,5 @@ * in `event/send.ts`. */ -// biome-ignore lint/performance/noBarrelFile: backward-compat alias, not a barrel +// backward-compat alias, not a barrel export { sendCommand as sendEventCommand } from "./event/send.js"; diff --git a/packages/cli/src/commands/snapshots/diff.ts b/packages/cli/src/commands/snapshots/diff.ts index cb8898716..7d198cf68 100644 --- a/packages/cli/src/commands/snapshots/diff.ts +++ b/packages/cli/src/commands/snapshots/diff.ts @@ -96,7 +96,7 @@ function errorMessage(err: unknown): string { /** Assert a path is an existing directory. */ function assertDirectory(path: string, label: string): void { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { if (statSync(path).isDirectory()) { return; @@ -106,7 +106,7 @@ function assertDirectory(path: string, label: string): void { } throw new ValidationError( `${label} directory does not exist: ${path}`, - "dir" + "dir", ); } @@ -126,7 +126,7 @@ type DiffDirs = { baseDir: string; headDir: string; outputDir: string }; function compareMatched( dirs: DiffDirs, rel: string, - opts: DiffOptions + opts: DiffOptions, ): ImageResult { let baseBuf: Buffer; let headBuf: Buffer; @@ -295,7 +295,7 @@ export const diffCommand = buildCommand({ this: SentryContext, flags: DiffFlags, baseDirArg: string, - headDirArg: string + headDirArg: string, ) { const baseDir = resolve(this.cwd, baseDirArg); const headDir = resolve(this.cwd, headDirArg); @@ -315,7 +315,7 @@ export const diffCommand = buildCommand({ const categorized = categorizeImages( baseFiles, headFiles, - Boolean(flags.selective) + Boolean(flags.selective), ); const dirs: DiffDirs = { baseDir, headDir, outputDir }; diff --git a/packages/cli/src/commands/snapshots/download.ts b/packages/cli/src/commands/snapshots/download.ts index 0c69f3eb1..51e53ff03 100644 --- a/packages/cli/src/commands/snapshots/download.ts +++ b/packages/cli/src/commands/snapshots/download.ts @@ -64,7 +64,7 @@ function formatDownloadResult(data: SnapshotDownloadResult): string { ["Snapshot ID", data.snapshotId], ["Images", String(data.imageCount)], ["Saved to", data.output], - ]) + ]), ); } @@ -82,13 +82,13 @@ function validateSnapshotFlags(flags: DownloadFlags): void { if (appId && snapshotId) { throw new ValidationError( "Provide only one of --app-id or --snapshot-id", - "app-id" + "app-id", ); } if (flags.branch && !appId) { throw new ValidationError( "--branch can only be used with --app-id", - "branch" + "branch", ); } if (!(appId || snapshotId)) { @@ -117,7 +117,7 @@ function resolveOptionalProject(env: NodeJS.ProcessEnv): string | undefined { async function resolveSnapshotId( org: string, flags: DownloadFlags, - project: string | undefined + project: string | undefined, ): Promise<string> { const snapshotId = flags["snapshot-id"]; if (snapshotId) { @@ -136,11 +136,11 @@ async function resolveSnapshotId( `Baseline snapshot for app '${appId}'${branchMsg}`, "not found", USAGE_HINT, - ["No baseline snapshot exists for this app yet."] + ["No baseline snapshot exists for this app yet."], ); } log.info( - `Found snapshot ${latest.headArtifactId} (${latest.imageCount} images)` + `Found snapshot ${latest.headArtifactId} (${latest.imageCount} images)`, ); return latest.headArtifactId; } @@ -209,7 +209,7 @@ export const downloadCommand = buildCommand({ const snapshotId = await resolveSnapshotId(org, flags, project); await waitForSnapshotArchive(org, snapshotId, () => - log.info("Building snapshot archive...") + log.info("Building snapshot archive..."), ); log.info(`Downloading snapshot ${snapshotId}...`); @@ -219,18 +219,18 @@ export const downloadCommand = buildCommand({ "Snapshot archive response had no body", response.status, "Empty response body", - `snapshots/${snapshotId}/archive/` + `snapshots/${snapshotId}/archive/`, ); } const output = resolve(this.cwd, flags.output ?? DEFAULT_OUTPUT); const imageCount = await extractZipStream( response.body as unknown as AsyncIterable<Uint8Array>, - output + output, ); if (imageCount === 0) { log.warn( - `No images were extracted from snapshot ${snapshotId} — the archive may be empty or corrupt.` + `No images were extracted from snapshot ${snapshotId} — the archive may be empty or corrupt.`, ); } diff --git a/packages/cli/src/commands/snapshots/upload.ts b/packages/cli/src/commands/snapshots/upload.ts index 600ccfe53..523a6eb50 100644 --- a/packages/cli/src/commands/snapshots/upload.ts +++ b/packages/cli/src/commands/snapshots/upload.ts @@ -95,16 +95,16 @@ function parsePrNumber(value: string): number { * normalized list, or `undefined` when neither is set. */ async function resolveAllImageNames( - flags: UploadFlags + flags: UploadFlags, ): Promise<string[] | undefined> { if (flags["all-image-file-names"] !== undefined) { const names = normalizeImageNames( - splitAndTrim(flags["all-image-file-names"], ",") + splitAndTrim(flags["all-image-file-names"], ","), ); if (names.length === 0) { throw new ValidationError( "--all-image-file-names must not be empty", - "all-image-file-names" + "all-image-file-names", ); } return names; @@ -118,14 +118,14 @@ async function resolveAllImageNames( log.debug(`Failed to read --all-image-file-names-file ${path}`, err); throw new ValidationError( `Failed to read --all-image-file-names-file: ${path}`, - "all-image-file-names-file" + "all-image-file-names-file", ); } const names = normalizeImageNames(splitAndTrim(content, "\n")); if (names.length === 0) { throw new ValidationError( `--all-image-file-names-file is empty or contains only blank lines: ${path}`, - "all-image-file-names-file" + "all-image-file-names-file", ); } return names; @@ -141,12 +141,12 @@ async function resolveAllImageNames( function collectVcs( flags: UploadFlags, cwd: string, - env: NodeJS.ProcessEnv + env: NodeJS.ProcessEnv, ): VcsInfo { if (flags["force-git-metadata"] && flags["no-git-metadata"]) { throw new ValidationError( "--force-git-metadata and --no-git-metadata cannot be used together", - "force-git-metadata" + "force-git-metadata", ); } const shouldCollect = @@ -157,7 +157,7 @@ function collectVcs( throw new ValidationError( "A PR number was provided but no base SHA could be determined. " + "Pass --base-sha explicitly or ensure your CI exposes the merge base.", - "pr-number" + "pr-number", ); } return vcs; @@ -171,7 +171,7 @@ function collectVcs( */ async function resolveSelective( flags: UploadFlags, - images: CollectedImage[] + images: CollectedImage[], ): Promise<{ selective: boolean; allImageNames?: string[] }> { const allImageNames = await resolveAllImageNames(flags); const selective = Boolean(flags.selective) || allImageNames !== undefined; @@ -184,9 +184,9 @@ async function resolveSelective( if (unknown.length > 0) { throw new ValidationError( `The following uploaded images are not in --all-image-file-names: ${unknown.join( - ", " + ", ", )}`, - "all-image-file-names" + "all-image-file-names", ); } } @@ -197,7 +197,7 @@ async function resolveSelective( async function runPooled<T>( items: T[], limit: number, - fn: (item: T) => Promise<void> + fn: (item: T) => Promise<void>, ): Promise<void> { for (let i = 0; i < items.length; i += limit) { await Promise.all(items.slice(i, i + limit).map(fn)); @@ -231,7 +231,7 @@ type UploadImagesResult = { async function uploadImages( org: string, project: string, - images: CollectedImage[] + images: CollectedImage[], ): Promise<UploadImagesResult> { const { objectstore } = await fetchSnapshotsUploadOptions(org, project); const config: ObjectstoreConfig = objectstore; @@ -243,7 +243,7 @@ async function uploadImages( if (!(orgId && projectId)) { throw new ValidationError( "Snapshot upload options are missing org/project scope", - "app-id" + "app-id", ); } @@ -328,7 +328,7 @@ function formatUploadResult(data: SnapshotUploadResult): string { rows.push(["URL", data.snapshot.snapshotUrl]); } return renderMarkdown( - `${colorTag("green", "Created snapshot")}\n\n${mdKvTable(rows)}` + `${colorTag("green", "Created snapshot")}\n\n${mdKvTable(rows)}`, ); } @@ -481,7 +481,7 @@ export const uploadCommand = buildCommand({ ) { throw new ValidationError( "--all-image-file-names and --all-image-file-names-file cannot be used together", - "all-image-file-names" + "all-image-file-names", ); } const vcs = collectVcs(flags, this.cwd, this.env); diff --git a/packages/cli/src/commands/sourcemap/inject.ts b/packages/cli/src/commands/sourcemap/inject.ts index c54cce1d9..5184e439c 100644 --- a/packages/cli/src/commands/sourcemap/inject.ts +++ b/packages/cli/src/commands/sourcemap/inject.ts @@ -37,7 +37,7 @@ function formatInjectResult(data: InjectCommandResult): string { mdKvTable([ ["Files modified", String(data.modified)], ["Files skipped", String(data.skipped)], - ]) + ]), ); if (data.files.length > 0) { @@ -45,7 +45,7 @@ function formatInjectResult(data: InjectCommandResult): string { for (const file of data.files) { const status = file.injected ? "✓" : "–"; lines.push( - `${status} ${file.jsPath} → ${colorTag("muted", file.debugId)}` + `${status} ${file.jsPath} → ${colorTag("muted", file.debugId)}`, ); } } @@ -132,7 +132,7 @@ export const injectCommand = buildCommand({ "dry-run"?: boolean; "allow-empty"?: boolean; }, - dir: string + dir: string, ) { // Discover pairs read-only first so we don't error after partially // mutating files. Zero *discovered* pairs (distinct from zero @@ -150,7 +150,7 @@ export const injectCommand = buildCommand({ : undefined; const ignoreMatcher = await buildIgnoreMatcher( ignorePatterns, - flags["ignore-file"] + flags["ignore-file"], ); const pairs = await discoverFilePairs(dir, extSet, ignoreMatcher); diff --git a/packages/cli/src/commands/sourcemap/resolve.ts b/packages/cli/src/commands/sourcemap/resolve.ts index c9761a5f6..c16571c3d 100644 --- a/packages/cli/src/commands/sourcemap/resolve.ts +++ b/packages/cli/src/commands/sourcemap/resolve.ts @@ -81,7 +81,7 @@ function formatResolveResult(data: ResolveCommandResult): string { ["JS files", String(data.total)], ["With sourcemap", String(data.resolved)], ["With debug ID", String(data.withDebugId)], - ]) + ]), ); if (data.files.length > 0) { @@ -93,7 +93,7 @@ function formatResolveResult(data: ResolveCommandResult): string { ? escapeMarkdownCell(file.debugId) : colorTag("muted", "—"); lines.push( - `| ${escapeMarkdownCell(file.path)} | ${describeMapStatus(file)} | ${debugId} |` + `| ${escapeMarkdownCell(file.path)} | ${describeMapStatus(file)} | ${debugId} |`, ); } } @@ -161,7 +161,7 @@ export const resolveCommand = buildCommand({ ignore?: string; "ignore-file"?: string; }, - dir: string + dir: string, ) { await assertDirectoryReadable(dir); @@ -175,13 +175,13 @@ export const resolveCommand = buildCommand({ : undefined; const ignoreMatcher = await buildIgnoreMatcher( ignorePatterns, - flags["ignore-file"] + flags["ignore-file"], ); const resolutions = await resolveDirectorySourcemaps( dir, extSet, - ignoreMatcher + ignoreMatcher, ); const absDir = resolvePath(dir); diff --git a/packages/cli/src/commands/sourcemap/upload.ts b/packages/cli/src/commands/sourcemap/upload.ts index f7709d58f..a99f06ac7 100644 --- a/packages/cli/src/commands/sourcemap/upload.ts +++ b/packages/cli/src/commands/sourcemap/upload.ts @@ -130,7 +130,7 @@ function jsRelativePath(jsPath: string, ctx: ArtifactContext): string { */ function buildArtifactPair( result: InjectResult, - ctx: ArtifactContext + ctx: ArtifactContext, ): ArtifactFile[] { const { jsPath, map, debugId } = result; const jsRelative = jsRelativePath(jsPath, ctx); @@ -176,7 +176,7 @@ function buildArtifactPair( // External map on disk. let mapRelative = relative(ctx.resolvedDir, map.mapPath).replaceAll( "\\", - "/" + "/", ); if (ctx.pathPrefixToStrip) { mapRelative = stripPrefix(mapRelative, ctx.pathPrefixToStrip); @@ -320,7 +320,7 @@ export const uploadCommand = buildCommand({ "no-rewrite"?: boolean; "allow-empty"?: boolean; }, - dir: string + dir: string, ) { // Validate the directory and discover pairs read-only first so we // don't write debug IDs when the upload won't proceed (empty dir, @@ -337,7 +337,7 @@ export const uploadCommand = buildCommand({ : undefined; const ignoreMatcher = await buildIgnoreMatcher( ignorePatterns, - flags["ignore-file"] + flags["ignore-file"], ); const pairs = await discoverFilePairs(dir, extSet, ignoreMatcher); @@ -365,7 +365,7 @@ export const uploadCommand = buildCommand({ // Validate mutually exclusive flags before any file mutation if (flags["strip-prefix"] && flags["strip-common-prefix"]) { throw new ValidationError( - "--strip-prefix and --strip-common-prefix are mutually exclusive" + "--strip-prefix and --strip-common-prefix are mutually exclusive", ); } @@ -420,7 +420,7 @@ export const uploadCommand = buildCommand({ urlPrefix, pathPrefixToStrip, noRewrite: flags["no-rewrite"] ?? false, - }) + }), ); await uploadSourcemaps({ @@ -435,7 +435,7 @@ export const uploadCommand = buildCommand({ // buildArtifactPair returns no entries for inline pairs whose rewrite was // aborted, so this excludes skipped pairs that results.length would count. const filesUploaded = artifactFiles.filter( - (f) => f.type === "minified_source" + (f) => f.type === "minified_source", ).length; yield new CommandOutput<UploadCommandResult>({ diff --git a/packages/cli/src/commands/span/list.ts b/packages/cli/src/commands/span/list.ts index 33e7f87fe..b20e7bc69 100644 --- a/packages/cli/src/commands/span/list.ts +++ b/packages/cli/src/commands/span/list.ts @@ -117,7 +117,7 @@ const FIELD_GROUP_ALIASES: Record<string, string[]> = { * @returns Deduplicated extra API field names, or undefined if none are needed */ function extractExtraApiFields( - fields: string[] | undefined + fields: string[] | undefined, ): string[] | undefined { if (!fields?.length) { return; @@ -179,7 +179,7 @@ function parseLimit(value: string): number { export function parseSort(value: string): SpanSortValue { if (!VALID_SORT_VALUES.includes(value as SpanSortValue)) { throw new Error( - `Invalid sort value. Must be one of: ${VALID_SORT_VALUES.join(", ")}` + `Invalid sort value. Must be one of: ${VALID_SORT_VALUES.join(", ")}`, ); } return value as SpanSortValue; @@ -192,7 +192,7 @@ export function parseSort(value: string): SpanSortValue { * trace-mode usage hint for span list. */ export function parseSpanListArgs( - args: string[] + args: string[], ): ReturnType<typeof parseDualModeArgs> { return parseDualModeArgs(args, TRACE_USAGE_HINT); } @@ -204,7 +204,7 @@ export function parseSpanListArgs( /** Append active non-default flags to a base next-page command. */ function appendFlagHints( base: string, - flags: Pick<ListFlags, "sort" | "query" | "period"> + flags: Pick<ListFlags, "sort" | "query" | "period">, ): string { const parts: string[] = []; if (flags.sort !== DEFAULT_SORT) { @@ -222,11 +222,11 @@ function traceNextPageHint( org: string, project: string, traceId: string, - flags: Pick<ListFlags, "sort" | "query" | "period"> + flags: Pick<ListFlags, "sort" | "query" | "period">, ): string { return appendFlagHints( `sentry span list ${org}/${project}/${traceId} -c next`, - flags + flags, ); } @@ -235,11 +235,11 @@ function tracePrevPageHint( org: string, project: string, traceId: string, - flags: Pick<ListFlags, "sort" | "query" | "period"> + flags: Pick<ListFlags, "sort" | "query" | "period">, ): string { return appendFlagHints( `sentry span list ${org}/${project}/${traceId} -c prev`, - flags + flags, ); } @@ -247,7 +247,7 @@ function tracePrevPageHint( function projectNextPageHint( org: string, project: string, - flags: Pick<ListFlags, "sort" | "query" | "period"> + flags: Pick<ListFlags, "sort" | "query" | "period">, ): string { return appendFlagHints(`sentry span list ${org}/${project} -c next`, flags); } @@ -256,7 +256,7 @@ function projectNextPageHint( function projectPrevPageHint( org: string, project: string, - flags: Pick<ListFlags, "sort" | "query" | "period"> + flags: Pick<ListFlags, "sort" | "query" | "period">, ): string { return appendFlagHints(`sentry span list ${org}/${project} -c prev`, flags); } @@ -336,7 +336,7 @@ function jsonTransformSpanList(data: SpanListData, fields?: string[]): unknown { } // --------------------------------------------------------------------------- -// Mode handlers — extracted from func() to stay under biome complexity limit +// Mode handlers — extracted from func() to keep it readable // --------------------------------------------------------------------------- /** Shared context passed to mode handlers from the Stricli command function. */ @@ -357,14 +357,14 @@ type ModeContext = { */ async function handleTraceMode( parsed: ParsedTraceTarget, - ctx: ModeContext + ctx: ModeContext, ): Promise<{ output: SpanListData; hint?: string }> { const { flags, cwd, extraApiFields, timeRange } = ctx; warnIfNormalized(parsed, "span.list"); const { traceId, org, project } = await resolveTraceOrgProject( parsed, cwd, - TRACE_USAGE_HINT + TRACE_USAGE_HINT, ); const queryParts = [`trace:${traceId}`]; if (flags.query) { @@ -375,12 +375,12 @@ async function handleTraceMode( const contextKey = buildPaginationContextKey( "span", `${org}/${project}/${traceId}`, - { sort: flags.sort, q: flags.query, period: serializeTimeRange(timeRange) } + { sort: flags.sort, q: flags.query, period: serializeTimeRange(timeRange) }, ); const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { data: spanItems, nextCursor } = await withProgress( @@ -397,7 +397,7 @@ async function handleTraceMode( }).catch((error: unknown): never => { // An unparseable user --query is a user input mistake, not a CLI bug. throw toSearchQueryError(error, flags.query); - }) + }), ); // Update pagination state (handles both advance and truncation) @@ -405,7 +405,7 @@ async function handleTraceMode( const hasPrev = hasPreviousPage(PAGINATION_KEY, contextKey); const flatSpans = spanItems.map((item) => - spanListItemToFlatSpan(item, extraApiFields) + spanListItemToFlatSpan(item, extraApiFields), ); const hasMore = !!nextCursor; @@ -447,13 +447,13 @@ async function handleTraceMode( */ async function handleProjectMode( target: string | undefined, - ctx: ModeContext + ctx: ModeContext, ): Promise<{ output: SpanListData; hint?: string }> { const { flags, cwd, extraApiFields, timeRange } = ctx; const { org, project } = await resolveProjectBoundFromArg( target, cwd, - COMMAND_NAME + COMMAND_NAME, ); // Resolve slug → numeric ID so the Events query scopes via the `project` // param. `project:<slug>` only matches actively-selected projects (#1317). @@ -463,12 +463,12 @@ async function handleProjectMode( const contextKey = buildPaginationContextKey( "span-search", `${org}/${project}`, - { sort: flags.sort, q: flags.query, period: serializeTimeRange(timeRange) } + { sort: flags.sort, q: flags.query, period: serializeTimeRange(timeRange) }, ); const { cursor, direction } = resolveCursor( flags.cursor, PROJECT_PAGINATION_KEY, - contextKey + contextKey, ); const { data: spanItems, nextCursor } = await withProgress( @@ -485,7 +485,7 @@ async function handleProjectMode( }).catch((error: unknown): never => { // An unparseable user --query is a user input mistake, not a CLI bug. throw toSearchQueryError(error, flags.query); - }) + }), ); // Update pagination state (handles both advance and truncation) @@ -493,12 +493,12 @@ async function handleProjectMode( PROJECT_PAGINATION_KEY, contextKey, direction, - nextCursor + nextCursor, ); const hasPrev = hasPreviousPage(PROJECT_PAGINATION_KEY, contextKey); const flatSpans = spanItems.map((item) => - spanListItemToFlatSpan(item, extraApiFields) + spanListItemToFlatSpan(item, extraApiFields), ); const hasMore = !!nextCursor; diff --git a/packages/cli/src/commands/span/view.ts b/packages/cli/src/commands/span/view.ts index 2bebe1dde..1a514d61c 100644 --- a/packages/cli/src/commands/span/view.ts +++ b/packages/cli/src/commands/span/view.ts @@ -147,7 +147,7 @@ function tryAutoSplitSpanArg(arg: string): SpanViewArgs | null { } log.warn( `Interpreting '${arg}' as <trace-target>/<span-id>. ` + - `Use separate arguments: sentry span view ${tracePrefix} ${possibleSpanId}` + `Use separate arguments: sentry span view ${tracePrefix} ${possibleSpanId}`, ); return { kind: "resolved", traceTarget, rawSpanIds: [possibleSpanId] }; } @@ -206,7 +206,7 @@ export function parsePositionalArgs(args: string[]): SpanViewArgs { */ async function validateAndRecoverSpanId( rawSpanId: string, - ctx: { org: string; project: string; traceId: string } + ctx: { org: string; project: string; traceId: string }, ): Promise<string> { try { return validateSpanId(rawSpanId); @@ -237,7 +237,7 @@ function warnMissingIds(spanIds: string[], foundIds: Set<string>): void { const missing = spanIds.filter((id) => !foundIds.has(id)); if (missing.length > 0) { log.warn( - `${missing.length} of ${spanIds.length} span(s) not found in trace:\n${formatIdList(missing)}` + `${missing.length} of ${spanIds.length} span(s) not found in trace:\n${formatIdList(missing)}`, ); } } @@ -271,7 +271,7 @@ type SpanViewData = { function buildJsonResults( results: SpanResult[], traceId: string, - details?: Map<string, TraceItemDetail> + details?: Map<string, TraceItemDetail>, ): unknown[] { return results.map((r) => { const base: Record<string, unknown> = { @@ -333,7 +333,7 @@ function formatSpanViewHuman(data: SpanViewData): string { // Standard span details (KV table + ancestor chain) const detail = data.details?.get(result.spanId); parts.push( - formatSpanDetails(result.span, result.ancestors, data.traceId, detail) + formatSpanDetails(result.span, result.ancestors, data.traceId, detail), ); // Show child tree if --spans > 0 and the span has children @@ -342,7 +342,7 @@ function formatSpanViewHuman(data: SpanViewData): string { const treeLines = formatSimpleSpanTree( data.traceId, [result.span], - data.spansDepth + data.spansDepth, ); if (treeLines.length > 0) { parts.push(`${treeLines.join("\n")}\n`); @@ -419,7 +419,7 @@ export const viewCommand = buildCommand({ const { traceId, org, project } = await resolveTraceOrgProject( traceTarget, cwd, - USAGE_HINT + USAGE_HINT, ); // Validate + recover span IDs now that trace context is available. @@ -427,8 +427,8 @@ export const viewCommand = buildCommand({ // fuzzy lookup via `ctx.traceId`. const spanIds = await Promise.all( rawSpanIds.map((raw) => - validateAndRecoverSpanId(raw, { org, project, traceId }) - ) + validateAndRecoverSpanId(raw, { org, project, traceId }), + ), ); // Fetch trace data (single fetch for all span lookups) const spans = await getDetailedTrace(org, traceId); @@ -462,7 +462,7 @@ export const viewCommand = buildCommand({ throw new ResolutionError( `Span "${spanIds[0]}" in trace ${traceId}`, "not found", - USAGE_HINT + USAGE_HINT, ); } const idList = formatIdList(spanIds); @@ -470,7 +470,7 @@ export const viewCommand = buildCommand({ `Spans in trace ${traceId}`, "none of the requested IDs were found", USAGE_HINT, - [`Requested IDs:\n${idList}`] + [`Requested IDs:\n${idList}`], ); } @@ -483,7 +483,7 @@ export const viewCommand = buildCommand({ span_id: r.spanId, project_slug: r.span.project_slug, })), - { org, fallbackProject: project, traceId } + { org, fallbackProject: project, traceId }, ); yield new CommandOutput({ diff --git a/packages/cli/src/commands/trace/list.ts b/packages/cli/src/commands/trace/list.ts index 7f285a2e6..129cb0200 100644 --- a/packages/cli/src/commands/trace/list.ts +++ b/packages/cli/src/commands/trace/list.ts @@ -93,7 +93,7 @@ const DEFAULT_PERIOD = "7d"; /** Append active non-default flags to a base command string. */ function appendTraceFlags( base: string, - flags: Pick<ListFlags, "sort" | "query" | "period"> + flags: Pick<ListFlags, "sort" | "query" | "period">, ): string { const parts: string[] = []; if (flags.sort !== "date") { @@ -110,7 +110,7 @@ function appendTraceFlags( function nextPageHint( org: string, project: string, - flags: Pick<ListFlags, "sort" | "query" | "period"> + flags: Pick<ListFlags, "sort" | "query" | "period">, ): string { return appendTraceFlags(`sentry trace list ${org}/${project} -c next`, flags); } @@ -119,7 +119,7 @@ function nextPageHint( function prevPageHint( org: string, project: string, - flags: Pick<ListFlags, "sort" | "query" | "period"> + flags: Pick<ListFlags, "sort" | "query" | "period">, ): string { return appendTraceFlags(`sentry trace list ${org}/${project} -c prev`, flags); } @@ -140,7 +140,7 @@ function parseLimit(value: string): number { export function parseSort(value: string): SortValue { if (!VALID_SORT_VALUES.includes(value as SortValue)) { throw new Error( - `Invalid sort value. Must be one of: ${VALID_SORT_VALUES.join(", ")}` + `Invalid sort value. Must be one of: ${VALID_SORT_VALUES.join(", ")}`, ); } return value as SortValue; @@ -173,7 +173,7 @@ function formatTraceListHuman(result: TraceListResult): string { */ function jsonTransformTraceList( result: TraceListResult, - fields?: string[] + fields?: string[], ): unknown { const items = fields && fields.length > 0 @@ -267,7 +267,7 @@ export const listCommand = buildListCommand("trace", { const { org, project } = await resolveProjectBoundFromArg( target, cwd, - COMMAND_NAME + COMMAND_NAME, ); // Resolve slug → numeric ID so the Events query scopes via the `project` // param. `project:<slug>` only matches actively-selected projects and can @@ -282,7 +282,7 @@ export const listCommand = buildListCommand("trace", { const { cursor, direction } = resolveCursor( flags.cursor, PAGINATION_KEY, - contextKey + contextKey, ); const { data: traces, nextCursor } = await withProgress( @@ -301,7 +301,7 @@ export const listCommand = buildListCommand("trace", { }).catch((error: unknown): never => { // An unparseable user --query is a user input mistake, not a CLI bug. throw toSearchQueryError(error, flags.query); - }) + }), ); // Update pagination state (handles both advance and truncation) diff --git a/packages/cli/src/commands/trace/logs.ts b/packages/cli/src/commands/trace/logs.ts index cc661c005..c7a20b6b8 100644 --- a/packages/cli/src/commands/trace/logs.ts +++ b/packages/cli/src/commands/trace/logs.ts @@ -211,7 +211,7 @@ export const logsCommand = buildCommand({ }).catch((error: unknown): never => { // An unparseable user --query is a user input mistake, not a CLI bug. throw toSearchQueryError(error, flags.query); - }) + }), ); const hasMore = logs.length >= flags.limit; diff --git a/packages/cli/src/commands/trace/view.ts b/packages/cli/src/commands/trace/view.ts index 065068537..9f618ab82 100644 --- a/packages/cli/src/commands/trace/view.ts +++ b/packages/cli/src/commands/trace/view.ts @@ -68,7 +68,7 @@ function buildViewHint( traceId: string, org: string, projectFilter: string | undefined, - summary: ReturnType<typeof computeTraceSummary> + summary: ReturnType<typeof computeTraceSummary>, ): string { if (projectFilter) { return `Filtered to project '${projectFilter}'. Full trace: sentry trace view ${org}/${traceId}`; @@ -112,7 +112,7 @@ const STANDARD_TRACE_VIEW_FIELDS = new Set([ * @returns Attribute names to request, or undefined when none are needed */ export function extractAdditionalAttributes( - fields: string[] | undefined + fields: string[] | undefined, ): string[] | undefined { if (!fields || fields.length === 0) { return; @@ -257,7 +257,7 @@ export function flattenSpanTree(spans: TraceSpan[]): TraceSpan[] { */ function mergeSpanDetails( span: TraceSpan, - details: Map<string, TraceItemDetail> + details: Map<string, TraceItemDetail>, ): TraceSpan & { data?: Record<string, unknown> } { const { children, ...rest } = span; const result: TraceSpan & { data?: Record<string, unknown> } = { ...rest }; @@ -303,14 +303,14 @@ export function formatTraceView(data: TraceViewData): string { if (attrsCount > 0) { const spanWord = attrsCount === 1 ? "span has" : "spans have"; parts.push( - `\n${attrsCount} ${spanWord} additional attributes. Use --json to see them.` + `\n${attrsCount} ${spanWord} additional attributes. Use --json to see them.`, ); } // Note spans with full detail data (from --full or auto-fetched with --json) if (data.details && data.details.size > 0) { parts.push( - `\n${data.details.size} span(s) have attribute data. Use --json to see full details.` + `\n${data.details.size} span(s) have attribute data. Use --json to see full details.`, ); } @@ -335,7 +335,7 @@ function filterSpanMeasurements(span: TraceSpan): TraceSpan { let cleanedMeasurements: Record<string, number> | undefined; if (measurements) { const nonZero = Object.fromEntries( - Object.entries(measurements).filter(([, v]) => v !== 0) + Object.entries(measurements).filter(([, v]) => v !== 0), ); if (Object.keys(nonZero).length > 0) { cleanedMeasurements = nonZero; @@ -364,7 +364,7 @@ function filterSpanMeasurements(span: TraceSpan): TraceSpan { */ function jsonTransformTraceView( data: TraceViewData, - fields?: string[] + fields?: string[], ): unknown { const { summary, spans, details } = data; const cleanedSpans = (spans as TraceSpan[]).map((span) => { @@ -395,11 +395,11 @@ type ResolvedTrace = { */ async function resolveTraceFromIssue( issueShortId: string, - cwd: string + cwd: string, ): Promise<ResolvedTrace> { const log = logger.withTag("trace.view"); log.warn( - `'${issueShortId}' is an issue short ID, not a trace ID. Looking up the issue's trace.` + `'${issueShortId}' is an issue short ID, not a trace ID. Looking up the issue's trace.`, ); const resolved = await resolveOrg({ cwd }); @@ -420,7 +420,7 @@ async function resolveTraceFromIssue( if (!traceId) { throw new ValidationError( `Could not find a trace for issue '${issueShortId}'. The latest event has no trace context.\n\n` + - `Try: sentry issue view ${issueShortId}` + `Try: sentry issue view ${issueShortId}`, ); } @@ -430,8 +430,8 @@ async function resolveTraceFromIssue( /** * Fetch per-span details when --full or --json is active. * - * Extracted from func() to keep cognitive complexity under the Biome - * limit of 15. Logs a warning for large traces and reports progress + * Extracted from func() to keep cognitive complexity manageable. + * Logs a warning for large traces and reports progress * on stderr for traces with more than {@link PROGRESS_THRESHOLD} spans. */ function fetchTraceSpanDetails( @@ -441,7 +441,7 @@ function fetchTraceSpanDetails( org: string; fallbackProject: string; traceId: string; - } + }, ): Promise<Map<string, TraceItemDetail>> { const log = logger.withTag("trace.view"); const flat = flattenSpanTree(spans); @@ -449,7 +449,7 @@ function fetchTraceSpanDetails( if (totalCount > LARGE_TRACE_WARN_THRESHOLD) { log.warn( `Trace has ${totalCount} spans \u2014 this may take a moment. ` + - "Use 'sentry span view' for specific spans." + "Use 'sentry span view' for specific spans.", ); } @@ -526,13 +526,13 @@ export const viewCommand = buildCommand({ } else { const parsed = await parseTraceTargetWithRecovery( correctedArgs, - USAGE_HINT + USAGE_HINT, ); warnIfNormalized(parsed, "trace.view"); const target = await resolveTraceOrgOptionalProject( parsed, cwd, - USAGE_HINT + USAGE_HINT, ); resolved = { ...target, @@ -573,7 +573,7 @@ export const viewCommand = buildCommand({ [ "Check that you are querying the right org/project", "The trace may be past your plan's retention window", - ] + ], ); } diff --git a/packages/cli/src/commands/trial/list.ts b/packages/cli/src/commands/trial/list.ts index e8e795b0c..e3c8100a8 100644 --- a/packages/cli/src/commands/trial/list.ts +++ b/packages/cli/src/commands/trial/list.ts @@ -238,7 +238,7 @@ export const listCommand = buildCommand({ const info = await withProgress( { message: "Fetching trials...", json: flags.json }, - () => getCustomerTrialInfo(resolved.org) + () => getCustomerTrialInfo(resolved.org), ); const productTrials = info.productTrials ?? []; @@ -253,7 +253,7 @@ export const listCommand = buildCommand({ lengthDays: t.lengthDays, startDate: t.startDate, endDate: t.endDate, - })) + })), ); // Add plan-level trial entry (available or active) at the top @@ -264,14 +264,14 @@ export const listCommand = buildCommand({ const hints: string[] = []; const hasAvailableProduct = entries.some( - (e) => e.status === "available" && e.category !== "plan" + (e) => e.status === "available" && e.category !== "plan", ); if (hasAvailableProduct) { hints.push("Tip: Use 'sentry trial start <name>' to start a trial"); } if (planEntry?.status === "available") { hints.push( - "Tip: Use 'sentry trial start plan' to start a Business plan trial" + "Tip: Use 'sentry trial start plan' to start a Business plan trial", ); } diff --git a/packages/cli/src/commands/trial/start.ts b/packages/cli/src/commands/trial/start.ts index e44faae0a..07263bb75 100644 --- a/packages/cli/src/commands/trial/start.ts +++ b/packages/cli/src/commands/trial/start.ts @@ -58,7 +58,7 @@ function isValidTrialArg(name: string): boolean { */ function parseTrialStartArgs( first: string, - second?: string + second?: string, ): { name: string; org?: string; warning?: string } { if (!second) { // Single arg — must be a trial name @@ -112,7 +112,7 @@ export const startCommand = buildCommand({ this: SentryContext, flags: { json?: boolean }, first: string, - second?: string + second?: string, ) { const logger = log.withTag("trial"); const parsed = parseTrialStartArgs(first, second); @@ -125,7 +125,7 @@ export const startCommand = buildCommand({ if (parsed.name !== "plan" && !isTrialName(parsed.name)) { throw new ValidationError( `Unknown trial name: '${parsed.name}'. Valid names: ${NAMES_LIST}`, - "name" + "name", ); } @@ -155,7 +155,7 @@ export const startCommand = buildCommand({ const displayName = getDisplayNameForTrialName(parsed.name); throw new ValidationError( `No ${displayName} trial available for organization '${orgSlug}'.`, - "name" + "name", ); } @@ -216,7 +216,7 @@ async function promptOpenBrowser(url: string): Promise<boolean> { */ async function* handlePlanTrial( orgSlug: string, - json: boolean + json: boolean, ): AsyncGenerator<unknown, void, undefined> { const logger = log.withTag("trial"); @@ -227,7 +227,7 @@ async function* handlePlanTrial( const planName = info.planDetails?.name ?? "Business"; throw new ValidationError( `Organization '${orgSlug}' is already on a ${planName} plan trial.`, - "name" + "name", ); } @@ -235,7 +235,7 @@ async function* handlePlanTrial( if (info.canTrial !== true) { throw new ValidationError( `No plan trial available for organization '${orgSlug}'.`, - "name" + "name", ); } @@ -246,7 +246,7 @@ async function* handlePlanTrial( if (!json) { const currentPlan = info.planDetails?.name ?? "current plan"; logger.info( - `The ${currentPlan} → Business plan trial must be activated in the Sentry UI.` + `The ${currentPlan} → Business plan trial must be activated in the Sentry UI.`, ); // Show URL and QR code through the output framework diff --git a/packages/cli/src/commands/wasm-split.ts b/packages/cli/src/commands/wasm-split.ts index 9b3982d7f..e53c5815a 100644 --- a/packages/cli/src/commands/wasm-split.ts +++ b/packages/cli/src/commands/wasm-split.ts @@ -142,7 +142,7 @@ export const wasmSplitCommand = buildCommand({ if (!input?.trim()) { throw new ValidationError( "Wasm file path is required: sentry wasm-split <input>", - "input" + "input", ); } @@ -195,7 +195,7 @@ function parseExplicitBuildId(value: string | undefined): Uint8Array | null { if (!bytes) { throw new ValidationError( `Invalid --build-id '${value}': expected a UUID.`, - "build-id" + "build-id", ); } return bytes; @@ -210,7 +210,7 @@ function parseExplicitBuildId(value: string | undefined): Uint8Array | null { */ function resolveExternalDebugInfo( url: string | undefined, - debugOut: string | undefined + debugOut: string | undefined, ): { externalDebugInfo?: string } { const resolved = url ?? (debugOut ? basename(debugOut) : undefined); return resolved ? { externalDebugInfo: resolved } : {}; @@ -228,13 +228,13 @@ async function readWasmModule(path: string): Promise<Uint8Array> { if (code === "EISDIR") { throw new ValidationError( `Path '${path}' is a directory, not a wasm file.`, - "input" + "input", ); } const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `Cannot read wasm file '${path}': ${msg}`, - "input" + "input", ); } } diff --git a/packages/cli/src/global.d.ts b/packages/cli/src/global.d.ts index 49be9913e..0d36faf7e 100644 --- a/packages/cli/src/global.d.ts +++ b/packages/cli/src/global.d.ts @@ -22,12 +22,12 @@ declare class Worker { onerror: ((event: ErrorEvent) => void) | null; addEventListener<K extends keyof WorkerEventMap>( type: K, - listener: (event: WorkerEventMap[K]) => void + listener: (event: WorkerEventMap[K]) => void, ): void; addEventListener(type: string, listener: (event: Event) => void): void; removeEventListener<K extends keyof WorkerEventMap>( type: K, - listener: (event: WorkerEventMap[K]) => void + listener: (event: WorkerEventMap[K]) => void, ): void; removeEventListener(type: string, listener: (event: Event) => void): void; ref(): void; @@ -36,7 +36,7 @@ declare class Worker { // RegExp.escape — ES2025 proposal, available in Node 23.6+ // Used by src/lib/api/projects.ts with a runtime feature-detection guard. -// biome-ignore lint/style/useConsistentTypeDefinitions: interface augmentation requires `interface`, not `type` +// interface augmentation requires `interface`, not `type` interface RegExpConstructor { escape?(s: string): string; } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index f2714355d..a164e756b 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -54,7 +54,7 @@ export type { SentrySDK } from "./sdk.generated.js"; * ``` */ export function createSentrySDK( - options?: import("./lib/sdk-types.js").SentryOptions + options?: import("./lib/sdk-types.js").SentryOptions, ) { const methods = createSDKMethods(buildInvoker(options)); const run = buildRunner(options); diff --git a/packages/cli/src/lib/agent-skills.ts b/packages/cli/src/lib/agent-skills.ts index cd81d506e..27f585ed3 100644 --- a/packages/cli/src/lib/agent-skills.ts +++ b/packages/cli/src/lib/agent-skills.ts @@ -44,7 +44,7 @@ export function detectClaudeCode(homeDir: string): boolean { */ export function getSkillInstallPath( homeDir: string, - rootDir: ".agents" | ".claude" = ".claude" + rootDir: ".agents" | ".claude" = ".claude", ): string { return join(homeDir, rootDir, "skills", "sentry-cli", "SKILL.md"); } @@ -75,12 +75,12 @@ export function getSkillInstallPath( */ async function atomicWriteFile( destPath: string, - content: string + content: string, ): Promise<void> { const dir = dirname(destPath); const tempPath = join( dir, - `.${basename(destPath)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp` + `.${basename(destPath)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`, ); try { await writeFile(tempPath, content, "utf-8"); @@ -103,7 +103,7 @@ async function atomicWriteFile( * this helper. Returns null on any filesystem failure. */ async function writeSkillFiles( - skillPath: string + skillPath: string, ): Promise<AgentSkillLocation | null> { try { const skillDir = dirname(skillPath); @@ -150,7 +150,7 @@ async function writeSkillFiles( * @returns Location info if installed to at least one detected target, null otherwise */ export async function installAgentSkills( - homeDir: string + homeDir: string, ): Promise<AgentSkillLocation | null> { const installTargets = [ { @@ -181,7 +181,7 @@ export async function installAgentSkills( } const location = await writeSkillFiles( - getSkillInstallPath(homeDir, target.rootDir) + getSkillInstallPath(homeDir, target.rootDir), ); if (location) { results.push(location); diff --git a/packages/cli/src/lib/alias.ts b/packages/cli/src/lib/alias.ts index 5b29831a0..4135820b2 100644 --- a/packages/cli/src/lib/alias.ts +++ b/packages/cli/src/lib/alias.ts @@ -78,7 +78,7 @@ export function findCommonWordPrefix(strings: string[]): string { * // Map { "frontend" => "fr", "functions" => "fu", "backend" => "b" } */ export function findShortestUniquePrefixes( - strings: string[] + strings: string[], ): Map<string, string> { const result = new Map<string, string>(); @@ -171,7 +171,7 @@ function groupByProjectSlug(pairs: OrgProjectPair[]): { */ function applyPrefixRelationships( slugs: string[], - slugToRemainder: Map<string, string> + slugToRemainder: Map<string, string>, ): void { // Collect all existing remainders to detect potential collisions const existingRemainders = new Set(slugToRemainder.values()); @@ -203,7 +203,7 @@ function applyPrefixRelationships( function processUniqueSlugs( pairs: OrgProjectPair[], uniqueSlugs: Set<string>, - aliasMap: Map<string, string> + aliasMap: Map<string, string>, ): void { const uniqueProjects = pairs.filter((p) => uniqueSlugs.has(p.project)); const uniqueProjectSlugs = [...new Set(uniqueProjects.map((p) => p.project))]; @@ -242,7 +242,7 @@ function processUniqueSlugs( function processCollidingSlugs( projectToOrgs: Map<string, Set<string>>, collidingSlugs: Set<string>, - aliasMap: Map<string, string> + aliasMap: Map<string, string>, ): void { // Get all orgs involved in collisions const collidingOrgs = new Set<string>(); @@ -306,7 +306,7 @@ function processCollidingSlugs( * // { aliasMap: Map { "org1/dashboard" => "o1/d", "org2/dashboard" => "o2/d" } } */ export function buildOrgAwareAliases( - pairs: OrgProjectPair[] + pairs: OrgProjectPair[], ): OrgAwareAliasResult { const aliasMap = new Map<string, string>(); @@ -353,7 +353,7 @@ export type AliasMapResult = { * @returns Alias map and DB entries */ export function buildProjectAliasMap<T extends { target: ResolvedTarget }>( - results: T[] + results: T[], ): AliasMapResult { const entries: Record<string, ProjectAliasEntry> = {}; const pairs = results.map((r) => ({ diff --git a/packages/cli/src/lib/api-schema.ts b/packages/cli/src/lib/api-schema.ts index 70ba5ede2..8b807356d 100644 --- a/packages/cli/src/lib/api-schema.ts +++ b/packages/cli/src/lib/api-schema.ts @@ -87,7 +87,7 @@ export function parseEndpointQuery(query: string): ParsedEndpointQuery { */ export function findEndpointsByPath( path: string, - method?: string + method?: string, ): ApiEndpoint[] { const methodUpper = method?.toUpperCase(); const normalized = normalizeApiPath(path); @@ -102,7 +102,7 @@ export function findEndpointsByPath( } const exact = candidates.filter( - (endpoint) => normalizeApiPath(endpoint.path) === normalized + (endpoint) => normalizeApiPath(endpoint.path) === normalized, ); if (exact.length > 0) { return exact; @@ -137,7 +137,7 @@ export function findEndpointsByIdentifier(identifier: string): ApiEndpoint[] { return schema.filter( (endpoint) => endpoint.fn.toLowerCase() === normalized || - endpoint.operationId.toLowerCase() === normalized + endpoint.operationId.toLowerCase() === normalized, ); } @@ -159,20 +159,20 @@ export function getEndpointsByResource(resource: string): ApiEndpoint[] { */ export function getEndpoint( resource: string, - operationQuery: string + operationQuery: string, ): ApiEndpoint | undefined { const lowerResource = resource.toLowerCase(); const lowerQuery = operationQuery.toLowerCase(); const candidates = schema.filter( - (e) => e.resource.toLowerCase() === lowerResource + (e) => e.resource.toLowerCase() === lowerResource, ); // Priority 1: operationId starts with the query (e.g., "list" → "List an Org's Issues") const startMatch = candidates.find( (e) => e.operationId.toLowerCase().startsWith(lowerQuery) || - e.fn.toLowerCase().startsWith(lowerQuery) + e.fn.toLowerCase().startsWith(lowerQuery), ); if (startMatch) { return startMatch; @@ -182,7 +182,7 @@ export function getEndpoint( return candidates.find( (e) => e.operationId.toLowerCase().includes(lowerQuery) || - e.fn.toLowerCase().includes(lowerQuery) + e.fn.toLowerCase().includes(lowerQuery), ); } @@ -209,7 +209,7 @@ export function searchEndpoints(query: string): ApiEndpoint[] { e.path.toLowerCase().includes(lower) || e.description.toLowerCase().includes(lower) || e.resource.toLowerCase().includes(lower) || - e.operationId.toLowerCase().includes(lower) + e.operationId.toLowerCase().includes(lower), ); if (!parsed.method) { return matches; diff --git a/packages/cli/src/lib/api/alerts.ts b/packages/cli/src/lib/api/alerts.ts index c1259e706..784667540 100644 --- a/packages/cli/src/lib/api/alerts.ts +++ b/packages/cli/src/lib/api/alerts.ts @@ -162,7 +162,7 @@ function pickDetectorOwner(owner: MetricDetector["owner"]): string | null { * nested `snubaQuery`) doesn't flatten everything to empty. */ function resolveThresholdSource( - source: Record<string, unknown> + source: Record<string, unknown>, ): Record<string, unknown> { const asRecord = (value: unknown): Record<string, unknown> | undefined => value && typeof value === "object" && !Array.isArray(value) @@ -187,7 +187,7 @@ function resolveThresholdSource( } function mapDetectorToMetricAlertRule( - detector: MetricDetector + detector: MetricDetector, ): MetricAlertRule { const source = detector.dataSources?.[0] ?? {}; const nested = resolveThresholdSource(source); @@ -232,7 +232,7 @@ function mapDetectorToMetricAlertRule( * with no `detectorIds`. Mirrors the sentry-mcp filter of the same name. */ function filterAttachedIssueAlertRules( - rules: IssueAlertRule[] + rules: IssueAlertRule[], ): IssueAlertRule[] { return rules.filter((rule) => (rule.detectorIds ?? []).length > 0); } @@ -248,7 +248,7 @@ function filterAttachedIssueAlertRules( export async function listIssueAlertsPaginated( orgSlug: string, projectSlug: string, - options: { perPage?: number; cursor?: string } = {} + options: { perPage?: number; cursor?: string } = {}, ): Promise<PaginatedResponse<IssueAlertRule[]>> { const regionUrl = await resolveOrgRegion(orgSlug); const { data, headers } = await apiRequestToRegion<IssueAlertRule[]>( @@ -261,7 +261,7 @@ export async function listIssueAlertsPaginated( per_page: options.perPage, cursor: options.cursor, }, - } + }, ); const { nextCursor } = parseLinkHeader(headers.get("link") ?? null); return { data: filterAttachedIssueAlertRules(data), nextCursor }; @@ -274,7 +274,7 @@ export async function listIssueAlertsPaginated( */ export async function getIssueAlertWorkflowDocument( orgSlug: string, - workflowId: string + workflowId: string, ): Promise<Record<string, unknown>> { const config = await getOrgSdkConfig(orgSlug); const result = await getOrganizationWorkflow({ @@ -286,7 +286,7 @@ export async function getIssueAlertWorkflowDocument( }); return unwrapResult<Record<string, unknown>>( result, - "Failed to fetch issue alert rule" + "Failed to fetch issue alert rule", ); } @@ -299,7 +299,7 @@ export async function getIssueAlertWorkflowDocument( */ export async function resolveErrorDetectorId( orgSlug: string, - projectSlug: string + projectSlug: string, ): Promise<number> { const config = await getOrgSdkConfig(orgSlug); const result = await listOrganizationDetectors({ @@ -309,7 +309,7 @@ export async function resolveErrorDetectorId( }); const detectors = unwrapResult<Array<{ id: string | number }>>( result, - "Failed to resolve error detector" + "Failed to resolve error detector", ); const detector = detectors[0]; if (!detector) { @@ -317,7 +317,7 @@ export async function resolveErrorDetectorId( `No error detector found for project '${projectSlug}'`, 404, undefined, - `/organizations/${orgSlug}/detectors/` + `/organizations/${orgSlug}/detectors/`, ); } return Number(detector.id); @@ -337,13 +337,13 @@ export async function resolveErrorDetectorId( export async function getIssueAlertRule( orgSlug: string, projectSlug: string, - ruleId: string + ruleId: string, ): Promise<IssueAlertRule> { const regionUrl = await resolveOrgRegion(orgSlug); const { data } = await apiRequestToRegion<IssueAlertRule[]>( regionUrl, `/organizations/${orgSlug}/workflows/`, - { params: { projectSlug, id: ruleId, per_page: 1 } } + { params: { projectSlug, id: ruleId, per_page: 1 } }, ); const rule = filterAttachedIssueAlertRules(data)[0]; if (!rule) { @@ -351,7 +351,7 @@ export async function getIssueAlertRule( `Issue alert rule '${ruleId}' not found`, 404, undefined, - `/organizations/${orgSlug}/workflows/` + `/organizations/${orgSlug}/workflows/`, ); } return rule; @@ -384,7 +384,7 @@ const METRIC_DETECTOR_QUERY = "type:metric_issue"; */ export async function listMetricAlertsPaginated( orgSlug: string, - options: { perPage?: number; cursor?: string } = {} + options: { perPage?: number; cursor?: string } = {}, ): Promise<PaginatedResponse<MetricAlertRule[]>> { const config = await getOrgSdkConfig(orgSlug); const result = await listOrganizationDetectors({ @@ -404,7 +404,7 @@ export async function listMetricAlertsPaginated( }); const { data, nextCursor } = unwrapPaginatedResult<MetricDetector[]>( result, - "Failed to list metric alert rules" + "Failed to list metric alert rules", ); return { data: data.map(mapDetectorToMetricAlertRule), nextCursor }; } @@ -426,7 +426,7 @@ export async function listMetricAlertsPaginated( */ export async function getMetricAlertRule( orgSlug: string, - ruleId: string + ruleId: string, ): Promise<MetricAlertRule> { const config = await getOrgSdkConfig(orgSlug); const result = await getOrganizationDetector({ @@ -443,14 +443,14 @@ export async function getMetricAlertRule( }); const data = unwrapResult<MetricDetector>( result, - `Failed to get metric alert rule '${ruleId}'` + `Failed to get metric alert rule '${ruleId}'`, ); if (data.type !== undefined && data.type !== "metric_issue") { throw new ApiError( `Metric alert rule '${ruleId}' not found`, 404, `Detector '${ruleId}' is of type '${data.type}', not a metric alert.`, - `/organizations/${orgSlug}/detectors/${encodeURIComponent(ruleId)}/` + `/organizations/${orgSlug}/detectors/${encodeURIComponent(ruleId)}/`, ); } return mapDetectorToMetricAlertRule(data); @@ -469,13 +469,13 @@ export async function getMetricAlertRule( */ export async function deleteIssueAlertRule( orgSlug: string, - ruleId: string + ruleId: string, ): Promise<void> { const regionUrl = await resolveOrgRegion(orgSlug); await apiRequestToRegionNoContent( regionUrl, `/organizations/${orgSlug}/workflows/${encodeURIComponent(ruleId)}/`, - { method: "DELETE" } + { method: "DELETE" }, ); } @@ -488,7 +488,7 @@ export async function deleteIssueAlertRule( export async function updateIssueAlertRule( orgSlug: string, workflowId: string, - body: Record<string, unknown> + body: Record<string, unknown>, ): Promise<Record<string, unknown>> { const config = await getOrgSdkConfig(orgSlug); const result = await updateOrganizationWorkflow({ @@ -505,7 +505,7 @@ export async function updateIssueAlertRule( }); return unwrapResult<Record<string, unknown>>( result, - "Failed to update issue alert rule" + "Failed to update issue alert rule", ); } @@ -518,7 +518,7 @@ export async function updateIssueAlertRule( */ export async function createIssueAlertRule( orgSlug: string, - body: Record<string, unknown> + body: Record<string, unknown>, ): Promise<Record<string, unknown>> { const config = await getOrgSdkConfig(orgSlug); const result = await createOrganizationWorkflow({ @@ -532,7 +532,7 @@ export async function createIssueAlertRule( }); return unwrapResult<Record<string, unknown>>( result, - "Failed to create issue alert rule" + "Failed to create issue alert rule", ); } @@ -578,7 +578,7 @@ function datasetEventTypes(dataset: string): string[] { * conversion in `mapDetectorToMetricAlertRule`. */ function buildDetectorDataSource( - body: Record<string, unknown> + body: Record<string, unknown>, ): Record<string, unknown> { const dataset = typeof body.dataset === "string" ? body.dataset : ""; const timeWindowMinutes = @@ -607,7 +607,7 @@ function buildDetectorDataSource( * (0 active / 1 disabled) maps onto the detector's `enabled` boolean. */ function buildDetectorBody( - body: Record<string, unknown> + body: Record<string, unknown>, ): Record<string, unknown> { const detectorBody: Record<string, unknown> = { name: typeof body.name === "string" ? body.name : "", @@ -646,12 +646,13 @@ function buildDetectorBody( function resolveDetectorProject(body: Record<string, unknown>): string { const projects = Array.isArray(body.projects) ? body.projects : []; const project = projects.find( - (value): value is string => typeof value === "string" && value.trim() !== "" + (value): value is string => + typeof value === "string" && value.trim() !== "", ); if (!project) { throw new ValidationError( "A project is required to create a metric alert rule (pass --project).", - "project" + "project", ); } return project; @@ -665,7 +666,7 @@ function resolveDetectorProject(body: Record<string, unknown>): string { */ export async function deleteMetricAlertRule( orgSlug: string, - ruleId: string + ruleId: string, ): Promise<void> { const config = await getOrgSdkConfig(orgSlug); const result = await deleteOrganizationDetector({ @@ -693,7 +694,7 @@ export async function deleteMetricAlertRule( */ export async function getMetricAlertRuleDocument( orgSlug: string, - ruleId: string + ruleId: string, ): Promise<Record<string, unknown>> { const rule = await getMetricAlertRule(orgSlug, ruleId); return { ...rule }; @@ -708,7 +709,7 @@ export async function getMetricAlertRuleDocument( export async function putMetricAlertRule( orgSlug: string, ruleId: string, - body: Record<string, unknown> + body: Record<string, unknown>, ): Promise<Record<string, unknown>> { const config = await getOrgSdkConfig(orgSlug); const result = await updateOrganizationDetector({ @@ -729,7 +730,7 @@ export async function putMetricAlertRule( }); return unwrapResult<Record<string, unknown>>( result, - `Failed to update metric alert rule '${ruleId}'` + `Failed to update metric alert rule '${ruleId}'`, ); } @@ -741,7 +742,7 @@ export async function putMetricAlertRule( */ export async function createMetricAlertRule( orgSlug: string, - body: Record<string, unknown> + body: Record<string, unknown>, ): Promise<Record<string, unknown>> { const projectSlug = resolveDetectorProject(body); const config = await getOrgSdkConfig(orgSlug); @@ -757,6 +758,6 @@ export async function createMetricAlertRule( }); return unwrapResult<Record<string, unknown>>( result, - "Failed to create metric alert rule" + "Failed to create metric alert rule", ); } diff --git a/packages/cli/src/lib/api/auth.ts b/packages/cli/src/lib/api/auth.ts index 06a757f45..4251b7038 100644 --- a/packages/cli/src/lib/api/auth.ts +++ b/packages/cli/src/lib/api/auth.ts @@ -7,7 +7,7 @@ const AuthStatusSchema = object({ auth: nullable( object({ scopes: array(string()), - }) + }), ), }); diff --git a/packages/cli/src/lib/api/chunk-upload.ts b/packages/cli/src/lib/api/chunk-upload.ts index 832fc80b9..3662762c2 100644 --- a/packages/cli/src/lib/api/chunk-upload.ts +++ b/packages/cli/src/lib/api/chunk-upload.ts @@ -143,13 +143,13 @@ export type UploadEncoding = (typeof UPLOAD_CODECS)[number]; * @returns Server-provided upload options (chunk size, concurrency, etc.) */ export async function getChunkUploadOptions( - orgSlug: string + orgSlug: string, ): Promise<ChunkServerOptions> { const regionUrl = await resolveOrgRegion(orgSlug); const { data } = await apiRequestToRegion<ChunkServerOptions>( regionUrl, `organizations/${orgSlug}/chunk-upload/`, - { schema: ChunkServerOptionsSchema } + { schema: ChunkServerOptionsSchema }, ); return data; } @@ -163,7 +163,7 @@ export async function getChunkUploadOptions( * Exported for testing. */ export function pickUploadEncoding( - compression: string[] + compression: string[], ): UploadEncoding | undefined { for (const codec of UPLOAD_CODECS) { // Skip zstd when the local runtime can't produce it (Node < 22.15). @@ -176,7 +176,7 @@ export function pickUploadEncoding( } if (compression.length > 0) { log.debug( - `server advertised unsupported codecs [${compression.join(", ")}]; falling back to plain upload` + `server advertised unsupported codecs [${compression.join(", ")}]; falling back to plain upload`, ); } return; @@ -191,7 +191,7 @@ export function pickUploadEncoding( */ export async function encodeChunk( buf: Buffer, - encoding: UploadEncoding | undefined + encoding: UploadEncoding | undefined, ): Promise<Uint8Array> { if (encoding === "zstd") { // pickUploadEncoding never selects zstd when the runtime lacks it, so this @@ -200,7 +200,7 @@ export async function encodeChunk( // corrupt the upload on the server. if (!zstdCompressAsync) { throw new Error( - "zstd encoding requested but unavailable on this runtime (Node < 22.15)" + "zstd encoding requested but unavailable on this runtime (Node < 22.15)", ); } // L3 is libzstd's default; passed explicitly for self-documenting @@ -264,7 +264,7 @@ export async function uploadChunk(params: { form.append( fieldName, new Blob([payload], { type: "application/octet-stream" }), - chunk.sha1 + chunk.sha1, ); const init: RequestInit = { method: "POST", body: form }; @@ -278,7 +278,7 @@ export async function uploadChunk(params: { `Chunk upload failed: ${response.status} ${response.statusText}`, response.status, await response.text().catch(() => ""), - url + url, ); } } @@ -294,7 +294,7 @@ export async function uploadChunk(params: { */ export async function hashChunks( zipPath: string, - chunkSize: number + chunkSize: number, ): Promise<{ chunks: ChunkInfo[]; overallChecksum: string }> { const fh = await open(zipPath, "r"); try { @@ -330,7 +330,7 @@ export async function hashChunks( */ export function hashBuffer( content: Buffer, - chunkSize: number + chunkSize: number, ): { chunks: ChunkInfo[]; overallChecksum: string } { const chunks: ChunkInfo[] = []; const overallHasher = createHash("sha1"); @@ -394,9 +394,9 @@ export async function uploadMissingChunks(params: { encoding, fetch: authFetch, url: serverOptions.url, - }) - ) - ) + }), + ), + ), ); } @@ -423,7 +423,7 @@ async function uploadBufferChunk(params: { form.append( fieldName, new Blob([payload], { type: "application/octet-stream" }), - chunk.sha1 + chunk.sha1, ); const init: RequestInit = { method: "POST", body: form }; @@ -440,7 +440,7 @@ async function uploadBufferChunk(params: { log.debug("Failed to read chunk upload error response body", err); return ""; }), - url + url, ); } } @@ -486,9 +486,9 @@ export async function uploadMissingBufferChunks(params: { encoding, fetch: authFetch, url: serverOptions.url, - }) - ) - ) + }), + ), + ), ); } @@ -538,7 +538,7 @@ export async function pollAssembly(params: { method: "POST", body, schema, - } + }, ); if (pollResult.state === "ok") { @@ -556,7 +556,7 @@ export async function pollAssembly(params: { `${entityName} assembly failed`, 500, pollResult.detail ?? "Unknown error", - endpoint + endpoint, ); } // "not_found", "assembling", or ("created" while waiting) — keep polling @@ -566,6 +566,6 @@ export async function pollAssembly(params: { `${entityName} assembly timed out`, 408, `Assembly did not complete within ${deadlineMs / 1000}s`, - endpoint + endpoint, ); } diff --git a/packages/cli/src/lib/api/code-mappings.ts b/packages/cli/src/lib/api/code-mappings.ts index 81df0a3b0..942ed7240 100644 --- a/packages/cli/src/lib/api/code-mappings.ts +++ b/packages/cli/src/lib/api/code-mappings.ts @@ -94,7 +94,7 @@ export type MergedCodeMappingsResponse = { * @returns Merged response with created/updated/errors counts */ export async function uploadCodeMappings( - options: CodeMappingsUploadOptions + options: CodeMappingsUploadOptions, ): Promise<MergedCodeMappingsResponse> { const { org, project, repository, defaultBranch, mappings } = options; const regionUrl = await resolveOrgRegion(org); @@ -115,7 +115,7 @@ export async function uploadCodeMappings( if (totalBatches > 1) { log.debug( - `Uploading batch ${batchNum}/${totalBatches} (${batch.length} mappings)` + `Uploading batch ${batchNum}/${totalBatches} (${batch.length} mappings)`, ); } @@ -131,7 +131,7 @@ export async function uploadCodeMappings( mappings: batch, }, schema: BulkCodeMappingsResponseSchema, - } + }, ); merged.created += data.created; diff --git a/packages/cli/src/lib/api/conversations.ts b/packages/cli/src/lib/api/conversations.ts index 882da8841..f2b0ebc5f 100644 --- a/packages/cli/src/lib/api/conversations.ts +++ b/packages/cli/src/lib/api/conversations.ts @@ -53,7 +53,7 @@ async function fetchConversationsPage( end?: string; project?: string; }, - perPage: number + perPage: number, ): Promise<PaginatedResponse<ConversationListItem[]>> { const params: Record<string, string> = { per_page: String(perPage), @@ -80,7 +80,7 @@ async function fetchConversationsPage( const { data, headers } = await apiRequestToRegion<ConversationListItem[]>( regionUrl, `/organizations/${orgSlug}/agents/conversations/`, - { params, schema: array(ConversationListItemSchema) } + { params, schema: array(ConversationListItemSchema) }, ); const { nextCursor } = parseLinkHeader(headers.get("link") ?? null); @@ -108,11 +108,11 @@ export async function listConversations( start?: string; end?: string; project?: string; - } = {} + } = {}, ): Promise<PaginatedResponse<ConversationListItem[]>> { const regionUrl = await resolveOrgRegion(orgSlug); return paginate(options, (perPage, cursor) => - fetchConversationsPage(regionUrl, orgSlug, { ...options, cursor }, perPage) + fetchConversationsPage(regionUrl, orgSlug, { ...options, cursor }, perPage), ); } @@ -123,7 +123,7 @@ export async function getConversationSpans( statsPeriod?: string; project?: string; perPage?: number; - } = {} + } = {}, ): Promise<{ spans: AgentConversationSpan[]; truncated: boolean; @@ -152,7 +152,7 @@ export async function getConversationSpans( await apiRequestToRegion<AgentConversationDetails>( regionUrl, `/organizations/${orgSlug}/agents/conversations/${encodeURIComponent(conversationId)}/`, - { params, schema: AgentConversationDetailsSchema } + { params, schema: AgentConversationDetailsSchema }, ); if (page === 0) { @@ -169,7 +169,7 @@ export async function getConversationSpans( const truncated = !!cursor; if (truncated) { log.warn( - `Pagination limit reached (${MAX_PAGINATION_PAGES} pages, ${spans.length} spans). Conversation transcript may be incomplete.` + `Pagination limit reached (${MAX_PAGINATION_PAGES} pages, ${spans.length} spans). Conversation transcript may be incomplete.`, ); } diff --git a/packages/cli/src/lib/api/dart-symbols.ts b/packages/cli/src/lib/api/dart-symbols.ts index 78b1d35a5..6ca5212e8 100644 --- a/packages/cli/src/lib/api/dart-symbols.ts +++ b/packages/cli/src/lib/api/dart-symbols.ts @@ -80,7 +80,7 @@ type AssembleCheckResult = { function checkAssembleResponse( response: DifAssembleResponse, checksum: string, - endpoint: string + endpoint: string, ): AssembleCheckResult { const missingChecksums = new Set<string>(); const entry: AssembleResponse | undefined = response[checksum]; @@ -95,7 +95,7 @@ function checkAssembleResponse( "Dart symbol map assembly failed", 500, entry.detail ?? "Unknown error", - endpoint + endpoint, ); } @@ -122,7 +122,7 @@ function checkAssembleResponse( * @throws {ApiError} If the upload or assembly fails */ export async function uploadDartSymbolMap( - options: DartSymbolMapUploadOptions + options: DartSymbolMapUploadOptions, ): Promise<void> { const { org, project, mapping } = options; @@ -133,7 +133,7 @@ export async function uploadDartSymbolMap( // Step 2: Hash the mapping file into chunks const { chunks, overallChecksum } = hashBuffer( mapping.content, - serverOptions.chunkSize + serverOptions.chunkSize, ); const regionUrl = await resolveOrgRegion(org); @@ -159,13 +159,13 @@ export async function uploadDartSymbolMap( method: "POST", body: assembleBody, schema: DifAssembleResponseSchema, - } + }, ); const { allDone, missingChecksums } = checkAssembleResponse( firstAssemble, overallChecksum, - assembleEndpoint + assembleEndpoint, ); if (allDone) { @@ -194,13 +194,13 @@ export async function uploadDartSymbolMap( method: "POST", body: assembleBody, schema: DifAssembleResponseSchema, - } + }, ); const { allDone: done } = checkAssembleResponse( pollResult, overallChecksum, - assembleEndpoint + assembleEndpoint, ); if (done) { @@ -212,6 +212,6 @@ export async function uploadDartSymbolMap( "Dart symbol map assembly timed out", 408, `Assembly did not complete within ${ASSEMBLE_MAX_WAIT_MS / 1000}s`, - assembleEndpoint + assembleEndpoint, ); } diff --git a/packages/cli/src/lib/api/dashboards.ts b/packages/cli/src/lib/api/dashboards.ts index 42e76a53e..5ee1e1917 100644 --- a/packages/cli/src/lib/api/dashboards.ts +++ b/packages/cli/src/lib/api/dashboards.ts @@ -11,7 +11,7 @@ import { listOrganizationDashboards, updateOrganizationDashboard, } from "@sentry/api"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import { array, safeParse } from "valibot"; @@ -62,7 +62,7 @@ import { */ export async function listDashboardsPaginated( orgSlug: string, - options: { perPage?: number; cursor?: string } = {} + options: { perPage?: number; cursor?: string } = {}, ): Promise<PaginatedResponse<DashboardListItem[]>> { const config = await getOrgSdkConfig(orgSlug); @@ -74,7 +74,7 @@ export async function listDashboardsPaginated( return unwrapPaginatedResult<DashboardListItem[]>( result, - "Failed to list dashboards" + "Failed to list dashboards", ); } @@ -87,7 +87,7 @@ export async function listDashboardsPaginated( */ export async function getDashboard( orgSlug: string, - dashboardId: string + dashboardId: string, ): Promise<DashboardDetail> { const config = await getOrgSdkConfig(orgSlug); @@ -101,7 +101,7 @@ export async function getDashboard( return unwrapResult<DashboardDetail>( result, - `Failed to get dashboard '${dashboardId}'` + `Failed to get dashboard '${dashboardId}'`, ); } @@ -114,7 +114,7 @@ export async function getDashboard( */ export async function createDashboard( orgSlug: string, - body: { title: string; widgets?: DashboardWidget[]; projects?: number[] } + body: { title: string; widgets?: DashboardWidget[]; projects?: number[] }, ): Promise<DashboardDetail> { const config = await getOrgSdkConfig(orgSlug); @@ -147,7 +147,7 @@ export async function updateDashboard( projects?: number[]; environment?: string[]; period?: string | null; - } + }, ): Promise<DashboardDetail> { const config = await getOrgSdkConfig(orgSlug); @@ -164,7 +164,7 @@ export async function updateDashboard( return unwrapResult<DashboardDetail>( result, - `Failed to update dashboard '${dashboardId}'` + `Failed to update dashboard '${dashboardId}'`, ); } @@ -183,7 +183,7 @@ export async function updateDashboard( export async function listDashboardRevisionsPaginated( orgSlug: string, dashboardId: string, - options: { perPage?: number; cursor?: string } = {} + options: { perPage?: number; cursor?: string } = {}, ): Promise<PaginatedResponse<DashboardRevision[]>> { const regionUrl = await resolveOrgRegion(orgSlug); const params: Record<string, string | number | undefined> = { @@ -194,11 +194,11 @@ export async function listDashboardRevisionsPaginated( const { data, headers } = await apiRequestToRegion<DashboardRevision[]>( regionUrl, `/organizations/${orgSlug}/dashboards/${dashboardId}/revisions/`, - { params, schema: array(DashboardRevisionSchema) } + { params, schema: array(DashboardRevisionSchema) }, ); const { nextCursor, prevCursor } = parseLinkHeader( - headers.get("link") ?? null + headers.get("link") ?? null, ); const out: PaginatedResponse<DashboardRevision[]> = { data }; if (nextCursor !== undefined) { @@ -221,14 +221,14 @@ export async function listDashboardRevisionsPaginated( export async function restoreDashboardRevision( orgSlug: string, dashboardId: string, - revisionId: string + revisionId: string, ): Promise<DashboardDetail> { const regionUrl = await resolveOrgRegion(orgSlug); const encodedRevisionId = encodeURIComponent(revisionId); const { data } = await apiRequestToRegion<DashboardDetail>( regionUrl, `/organizations/${orgSlug}/dashboards/${dashboardId}/revisions/${encodedRevisionId}/restore/`, - { method: "POST", schema: DashboardDetailSchema } + { method: "POST", schema: DashboardDetailSchema }, ); return data; } @@ -309,7 +309,7 @@ const VALID_INTERVALS = ["1m", "5m", "15m", "30m", "1h", "4h", "12h", "1d"]; export function computeOptimalInterval( statsPeriod: string | undefined, widget: DashboardWidget, - periodSeconds?: number + periodSeconds?: number, ): string | undefined { const totalSeconds = periodSeconds ?? (statsPeriod ? periodToSeconds(statsPeriod) : undefined); @@ -321,7 +321,7 @@ export function computeOptimalInterval( // Use DEFAULT_TERM_WIDTH as fallback for non-TTY (matches formatter). const termWidth = Math.max( MIN_TERM_WIDTH, - process.stdout.columns || DEFAULT_TERM_WIDTH + process.stdout.columns || DEFAULT_TERM_WIDTH, ); const layoutW = widget.layout?.w ?? GRID_COLS; const chartWidth = @@ -358,7 +358,7 @@ export function computeOptimalInterval( */ function parseEventsStatsResponse( raw: unknown, - yAxis: string[] + yAxis: string[], ): TimeseriesResult { const series: TimeseriesResult["series"] = []; @@ -378,7 +378,7 @@ function parseEventsStatsResponse( // Grouped response: record of group-label → series if (raw && typeof raw === "object" && !Array.isArray(raw)) { const entries = Object.entries(raw as Record<string, unknown>).filter( - ([, v]) => v && typeof v === "object" && "data" in (v as object) + ([, v]) => v && typeof v === "object" && "data" in (v as object), ); // Sort by order field if present @@ -405,7 +405,7 @@ function parseEventsStatsResponse( /** Extract {timestamp, value} pairs from an events-stats series. */ function extractTimeseriesValues( - series: EventsStatsSeries + series: EventsStatsSeries, ): { timestamp: number; value: number }[] { return series.data.map(([timestamp, counts]) => ({ timestamp, @@ -442,7 +442,7 @@ type WidgetQueryParams = { }; async function queryWidgetTimeseries( - params: WidgetQueryParams + params: WidgetQueryParams, ): Promise<TimeseriesResult> { const { regionUrl, @@ -489,7 +489,7 @@ async function queryWidgetTimeseries( const { data: raw } = await apiRequestToRegion<unknown>( regionUrl, `/organizations/${orgSlug}/events-stats/`, - { params: reqParams } + { params: reqParams }, ); const parsed = parseEventsStatsResponse(raw, aggregates); @@ -512,7 +512,7 @@ async function queryWidgetTimeseries( * @param options - Additional query options */ async function queryWidgetTable( - params: WidgetQueryParams + params: WidgetQueryParams, ): Promise<TableResult> { const { regionUrl, @@ -549,7 +549,7 @@ async function queryWidgetTable( project: options.project?.map(String), }, schema: EventsTableResponseSchema, - } + }, ); const meta = data.meta; @@ -575,7 +575,7 @@ async function queryWidgetTable( * doesn't break the entire dashboard render. */ async function queryWidgetData( - params: WidgetQueryParams + params: WidgetQueryParams, ): Promise<WidgetDataResult> { const { widget } = params; @@ -653,7 +653,7 @@ async function queryWidgetData( function collectBatchResults( batchResults: PromiseSettledResult<WidgetDataResult>[], startIndex: number, - results: Map<number, WidgetDataResult> + results: Map<number, WidgetDataResult>, ): void { for (let j = 0; j < batchResults.length; j++) { const result = batchResults[j]; @@ -675,7 +675,7 @@ export async function queryAllWidgets( regionUrl: string, orgSlug: string, dashboard: DashboardDetail, - options: WidgetQueryOptions = {} + options: WidgetQueryOptions = {}, ): Promise<Map<number, WidgetDataResult>> { const widgets = dashboard.widgets ?? []; // When absolute start/end are provided, skip relative statsPeriod — @@ -712,8 +712,8 @@ export async function queryAllWidgets( end: options.end, periodSeconds: options.periodSeconds, options: mergedOptions, - }) - ) + }), + ), ); collectBatchResults(batchResults, i, results); } diff --git a/packages/cli/src/lib/api/debug-files.ts b/packages/cli/src/lib/api/debug-files.ts index 7df24aa61..35cfaafa7 100644 --- a/packages/cli/src/lib/api/debug-files.ts +++ b/packages/cli/src/lib/api/debug-files.ts @@ -140,7 +140,7 @@ type AssembleEvaluation = { * `skip_serializing_if none` behavior). */ function buildAssembleBody( - chunked: ChunkedDif[] + chunked: ChunkedDif[], ): Record<string, { name: string; debug_id?: string; chunks: string[] }> { const body: Record< string, @@ -177,7 +177,7 @@ function buildAssembleBody( function evaluateAssembly( response: DifAssembleResponse, chunked: ChunkedDif[], - wait: boolean + wait: boolean, ): AssembleEvaluation { const missingChecksums = new Set<string>(); let allHeld = true; @@ -209,7 +209,7 @@ function evaluateAssembly( */ function classifyEntry( entry: AssembleResponse | undefined, - cd: ChunkedDif + cd: ChunkedDif, ): { held: boolean; terminal: boolean; missing: string[] } { if (!entry) { return { @@ -270,12 +270,12 @@ async function uploadMissing(params: { async function postAssemble( regionUrl: string, endpoint: string, - body: unknown + body: unknown, ): Promise<DifAssembleResponse> { const { data } = await apiRequestToRegion<DifAssembleResponse>( regionUrl, endpoint, - { method: "POST", body, schema: DifAssembleResponseSchema } + { method: "POST", body, schema: DifAssembleResponseSchema }, ); return data; } @@ -283,7 +283,7 @@ async function postAssemble( /** Build per-file results from the last-observed assemble response. */ function buildResults( chunked: ChunkedDif[], - response: DifAssembleResponse + response: DifAssembleResponse, ): DebugFileUploadResult[] { return chunked.map((cd) => { const entry = response[cd.overallChecksum]; @@ -311,7 +311,7 @@ function buildResults( */ function filterBySize( difs: DebugFileUpload[], - maxFileSize: number + maxFileSize: number, ): { accepted: DebugFileUpload[]; dropped: DebugFileUpload[] } { if (maxFileSize <= 0) { return { accepted: difs, dropped: [] }; @@ -321,7 +321,7 @@ function filterBySize( for (const dif of difs) { if (dif.content.length > maxFileSize) { log.warn( - `Skipping ${dif.name}: size ${dif.content.length} exceeds server maximum file size ${maxFileSize}` + `Skipping ${dif.name}: size ${dif.content.length} exceeds server maximum file size ${maxFileSize}`, ); dropped.push(dif); continue; @@ -346,7 +346,7 @@ function filterBySize( */ function buildOversizeResults( dropped: DebugFileUpload[], - maxFileSize: number + maxFileSize: number, ): DebugFileUploadResult[] { return dropped.map((dif) => ({ name: dif.name, @@ -371,7 +371,7 @@ function clampMaxWait(requestedMs: number, serverMaxWaitSec: number): number { const serverMaxMs = serverMaxWaitSec * 1000; if (serverMaxMs < requestedMs) { log.debug( - `Clamping assembly wait from ${requestedMs}ms to server maximum ${serverMaxMs}ms` + `Clamping assembly wait from ${requestedMs}ms to server maximum ${serverMaxMs}ms`, ); return serverMaxMs; } @@ -397,7 +397,7 @@ function clampMaxWait(requestedMs: number, serverMaxWaitSec: number): number { * not complete within the effective (clamped) wait. */ export async function uploadDebugFiles( - options: DebugFilesUploadOptions + options: DebugFilesUploadOptions, ): Promise<DebugFileUploadResult[]> { const { org, project, difs, wait, maxWaitMs } = options; @@ -426,14 +426,14 @@ export async function uploadDebugFiles( throw new ValidationError( `All ${difs.length} debug file(s) exceed the maximum file size ` + `(${effectiveMaxFileSize} bytes). Nothing was uploaded.`, - "file" + "file", ); } const chunked: ChunkedDif[] = accepted.map((dif) => { const { chunks, overallChecksum } = hashBuffer( dif.content, - serverOptions.chunkSize + serverOptions.chunkSize, ); return { dif, chunks, overallChecksum }; }); @@ -466,13 +466,13 @@ export async function uploadDebugFiles( "Debug file assembly timed out", 408, `Assembly did not complete within ${Math.round(effectiveMaxWaitMs / 1000)}s`, - endpoint + endpoint, ); } // No-wait mode: the server kept reporting missing chunks past the // deadline. Stop and report the last-observed state rather than hang. log.warn( - "Chunk delivery did not settle before the deadline — some files may not have been fully uploaded" + "Chunk delivery did not settle before the deadline — some files may not have been fully uploaded", ); break; } diff --git a/packages/cli/src/lib/api/events.ts b/packages/cli/src/lib/api/events.ts index ba2297ed0..5ea97360f 100644 --- a/packages/cli/src/lib/api/events.ts +++ b/packages/cli/src/lib/api/events.ts @@ -39,7 +39,7 @@ import { listOrganizations } from "./organizations.js"; */ export async function getLatestEvent( orgSlug: string, - issueId: string + issueId: string, ): Promise<SentryEvent> { const config = await getOrgSdkConfig(orgSlug); @@ -62,7 +62,7 @@ export async function getLatestEvent( export async function getEvent( orgSlug: string, projectSlug: string, - eventId: string + eventId: string, ): Promise<SentryEvent> { const config = await getOrgSdkConfig(orgSlug); @@ -87,7 +87,7 @@ export async function getEvent( export async function listEventAttachments( orgSlug: string, projectSlug: string, - eventId: string + eventId: string, ): Promise<EventAttachmentDetailsResponse[]> { const config = await getOrgSdkConfig(orgSlug); const { data } = await autoPaginate( @@ -110,10 +110,10 @@ export async function listEventAttachments( }); return unwrapPaginatedResult<EventAttachmentDetailsResponse[]>( result, - "Failed to list event attachments" + "Failed to list event attachments", ); }, - API_MAX_PER_PAGE * MAX_PAGINATION_PAGES + 1 + API_MAX_PER_PAGE * MAX_PAGINATION_PAGES + 1, ); return data; } @@ -137,7 +137,7 @@ export type ResolvedEvent = { */ export async function resolveEventInOrg( orgSlug: string, - eventId: string + eventId: string, ): Promise<ResolvedEvent | null> { const config = await getOrgSdkConfig(orgSlug); @@ -183,7 +183,7 @@ export type FindEventAcrossOrgsOptions = { */ export async function findEventAcrossOrgs( eventId: string, - options?: FindEventAcrossOrgsOptions + options?: FindEventAcrossOrgsOptions, ): Promise<ResolvedEvent | null> { const excludeSet = options?.excludeOrgs ? new Set(options.excludeOrgs) @@ -195,7 +195,7 @@ export async function findEventAcrossOrgs( const limit = pLimit(ORG_FANOUT_CONCURRENCY); const results = await Promise.allSettled( - orgs.map((org) => limit(() => resolveEventInOrg(org.slug, eventId))) + orgs.map((org) => limit(() => resolveEventInOrg(org.slug, eventId))), ); // First pass: return the first successful match @@ -266,7 +266,7 @@ export type ListIssueEventsOptions = { export async function listIssueEvents( orgSlug: string, issueId: string, - options: ListIssueEventsOptions = {} + options: ListIssueEventsOptions = {}, ): Promise<PaginatedResponse<IssueEvent[]>> { const { limit = 25, query, full, cursor, statsPeriod, start, end } = options; @@ -299,7 +299,7 @@ export async function listIssueEvents( const paginated = unwrapPaginatedResult( result, - "Failed to list issue events" + "Failed to list issue events", ); allEvents.push(...(paginated.data as IssueEvent[])); diff --git a/packages/cli/src/lib/api/explore.ts b/packages/cli/src/lib/api/explore.ts index 0014696ec..b5533e8a7 100644 --- a/packages/cli/src/lib/api/explore.ts +++ b/packages/cli/src/lib/api/explore.ts @@ -60,7 +60,7 @@ async function fetchEventsPage( regionUrl: string, orgSlug: string, options: ExploreQueryOptions, - perPage: number + perPage: number, ): Promise<PaginatedResponse<EventsTableResponse>> { const { data, headers } = await apiRequestToRegion<EventsTableResponse>( regionUrl, @@ -81,7 +81,7 @@ async function fetchEventsPage( cursor: options.cursor, }, schema: EventsTableResponseSchema, - } + }, ); const { nextCursor } = parseLinkHeader(headers.get("link") ?? null); @@ -111,7 +111,7 @@ export async function queryMetricsMeta( start?: string; end?: string; project?: string; - } + }, ): Promise<MetricMeta[]> { const regionUrl = await resolveOrgRegion(orgSlug); const query = options?.project ? `project:${options.project}` : undefined; @@ -136,7 +136,7 @@ export async function queryMetricsMeta( regionUrl, orgSlug, { ...baseOptions, cursor }, - API_MAX_PER_PAGE + API_MAX_PER_PAGE, ); allRows.push(...result.data.data); @@ -171,7 +171,7 @@ export async function queryMetricsMeta( */ export async function queryEvents( orgSlug: string, - options: ExploreQueryOptions + options: ExploreQueryOptions, ): Promise<PaginatedResponse<EventsTableResponse>> { const regionUrl = await resolveOrgRegion(orgSlug); const limit = options.limit ?? 25; @@ -192,7 +192,7 @@ export async function queryEvents( regionUrl, orgSlug, { ...options, cursor }, - perPage + perPage, ); if (page === 0) { diff --git a/packages/cli/src/lib/api/feedback.ts b/packages/cli/src/lib/api/feedback.ts index 798acbf4c..7ab66b9d8 100644 --- a/packages/cli/src/lib/api/feedback.ts +++ b/packages/cli/src/lib/api/feedback.ts @@ -57,7 +57,7 @@ export type FeedbackPage = { */ export function buildFeedbackQuery( status: FeedbackStatus, - query?: string + query?: string, ): string { const statusFilter = status === "all" ? undefined : STATUS_FILTERS[status]; const userQuery = query?.trim() || undefined; @@ -76,7 +76,7 @@ export function buildFeedbackQuery( export async function listFeedback( orgSlug: string, projectSlug: string, - options: ListFeedbackOptions + options: ListFeedbackOptions, ): Promise<FeedbackPage> { const { issues, nextCursor } = await listIssuesAllPages( orgSlug, @@ -91,7 +91,7 @@ export async function listFeedback( startCursor: options.cursor, projectId: options.projectId, collapse: buildIssueListCollapse({ shouldCollapseStats: false }), - } + }, ); return { diff --git a/packages/cli/src/lib/api/infrastructure.ts b/packages/cli/src/lib/api/infrastructure.ts index 70f40a41b..3885a319e 100644 --- a/packages/cli/src/lib/api/infrastructure.ts +++ b/packages/cli/src/lib/api/infrastructure.ts @@ -9,7 +9,7 @@ import { promisify } from "node:util"; import { zstdCompress as zstdCompressCb } from "node:zlib"; import { parseSentryLinkHeader } from "@sentry/api"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import { type GenericSchema, safeParse } from "valibot"; @@ -63,26 +63,26 @@ function enrich403Detail(rawDetail: string | undefined): string { if (isEnvTokenActive()) { if (scopes.length > 0) { lines.push( - `Your ${getActiveEnvVarName()} token is missing the required scope(s) '${scopes.join("', '")}'.` + `Your ${getActiveEnvVarName()} token is missing the required scope(s) '${scopes.join("', '")}'.`, ); } else { lines.push( - `Your ${getActiveEnvVarName()} token may lack the required scope for this operation.` + `Your ${getActiveEnvVarName()} token may lack the required scope for this operation.`, ); } lines.push( - "Check token scopes at: https://sentry.io/settings/account/api/auth-tokens/" + "Check token scopes at: https://sentry.io/settings/account/api/auth-tokens/", ); } else if (scopes.length > 0) { const scopeArgs = scopes.map((s) => `--scope ${s}`).join(" "); lines.push( `Your token is missing the required scope(s) '${scopes.join("', '")}'.`, - `Re-authenticate with: sentry auth refresh ${scopeArgs}` + `Re-authenticate with: sentry auth refresh ${scopeArgs}`, ); } else { lines.push( "You may not have access to this resource.", - "Re-authenticate with: sentry auth login" + "Re-authenticate with: sentry auth login", ); } return lines.join("\n "); @@ -128,12 +128,12 @@ export function enrich401Detail(rawDetail: string | undefined): string { const expired = rawDetail?.toLowerCase().includes("expired"); lines.push( `Your ${getActiveEnvVarName()} token ${expired ? "has expired" : "is not recognized or has been revoked"}.`, - "Create a new token at: https://sentry.io/settings/account/api/auth-tokens/" + "Create a new token at: https://sentry.io/settings/account/api/auth-tokens/", ); } else { lines.push( "Not authenticated or your session has expired.", - "Re-authenticate with: sentry auth login" + "Re-authenticate with: sentry auth login", ); } return lines.join("\n "); @@ -153,7 +153,7 @@ export function enrich401Detail(rawDetail: string | undefined): string { function enrichDetail( status: number, detail: string | undefined, - hasUsableDetail: boolean + hasUsableDetail: boolean, ): string | undefined { if (status === 403) { return enrich403Detail(hasUsableDetail ? detail : undefined); @@ -211,7 +211,7 @@ export type ApiRequestOptions<T = unknown> = { export function throwApiError( error: unknown, response: Response | undefined, - context: string + context: string, ): never { // Network-level failure: no HTTP response received (DNS, timeout, ECONNREFUSED, etc.) if (!response) { @@ -220,7 +220,7 @@ export function throwApiError( throw new ApiError( `${context}: Network error`, 0, - `Unable to reach Sentry API. Cause: ${cause}\n\n Check your internet connection and try again.` + `Unable to reach Sentry API. Cause: ${cause}\n\n Check your internet connection and try again.`, ); } @@ -243,7 +243,7 @@ export function throwApiError( status, enrichDetail(status, detail, hasUsableDetail), undefined, - is403 + is403, ); } @@ -263,7 +263,7 @@ export function unwrapResult<T>( result: | { data: unknown; error: undefined } | { data: undefined; error: unknown }, - context: string + context: string, ): T { const { data, error } = result as { data: unknown; @@ -302,12 +302,12 @@ export function unwrapPaginatedResult<T>( result: | { data: unknown; error: undefined } | { data: undefined; error: unknown }, - context: string + context: string, ): PaginatedResponse<T> { const response = (result as { response?: Response }).response; const data = unwrapResult<T>(result, context); const { nextCursor, prevCursor } = parseLinkHeader( - response?.headers.get("link") ?? null + response?.headers.get("link") ?? null, ); const out: PaginatedResponse<T> = { data }; if (response) { @@ -331,7 +331,7 @@ export function unwrapPaginatedResult<T>( * @internal Exported for testing */ export function buildSearchParams( - params?: Record<string, string | number | boolean | string[] | undefined> + params?: Record<string, string | number | boolean | string[] | undefined>, ): URLSearchParams | undefined { if (!params) { return; @@ -363,7 +363,7 @@ export function buildSearchParams( */ export function appendSearchParams( endpoint: string, - params?: ApiRequestOptions["params"] + params?: ApiRequestOptions["params"], ): string { const searchParams = buildSearchParams(params); if (!searchParams) { @@ -395,7 +395,7 @@ export async function getOrgSdkConfig(orgSlug: string) { */ export const MAX_PAGINATION_PAGES = Math.max( 1, - Number(getEnv().SENTRY_MAX_PAGINATION_PAGES) || 50 + Number(getEnv().SENTRY_MAX_PAGINATION_PAGES) || 50, ); /** @@ -448,7 +448,7 @@ export type PaginatedResponse<T> = { export async function autoPaginate<T>( fetchPage: (cursor: string | undefined) => Promise<PaginatedResponse<T[]>>, limit: number, - initialCursor?: string + initialCursor?: string, ): Promise<PaginatedResponse<T[]>> { // Fast path: single-page fetch when limit fits in one API page if (limit <= API_MAX_PER_PAGE) { @@ -477,7 +477,7 @@ export async function autoPaginate<T>( // Safety limit reached — warn and return what we have, no nextCursor logger.warn( `Pagination limit reached (${MAX_PAGINATION_PAGES} pages, ${allRows.length} items). ` + - "Results may be incomplete." + "Results may be incomplete.", ); return { data: allRows.slice(0, limit) }; } @@ -502,9 +502,9 @@ export function paginate<T>( options: { limit?: number; cursor?: string }, fetchPage: ( perPage: number, - cursor: string | undefined + cursor: string | undefined, ) => Promise<PaginatedResponse<T[]>>, - defaultLimit = 10 + defaultLimit = 10, ): Promise<PaginatedResponse<T[]>> { const limit = options.limit ?? defaultLimit; let remaining = limit; @@ -512,13 +512,13 @@ export function paginate<T>( async (cursor) => { const result = await fetchPage( Math.min(remaining, API_MAX_PER_PAGE), - cursor + cursor, ); remaining -= result.data.length; return result; }, limit, - options.cursor + options.cursor, ); } @@ -536,10 +536,10 @@ export function paginate<T>( */ export async function fetchAllPages<T>( fetchPage: ( - cursor: string | undefined + cursor: string | undefined, ) => Promise<PaginatedResponse<unknown>>, schema: GenericSchema<unknown, T[]>, - context: string + context: string, ): Promise<T[]> { const items: T[] = []; const seen = new Set<string>(); @@ -562,7 +562,7 @@ export async function fetchAllPages<T>( } throw new ApiError( `Pagination exceeded ${MAX_PAGINATION_PAGES} pages when ${context}`, - 0 + 0, ); } @@ -579,7 +579,7 @@ export async function fetchAllPages<T>( export async function apiRequestToRegion<T>( regionUrl: string, endpoint: string, - options: ApiRequestOptions<T> = {} + options: ApiRequestOptions<T> = {}, ): Promise<{ data: T; headers: Headers }> { const { method = "GET", @@ -634,7 +634,7 @@ export async function apiRequestToRegion<T>( `API returned ${response.status} ${response.statusText} (no body)`, response.status, "The server returned no content — the request may have matched no records.", - endpoint + endpoint, ); } @@ -647,7 +647,7 @@ export async function apiRequestToRegion<T>( `Invalid JSON in API response from ${endpoint}`, response.status, "The server returned a non-JSON response body (possible proxy or CDN issue).", - endpoint + endpoint, ); } @@ -672,7 +672,7 @@ export async function apiRequestToRegion<T>( throw new ApiError( `Unexpected response format from ${endpoint}`, response.status, - result.issues.map((issue) => issue.message).join(", ") + result.issues.map((issue) => issue.message).join(", "), ); } return { data: result.output, headers: response.headers }; @@ -690,7 +690,7 @@ export async function apiRequestToRegion<T>( */ async function throwRawApiError( response: Response, - endpoint: string + endpoint: string, ): Promise<never> { let detail: string | undefined; try { @@ -732,7 +732,7 @@ async function throwRawApiError( response.status, enrichDetail(response.status, detail, detail !== undefined), endpoint, - is403 + is403, ); } @@ -743,7 +743,7 @@ async function throwRawApiError( export async function apiRequestToRegionNoContent( regionUrl: string, endpoint: string, - options: Omit<ApiRequestOptions, "schema"> = {} + options: Omit<ApiRequestOptions, "schema"> = {}, ): Promise<void> { const { method = "GET", body, params } = options; const config = getSdkConfig(regionUrl); @@ -786,12 +786,12 @@ export async function apiRequestToRegionNoContent( */ export async function apiRequest<T>( endpoint: string, - options: ApiRequestOptions<T> = {} + options: ApiRequestOptions<T> = {}, ): Promise<T> { const { data } = await apiRequestToRegion<T>( getApiBaseUrl(), endpoint, - options + options, ); return data; } @@ -859,7 +859,7 @@ export async function rawApiRequest( * origins outside the active token's trust scope before adding credentials. */ baseUrl?: string; - } = {} + } = {}, ): Promise<{ status: number; statusText: string; @@ -890,7 +890,7 @@ export async function rawApiRequest( // Object bodies: application/json (auto-stringified). const isStringBody = typeof body === "string"; const hasContentType = Object.keys(customHeaders).some( - (k) => k.toLowerCase() === "content-type" + (k) => k.toLowerCase() === "content-type", ); const headers: Record<string, string> = { ...customHeaders }; diff --git a/packages/cli/src/lib/api/issue-app-links.ts b/packages/cli/src/lib/api/issue-app-links.ts index 090d0bc38..d892dc1df 100644 --- a/packages/cli/src/lib/api/issue-app-links.ts +++ b/packages/cli/src/lib/api/issue-app-links.ts @@ -129,7 +129,7 @@ function parseTarget(raw: string) { if (!url) { throw new ValidationError( "External issue must be an absolute HTTP(S) URL without credentials.", - "url" + "url", ); } const linear = @@ -137,7 +137,7 @@ function parseTarget(raw: string) { if (url.hostname === "linear.app" && !linear) { throw new ValidationError( "Expected a Linear issue URL containing /issue/TEAM-123", - "url" + "url", ); } const identity = linear @@ -150,14 +150,14 @@ function parseTarget(raw: string) { export function findAppIssueLink( links: AppIssueLink[], url: string, - appSlug?: string + appSlug?: string, ): AppIssueLink | undefined { const target = parseTarget(url); const matches = links.filter((link) => { if (appSlug && link.serviceType !== appSlug) { return false; } - // biome-ignore lint/plugin: Invalid persisted URLs cannot identify the requested target. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- Invalid persisted URLs cannot identify the requested target. try { return parseTarget(link.webUrl).identity === target.identity; } catch { @@ -168,7 +168,7 @@ export function findAppIssueLink( if (matches.length > 1) { throw new ValidationError( "Multiple app links match this URL; specify the app with --app", - "app" + "app", ); } return matches[0]; @@ -183,7 +183,7 @@ function requireIssueTarget(orgSlug: string, issueId: string): void { ) { throw new ValidationError( "App links require an organization and numeric Sentry issue ID", - "issueId" + "issueId", ); } } @@ -191,7 +191,7 @@ function requireIssueTarget(orgSlug: string, issueId: string): void { /** Retrieve all app associations in the issue's region. */ export async function listAppIssueLinks( orgSlug: string, - issueId: string + issueId: string, ): Promise<AppIssueLink[]> { requireIssueTarget(orgSlug, issueId); const config = getSdkConfig(await resolveOrgRegion(orgSlug)); @@ -205,7 +205,7 @@ export async function listAppIssueLinks( return unwrapPaginatedResult(result, "Failed to list app issue links"); }, vGroupExternalIssueResponse, - "listing app issue links" + "listing app issue links", ); } @@ -213,17 +213,17 @@ export async function listAppIssueLinks( function checkExisting( links: AppIssueLink[], url: string, - appSlug: string + appSlug: string, ): AppIssueLink | undefined { const existing = findAppIssueLink(links, url, appSlug); if ( links.some( - (link) => link.serviceType === appSlug && link.id !== existing?.id + (link) => link.serviceType === appSlug && link.id !== existing?.id, ) ) { throw new ValidationError( `This issue already has a different ${appSlug} link. Unlink it before linking another issue.`, - "app" + "app", ); } return existing; @@ -238,14 +238,14 @@ function validateUri(uri: unknown): asserts uri is string { ) { throw new ValidationError( "The installed app has an invalid relative action URI", - "app" + "app", ); } } async function resolveInstallation( orgSlug: string, - appSlug: string + appSlug: string, ): Promise<AppInstallation> { const config = getSdkConfig(getControlSiloUrl()); const installations = await fetchAllPages( @@ -257,17 +257,17 @@ async function resolveInstallation( }); return unwrapPaginatedResult( result, - "Failed to list Sentry App installations" + "Failed to list Sentry App installations", ); }, vListOrganizationSentryAppInstallationsResponse, - "listing Sentry App installations" + "listing Sentry App installations", ); const matches = installations.filter( (item) => item.organization.slug === orgSlug && item.app.slug === appSlug && - item.status === "installed" + item.status === "installed", ); const installation = matches[0]; if (matches.length !== 1 || !installation) { @@ -275,7 +275,7 @@ async function resolveInstallation( matches.length ? `Multiple installed apps match ${appSlug}` : `App ${appSlug} is not installed in this organization`, - "app" + "app", ); } return installation; @@ -283,7 +283,7 @@ async function resolveInstallation( async function getLinkForm( orgSlug: string, - installation: AppInstallation + installation: AppInstallation, ): Promise<LinkForm> { const config = getSdkConfig(getControlSiloUrl()); const components = await fetchAllPages( @@ -296,25 +296,25 @@ async function getLinkForm( return unwrapPaginatedResult(result, "Failed to list app components"); }, vListOrganizationSentryAppComponentsResponse, - "listing Sentry App components" + "listing Sentry App components", ); const matches = components.filter( (item) => item.type === "issue-link" && - item.sentryApp.uuid === installation.app.uuid + item.sentryApp.uuid === installation.app.uuid, ); const component = matches[0]; if (matches.length !== 1 || !component) { throw new ValidationError( `App ${installation.app.slug} does not expose an unambiguous issue-link form`, - "app" + "app", ); } if (component.error) { throw new ApiError( `App ${installation.app.slug} could not prepare its issue-link form`, 0, - JSON.stringify(component.error) + JSON.stringify(component.error), ); } // App-defined form schemas are intentionally untyped in the API contract. @@ -322,7 +322,7 @@ async function getLinkForm( if (!form.success) { throw new ValidationError( `App ${installation.app.slug} does not expose a supported issue-link form`, - "app" + "app", ); } validateUri(form.output.uri); @@ -347,7 +347,7 @@ async function getChoices({ } validateUri(field.uri); const dependentData = Object.fromEntries( - (field.depends_on ?? []).map((name) => [name, values[name]]) + (field.depends_on ?? []).map((name) => [name, values[name]]), ); const result = await getSentryAppInstallationExternalRequestOptions({ ...getSdkConfig(getControlSiloUrl()), @@ -363,7 +363,7 @@ async function getChoices({ }); const parsed = safeParse( ChoicesResponseSchema, - unwrapResult(result, "Failed to search app issues") + unwrapResult(result, "Failed to search app issues"), ); if (!parsed.success) { throw new ApiError("App search returned invalid issue choices", 0); @@ -382,13 +382,13 @@ function choiceLabelKey(label: string | number): string | undefined { function validateLinearChoice( choice: Choice, choices: Choice[], - key: string + key: string, ): void { const valueKey = String(choice[0]).toUpperCase(); const labelKey = choiceLabelKey(choice[1]); const identified = choices.filter( ([value, label]) => - String(value).toUpperCase() === key || choiceLabelKey(label) === key + String(value).toUpperCase() === key || choiceLabelKey(label) === key, ); if ( (LINEAR_ISSUE_KEY.test(valueKey) && valueKey !== key) || @@ -399,7 +399,7 @@ function validateLinearChoice( ) { throw new ValidationError( "App issue choice conflicts with the requested issue URL", - "field" + "field", ); } } @@ -408,7 +408,7 @@ function selectChoice( choices: Choice[], query: string, linearKey?: string, - supplied?: string + supplied?: string, ): string | number { const wanted = supplied ?? query; const matches = choices.filter( @@ -419,7 +419,7 @@ function selectChoice( choiceLabelKey(label) === linearKey && (supplied === undefined || supplied === query || - String(value) === supplied)) + String(value) === supplied)), ); const choice = matches[0]; if (matches.length !== 1 || !choice) { @@ -431,7 +431,7 @@ function selectChoice( matches.length ? "App search returned multiple exact issue matches" : missingMessage, - "url" + "url", ); } if (linearKey) { @@ -457,7 +457,7 @@ async function resolveFields( options: ResolveAppIssueLinkOptions, form: LinkForm, installationUuid: string, - targetKey: string | undefined + targetKey: string | undefined, ): Promise<Record<string, string | number>> { const required = form.required_fields ?? []; const fields = [...required, ...(form.optional_fields ?? [])]; @@ -467,26 +467,26 @@ async function resolveFields( (field) => field === targetField || required.includes(field) || - values[field.name] !== undefined + values[field.name] !== undefined, ); addDependencies(pending, fields); const resolved = new Set<string>(); while (pending.length) { const index = pending.findIndex((item) => - (item.depends_on ?? []).every((name) => resolved.has(name)) + (item.depends_on ?? []).every((name) => resolved.has(name)), ); const field = pending[index]; if (!field) { const missing = new Set( pending.flatMap((item) => - (item.depends_on ?? []).filter((name) => values[name] === undefined) - ) + (item.depends_on ?? []).filter((name) => values[name] === undefined), + ), ); throw new ValidationError( missing.size ? `Missing app link fields: ${[...missing].map((name) => `--field ${name}=VALUE`).join(", ")}` : "App link fields have circular dependencies", - "field" + "field", ); } pending.splice(index, 1); @@ -505,13 +505,13 @@ async function resolveFields( function seedFields( fields: Field[], - supplied: Record<string, string> + supplied: Record<string, string>, ): Record<string, string | number> { const values: Record<string, string | number> = {}; if (new Set(fields.map((field) => field.name)).size !== fields.length) { throw new ValidationError( "App link schema contains duplicate field names", - "app" + "app", ); } for (const [name, value] of Object.entries(supplied)) { @@ -521,7 +521,7 @@ function seedFields( ) { throw new ValidationError( `Unknown or reserved app link field: ${name}`, - "field" + "field", ); } if (value !== "") { @@ -532,13 +532,13 @@ function seedFields( if (RESERVED_FIELDS.has(field.name)) { throw new ValidationError( `App link schema uses reserved field ${field.name}`, - "app" + "app", ); } if (field.multiple) { throw new ValidationError( `App link field ${field.name} requires multiple values and is not supported`, - "field" + "field", ); } if ( @@ -562,7 +562,7 @@ function findTargetField(fields: Field[], required: Field[]): Field { if (!targetField) { throw new ValidationError( "Cannot identify one external issue field in the app link schema", - "app" + "app", ); } return targetField; @@ -573,7 +573,7 @@ function validateFieldValue( fieldName: string, value: string | number | undefined, query: string | number | undefined, - supplied?: string + supplied?: string, ): asserts value is string | number { if ( supplied !== undefined && @@ -582,13 +582,13 @@ function validateFieldValue( ) { throw new ValidationError( `App field ${fieldName} conflicts with the requested issue URL`, - "field" + "field", ); } if (value === undefined || value === "") { throw new ValidationError( `Missing app link fields: --field ${fieldName}=VALUE`, - "field" + "field", ); } } @@ -638,7 +638,7 @@ async function resolveFieldValue({ optionsResponse.choices, String(value), targetKey, - supplied + supplied, ); } } else if (isTarget && URL_FIELD.test(field.name)) { @@ -650,14 +650,14 @@ async function resolveFieldValue({ /** Resolve the installed app and form using reads only; never register a local-only fallback. */ export async function resolveAppIssueLink( - options: ResolveAppIssueLinkOptions + options: ResolveAppIssueLinkOptions, ): Promise<PreparedAppIssueLink> { const { key } = parseTarget(options.url); const { appSlug } = options; const existing = checkExisting( await listAppIssueLinks(options.orgSlug, options.issueId), options.url, - appSlug + appSlug, ); const installation = await resolveInstallation(options.orgSlug, appSlug); const form = await getLinkForm(options.orgSlug, installation); @@ -675,7 +675,7 @@ export async function resolveAppIssueLink( /** Execute the callback with the backend's atomic no-op and replacement guard. */ export async function linkAppIssue( - prepared: PreparedAppIssueLink + prepared: PreparedAppIssueLink, ): Promise<{ link: AppIssueLink; changed: boolean }> { const result = await executeSentryAppInstallationExternalIssueAction({ ...getSdkConfig(getControlSiloUrl()), @@ -700,12 +700,12 @@ export async function linkAppIssue( export async function unlinkAppIssueLink( orgSlug: string, issueId: string, - linkId: string + linkId: string, ): Promise<void> { if (!isAllDigits(linkId)) { throw new ValidationError( "App unlink requires the numeric association ID", - "linkId" + "linkId", ); } requireIssueTarget(orgSlug, issueId); diff --git a/packages/cli/src/lib/api/issue-integrations.ts b/packages/cli/src/lib/api/issue-integrations.ts index fcc2db348..1b22c13f6 100644 --- a/packages/cli/src/lib/api/issue-integrations.ts +++ b/packages/cli/src/lib/api/issue-integrations.ts @@ -71,7 +71,7 @@ function parseUrl(value: string): URL { if (!url) { throw new ValidationError( "External issue must be an absolute HTTP(S) URL without credentials.", - "url" + "url", ); } return url; @@ -155,7 +155,7 @@ function issueIdentity(url: URL, provider: string): string | undefined { function matchesIntegration( url: URL, integration: NativeIntegration, - explicitlySelected: boolean + explicitlySelected: boolean, ): boolean { const provider = integration.provider.key; // Older Enterprise metadata may omit its host. Only an explicit selection @@ -201,7 +201,7 @@ function matchesIntegration( /** Read every integration page; partial discovery could hide an ambiguous match. */ async function listIntegrations( orgSlug: string, - issueId: string + issueId: string, ): Promise<NativeIntegration[]> { const config = getSdkConfig(await resolveOrgRegion(orgSlug)); return fetchAllPages( @@ -214,7 +214,7 @@ async function listIntegrations( return unwrapPaginatedResult(result, "Failed to list issue integrations"); }, vIssueIntegrationsResponse, - "listing issue integrations" + "listing issue integrations", ); } @@ -236,14 +236,14 @@ function flattenLinks(integrations: NativeIntegration[]): NativeIssueLink[] { url: url.href, }, ]; - }) + }), ); } /** List every link, including links whose provider is no longer supported. */ export async function listNativeIssueLinks( orgSlug: string, - issueId: string + issueId: string, ): Promise<NativeIssueLink[]> { return flattenLinks(await listIntegrations(orgSlug, issueId)); } @@ -277,17 +277,17 @@ function matchesNativeUrl(link: NativeIssueLink, target: URL): boolean { export function findNativeIssueLink( links: NativeIssueLink[], url: string, - integrationId?: string + integrationId?: string, ): NativeIssueLink | undefined { const target = parseUrl(url); const matches = links.filter( (link) => (!integrationId || integrationId === link.integrationId) && - matchesNativeUrl(link, target) + matchesNativeUrl(link, target), ); if (matches.length > 1) { throw new ValidationError( - "This issue is linked through multiple integrations. Specify --integration <id>." + "This issue is linked through multiple integrations. Specify --integration <id>.", ); } return matches[0]; @@ -297,23 +297,23 @@ export function findNativeIssueLink( export function selectNativeIntegration( integrations: NativeIntegration[], url: string, - integrationId?: string + integrationId?: string, ): NativeIntegration { const target = parseUrl(url); const candidates = integrations.filter( (integration) => integration.status === "active" && (!integrationId || integrationId === integration.id) && - matchesIntegration(target, integration, Boolean(integrationId)) + matchesIntegration(target, integration, Boolean(integrationId)), ); if (candidates.length === 0) { throw new ValidationError( - "No installed native issue-tracker integration matches this URL. Check --integration, or use --app <slug> for a Sentry App." + "No installed native issue-tracker integration matches this URL. Check --integration, or use --app <slug> for a Sentry App.", ); } if (candidates.length > 1) { throw new ValidationError( - `Multiple integrations match this URL. Specify --integration <id>: ${candidates.map((integration) => `${integration.id} (${integration.name})`).join(", ")}` + `Multiple integrations match this URL. Specify --integration <id>: ${candidates.map((integration) => `${integration.id} (${integration.name})`).join(", ")}`, ); } const selected = candidates[0]; @@ -335,7 +335,7 @@ export async function resolveNativeIssueLink(options: { const selected = selectNativeIntegration( integrations, url.href, - options.integrationId + options.integrationId, ); return { orgSlug: options.orgSlug, @@ -347,14 +347,14 @@ export async function resolveNativeIssueLink(options: { existing: findNativeIssueLink( flattenLinks(integrations), url.href, - selected.id + selected.id, ), }; } /** Link by URL; the backend resolves provider identifiers and enforces idempotency. */ export async function linkNativeIssue( - prepared: PreparedNativeIssueLink + prepared: PreparedNativeIssueLink, ): Promise<{ link: NativeIssueLink; changed: boolean }> { const result = await updateOrganizationIssueIntegration({ ...getSdkConfig(prepared.regionUrl), @@ -367,12 +367,12 @@ export async function linkNativeIssue( }); const parsed = safeParse( vExternalIssueLinkResponse, - unwrapResult<unknown>(result, "Failed to link external issue") + unwrapResult<unknown>(result, "Failed to link external issue"), ); if (!parsed.success) { throw new ApiError( "Unexpected response format after linking; inspect the current links before retrying", - 0 + 0, ); } const data = parsed.output; @@ -391,12 +391,12 @@ export async function linkNativeIssue( export async function unlinkNativeIssueLink( orgSlug: string, issueId: string, - link: NativeIssueLink + link: NativeIssueLink, ): Promise<void> { const externalIssue = Number(link.id); if (!Number.isSafeInteger(externalIssue) || externalIssue <= 0) { throw new ValidationError( - "External issue link ID must be a safe positive integer." + "External issue link ID must be a safe positive integer.", ); } const result = await deleteOrganizationIssueIntegration({ diff --git a/packages/cli/src/lib/api/issues.ts b/packages/cli/src/lib/api/issues.ts index 19c08614b..06f3726b3 100644 --- a/packages/cli/src/lib/api/issues.ts +++ b/packages/cli/src/lib/api/issues.ts @@ -149,7 +149,7 @@ export async function listIssuesPaginated( start?: string; /** Absolute end datetime (ISO-8601). Mutually exclusive with statsPeriod. */ end?: string; - } = {} + } = {}, ): Promise<PaginatedResponse<SentryIssue[]>> { // When we have a numeric project ID, use the `project` query param (Array<number>) // instead of `project:<slug>` in the search query. The API's `project` param @@ -232,11 +232,11 @@ export async function listIssuesAllPages( start?: string; /** Absolute end datetime (ISO-8601). Mutually exclusive with statsPeriod. */ end?: string; - } + }, ): Promise<IssuesPage> { if (options.limit < 1) { throw new Error( - `listIssuesAllPages: limit must be at least 1, got ${options.limit}` + `listIssuesAllPages: limit must be at least 1, got ${options.limit}`, ); } @@ -292,7 +292,7 @@ export async function listIssuesAllPages( */ export function getIssue( issueId: string, - options?: { collapse?: IssueCollapseField[] } + options?: { collapse?: IssueCollapseField[] }, ): Promise<SentryIssue> { return apiRequest<SentryIssue>(`/issues/${issueId}/`, { params: options?.collapse ? { collapse: options.collapse } : undefined, @@ -316,7 +316,7 @@ export function getIssue( export async function getIssueInOrg( orgSlug: string, issueId: string, - options?: { collapse?: IssueCollapseField[] } + options?: { collapse?: IssueCollapseField[] }, ): Promise<SentryIssue> { const regionUrl = await resolveOrgRegion(orgSlug); const { data } = await apiRequestToRegion<SentryIssue>( @@ -324,7 +324,7 @@ export async function getIssueInOrg( `/organizations/${orgSlug}/issues/${issueId}/`, { params: options?.collapse ? { collapse: options.collapse } : undefined, - } + }, ); return data; } @@ -345,7 +345,7 @@ export async function getIssueInOrg( export async function getIssueByShortId( orgSlug: string, shortId: string, - options?: { collapse?: IssueCollapseField[] } + options?: { collapse?: IssueCollapseField[] }, ): Promise<SentryIssue> { const normalizedShortId = shortId.toUpperCase(); const regionUrl = await resolveOrgRegion(orgSlug); @@ -357,7 +357,7 @@ export async function getIssueByShortId( `/organizations/${orgSlug}/shortids/${normalizedShortId}/`, { params: options?.collapse ? { collapse: options.collapse } : undefined, - } + }, ); data = result.data; } catch (error) { @@ -373,7 +373,7 @@ export async function getIssueByShortId( "The issue may have been deleted or merged", `Verify the short ID and org: sentry issue view ${orgSlug}/${normalizedShortId}`, `List issues in this org: sentry issue list ${orgSlug}/`, - ].join("\n ") + ].join("\n "), ); } throw error; @@ -383,7 +383,7 @@ export async function getIssueByShortId( throw new ApiError( `Short ID ${normalizedShortId} resolved but no issue group returned`, 404, - "Issue not found" + "Issue not found", ); } return data.group; @@ -403,7 +403,7 @@ export async function getIssueByShortId( export async function tryGetIssueByShortId( orgSlug: string, shortId: string, - options?: { collapse?: IssueCollapseField[] } + options?: { collapse?: IssueCollapseField[] }, ): Promise<SentryIssue | null> { try { return await getIssueByShortId(orgSlug, shortId, options); @@ -496,7 +496,7 @@ export type ParsedResolveSpec = * well-formed `<repo>@<sha>` payload. */ export function parseResolveSpec( - spec: string | undefined + spec: string | undefined, ): ParsedResolveSpec | null { if (!spec) { return null; @@ -524,7 +524,7 @@ export function parseResolveSpec( if (splitIdx <= 0 || splitIdx === payload.length - 1) { throw new ValidationError( `Invalid --in spec: '${spec}' — expected '${RESOLVE_COMMIT_EXPLICIT_PREFIX}<repo>@<sha>'.`, - "in" + "in", ); } const repository = payload.slice(0, splitIdx).trim(); @@ -532,7 +532,7 @@ export function parseResolveSpec( if (!(repository && commit)) { throw new ValidationError( `Invalid --in spec: '${spec}' — repo and SHA must both be non-empty.`, - "in" + "in", ); } return { kind: "commit", spec: { kind: "explicit", repository, commit } }; @@ -545,7 +545,7 @@ export function parseResolveSpec( `Invalid --in spec: '${spec}' is not a recognized sentinel.\n\n` + `Expected '${RESOLVE_NEXT_RELEASE_SENTINEL}', '${RESOLVE_COMMIT_SENTINEL}', or '${RESOLVE_COMMIT_EXPLICIT_PREFIX}<repo>@<sha>'.\n` + "If you meant a literal release name, it cannot start with '@'.", - "in" + "in", ); } return { kind: "static", details: { inRelease: trimmed } }; @@ -587,7 +587,7 @@ export async function updateIssueStatus( /** Substatus for archive granularity. */ substatus?: IssueSubstatus; orgSlug?: string; - } + }, ): Promise<SentryIssue> { const body: Record<string, unknown> = { status }; if (options?.statusDetails) { @@ -603,7 +603,7 @@ export async function updateIssueStatus( const { data } = await apiRequestToRegion<SentryIssue>( regionUrl, `/organizations/${encodeURIComponent(options.orgSlug)}/issues/${encodeURIComponent(issueId)}/`, - { method: "PUT", body } + { method: "PUT", body }, ); return data; } @@ -637,11 +637,11 @@ export type MergeIssuesResult = { */ export async function mergeIssues( orgSlug: string, - groupIds: readonly string[] + groupIds: readonly string[], ): Promise<MergeIssuesResult> { if (groupIds.length < 2) { throw new ValidationError( - `Need at least 2 issues to merge (got ${groupIds.length}).` + `Need at least 2 issues to merge (got ${groupIds.length}).`, ); } // The bulk mutate endpoint accepts repeated `?id=X` query params plus a @@ -667,9 +667,9 @@ export async function mergeIssues( await Promise.all( affectedIds.map((id) => invalidateCachedResponsesMatching( - `${apiBase}/api/0/issues/${encodeURIComponent(id)}/` - ) - ) + `${apiBase}/api/0/issues/${encodeURIComponent(id)}/`, + ), + ), ); return data.merge; } catch (error) { @@ -682,7 +682,7 @@ export async function mergeIssues( `No matching issues found for merge in '${orgSlug}'.`, 204, "All provided issue IDs are out of scope or no longer exist.", - path + path, ); } throw error; @@ -705,7 +705,7 @@ export async function mergeIssues( export async function getSharedIssue( baseUrl: string, orgSlug: string, - shareId: string + shareId: string, ): Promise<{ id: string }> { const path = `organizations/${encodeURIComponent(orgSlug)}/shared/issues/${encodeURIComponent(shareId)}/`; const url = `${baseUrl.replace(TRAILING_SLASH_RE, "")}/api/0/${path}`; @@ -724,7 +724,7 @@ export async function getSharedIssue( "TLS certificate error", 0, buildTlsErrorDetail(error), - path + path, ); } throw error; @@ -737,14 +737,14 @@ export async function getSharedIssue( 404, "The share link may have been disabled by the issue owner.\n" + " Ask them to re-enable sharing, or use the issue ID directly.", - path + path, ); } throw new ApiError( "Failed to resolve share link", response.status, undefined, - path + path, ); } @@ -759,7 +759,7 @@ export async function getSharedIssue( "Share link returned invalid JSON", response.status, undefined, - path + path, ); } @@ -774,7 +774,7 @@ export async function getSharedIssue( "Share link response missing a valid issue ID", response.status, undefined, - path + path, ); } diff --git a/packages/cli/src/lib/api/logs.ts b/packages/cli/src/lib/api/logs.ts index a90540ae3..4f8d2aeb8 100644 --- a/packages/cli/src/lib/api/logs.ts +++ b/packages/cli/src/lib/api/logs.ts @@ -6,7 +6,7 @@ */ import { listOrganizationEvents } from "@sentry/api"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import { type GenericSchema, safeParse } from "valibot"; @@ -52,7 +52,7 @@ function toApiSort(sort: LogSortDirection | undefined): string { */ function resolveNumericProjectId( projectSlug: string, - projectId: number | undefined + projectId: number | undefined, ): number | undefined { if (projectId !== undefined) { return projectId; @@ -81,7 +81,7 @@ function assertObjectResponse(data: unknown, context: string): void { `${context}: unexpected response format`, 0, `Expected an object but received ${data === null ? "null" : typeof data}. ` + - "This may indicate an incompatible self-hosted Sentry version or a proxy interfering with the response." + "This may indicate an incompatible self-hosted Sentry version or a proxy interfering with the response.", ); } } @@ -93,7 +93,7 @@ function assertObjectResponse(data: unknown, context: string): void { function safeParseResponse<T>( schema: GenericSchema<unknown, T>, data: unknown, - context: string + context: string, ): T { assertObjectResponse(data, context); const result = safeParse(schema, data); @@ -112,7 +112,7 @@ function safeParseResponse<T>( throw new ApiError( `${context}: unexpected response format`, 0, - result.issues.map((issue) => issue.message).join(", ") + result.issues.map((issue) => issue.message).join(", "), ); } return result.output; @@ -168,11 +168,11 @@ type ListLogsOptions = { export async function listLogs( orgSlug: string, projectSlug: string, - options: ListLogsOptions = {} + options: ListLogsOptions = {}, ): Promise<SentryLog[]> { const numericProjectId = resolveNumericProjectId( projectSlug, - options.projectId + options.projectId, ); // Only fall back to `project:<slug>` search scoping when we have no numeric @@ -224,16 +224,16 @@ export async function listLogs( const { data: raw, nextCursor } = unwrapPaginatedResult<unknown>( result, - "Failed to list logs" + "Failed to list logs", ); const logsResponse = safeParseResponse( LogsResponseSchema, raw, - "Failed to list logs" + "Failed to list logs", ); return { data: logsResponse.data, nextCursor }; }, - API_MAX_PER_PAGE + API_MAX_PER_PAGE, ); return data; @@ -276,7 +276,7 @@ async function getLogsBatch( orgSlug: string, projectSlug: string, batchIds: string[], - { config, extraFields, projectId }: GetLogsBatchOptions + { config, extraFields, projectId }: GetLogsBatchOptions, ): Promise<DetailedSentryLog[]> { const numericProjectId = resolveNumericProjectId(projectSlug, projectId); @@ -310,7 +310,7 @@ async function getLogsBatch( const logsResponse = safeParseResponse( DetailedLogsResponseSchema, data, - "Failed to get log" + "Failed to get log", ); return logsResponse.data; } @@ -344,7 +344,7 @@ export async function getLogs( orgSlug: string, projectSlug: string, logIds: string[], - options: GetLogsOptions = {} + options: GetLogsOptions = {}, ): Promise<DetailedSentryLog[]> { const { extraFields, projectId } = options; const config = await getOrgSdkConfig(orgSlug); @@ -363,8 +363,8 @@ export async function getLogs( const results = await Promise.all( batches.map((batch) => - getLogsBatch(orgSlug, projectSlug, batch, batchOptions) - ) + getLogsBatch(orgSlug, projectSlug, batch, batchOptions), + ), ); return results.flat(); @@ -408,7 +408,7 @@ type ListTraceLogsOptions = { export async function listTraceLogs( orgSlug: string, traceId: string, - options: ListTraceLogsOptions = {} + options: ListTraceLogsOptions = {}, ): Promise<TraceLog[]> { const regionUrl = await resolveOrgRegion(orgSlug); @@ -437,7 +437,7 @@ export async function listTraceLogs( const { nextCursor } = parseLinkHeader(headers.get("link") ?? null); return { data: response.data, nextCursor }; }, - API_MAX_PER_PAGE + API_MAX_PER_PAGE, ); return data; @@ -466,7 +466,7 @@ export function getLogItemDetail( orgSlug: string, projectSlug: string, logId: string, - traceId: string + traceId: string, ): Promise<TraceItemDetail> { return getTraceItemDetail(orgSlug, projectSlug, logId, { traceId, diff --git a/packages/cli/src/lib/api/monitors.ts b/packages/cli/src/lib/api/monitors.ts index 8a71666e4..755a195bc 100644 --- a/packages/cli/src/lib/api/monitors.ts +++ b/packages/cli/src/lib/api/monitors.ts @@ -43,7 +43,7 @@ export async function listMonitors(orgSlug: string): Promise<SentryMonitor[]> { }); return unwrapPaginatedResult<SentryMonitor[]>( result, - "Failed to list monitors" + "Failed to list monitors", ); }, MAX_PAGINATION_PAGES * API_MAX_PER_PAGE); @@ -60,7 +60,7 @@ export async function listMonitors(orgSlug: string): Promise<SentryMonitor[]> { */ export async function listMonitorsPaginated( orgSlug: string, - options: { cursor?: string; perPage?: number } = {} + options: { cursor?: string; perPage?: number } = {}, ): Promise<PaginatedResponse<SentryMonitor[]>> { const config = await getOrgSdkConfig(orgSlug); @@ -75,6 +75,6 @@ export async function listMonitorsPaginated( return unwrapPaginatedResult<SentryMonitor[]>( result, - "Failed to list monitors" + "Failed to list monitors", ); } diff --git a/packages/cli/src/lib/api/organizations.ts b/packages/cli/src/lib/api/organizations.ts index 949f92c55..959082098 100644 --- a/packages/cli/src/lib/api/organizations.ts +++ b/packages/cli/src/lib/api/organizations.ts @@ -39,7 +39,7 @@ import { function normalizeOrganizationRegion( raw: string | undefined, responseOrigin: string, - baseUrl: string + baseUrl: string, ): string | undefined { if (!raw) { if (normalizeHttpOrigin(baseUrl) === responseOrigin) { @@ -66,7 +66,7 @@ export async function getUserRegions(): Promise<Region[]> { const { data } = await apiRequestToRegion<UserRegionsResponse>( getControlSiloUrl(credential), "/users/me/regions/", - { schema: UserRegionsResponseSchema, credential, validatedRedirects: true } + { schema: UserRegionsResponseSchema, credential, validatedRedirects: true }, ); return data.regions; } @@ -85,7 +85,7 @@ export async function getUserRegions(): Promise<Region[]> { export async function listOrganizationsPage( baseUrl: string, options: { cursor?: string; perPage?: number } = {}, - credential = getCredentialContext() + credential = getCredentialContext(), ): Promise<PaginatedResponse<SentryOrganization[]>> { const config = getSdkConfig(baseUrl, { credential, @@ -101,7 +101,7 @@ export async function listOrganizationsPage( // throwApiError() in infrastructure.ts — no per-endpoint catch needed. const paginated = unwrapPaginatedResult<SentryOrganization[]>( result, - "Failed to list organizations" + "Failed to list organizations", ); // CLI-1CQ: self-hosted instances can return non-array data from @@ -111,7 +111,7 @@ export async function listOrganizationsPage( "Failed to list organizations: unexpected response format", 0, `Expected an array from ${baseUrl}/api/0/organizations/ but received ${typeof paginated.data}. ` + - "This may indicate an incompatible self-hosted Sentry version or a proxy interfering with the response." + "This may indicate an incompatible self-hosted Sentry version or a proxy interfering with the response.", ); } return paginated; @@ -171,13 +171,13 @@ async function listOrganizationPages({ if (pageIndex >= MAX_PAGINATION_PAGES) { throw new ApiError( "Failed to list organizations: pagination limit exceeded", - 0 + 0, ); } const page = await listOrganizationsPage( baseUrl, { cursor, perPage: API_MAX_PER_PAGE }, - credential + credential, ); const responseOrigin = page.response && getResponseRequestOrigin(page.response); @@ -189,7 +189,7 @@ async function listOrganizationPages({ const region = normalizeOrganizationRegion( org.links?.regionUrl, responseOrigin, - baseUrl + baseUrl, ); return region ? [ @@ -238,11 +238,10 @@ async function listOrganizationPages({ * Most callers should use {@link listOrganizations} instead. */ export async function listOrganizationsUncached( - credential = getCredentialContext() + credential = getCredentialContext(), ): Promise<SentryOrganization[]> { - const { invalidateCachedOrganizations, setOrgRegions } = await import( - "../db/regions.js" - ); + const { invalidateCachedOrganizations, setOrgRegions } = + await import("../db/regions.js"); if (!credential) { throw new AuthError("not_authenticated"); } @@ -269,7 +268,7 @@ export async function listOrganizationsUncached( * Uses region-aware routing for multi-region support. */ export async function getOrganization( - orgSlug: string + orgSlug: string, ): Promise<SentryOrganization> { const config = await getOrgSdkConfig(orgSlug); diff --git a/packages/cli/src/lib/api/preprod-artifacts.ts b/packages/cli/src/lib/api/preprod-artifacts.ts index f8acc968d..70a2730f7 100644 --- a/packages/cli/src/lib/api/preprod-artifacts.ts +++ b/packages/cli/src/lib/api/preprod-artifacts.ts @@ -76,11 +76,11 @@ export type BuildInstallDetails = InferOutput<typeof BuildInstallDetailsSchema>; */ export async function getBuildInstallDetails( org: string, - buildId: string + buildId: string, ): Promise<BuildInstallDetails> { const regionUrl = await resolveOrgRegion(org); const endpoint = `organizations/${org}/preprodartifacts/${encodeURIComponent( - buildId + buildId, )}/install-details/`; const { data } = await apiRequestToRegion(regionUrl, endpoint, { schema: BuildInstallDetailsSchema, @@ -113,7 +113,7 @@ export function buildFormatFromUrl(url: string): BuildFormat { } throw new ValidationError( "Unsupported build format in download URL", - "installUrl" + "installUrl", ); } @@ -129,7 +129,7 @@ function isRegionOrigin(url: string, regionUrl: string): boolean { } catch (err) { log.debug( "Could not compare download URL origin; withholding auth token", - err + err, ); return false; } @@ -149,7 +149,7 @@ function isRegionOrigin(url: string, regionUrl: string): boolean { export async function downloadBuildArtifact( regionUrl: string, url: string, - destPath: string + destPath: string, ): Promise<void> { const headers: Record<string, string> = {}; if (isRegionOrigin(url, regionUrl)) { @@ -165,14 +165,14 @@ export async function downloadBuildArtifact( "Failed to download build artifact", response.status, response.statusText || "Download failed", - url + url, ); } // Stream to disk so large (hundreds-of-MB) artifacts never buffer in memory. await pipeline( Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]), - createWriteStream(destPath) + createWriteStream(destPath), ); } @@ -218,7 +218,7 @@ const BuildAssembleResponseSchema = object({ function buildAssembleBody( checksum: string, chunkShas: string[], - metadata: BuildUploadMetadata + metadata: BuildUploadMetadata, ): Record<string, unknown> { const body: Record<string, unknown> = { checksum, chunks: chunkShas }; if (metadata.buildConfiguration) { @@ -248,7 +248,7 @@ function buildAssembleBody( * @throws {ApiError} On an assembly error or timeout. */ export async function uploadBuild( - options: BuildUploadOptions + options: BuildUploadOptions, ): Promise<string> { const { org, project, contentPath, metadata } = options; const serverOptions = @@ -256,7 +256,7 @@ export async function uploadBuild( const encoding = pickUploadEncoding(serverOptions.compression); const { chunks, overallChecksum } = await hashChunks( contentPath, - serverOptions.chunkSize + serverOptions.chunkSize, ); const regionUrl = await resolveOrgRegion(org); const endpoint = `projects/${org}/${project}/files/preprodartifacts/assemble/`; @@ -264,7 +264,7 @@ export async function uploadBuild( const body = buildAssembleBody( overallChecksum, chunks.map((chunk) => chunk.sha1), - metadata + metadata, ); const deadline = Date.now() + ASSEMBLE_MAX_WAIT_MS; @@ -280,7 +280,7 @@ export async function uploadBuild( "Build assembly failed", 500, data.detail ?? "Unknown error", - endpoint + endpoint, ); } if (data.artifactUrl) { @@ -293,7 +293,7 @@ export async function uploadBuild( "Build assembled but no artifact URL was returned", 500, data.detail ?? "", - endpoint + endpoint, ); } @@ -318,7 +318,7 @@ export async function uploadBuild( "Build assembly timed out", 408, `Assembly did not complete within ${ASSEMBLE_MAX_WAIT_MS / 1000}s`, - endpoint + endpoint, ); } @@ -357,7 +357,7 @@ export type LatestBaseSnapshot = { /** Build the `.../snapshots/{id}/archive/` endpoint path. */ function snapshotArchiveEndpoint(org: string, snapshotId: string): string { return `organizations/${org}/preprodartifacts/snapshots/${encodeURIComponent( - snapshotId + snapshotId, )}/archive/`; } @@ -374,7 +374,7 @@ function snapshotArchiveEndpoint(org: string, snapshotId: string): string { export async function getLatestBaseSnapshot( org: string, appId: string, - opts: { branch?: string; project?: string } = {} + opts: { branch?: string; project?: string } = {}, ): Promise<LatestBaseSnapshot | null> { const regionUrl = await resolveOrgRegion(org); try { @@ -384,7 +384,7 @@ export async function getLatestBaseSnapshot( { params: { app_id: appId, branch: opts.branch, project: opts.project }, schema: nullable(LatestBaseSnapshotSchema), - } + }, ); return data ? { headArtifactId: data.head_artifact_id, imageCount: data.image_count } @@ -424,13 +424,13 @@ export type SnapshotsUploadOptions = InferOutput< */ export async function fetchSnapshotsUploadOptions( org: string, - project: string + project: string, ): Promise<SnapshotsUploadOptions> { const regionUrl = await resolveOrgRegion(org); const { data } = await apiRequestToRegion( regionUrl, `projects/${org}/${project}/preprodartifacts/snapshots/upload-options/`, - { params: { usecase: "auto" }, schema: SnapshotsUploadOptionsSchema } + { params: { usecase: "auto" }, schema: SnapshotsUploadOptionsSchema }, ); return data; } @@ -457,7 +457,7 @@ export type CreateSnapshotResponse = InferOutput< export async function createPreprodSnapshot( org: string, project: string, - manifest: Record<string, unknown> + manifest: Record<string, unknown>, ): Promise<CreateSnapshotResponse> { const regionUrl = await resolveOrgRegion(org); const { data } = await apiRequestToRegion( @@ -469,7 +469,7 @@ export async function createPreprodSnapshot( // A large image suite makes a large manifest; compress like the legacy CLI. bodyEncoding: "zstd", schema: CreateSnapshotResponseSchema, - } + }, ); return data; } @@ -483,13 +483,13 @@ const SnapshotArchiveStatusSchema = object({ ready: boolean() }); */ export async function getSnapshotArchiveReady( org: string, - snapshotId: string + snapshotId: string, ): Promise<boolean> { const regionUrl = await resolveOrgRegion(org); const { data } = await apiRequestToRegion( regionUrl, snapshotArchiveEndpoint(org, snapshotId), - { schema: SnapshotArchiveStatusSchema } + { schema: SnapshotArchiveStatusSchema }, ); return data.ready; } @@ -501,13 +501,13 @@ export async function getSnapshotArchiveReady( */ export async function triggerSnapshotArchiveBuild( org: string, - snapshotId: string + snapshotId: string, ): Promise<void> { const regionUrl = await resolveOrgRegion(org); await apiRequestToRegionNoContent( regionUrl, snapshotArchiveEndpoint(org, snapshotId), - { method: "POST" } + { method: "POST" }, ); } @@ -522,7 +522,7 @@ export async function triggerSnapshotArchiveBuild( export async function waitForSnapshotArchive( org: string, snapshotId: string, - onBuildStarted?: () => void + onBuildStarted?: () => void, ): Promise<void> { if (await getSnapshotArchiveReady(org, snapshotId)) { return; @@ -536,7 +536,7 @@ export async function waitForSnapshotArchive( throw new TimeoutError( `Snapshot archive was not ready after ${ SNAPSHOT_ARCHIVE_TIMEOUT_MS / 1000 - }s. The build may still be running; try again shortly.` + }s. The build may still be running; try again shortly.`, ); } await new Promise((r) => setTimeout(r, SNAPSHOT_ARCHIVE_POLL_MS)); @@ -556,12 +556,12 @@ export async function waitForSnapshotArchive( */ export async function openSnapshotArchive( org: string, - snapshotId: string + snapshotId: string, ): Promise<Response> { const regionUrl = stripTrailingSlashes(await resolveOrgRegion(org)); const url = `${regionUrl}/api/0/${snapshotArchiveEndpoint( org, - snapshotId + snapshotId, )}?download`; const headers: Record<string, string> = {}; const token = getAuthToken(); @@ -574,7 +574,7 @@ export async function openSnapshotArchive( "Failed to download snapshot archive", response.status, response.statusText || "Download failed", - url + url, ); } return response; diff --git a/packages/cli/src/lib/api/proguard.ts b/packages/cli/src/lib/api/proguard.ts index b7e9871ca..5f2147488 100644 --- a/packages/cli/src/lib/api/proguard.ts +++ b/packages/cli/src/lib/api/proguard.ts @@ -93,7 +93,7 @@ type AssembleCheckResult = { function checkAssembleResponse( response: DifAssembleResponse, checksums: string[], - endpoint: string + endpoint: string, ): AssembleCheckResult { const missingChecksums = new Set<string>(); let allDone = true; @@ -110,7 +110,7 @@ function checkAssembleResponse( "ProGuard mapping assembly failed", 500, entry.detail ?? "Unknown error", - endpoint + endpoint, ); } if (entry.state === "ok" || entry.state === "created") { @@ -138,7 +138,7 @@ function checkAssembleResponse( * @throws {ApiError} If the upload or assembly fails */ export async function uploadProguardMappings( - options: ProguardUploadOptions + options: ProguardUploadOptions, ): Promise<void> { const { org, project, mappings } = options; @@ -150,7 +150,7 @@ export async function uploadProguardMappings( const chunkedMappings: ChunkedMapping[] = mappings.map((mapping) => { const { chunks, overallChecksum } = hashBuffer( mapping.content, - serverOptions.chunkSize + serverOptions.chunkSize, ); return { mapping, chunks, overallChecksum }; }); @@ -175,14 +175,14 @@ export async function uploadProguardMappings( method: "POST", body: assembleBody, schema: DifAssembleResponseSchema, - } + }, ); const checksums = chunkedMappings.map((cm) => cm.overallChecksum); const { allDone, missingChecksums } = checkAssembleResponse( firstAssemble, checksums, - assembleEndpoint + assembleEndpoint, ); if (allDone) { @@ -235,7 +235,7 @@ async function pollDifAssembly(params: { method: "POST", body, schema: DifAssembleResponseSchema, - } + }, ); const { allDone } = checkAssembleResponse(pollResult, checksums, endpoint); @@ -248,6 +248,6 @@ async function pollDifAssembly(params: { "ProGuard mapping assembly timed out", 408, `Assembly did not complete within ${ASSEMBLE_MAX_WAIT_MS / 1000}s`, - endpoint + endpoint, ); } diff --git a/packages/cli/src/lib/api/projects.ts b/packages/cli/src/lib/api/projects.ts index 68e6d1dc1..2c7ca0c43 100644 --- a/packages/cli/src/lib/api/projects.ts +++ b/packages/cli/src/lib/api/projects.ts @@ -86,7 +86,7 @@ export async function listProjects(orgSlug: string): Promise<SentryProject[]> { }); return unwrapPaginatedResult<SentryProject[]>( result, - "Failed to list projects" + "Failed to list projects", ); }, MAX_PAGINATION_PAGES * API_MAX_PER_PAGE); @@ -99,7 +99,7 @@ export async function listProjects(orgSlug: string): Promise<SentryProject[]> { } catch (error) { reportBestEffortFailure( `Failed to cache projects for org '${orgSlug}'`, - error + error, ); } @@ -120,12 +120,12 @@ export async function listProjects(orgSlug: string): Promise<SentryProject[]> { */ export async function listProjectsPaginated( orgSlug: string, - options: { cursor?: string; perPage?: number } = {} + options: { cursor?: string; perPage?: number } = {}, ): Promise<PaginatedResponse<SentryProject[]>> { const config = await getOrgSdkConfig(orgSlug); const perPage = Math.min( options.perPage ?? API_MAX_PER_PAGE, - API_MAX_PER_PAGE + API_MAX_PER_PAGE, ); const result = await listOrganizationProjects({ @@ -139,7 +139,7 @@ export async function listProjectsPaginated( return unwrapPaginatedResult<SentryProject[]>( result, - "Failed to list projects" + "Failed to list projects", ); } @@ -156,10 +156,10 @@ export async function listProjectsPaginated( */ export function listProjectsAllPages( orgSlug: string, - options: { limit: number; cursor?: string } + options: { limit: number; cursor?: string }, ): Promise<PaginatedResponse<SentryProject[]>> { return paginate(options, (perPage, cursor) => - listProjectsPaginated(orgSlug, { cursor, perPage }) + listProjectsPaginated(orgSlug, { cursor, perPage }), ); } @@ -188,7 +188,7 @@ type CreateProjectBody = { export async function createProject( orgSlug: string, teamSlug: string, - body: CreateProjectBody + body: CreateProjectBody, ): Promise<SentryProject> { const config = await getOrgSdkConfig(orgSlug); const result = await createTeamProject({ @@ -220,7 +220,7 @@ export type CreatedProjectDetails = { function seedProjectCaches( orgSlug: string, project: SentryProject, - dsn: string | null + dsn: string | null, ): void { try { const orgName = resolveOrgDisplayName(orgSlug, project.organization?.name); @@ -230,7 +230,7 @@ function seedProjectCaches( } catch (error) { reportBestEffortFailure( `Failed to seed project cache for '${project.slug}'`, - error + error, ); } if (dsn) { @@ -248,7 +248,7 @@ function seedProjectCaches( } catch (error) { reportBestEffortFailure( `Failed to seed DSN key cache for '${project.slug}'`, - error + error, ); } } @@ -259,7 +259,7 @@ function seedProjectCaches( * DSN format: https://<public_key>@<host>/<project_id> */ function extractPublicKeyFromDsn(dsn: string): string | null { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = new URL(dsn); return url.username || null; @@ -280,7 +280,7 @@ function extractPublicKeyFromDsn(dsn: string): string | null { export async function createProjectWithDsn( orgSlug: string, teamSlug: string, - body: CreateProjectBody + body: CreateProjectBody, ): Promise<CreatedProjectDetails> { const project = await createProject(orgSlug, teamSlug, body); const dsn = await tryGetPrimaryDsn(orgSlug, project.slug); @@ -346,7 +346,7 @@ export const MEMBER_PROJECT_CREATION_DISABLED_DETAIL = "disabled this feature"; */ export async function createProjectWithAutoTeam( orgSlug: string, - body: CreateProjectBody + body: CreateProjectBody, ): Promise<CreatedAutoTeamProjectDetails> { const config = await getOrgSdkConfig(orgSlug); const result = await createOrganizationProject({ @@ -357,7 +357,7 @@ export async function createProjectWithAutoTeam( // `team_slug` is returned but not in the spec — see the ProjectWithAutoTeam overlay. const data = unwrapResult<ProjectWithAutoTeam>( result, - "Failed to create project" + "Failed to create project", ); const dsn = await tryGetPrimaryDsn(orgSlug, data.slug); const url = buildProjectUrl(orgSlug, data.slug); @@ -377,7 +377,7 @@ export async function createProjectWithAutoTeam( */ export async function deleteProject( orgSlug: string, - projectSlug: string + projectSlug: string, ): Promise<void> { const config = await getOrgSdkConfig(orgSlug); const result = await sdkDeleteProject({ @@ -412,7 +412,7 @@ export type ProjectSearchResult = { * @returns Matching projects and the org list used during search */ export async function findProjectsBySlug( - projectSlug: string + projectSlug: string, ): Promise<ProjectSearchResult> { const isNumericId = isAllDigits(projectSlug); @@ -440,9 +440,9 @@ export async function findProjectsBySlug( return null; } return { ...project, orgSlug: org.slug }; - }) - ) - ) + }), + ), + ), ); return { @@ -499,7 +499,7 @@ export function matchesWordBoundary(a: string, b: string): boolean { * @returns Array of matching projects with their org context */ export async function findProjectsByPattern( - pattern: string + pattern: string, ): Promise<ProjectWithOrg[]> { const orgs = await listOrganizations(); @@ -512,9 +512,9 @@ export async function findProjectsByPattern( return projects .filter((p) => matchesWordBoundary(pattern, p.slug)) .map((p) => ({ ...p, orgSlug: org.slug })); - }) - ) - ) + }), + ), + ), ); return searchResults @@ -533,7 +533,7 @@ export async function findProjectsByPattern( * @returns The matching project, or null if not found */ export async function findProjectByDsnKey( - publicKey: string + publicKey: string, ): Promise<SentryProject | null> { const regionsResult = await withAuthGuard(() => getUserRegions()); const regions = regionsResult.ok ? regionsResult.value : ([] as Region[]); @@ -547,7 +547,7 @@ export async function findProjectByDsnKey( const { data: projects } = await apiRequestToRegion<SentryProject[]>( getApiBaseUrl(), "/projects/", - { params: { query: `dsn:${publicKey}` } } + { params: { query: `dsn:${publicKey}` } }, ); return projects[0] ?? null; } @@ -562,18 +562,18 @@ export async function findProjectByDsnKey( const { data } = await apiRequestToRegion<SentryProject[]>( region.url, "/projects/", - { params: { query: `dsn:${publicKey}` } } + { params: { query: `dsn:${publicKey}` } }, ); return data; } catch (error) { reportBestEffortFailure( `DSN key lookup failed in region '${region.url}'`, - error + error, ); return []; } - }) - ) + }), + ), ); for (const projects of results) { @@ -601,7 +601,7 @@ export async function findProjectByDsnKey( */ export async function getProject( orgSlug: string, - projectSlug: string + projectSlug: string, ): Promise<SentryProject> { const config = await getOrgSdkConfig(orgSlug); @@ -642,7 +642,7 @@ export async function getProject( */ export function resolveOrgDisplayName( orgSlug: string, - explicitName?: string + explicitName?: string, ): string { if (explicitName) { return explicitName; @@ -682,7 +682,7 @@ function toProjectDsn(key: SdkProjectKey): ProjectDsn { export async function listProjectDsns( orgSlug: string, projectSlug: string, - options: { limit?: number; cursor?: string } = {} + options: { limit?: number; cursor?: string } = {}, ): Promise<PaginatedResponse<ProjectDsn[]>> { const config = await getOrgSdkConfig(orgSlug); @@ -699,7 +699,7 @@ export async function listProjectDsns( }); const page = unwrapPaginatedResult<SdkProjectKey[]>( result, - "Failed to list project DSNs" + "Failed to list project DSNs", ); return { @@ -716,7 +716,7 @@ export async function listProjectDsns( */ export async function listOrganizationDsns( orgSlug: string, - options: { limit?: number; cursor?: string } = {} + options: { limit?: number; cursor?: string } = {}, ): Promise<PaginatedResponse<(ProjectDsn & { project: string })[]>> { const config = await getOrgSdkConfig(orgSlug); const projectLookups = new Map<number, Promise<string>>(); @@ -733,7 +733,7 @@ export async function listOrganizationDsns( }); const page = unwrapPaginatedResult<SdkProjectKey[]>( result, - "Failed to list organization DSNs" + "Failed to list organization DSNs", ); const data = await Promise.all( @@ -747,7 +747,7 @@ export async function listOrganizationDsns( projectLookups.set(key.projectId, project); } return { ...toProjectDsn(key), project: await project }; - }) + }), ); return { ...page, data }; @@ -765,7 +765,7 @@ export async function listOrganizationDsns( export async function getProjectKeys( orgSlug: string, projectSlug: string, - options: { status?: "active" | "inactive" } = {} + options: { status?: "active" | "inactive" } = {}, ): Promise<ProjectKey[]> { const config = await getOrgSdkConfig(orgSlug); @@ -794,7 +794,7 @@ export async function getProjectKeys( */ export async function tryGetPrimaryDsn( orgSlug: string, - projectSlug: string + projectSlug: string, ): Promise<string | null> { try { const keys = await getProjectKeys(orgSlug, projectSlug); @@ -803,7 +803,7 @@ export async function tryGetPrimaryDsn( } catch (error) { reportBestEffortFailure( `Failed to fetch DSN for '${orgSlug}/${projectSlug}'`, - error + error, ); return null; } diff --git a/packages/cli/src/lib/api/releases.ts b/packages/cli/src/lib/api/releases.ts index c9996f7c9..db4c0ba3e 100644 --- a/packages/cli/src/lib/api/releases.ts +++ b/packages/cli/src/lib/api/releases.ts @@ -62,7 +62,7 @@ export type ListReleasesOptions = { export async function listReleasesPaginated( orgSlug: string, - options: ListReleasesOptions = {} + options: ListReleasesOptions = {}, ): Promise<PaginatedResponse<SentryRelease[]>> { const config = await getOrgSdkConfig(orgSlug); @@ -95,7 +95,7 @@ export async function listReleasesPaginated( return unwrapPaginatedResult<SentryRelease[]>( result, - "Failed to list releases" + "Failed to list releases", ); } @@ -108,14 +108,14 @@ export async function listReleasesPaginated( export async function listReleasesForProject( orgSlug: string, projectSlug: string, - options: Omit<ListReleasesOptions, "project"> = {} + options: Omit<ListReleasesOptions, "project"> = {}, ): Promise<SentryRelease[]> { // Resolve slug → numeric ID (the API requires numeric project IDs) const info = await getProject(orgSlug, projectSlug); const n = Number(info.id); if (!Number.isInteger(n) || n <= 0) { throw new ValidationError( - `Project "${projectSlug}" has an invalid numeric ID: ${info.id}` + `Project "${projectSlug}" has an invalid numeric ID: ${info.id}`, ); } const { data } = await listReleasesPaginated(orgSlug, { @@ -157,7 +157,7 @@ export async function getRelease( adoptionStages?: boolean; /** Period for health stats: "24h", "7d", "14d", etc. Defaults to "24h". */ healthStatsPeriod?: string; - } + }, ): Promise<SentryRelease> { const config = await getOrgSdkConfig(orgSlug); @@ -186,7 +186,7 @@ export async function getRelease( return unwrapResult<SentryRelease>( result, - `Failed to get release '${version}'` + `Failed to get release '${version}'`, ); } @@ -213,7 +213,7 @@ export async function createRelease( author_email?: string; timestamp?: string; }>; - } + }, ): Promise<SentryRelease> { const config = await getOrgSdkConfig(orgSlug); @@ -259,7 +259,7 @@ export async function updateRelease( author_email?: string; timestamp?: string; }>; - } + }, ): Promise<SentryRelease> { const config = await getOrgSdkConfig(orgSlug); @@ -276,7 +276,7 @@ export async function updateRelease( return unwrapResult<SentryRelease>( result, - `Failed to update release '${version}'` + `Failed to update release '${version}'`, ); } @@ -288,7 +288,7 @@ export async function updateRelease( */ export async function deleteRelease( orgSlug: string, - version: string + version: string, ): Promise<void> { const config = await getOrgSdkConfig(orgSlug); @@ -312,7 +312,7 @@ export async function deleteRelease( */ export async function listReleaseDeploys( orgSlug: string, - version: string + version: string, ): Promise<SentryDeploy[]> { const config = await getOrgSdkConfig(orgSlug); @@ -326,7 +326,7 @@ export async function listReleaseDeploys( return unwrapResult<SentryDeploy[]>( result, - `Failed to list deploys for release '${version}'` + `Failed to list deploys for release '${version}'`, ); } @@ -347,7 +347,7 @@ export async function createReleaseDeploy( url?: string; dateStarted?: string; dateFinished?: string; - } + }, ): Promise<SentryDeploy> { const config = await getOrgSdkConfig(orgSlug); @@ -379,9 +379,9 @@ export async function createReleaseDeploy( */ async function getPreviousReleaseCommit( orgSlug: string, - version: string + version: string, ): Promise<string | undefined> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const regionUrl = await resolveOrgRegion(orgSlug); const encodedVersion = encodeURIComponent(version); @@ -390,7 +390,7 @@ async function getPreviousReleaseCommit( }>( regionUrl, `organizations/${orgSlug}/releases/${encodedVersion}/previous-with-commits/`, - { method: "GET" } + { method: "GET" }, ); return data?.lastCommit?.id; } catch { @@ -434,7 +434,7 @@ export const NO_REPO_INTEGRATIONS_MESSAGE = export async function setCommitsAuto( orgSlug: string, version: string, - cwd?: string + cwd?: string, ): Promise<SentryRelease> { const localRepo = getRepositoryName(cwd); if (!localRepo) { @@ -445,7 +445,7 @@ export async function setCommitsAuto( "sentry release set-commits <version> --local", "sentry release set-commits <version> --commit owner/repo@abc123", ], - "repository" + "repository", ); } @@ -466,7 +466,7 @@ export async function setCommitsAuto( } const match = result.data.find( - (r) => r.name.toLowerCase() === localRepoLower + (r) => r.name.toLowerCase() === localRepoLower, ); if (match) { const headCommit = getHeadCommit(cwd); @@ -495,7 +495,7 @@ export async function setCommitsAuto( throw new ValidationError( `No Sentry repository matching '${localRepo}'.`, - "repository" + "repository", ); } @@ -517,7 +517,7 @@ export async function setCommitsWithRefs( repository: string; commit: string; previousCommit?: string; - }> + }>, ): Promise<SentryRelease> { const regionUrl = await resolveOrgRegion(orgSlug); const encodedVersion = encodeURIComponent(version); @@ -527,7 +527,7 @@ export async function setCommitsWithRefs( { method: "PUT", body: { refs }, - } + }, ); return data; } @@ -550,7 +550,7 @@ export function setCommitsLocal( author_name?: string; author_email?: string; timestamp?: string; - }> + }>, ): Promise<SentryRelease> { return updateRelease(orgSlug, version, { commits }); } @@ -574,7 +574,7 @@ export type ProjectEnvironment = { */ export async function listProjectEnvironments( orgSlug: string, - projectSlug: string + projectSlug: string, ): Promise<ProjectEnvironment[]> { const config = await getOrgSdkConfig(orgSlug); const result = await sdkListProjectEnvironments({ @@ -587,6 +587,6 @@ export async function listProjectEnvironments( }); return unwrapResult<ProjectEnvironment[]>( result, - "Failed to list environments" + "Failed to list environments", ); } diff --git a/packages/cli/src/lib/api/replays.ts b/packages/cli/src/lib/api/replays.ts index 987c7c39e..f43793695 100644 --- a/packages/cli/src/lib/api/replays.ts +++ b/packages/cli/src/lib/api/replays.ts @@ -128,16 +128,16 @@ type FetchReplayRecordingSegmentsPageOptions = { * its object boundary. The SDK invokes response validators outside its normal * error-result path, so convert Zod failures to the CLI's API error type here. */ -// biome-ignore lint/suspicious/useAwait: the SDK's responseValidator hook requires a Promise-returning function +// the SDK's responseValidator hook requires a Promise-returning function async function validateReplayRecordingSegmentsResponse( - data: unknown + data: unknown, ): Promise<void> { const result = safeParse(vListProjectReplayRecordingSegmentsResponse, data); if (!result.success) { throw new ApiError( "Unexpected replay recording segments response", 0, - result.issues.map((issue) => issue.message).join(", ") + result.issues.map((issue) => issue.message).join(", "), ); } } @@ -186,7 +186,7 @@ function normalizeReplayProjectId< async function fetchReplayPage( regionUrl: string, orgSlug: string, - page: FetchReplayPageOptions + page: FetchReplayPageOptions, ): Promise<PaginatedResponse<ReplayListItem[]>> { const { cursor, options, perPage } = page; const { data, headers } = await apiRequestToRegion( @@ -216,7 +216,7 @@ async function fetchReplayPage( unknown, ReplayListResponse >, - } + }, ); const { nextCursor } = parseLinkHeader(headers.get("link") ?? null); @@ -233,14 +233,14 @@ async function fetchReplayPage( */ export async function listReplays( orgSlug: string, - options: ListReplaysOptions = {} + options: ListReplaysOptions = {}, ): Promise<PaginatedResponse<ReplayListItem[]>> { const regionUrl = await resolveOrgRegion(orgSlug); return paginate( options, (perPage, cursor) => fetchReplayPage(regionUrl, orgSlug, { options, perPage, cursor }), - 25 + 25, ); } @@ -249,7 +249,7 @@ export async function listReplays( */ export async function getReplay( orgSlug: string, - replayId: string + replayId: string, ): Promise<ReplayDetails> { const regionUrl = await resolveOrgRegion(orgSlug); const { data } = await apiRequestToRegion( @@ -260,7 +260,7 @@ export async function getReplay( unknown, ReplayDetailsResponse >, - } + }, ); return normalizeReplayProjectId(data.data); } @@ -276,7 +276,7 @@ export async function getReplay( export async function resolveReplay( orgSlug: string, replayOrTraceId: string, - options: Pick<ListReplaysOptions, "projectSlugs"> = {} + options: Pick<ListReplaysOptions, "projectSlugs"> = {}, ): Promise<ReplayDetails> { try { return await getReplay(orgSlug, replayOrTraceId); @@ -286,7 +286,7 @@ export async function resolveReplay( } log.debug( - `Replay ${replayOrTraceId} not found by id; searching by linked trace` + `Replay ${replayOrTraceId} not found by id; searching by linked trace`, ); const { data } = await listReplays(orgSlug, { query: `trace:${replayOrTraceId}`, @@ -317,7 +317,7 @@ export async function getReplayRecordingSegments( orgSlug: string, projectSlugOrId: string, replayId: string, - options: GetReplayRecordingSegmentsOptions = {} + options: GetReplayRecordingSegmentsOptions = {}, ): Promise<ListProjectReplayRecordingSegmentsResponse> { const expectedSegments = options.expectedSegments ?? Number.POSITIVE_INFINITY; const segments: ListProjectReplayRecordingSegmentsResponse = []; @@ -342,7 +342,7 @@ export async function getReplayRecordingSegments( log.warn( `Pagination limit reached (${MAX_PAGINATION_PAGES} pages, ${segments.length} segments). ` + - "The recording may be incomplete." + "The recording may be incomplete.", ); return segments; } @@ -351,7 +351,7 @@ export async function getReplayRecordingSegments( * Fetch one SDK-backed recording page while preserving its cursor metadata. */ async function fetchReplayRecordingSegmentsPage( - options: FetchReplayRecordingSegmentsPageOptions + options: FetchReplayRecordingSegmentsPageOptions, ): Promise<PaginatedResponse<ListProjectReplayRecordingSegmentsResponse>> { const { cursor, orgSlug, projectSlugOrId, replayId } = options; const config = await getOrgSdkConfig(orgSlug); @@ -374,7 +374,7 @@ async function fetchReplayRecordingSegmentsPage( return unwrapPaginatedResult<ListProjectReplayRecordingSegmentsResponse>( result, - "Failed to fetch replay recording segments" + "Failed to fetch replay recording segments", ); } @@ -383,7 +383,7 @@ async function fetchReplayRecordingSegmentsPage( */ export async function listReplayIdsForIssue( orgSlug: string, - issueId: string | number + issueId: string | number, ): Promise<string[]> { const normalizedIssueId = String(issueId); const regionUrl = await resolveOrgRegion(orgSlug); @@ -399,7 +399,7 @@ export async function listReplayIdsForIssue( statsPeriod: "90d", }, schema: ReplayIdsByResourceSchema, - } + }, ); return data[normalizedIssueId] ?? []; diff --git a/packages/cli/src/lib/api/repositories.ts b/packages/cli/src/lib/api/repositories.ts index c91395347..9b8e0164b 100644 --- a/packages/cli/src/lib/api/repositories.ts +++ b/packages/cli/src/lib/api/repositories.ts @@ -31,7 +31,7 @@ const log = logger.withTag("api.repositories"); * list, use {@link listAllRepositories} — it walks every page. */ export async function listRepositories( - orgSlug: string + orgSlug: string, ): Promise<SentryRepository[]> { const config = await getOrgSdkConfig(orgSlug); @@ -42,7 +42,7 @@ export async function listRepositories( return unwrapResult<SentryRepository[]>( result, - "Failed to list repositories" + "Failed to list repositories", ); } @@ -57,7 +57,7 @@ export async function listRepositories( */ export async function listRepositoriesPaginated( orgSlug: string, - options: { cursor?: string; perPage?: number } = {} + options: { cursor?: string; perPage?: number } = {}, ): Promise<PaginatedResponse<SentryRepository[]>> { const config = await getOrgSdkConfig(orgSlug); @@ -72,7 +72,7 @@ export async function listRepositoriesPaginated( const paginated = unwrapPaginatedResult<SentryRepository[]>( result, - "Failed to list repositories" + "Failed to list repositories", ); if (!Array.isArray(paginated.data)) { @@ -80,7 +80,7 @@ export async function listRepositoriesPaginated( "Failed to list repositories: unexpected response format", result.response.status, "Expected the repositories endpoint to return an array. Check the configured Sentry URL and the endpoint response.", - new URL(result.request.url).pathname + new URL(result.request.url).pathname, ); } return paginated; @@ -97,7 +97,7 @@ export async function listRepositoriesPaginated( * @returns All Sentry-registered repositories across all pages */ export async function listAllRepositories( - orgSlug: string + orgSlug: string, ): Promise<SentryRepository[]> { const { data } = await autoPaginate( (cursor) => @@ -105,7 +105,7 @@ export async function listAllRepositories( cursor, perPage: API_MAX_PER_PAGE, }), - MAX_PAGINATION_PAGES * API_MAX_PER_PAGE + MAX_PAGINATION_PAGES * API_MAX_PER_PAGE, ); return data; } @@ -129,7 +129,7 @@ export async function listAllRepositories( * @returns All Sentry-registered repositories for the org */ export async function listRepositoriesCached( - orgSlug: string + orgSlug: string, ): Promise<SentryRepository[]> { const cached = getCachedRepos(orgSlug); if (cached) { @@ -142,7 +142,7 @@ export async function listRepositoriesCached( // Non-essential: the primary API fetch already succeeded. A read-only // DB or transient write failure shouldn't fail the whole command. log.debug( - `Could not persist repo cache for '${orgSlug}': ${error instanceof Error ? error.message : String(error)}` + `Could not persist repo cache for '${orgSlug}': ${error instanceof Error ? error.message : String(error)}`, ); } return fresh; diff --git a/packages/cli/src/lib/api/seer.ts b/packages/cli/src/lib/api/seer.ts index 8779d8673..7e6b1326d 100644 --- a/packages/cli/src/lib/api/seer.ts +++ b/packages/cli/src/lib/api/seer.ts @@ -33,7 +33,7 @@ const EXPLORER_MODE_PARAMS = { mode: "explorer" }; * `WAITING_FOR_USER_RESPONSE`. */ export function normalizeAgentStatus( - status: string | null | undefined + status: string | null | undefined, ): string { if (!status) { return "PROCESSING"; @@ -68,7 +68,7 @@ export function normalizeAgentStatus( */ export async function triggerRootCauseAnalysis( orgSlug: string, - issueId: string + issueId: string, ): Promise<{ run_id?: number; sentry_run_id: string | null }> { const regionUrl = await resolveOrgRegion(orgSlug); @@ -96,7 +96,7 @@ export async function triggerRootCauseAnalysis( */ export async function getAutofixState( orgSlug: string, - issueId: string + issueId: string, ): Promise<AutofixState | null> { const regionUrl = await resolveOrgRegion(orgSlug); @@ -106,7 +106,7 @@ export async function getAutofixState( { params: EXPLORER_MODE_PARAMS, schema: AutofixResponseSchema, - } + }, ); if (!data.autofix) { @@ -132,7 +132,7 @@ export async function getAutofixState( export async function triggerSolutionPlanning( orgSlug: string, issueId: string, - runId: string | number + runId: string | number, ): Promise<unknown> { const regionUrl = await resolveOrgRegion(orgSlug); @@ -151,7 +151,7 @@ export async function triggerSolutionPlanning( ...runIdBodyField, referrer: "api.cli", }, - } + }, ); return data; } diff --git a/packages/cli/src/lib/api/sourcemaps.ts b/packages/cli/src/lib/api/sourcemaps.ts index a30c82473..67425a1ad 100644 --- a/packages/cli/src/lib/api/sourcemaps.ts +++ b/packages/cli/src/lib/api/sourcemaps.ts @@ -39,7 +39,7 @@ import { apiRequestToRegion } from "./infrastructure.js"; // ── Re-exports for backward compatibility ─────────────────────────── // These were originally defined in this module. External consumers // (commands, tests) may still import them from here. -// biome-ignore lint/performance/noBarrelFile: backward-compat re-exports, not a barrel +// backward-compat re-exports, not a barrel export { type AssembleResponse, AssembleResponseSchema, @@ -126,7 +126,7 @@ export function resolveUploadWait(flags: { if (waitFor !== undefined && (!Number.isFinite(waitFor) || waitFor <= 0)) { throw new ValidationError( "--wait-for must be a positive number of seconds.", - "wait-for" + "wait-for", ); } return { @@ -173,7 +173,7 @@ export async function buildArtifactBundle( release?: string; dist?: string; compression?: ZipCompression; - } + }, ): Promise<void> { // Build manifest.json const filesManifest: Record< @@ -279,7 +279,7 @@ export async function uploadSourcemaps(options: UploadOptions): Promise<void> { }); } finally { // Always clean up the temp file - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await unlink(tmpZipPath).catch(() => { // Best-effort cleanup — OS temp directory will eventually purge it }); @@ -318,7 +318,7 @@ async function uploadArtifactBundle(opts: { // Step 3: Split into chunks, hash each chunk + compute overall checksum const { chunks, overallChecksum } = await hashChunks( tmpZipPath, - serverOptions.chunkSize + serverOptions.chunkSize, ); const regionUrl = await resolveOrgRegion(org); @@ -340,7 +340,7 @@ async function uploadArtifactBundle(opts: { method: "POST", body: assembleBody, schema: AssembleResponseSchema, - } + }, ); // Fail fast on server-side assembly error @@ -349,7 +349,7 @@ async function uploadArtifactBundle(opts: { "Artifact bundle assembly failed", 500, firstAssemble.detail ?? "Unknown error", - assembleEndpoint + assembleEndpoint, ); } diff --git a/packages/cli/src/lib/api/status-page.ts b/packages/cli/src/lib/api/status-page.ts index e73854814..da30f3d6a 100644 --- a/packages/cli/src/lib/api/status-page.ts +++ b/packages/cli/src/lib/api/status-page.ts @@ -98,7 +98,7 @@ type SummaryResponse = { * custom URL to point at a self-hosted or regional Statuspage instance. */ export async function fetchSentryStatus( - baseUrl: string = SENTRY_STATUS_PAGE_URL + baseUrl: string = SENTRY_STATUS_PAGE_URL, ): Promise<SentryStatus> { const normalized = baseUrl.replace(TRAILING_SLASHES, ""); @@ -115,7 +115,7 @@ export async function fetchSentryStatus( * throws. */ async function tryFetchStatuspageSummary( - normalized: string + normalized: string, ): Promise<SentryStatus | undefined> { const endpoint = `${normalized}/api/v2/summary.json`; @@ -181,7 +181,7 @@ async function tryFetchStatuspageSummary( * HTTP 200 whenever the web process is up, masking real backend outages. */ async function probeSelfHostedHealth( - normalized: string + normalized: string, ): Promise<SentryStatus> { const healthEndpoint = `${normalized}/_health/?full=1`; try { diff --git a/packages/cli/src/lib/api/teams.ts b/packages/cli/src/lib/api/teams.ts index 6106f50cc..2df82d54f 100644 --- a/packages/cli/src/lib/api/teams.ts +++ b/packages/cli/src/lib/api/teams.ts @@ -10,7 +10,7 @@ import { listOrganizationTeams, listProjectTeams as sdkListProjectTeams, } from "@sentry/api"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import type { SentryTeam } from "../../types/index.js"; @@ -49,7 +49,7 @@ export async function listTeams(orgSlug: string): Promise<SentryTeam[]> { */ export async function listTeamsPaginated( orgSlug: string, - options: { cursor?: string; perPage?: number } = {} + options: { cursor?: string; perPage?: number } = {}, ): Promise<PaginatedResponse<SentryTeam[]>> { const config = await getOrgSdkConfig(orgSlug); @@ -77,7 +77,7 @@ export async function listTeamsPaginated( */ export async function listProjectTeams( orgSlug: string, - projectSlug: string + projectSlug: string, ): Promise<SentryTeam[]> { const config = await getOrgSdkConfig(orgSlug); const result = await sdkListProjectTeams({ @@ -104,7 +104,7 @@ export async function listProjectTeams( */ export async function createTeam( orgSlug: string, - slug: string + slug: string, ): Promise<SentryTeam> { const config = await getOrgSdkConfig(orgSlug); const result = await createOrganizationTeam({ @@ -122,7 +122,7 @@ export async function createTeam( extra: { orgSlug, teamSlug: team.slug, context: "auto-add member" }, }); logger.warn( - `Team '${team.slug}' was created but you could not be added as a member.` + `Team '${team.slug}' was created but you could not be added as a member.`, ); } @@ -139,7 +139,7 @@ export async function createTeam( export async function addMemberToTeam( orgSlug: string, teamSlug: string, - memberId: string + memberId: string, ): Promise<void> { const config = await getOrgSdkConfig(orgSlug); const result = await addOrganizationMemberTeam({ diff --git a/packages/cli/src/lib/api/traces.ts b/packages/cli/src/lib/api/traces.ts index ab6c9e889..a3193befd 100644 --- a/packages/cli/src/lib/api/traces.ts +++ b/packages/cli/src/lib/api/traces.ts @@ -117,7 +117,7 @@ async function requestTrace( regionUrl: string, orgSlug: string, traceId: string, - params: GetDetailedTraceOptions & { statsPeriod?: string } + params: GetDetailedTraceOptions & { statsPeriod?: string }, ): Promise<TraceSpan[]> { const { data } = await apiRequestToRegion<TraceSpan[]>( regionUrl, @@ -130,7 +130,7 @@ async function requestTrace( project: params.projectId ?? -1, additional_attributes: params.additionalAttributes, }, - } + }, ); return data.map(normalizeTraceSpan); } @@ -155,7 +155,7 @@ async function requestTrace( export async function getDetailedTrace( orgSlug: string, traceId: string, - options: GetDetailedTraceOptions = {} + options: GetDetailedTraceOptions = {}, ): Promise<TraceSpan[]> { const regionUrl = await resolveOrgRegion(orgSlug); const shared = { @@ -180,7 +180,7 @@ export async function getDetailedTrace( } log.debug( - `No spans for trace ${traceId} in ${TRACE_LOOKUP_STATS_PERIOD}; retrying ${TRACE_LOOKUP_FALLBACK_STATS_PERIOD}` + `No spans for trace ${traceId} in ${TRACE_LOOKUP_STATS_PERIOD}; retrying ${TRACE_LOOKUP_FALLBACK_STATS_PERIOD}`, ); return requestTrace(regionUrl, orgSlug, traceId, { ...shared, @@ -209,7 +209,7 @@ export async function getTraceItemDetail( orgSlug: string, projectSlug: string, itemId: string, - { traceId, itemType }: GetTraceItemDetailOptions + { traceId, itemType }: GetTraceItemDetailOptions, ): Promise<TraceItemDetail> { const regionUrl = await resolveOrgRegion(orgSlug); const { data } = await apiRequestToRegion<TraceItemDetail>( @@ -218,7 +218,7 @@ export async function getTraceItemDetail( { params: { trace_id: traceId, item_type: itemType }, schema: TraceItemDetailSchema, - } + }, ); return data; } @@ -235,7 +235,7 @@ export function getSpanDetails( orgSlug: string, projectSlug: string, spanId: string, - traceId: string + traceId: string, ): Promise<TraceItemDetail> { return getTraceItemDetail(orgSlug, projectSlug, spanId, { traceId, @@ -253,7 +253,7 @@ export function getSpanDetails( export async function getTraceMeta( orgSlug: string, traceId: string, - statsPeriod = "14d" + statsPeriod = "14d", ): Promise<TraceMeta> { const regionUrl = await resolveOrgRegion(orgSlug); @@ -263,7 +263,7 @@ export async function getTraceMeta( { params: { statsPeriod }, schema: TraceMetaSchema, - } + }, ); return data; } @@ -281,15 +281,15 @@ const SPAN_DETAIL_CONCURRENCY = 15; * and EAP storage internals (tags[], precise timestamps, etc.). */ export function attributesToDict( - attributes: TraceItemDetail["attributes"] + attributes: TraceItemDetail["attributes"], ): Record<string, unknown> { return Object.fromEntries( attributes .filter( (a) => - !(REDUNDANT_DETAIL_ATTRS.has(a.name) || a.name.startsWith("tags[")) + !(REDUNDANT_DETAIL_ATTRS.has(a.name) || a.name.startsWith("tags[")), ) - .map((a) => [a.name, a.value]) + .map((a) => [a.name, a.value]), ); } @@ -318,7 +318,7 @@ export type FetchMultiSpanDetailsOptions = { */ export async function fetchMultiSpanDetails( spans: Array<{ span_id: string; project_slug?: string }>, - options: FetchMultiSpanDetailsOptions + options: FetchMultiSpanDetailsOptions, ): Promise<Map<string, TraceItemDetail>> { const { org, fallbackProject, traceId, onProgress } = options; const limit = pLimit(SPAN_DETAIL_CONCURRENCY); @@ -334,7 +334,7 @@ export async function fetchMultiSpanDetails( // targets where neither the span nor the fallback has a project. if (!projectSlug) { log.debug( - `Skipping detail fetch for span ${span.span_id}: no project slug available` + `Skipping detail fetch for span ${span.span_id}: no project slug available`, ); completed += 1; onProgress?.(completed, total); @@ -345,7 +345,7 @@ export async function fetchMultiSpanDetails( org, projectSlug, span.span_id, - traceId + traceId, ); details.set(span.span_id, detail); } catch { @@ -399,7 +399,7 @@ const TRANSACTION_FIELDS = [ */ function resolveNumericProjectId( projectSlug: string, - projectId: number | undefined + projectId: number | undefined, ): number | undefined { if (projectId !== undefined) { return projectId; @@ -436,19 +436,18 @@ type ListTransactionsOptions = { * Internal helper used by {@link listTransactions} for both single-page and * multi-page (auto-paginating) fetches. */ -// biome-ignore lint/nursery/useMaxParams: internal helper mirrors the public API surface async function fetchTransactionsPage( regionUrl: string, orgSlug: string, projectSlug: string, options: ListTransactionsOptions, - perPage: number + perPage: number, ): Promise<PaginatedResponse<TransactionListItem[]>> { // Prefer the numeric `project=` param — `project:<slug>` in the search query // only matches projects that are actively selected (#1317). const numericProjectId = resolveNumericProjectId( projectSlug, - options.projectId + options.projectId, ); const projectFilter = numericProjectId === undefined ? `project:${projectSlug}` : ""; @@ -483,7 +482,7 @@ async function fetchTransactionsPage( cursor: options.cursor, }, schema: TransactionsResponseSchema, - } + }, ); const { nextCursor } = parseLinkHeader(headers.get("link") ?? null); @@ -510,7 +509,7 @@ async function fetchTransactionsPage( export async function listTransactions( orgSlug: string, projectSlug: string, - options: ListTransactionsOptions = {} + options: ListTransactionsOptions = {}, ): Promise<PaginatedResponse<TransactionListItem[]>> { const regionUrl = await resolveOrgRegion(orgSlug); return paginate(options, (perPage, cursor) => @@ -519,8 +518,8 @@ export async function listTransactions( orgSlug, projectSlug, { ...options, cursor }, - perPage - ) + perPage, + ), ); } @@ -575,13 +574,12 @@ type ListSpansOptions = { * Internal helper used by {@link listSpans} for both single-page and * multi-page (auto-paginating) fetches. */ -// biome-ignore lint/nursery/useMaxParams: internal helper mirrors the public API surface async function fetchSpansPage( regionUrl: string, orgSlug: string, projectSlug: string, options: ListSpansOptions, - perPage: number + perPage: number, ): Promise<PaginatedResponse<SpanListItem[]>> { // Prefer the numeric `project=` param — `project:<slug>` in the search query // only matches projects that are actively selected (#1317). @@ -625,7 +623,7 @@ async function fetchSpansPage( cursor: options.cursor, }, schema: SpansResponseSchema, - } + }, ); const { nextCursor } = parseLinkHeader(headers.get("link") ?? null); @@ -647,7 +645,7 @@ async function fetchSpansPage( export async function listSpans( orgSlug: string, projectSlug: string, - options: ListSpansOptions = {} + options: ListSpansOptions = {}, ): Promise<PaginatedResponse<SpanListItem[]>> { const regionUrl = await resolveOrgRegion(orgSlug); return paginate(options, (perPage, cursor) => @@ -656,7 +654,7 @@ export async function listSpans( orgSlug, projectSlug, { ...options, cursor }, - perPage - ) + perPage, + ), ); } diff --git a/packages/cli/src/lib/api/trials.ts b/packages/cli/src/lib/api/trials.ts index f59f5667f..8776df30e 100644 --- a/packages/cli/src/lib/api/trials.ts +++ b/packages/cli/src/lib/api/trials.ts @@ -24,13 +24,13 @@ import { apiRequestToRegion } from "./infrastructure.js"; * @returns Customer trial info with product trials, plan trial status, and plan details */ export async function getCustomerTrialInfo( - orgSlug: string + orgSlug: string, ): Promise<CustomerTrialInfo> { // /customers/ is a control silo endpoint (billing), not region-scoped const { data } = await apiRequestToRegion<CustomerTrialInfo>( getControlSiloUrl(), `/customers/${orgSlug}/`, - { schema: CustomerTrialInfoSchema } + { schema: CustomerTrialInfoSchema }, ); return data; } @@ -46,7 +46,7 @@ export async function getCustomerTrialInfo( * @returns Array of product trials (may be empty) */ export async function getProductTrials( - orgSlug: string + orgSlug: string, ): Promise<ProductTrial[]> { const info = await getCustomerTrialInfo(orgSlug); return info.productTrials ?? []; @@ -64,7 +64,7 @@ export async function getProductTrials( */ export async function startProductTrial( orgSlug: string, - category: string + category: string, ): Promise<void> { // /customers/ is a control silo endpoint (billing), not region-scoped await apiRequestToRegion( @@ -76,6 +76,6 @@ export async function startProductTrial( referrer: "sentry-cli", productTrial: { category, reasonCode: 0 }, }, - } + }, ); } diff --git a/packages/cli/src/lib/api/users.ts b/packages/cli/src/lib/api/users.ts index ed0ca54e3..05cdbcda3 100644 --- a/packages/cli/src/lib/api/users.ts +++ b/packages/cli/src/lib/api/users.ts @@ -21,7 +21,7 @@ export async function getCurrentUser(): Promise<SentryUser> { const { data } = await apiRequestToRegion<SentryUser>( getControlSiloUrl(), "/auth/", - { schema: SentryUserSchema } + { schema: SentryUserSchema }, ); return data; } diff --git a/packages/cli/src/lib/arg-parsing.ts b/packages/cli/src/lib/arg-parsing.ts index f7afaa872..7b57bf5c2 100644 --- a/packages/cli/src/lib/arg-parsing.ts +++ b/packages/cli/src/lib/arg-parsing.ts @@ -126,7 +126,7 @@ const WHITESPACE_SPLIT_PATTERN = /\s+/; */ export function looksLikeIssueShortId( str: string, - opts?: { ignoreCase?: boolean } + opts?: { ignoreCase?: boolean }, ): boolean { if (opts?.ignoreCase) { return matchesIssueShortIdIgnoreCase(str); @@ -166,7 +166,7 @@ export function isIssueCommandToken(slug: string): boolean { function rejectIssueCommandTokenWithNumericSuffix( arg: string, projectSlug: string, - suffix: string + suffix: string, ): void { if (!(isIssueCommandToken(projectSlug) && NUMERIC_SUFFIX_RE.test(suffix))) { return; @@ -181,7 +181,7 @@ function rejectIssueCommandTokenWithNumericSuffix( "sentry issue explain 123456789", "sentry issue explain my-org/project/PROJECT-G", ], - "issue" + "issue", ); } @@ -345,7 +345,7 @@ export function looksLikePath(arg: string): boolean { */ export function detectSwappedViewArgs( first: string, - second: string + second: string, ): string | null { if (second.includes("/") && !first.includes("/")) { return `Arguments appear reversed. Interpreting as: ${second} ${first}`; @@ -376,7 +376,7 @@ export function detectSwappedViewArgs( export function detectSwappedTrialArgs( first: string, second: string, - isKnownName: (value: string) => boolean + isKnownName: (value: string) => boolean, ): { name: string; org: string; warning: string } | null { // If first is already a known name, order is correct if (isKnownName(first)) { @@ -421,7 +421,7 @@ export function validateLimit(value: string, min = 1, max = 1000): number { if (Number.isNaN(num) || num < min || num > max) { throw new ValidationError( `--limit must be between ${min} and ${max}`, - "limit" + "limit", ); } return num; @@ -444,7 +444,7 @@ export function parseLogSort(value: string): LogSortDirection { if (!VALID_LOG_SORT_DIRECTIONS.includes(value as LogSortDirection)) { throw new ValidationError( `Invalid sort value. Must be one of: ${VALID_LOG_SORT_DIRECTIONS.join(", ")}`, - "sort" + "sort", ); } return value as LogSortDirection; @@ -663,14 +663,14 @@ function rejectAtSelector(value: string, label: string): void { throw new ValidationError( `'${value}' is an issue selector, not ${article} ${label}.\n` + ` Use: sentry issue view ${value}`, - label + label, ); } throw new ValidationError( `Invalid ${label}: '${value}' starts with '@'.\n` + " Slugs contain only letters, numbers, hyphens, and underscores.", - label + label, ); } @@ -688,7 +688,7 @@ function parseSlashOrgProject(input: string): ParsedOrgProject { // "/cli" → search for project across all orgs if (!rawProject) { throw new ValidationError( - 'Invalid format: "/" requires a project slug (e.g., "/cli")' + 'Invalid format: "/" requires a project slug (e.g., "/cli")', ); } rejectAtSelector(rawProject, "project slug"); @@ -906,7 +906,7 @@ export type ParsedIssueArg = function parseMultiSlashIssueArg( arg: string, org: string, - rest: string + rest: string, ): ParsedIssueArg { const slashIdx = rest.indexOf("/"); const project = rest.slice(0, slashIdx); @@ -915,7 +915,7 @@ function parseMultiSlashIssueArg( if (!(project && remainder)) { throw new ValidationError( `Invalid issue format: "${arg}". Missing project or issue ID segment.`, - "issue" + "issue", ); } @@ -971,7 +971,7 @@ function parseMultiSlashIssueArg( function parseAfterSlash( arg: string, org: string, - rest: string + rest: string, ): ParsedIssueArg { if (isAllDigits(rest)) { // "my-org/123456789" → explicit org + numeric ID @@ -992,14 +992,14 @@ function parseAfterSlash( if (!project) { throw new ValidationError( `Invalid issue format: "${arg}". Cannot use trailing slash before suffix.`, - "issue" + "issue", ); } if (!suffix) { throw new ValidationError( `Invalid issue format: "${arg}". Missing suffix after dash.`, - "issue" + "issue", ); } @@ -1025,7 +1025,7 @@ function parseWithSlash(arg: string): ParsedIssueArg { if (!rest) { throw new ValidationError( `Invalid issue format: "${arg}". Missing issue ID after slash.`, - "issue" + "issue", ); } @@ -1137,14 +1137,14 @@ function parseWithHash(arg: string): ParsedIssueArg { `Invalid issue identifier: "${arg}".\n` + " Use a single '#' separating the project from the short ID, e.g. `org/project#PROJ-123`.\n" + " Or use a slash: `org/project/PROJ-123`.", - "issue identifier" + "issue identifier", ); } if (fragment.includes(":")) { throw new ValidationError( `Invalid issue identifier: "${arg}". Do not mix '#' and ':' separators.\n` + " Use `org/project#PROJ-123` or `project:PROJ-123`.", - "issue identifier" + "issue identifier", ); } // Validate the fragment on its own — it must not contain forbidden characters @@ -1182,14 +1182,14 @@ function parseWithDash(arg: string): ParsedIssueArg { if (!projectSlug) { throw new ValidationError( `Invalid issue format: "${arg}". Missing project before suffix.`, - "issue" + "issue", ); } if (!suffix) { throw new ValidationError( `Invalid issue format: "${arg}". Missing suffix after dash.`, - "issue" + "issue", ); } @@ -1226,7 +1226,7 @@ function parseWithDash(arg: string): ParsedIssueArg { export function parseSlashSeparatedArg( arg: string, idLabel: string, - usageHint: string + usageHint: string, ): { id: string; targetArg: string | undefined } { // Trim whitespace — agents may pass trailing newlines const trimmed = arg.trim(); @@ -1290,7 +1290,7 @@ export function parseIssueArg(arg: string): ParsedIssueArg { if (!input) { throw new ValidationError( "Issue identifier is empty after trimming whitespace.", - "issue identifier" + "issue identifier", ); } @@ -1305,7 +1305,7 @@ export function parseIssueArg(arg: string): ParsedIssueArg { // URL recognized but no issue ID (e.g., trace or project settings URL) throw new ValidationError( "This Sentry URL does not contain an issue ID. Use an issue URL like:\n" + - " https://sentry.io/organizations/{org}/issues/{id}/" + " https://sentry.io/organizations/{org}/issues/{id}/", ); } @@ -1407,7 +1407,7 @@ function parseBareIssueIdentifier(input: string): ParsedIssueArg { */ export function buildProjectQuery( query: string | undefined, - projectFilter: string | undefined + projectFilter: string | undefined, ): string | undefined { if (!projectFilter) { return query; diff --git a/packages/cli/src/lib/async-channel.ts b/packages/cli/src/lib/async-channel.ts index 16e8a84d1..11cca4f19 100644 --- a/packages/cli/src/lib/async-channel.ts +++ b/packages/cli/src/lib/async-channel.ts @@ -43,7 +43,7 @@ export type AsyncChannel<T> = AsyncIterable<T> & { * is called, and a pending resolver for when `next()` is called first. */ export function createAsyncChannel<T>( - options?: AsyncChannelOptions + options?: AsyncChannelOptions, ): AsyncChannel<T> { const buffer: T[] = []; let pending: diff --git a/packages/cli/src/lib/auth-hint.ts b/packages/cli/src/lib/auth-hint.ts index 9e263824a..906af8fd3 100644 --- a/packages/cli/src/lib/auth-hint.ts +++ b/packages/cli/src/lib/auth-hint.ts @@ -23,7 +23,7 @@ * is wrong, just a helpful breadcrumb). */ -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import { getActiveEnvVarName, @@ -85,7 +85,7 @@ export function maybeWarnEnvTokenIgnored(): void { log.info( `${describeIgnoredTokenSource()} but using stored login for ${userLabel}.\n` + - " Set SENTRY_FORCE_ENV_TOKEN=1 to prefer it." + " Set SENTRY_FORCE_ENV_TOKEN=1 to prefer it.", ); } diff --git a/packages/cli/src/lib/auto-auth.ts b/packages/cli/src/lib/auto-auth.ts index 35db74ffe..2542e86bd 100644 --- a/packages/cli/src/lib/auto-auth.ts +++ b/packages/cli/src/lib/auto-auth.ts @@ -33,7 +33,7 @@ export type AutoAuthDeps = { */ export function shouldAutoAuth( err: unknown, - isInteractive: () => boolean + isInteractive: () => boolean, ): err is InstanceType<typeof AuthError> { return ( err instanceof AuthError && @@ -73,7 +73,7 @@ export function assertAutoLoginHostTrusted(): void { export async function recoverWithAutoLogin( err: unknown, retry: () => Promise<void>, - deps: AutoAuthDeps + deps: AutoAuthDeps, ): Promise<number | undefined> { const isInteractive = deps.isInteractive ?? (() => isatty(0)); if (!shouldAutoAuth(err, isInteractive)) { @@ -91,7 +91,7 @@ export async function recoverWithAutoLogin( write( err.reason === "expired" ? "Authentication expired. Starting login flow...\n\n" - : "Authentication required. Starting login flow...\n\n" + : "Authentication required. Starting login flow...\n\n", ); const loginSuccess = await deps.runInteractiveLogin(); diff --git a/packages/cli/src/lib/banner.ts b/packages/cli/src/lib/banner.ts index b2bcef8ae..1d569c3bc 100644 --- a/packages/cli/src/lib/banner.ts +++ b/packages/cli/src/lib/banner.ts @@ -139,7 +139,7 @@ export function bannerLinesForWidth(columns: number): BannerLine[] { * string when the terminal is too narrow for even the text mark. */ export function formatBanner( - columns: number = process.stdout.columns ?? 80 + columns: number = process.stdout.columns ?? 80, ): string { return bannerLinesForWidth(columns) .map(({ content, color }) => chalk.hex(color)(content)) diff --git a/packages/cli/src/lib/bash-hook/traceback.ts b/packages/cli/src/lib/bash-hook/traceback.ts index f33efe959..5af19016e 100644 --- a/packages/cli/src/lib/bash-hook/traceback.ts +++ b/packages/cli/src/lib/bash-hook/traceback.ts @@ -122,7 +122,7 @@ export function parseTracebackContent(content: string): TracebackData { * the error line. File contents are cached to avoid redundant reads. */ async function enrichFramesWithSourceContext( - frames: StackFrame[] + frames: StackFrame[], ): Promise<void> { const sourceCache = new Map<string, string[] | null>(); @@ -205,12 +205,12 @@ export async function buildBashErrorEvent(opts: { if (code === "ENOENT") { throw new ValidationError( `Traceback file not found: ${opts.tracebackPath}`, - "traceback" + "traceback", ); } throw new ValidationError( `Cannot read traceback file ${opts.tracebackPath}: ${(err as Error).message}`, - "traceback" + "traceback", ); } diff --git a/packages/cli/src/lib/binary.ts b/packages/cli/src/lib/binary.ts index e64f5059e..7440bf98f 100644 --- a/packages/cli/src/lib/binary.ts +++ b/packages/cli/src/lib/binary.ts @@ -155,7 +155,7 @@ export function parseInstallationMethod(value: string): InstallationMethod { if (!VALID_METHODS.includes(normalized)) { throw new Error( - `Invalid method: ${value}. Must be one of: ${VALID_METHODS.join(", ")}` + `Invalid method: ${value}. Must be one of: ${VALID_METHODS.join(", ")}`, ); } @@ -237,7 +237,7 @@ export function getPlatformBinaryName(): string { */ export function getBinaryDownloadUrl( version: string, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): string { const tag = `${source.tagPrefix}${version}`; return `https://github.com/${source.githubRepo}/releases/download/${tag}/${getPlatformBinaryName()}`; @@ -245,7 +245,7 @@ export function getBinaryDownloadUrl( /** Build the GitHub API base URL for a release source. */ export function getGitHubReleasesUrl( - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): string { return `https://api.github.com/repos/${source.githubRepo}/releases`; } @@ -253,7 +253,7 @@ export function getGitHubReleasesUrl( /** Build the GitHub API URL for one source-specific release tag. */ export function getGitHubReleaseByTagUrl( version: string, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): string { const tag = `${source.tagPrefix}${version}`; return `${getGitHubReleasesUrl(source)}/tags/${encodeURIComponent(tag)}`; @@ -261,7 +261,7 @@ export function getGitHubReleaseByTagUrl( /** Build the GitHub API URL used to discover a source's latest CLI release. */ export function getGitHubLatestReleaseUrl( - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): string { return source.tagPrefix ? `${getGitHubReleasesUrl(source)}?per_page=100` @@ -270,7 +270,7 @@ export function getGitHubLatestReleaseUrl( /** Build the GitHub API URL used to verify that a source repository exists. */ export function getGitHubRepositoryUrl( - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): string { return `https://api.github.com/repos/${source.githubRepo}`; } @@ -291,7 +291,7 @@ export class UpgradeSourceNotFoundError extends UpgradeError { constructor() { super( "network_error", - "No CLI upgrade source was found: every source returned HTTP 404" + "No CLI upgrade source was found: every source returned HTTP 404", ); this.name = "UpgradeSourceNotFoundError"; } @@ -311,7 +311,7 @@ export type ResolveUpgradeSourceOptions = { async function fetchUpgradeProbe( source: UpgradeSource, - options: ResolveUpgradeSourceOptions + options: ResolveUpgradeSourceOptions, ): Promise<Response> { try { return await (options.fetch ?? customFetch)(options.getProbeUrl(source), { @@ -329,7 +329,7 @@ async function fetchUpgradeProbe( throw new UpgradeTransportError(buildTlsErrorDetail(error)); } throw new UpgradeTransportError( - `Failed to connect to GitHub: ${stringifyUnknown(error)}` + `Failed to connect to GitHub: ${stringifyUnknown(error)}`, ); } } @@ -342,7 +342,7 @@ async function fetchUpgradeProbe( * network failure aborts immediately. */ export async function resolveUpgradeSource( - options: ResolveUpgradeSourceOptions + options: ResolveUpgradeSourceOptions, ): Promise<ResolvedUpgradeSource> { for (const source of options.sources ?? UPGRADE_SOURCES) { const response = await fetchUpgradeProbe(source, options); @@ -352,7 +352,7 @@ export async function resolveUpgradeSource( if (response.status !== 404) { throw new UpgradeError( "network_error", - `Failed to fetch from GitHub: HTTP ${response.status}` + `Failed to fetch from GitHub: HTTP ${response.status}`, ); } } @@ -442,7 +442,7 @@ export function getBinaryPaths(installPath: string): { */ export function determineInstallDir( homeDir: string, - env: NodeJS.ProcessEnv + env: NodeJS.ProcessEnv, ): string { const pathDirs = (env.PATH ?? "").split(delimiter); @@ -496,7 +496,7 @@ export function getGitHubHeaders(): Record<string, string> { export async function fetchWithUpgradeError( url: string, init: RequestInit, - serviceName: string + serviceName: string, ): Promise<Response> { try { return await customFetch(url, init); @@ -513,7 +513,7 @@ export async function fetchWithUpgradeError( } const msg = stringifyUnknown(error); throw new UpgradeTransportError( - `Failed to connect to ${serviceName}: ${msg}` + `Failed to connect to ${serviceName}: ${msg}`, ); } } @@ -522,7 +522,7 @@ export async function fetchWithUpgradeError( export async function parseUpgradeJson( response: Response, signal: AbortSignal | undefined, - invalidMessage: string + invalidMessage: string, ): Promise<unknown> { try { return await response.json(); @@ -534,7 +534,7 @@ export async function parseUpgradeJson( throw new UpgradeError("network_error", invalidMessage); } throw new UpgradeTransportError( - `${invalidMessage}: ${stringifyUnknown(error)}` + `${invalidMessage}: ${stringifyUnknown(error)}`, ); } } @@ -562,7 +562,7 @@ export function replaceBinarySync(tempPath: string, installPath: string): void { renameSync(installPath, oldPath); } catch { // Current binary might not exist (fresh install) or .old already exists - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { unlinkSync(oldPath); renameSync(installPath, oldPath); @@ -590,7 +590,7 @@ export function replaceBinarySync(tempPath: string, installPath: string): void { */ export function cleanupOldBinary(oldPath: string): void { // Fire-and-forget: don't await, just let cleanup run in background - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. unlink(oldPath).catch(() => { // Intentionally ignore errors — file may not exist }); @@ -666,7 +666,7 @@ function handleExistingLock(lockPath: string): void { } throw new UpgradeError( "execution_failed", - "Another upgrade is already in progress" + "Another upgrade is already in progress", ); } @@ -690,7 +690,7 @@ function handleExistingLock(lockPath: string): void { * @param lockPath - Path to the lock file */ export function releaseLock(lockPath: string): void { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { unlinkSync(lockPath); } catch { @@ -711,7 +711,7 @@ export function releaseLock(lockPath: string): void { */ export async function installBinary( sourcePath: string, - installDir: string + installDir: string, ): Promise<string> { await mkdir(installDir, { recursive: true, mode: 0o755 }); diff --git a/packages/cli/src/lib/browser.ts b/packages/cli/src/lib/browser.ts index 8e200aa8a..bc9b2ca90 100644 --- a/packages/cli/src/lib/browser.ts +++ b/packages/cli/src/lib/browser.ts @@ -55,7 +55,7 @@ export async function openBrowser(url: string): Promise<boolean> { return false; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const proc = spawn(command, args, { stdio: ["ignore", "ignore", "ignore"], @@ -113,7 +113,7 @@ export async function openOrShowUrl(url: string): Promise<boolean> { */ export async function openInBrowser( url: string | undefined, - entityName = "resource" + entityName = "resource", ): Promise<void> { if (!url) { process.stdout.write(`No URL available for this ${entityName}.\n`); diff --git a/packages/cli/src/lib/build/index.ts b/packages/cli/src/lib/build/index.ts index b981c23c4..327f00b52 100644 --- a/packages/cli/src/lib/build/index.ts +++ b/packages/cli/src/lib/build/index.ts @@ -138,7 +138,7 @@ export function detectBuildFormat(content: Uint8Array): BuildFormat | null { * @param filePath - Path to the build file. */ export async function detectBuildFormatFromFile( - filePath: string + filePath: string, ): Promise<BuildFormat | null> { const src = await open(filePath, "r"); try { @@ -170,7 +170,7 @@ export async function detectBuildFormatFromFile( position += read.bytesRead; unzip.push( Uint8Array.prototype.slice.call(buf, 0, read.bytesRead), - position >= size + position >= size, ); } return classifyBuildFormat(names); @@ -194,7 +194,7 @@ export type PipelinePlugin = { name: string; version: string }; * @param pipeline - The `SENTRY_PIPELINE` value, if set. */ export function parsePluginFromPipeline( - pipeline: string | undefined + pipeline: string | undefined, ): PipelinePlugin | null { if (!pipeline) { return null; @@ -240,7 +240,7 @@ function buildMetadataFile(plugin: PipelinePlugin | null): string { export async function normalizeBuildFile( filePath: string, outPath: string, - plugin: PipelinePlugin | null + plugin: PipelinePlugin | null, ): Promise<void> { const zip = await DeterministicZipWriter.create(outPath); try { @@ -292,28 +292,28 @@ export function validateXcarchiveDirectory(dirPath: string): void { if (!existsSync(join(root, "Info.plist"))) { throw new ValidationError( "Invalid XCArchive: missing Info.plist at the archive root", - "path" + "path", ); } const products = join(root, "Products"); if (!(existsSync(products) && statSync(products).isDirectory())) { throw new ValidationError( "Invalid XCArchive: missing Products/ directory", - "path" + "path", ); } const apps = findAppBundles(products); if (apps.length === 0) { throw new ValidationError( "Invalid XCArchive: no .app bundles found under Products/", - "path" + "path", ); } for (const app of apps) { if (!existsSync(join(app, "Info.plist"))) { throw new ValidationError( `Invalid XCArchive: missing Info.plist in .app bundle: ${basename(app)}`, - "path" + "path", ); } } @@ -377,7 +377,9 @@ async function collectArchiveEntries(root: string): Promise<ArchiveEntry[]> { }; await walk(root, ""); - entries.sort((a, b) => (a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0)); + entries.sort((a, b) => + a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0, + ); return entries; } @@ -406,7 +408,7 @@ async function collectArchiveEntries(root: string): Promise<ArchiveEntry[]> { export async function normalizeBuildDirectory( dirPath: string, outPath: string, - plugin: PipelinePlugin | null + plugin: PipelinePlugin | null, ): Promise<void> { const root = resolve(dirPath); const dirName = basename(root); @@ -484,7 +486,7 @@ type StagedIpaEntry = { name: string; stagePath: string }; */ async function stageIpaPayload( ipaPath: string, - stageDir: string + stageDir: string, ): Promise<StagedIpaEntry[]> { const staged: StagedIpaEntry[] = []; let index = 0; @@ -533,7 +535,7 @@ async function stageIpaPayload( position += bytesRead; unzip.push( Uint8Array.prototype.slice.call(buf, 0, bytesRead), - position >= size + position >= size, ); } } finally { @@ -563,7 +565,7 @@ async function stageIpaPayload( export async function normalizeIpa( ipaPath: string, outPath: string, - plugin: PipelinePlugin | null + plugin: PipelinePlugin | null, ): Promise<void> { const stageDir = await mkdtemp(join(tmpdir(), "sentry-ipa-")); try { diff --git a/packages/cli/src/lib/build/vcs.ts b/packages/cli/src/lib/build/vcs.ts index b85d6c6ba..79cc36000 100644 --- a/packages/cli/src/lib/build/vcs.ts +++ b/packages/cli/src/lib/build/vcs.ts @@ -87,10 +87,7 @@ export function isCi(env: NodeJS.ProcessEnv = process.env): boolean { // Treat explicit opt-out values ("", "false", "0") as not-in-CI so a local // `CI=false` does not silently enable git-metadata collection. return ( - value !== undefined && - value !== "" && - value !== "false" && - value !== "0" + value !== undefined && value !== "" && value !== "false" && value !== "0" ); }); } @@ -122,7 +119,7 @@ function validateShaFlag(value: string, flagName: string): string | undefined { if (!SHA1_RE.test(trimmed)) { throw new ValidationError( `Invalid --${flagName}: expected a 40-character hex SHA-1`, - flagName + flagName, ); } return trimmed; @@ -137,7 +134,7 @@ function validateShaFlag(value: string, flagName: string): string | undefined { */ function readGithubEventSha( kind: "head" | "base", - env: NodeJS.ProcessEnv + env: NodeJS.ProcessEnv, ): string | undefined { const eventPath = env.GITHUB_EVENT_PATH; if (!eventPath) { @@ -224,7 +221,7 @@ function resolveHeadSha( flags: VcsFlags, cwd: string, env: NodeJS.ProcessEnv, - auto: boolean + auto: boolean, ): string | undefined { if (flags["head-sha"] !== undefined) { return validateShaFlag(flags["head-sha"], "head-sha"); @@ -239,7 +236,7 @@ function resolveHeadSha( function resolveHeadRepoName( flags: VcsFlags, cwd: string, - auto: boolean + auto: boolean, ): string | undefined { return flags["head-repo-name"] || (auto ? getRepositoryName(cwd) : undefined); } @@ -257,7 +254,7 @@ function resolveBaseSha( baseRef: string | undefined, cwd: string, env: NodeJS.ProcessEnv, - auto: boolean + auto: boolean, ): string | undefined { if (flags["base-sha"] !== undefined) { return validateShaFlag(flags["base-sha"], "base-sha"); @@ -289,7 +286,7 @@ export function collectVcsMetadata( flags: VcsFlags, cwd: string, env: NodeJS.ProcessEnv, - autoCollect: boolean + autoCollect: boolean, ): VcsInfo { const remoteUrl = autoCollect ? getRemoteUrl(cwd) : undefined; diff --git a/packages/cli/src/lib/build/zip-writer.ts b/packages/cli/src/lib/build/zip-writer.ts index 3d87c5bb0..b9c34687a 100644 --- a/packages/cli/src/lib/build/zip-writer.ts +++ b/packages/cli/src/lib/build/zip-writer.ts @@ -126,7 +126,7 @@ export class DeterministicZipWriter { name: string, crc: number, size: number, - options: ZipEntryOptions + options: ZipEntryOptions, ): Promise<CentralRecord> { const nameBytes = Buffer.from(name, "utf-8"); // fflate sets the UTF-8 name flag (bit 11) when the encoded name is longer @@ -171,7 +171,7 @@ export class DeterministicZipWriter { async addData( name: string, data: Uint8Array, - options: ZipEntryOptions = {} + options: ZipEntryOptions = {}, ): Promise<void> { const buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength); const crc = crc32(buf) >>> 0; @@ -194,7 +194,7 @@ export class DeterministicZipWriter { async addFile( name: string, sourcePath: string, - options: ZipEntryOptions = {} + options: ZipEntryOptions = {}, ): Promise<void> { const src = await open(sourcePath, "r"); try { @@ -285,6 +285,7 @@ export class DeterministicZipWriter { * once; the resulting file is incomplete but the handle won't leak. */ async close(): Promise<void> { + // oxlint-disable-next-line sentry-cli/no-silent-catch -- Closing an already closed handle is harmless during error cleanup. await this.fh.close().catch(() => { // Already closed — ignore. }); diff --git a/packages/cli/src/lib/cache-hint.ts b/packages/cli/src/lib/cache-hint.ts index 4254bff3e..b2b716104 100644 --- a/packages/cli/src/lib/cache-hint.ts +++ b/packages/cli/src/lib/cache-hint.ts @@ -71,7 +71,7 @@ export function formatCacheHint(): string | undefined { * - Neither → `undefined` */ export function appendCacheHint( - existingHint: string | undefined + existingHint: string | undefined, ): string | undefined { const cacheHint = formatCacheHint(); if (existingHint && cacheHint) { diff --git a/packages/cli/src/lib/cache-keys.ts b/packages/cli/src/lib/cache-keys.ts index 41a7f8c29..c6a021221 100644 --- a/packages/cli/src/lib/cache-keys.ts +++ b/packages/cli/src/lib/cache-keys.ts @@ -55,7 +55,7 @@ type CrossEndpointRule = { extra?: (matchGroups: RegExpMatchArray) => string[]; extraAbsolute?: ( matchGroups: RegExpMatchArray, - ctx: { apiBaseUrl: string } + ctx: { apiBaseUrl: string }, ) => string[]; }; @@ -101,10 +101,10 @@ const CROSS_ENDPOINT_RULES: CrossEndpointRule[] = [ */ export function computeInvalidationPrefixes( fullUrl: string, - apiBaseUrl: string + apiBaseUrl: string, ): string[] { let parsed: URL; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { parsed = new URL(fullUrl); } catch { @@ -140,7 +140,7 @@ export function computeInvalidationPrefixes( function* applyCrossEndpointRules( relPath: string, base: string, - apiBaseUrl: string + apiBaseUrl: string, ): Generator<string> { for (const rule of CROSS_ENDPOINT_RULES) { const match = relPath.match(rule.match); diff --git a/packages/cli/src/lib/clipboard.ts b/packages/cli/src/lib/clipboard.ts index aa41c9b11..e228913bf 100644 --- a/packages/cli/src/lib/clipboard.ts +++ b/packages/cli/src/lib/clipboard.ts @@ -53,7 +53,7 @@ export async function copyToClipboard(text: string): Promise<boolean> { return false; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const proc = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"], @@ -88,7 +88,7 @@ export async function copyToClipboard(text: string): Promise<boolean> { */ export function setupCopyKeyListener( stdin: NodeJS.ReadStream, - getText: () => string + getText: () => string, ): () => void { if (!stdin.isTTY) { return () => { diff --git a/packages/cli/src/lib/close-dispatcher.ts b/packages/cli/src/lib/close-dispatcher.ts index 63c1a1a39..2d6516040 100644 --- a/packages/cli/src/lib/close-dispatcher.ts +++ b/packages/cli/src/lib/close-dispatcher.ts @@ -22,7 +22,7 @@ export async function closeGlobalDispatcher(): Promise<void> { GLOBAL_DISPATCHER ] as ClosableDispatcher | undefined; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await dispatcher?.destroy?.(); } catch { diff --git a/packages/cli/src/lib/command-suggestions.ts b/packages/cli/src/lib/command-suggestions.ts index 6730394f9..1af3b0621 100644 --- a/packages/cli/src/lib/command-suggestions.ts +++ b/packages/cli/src/lib/command-suggestions.ts @@ -132,7 +132,7 @@ const SUGGESTIONS: ReadonlyMap<string, CommandSuggestion> = new Map([ */ export function getCommandSuggestion( routeContext: string, - unknownToken: string + unknownToken: string, ): CommandSuggestion | undefined { const key = routeContext ? `${routeContext}/${unknownToken.toLowerCase()}` diff --git a/packages/cli/src/lib/command.ts b/packages/cli/src/lib/command.ts index 16f6e8cc6..bc120ea56 100644 --- a/packages/cli/src/lib/command.ts +++ b/packages/cli/src/lib/command.ts @@ -126,7 +126,7 @@ type SentryCommandFunction< this: CONTEXT, flags: FLAGS, ...args: ARGS - // biome-ignore lint/suspicious/noConfusingVoidType: void is required here — generators that don't return a value have implicit void return, which is distinct from undefined in TypeScript's type system + // void is required here — generators that don't return a value have implicit void return, which is distinct from undefined in TypeScript's type system ) => AsyncGenerator<unknown, CommandReturn | void, undefined>; /** @@ -156,7 +156,7 @@ type LocalCommandBuilderArguments< * }) * ``` */ - // biome-ignore lint/suspicious/noExplicitAny: Variance erasure — OutputConfig<T>.human is contravariant in T, but the builder erases T because it doesn't know the output type. Using `any` allows commands to declare OutputConfig<SpecificType> while the wrapper handles it generically. + // oxlint-disable-next-line typescript/no-explicit-any -- Variance erasure — OutputConfig<T>.human is contravariant in T, but the builder erases T because it doesn't know the output type. Using `any` allows commands to declare OutputConfig<SpecificType> while the wrapper handles it generically. readonly output?: OutputConfig<any>; /** * Whether the command requires authentication. Defaults to `true`. @@ -297,7 +297,7 @@ const LOG_LEVEL_KEY = "log-level"; */ export function applyLoggingFlags( logLevel: LogLevelName | undefined, - verbose: boolean + verbose: boolean, ): void { if (logLevel) { setLogLevel(parseLogLevel(logLevel)); @@ -329,7 +329,7 @@ export function applyOrgProjectFlags( org: string | undefined, project: string | undefined, commandOwnsOrg: boolean, - commandOwnsProject: boolean + commandOwnsProject: boolean, ): void { const env = getEnv(); const orgTrimmed = commandOwnsOrg ? undefined : org?.trim(); @@ -384,7 +384,7 @@ export function applyOrgProjectFlags( * Build the `--fields` flag definition, enriched with available field names * when a schema is registered on the output config. */ -// biome-ignore lint/suspicious/noExplicitAny: OutputConfig type is erased at the builder level +// oxlint-disable-next-line typescript/no-explicit-any -- OutputConfig type is erased at the builder level function buildFieldsFlag(outputConfig?: OutputConfig<any>) { if (!outputConfig?.schema) { return FIELDS_FLAG; @@ -407,8 +407,8 @@ function buildFieldsFlag(outputConfig?: OutputConfig<any>) { */ function enrichDocsWithSchema( docs: CommandDocumentation, - // biome-ignore lint/suspicious/noExplicitAny: OutputConfig type is erased at the builder level - outputConfig?: OutputConfig<any> + // oxlint-disable-next-line typescript/no-explicit-any -- OutputConfig type is erased at the builder level + outputConfig?: OutputConfig<any>, ): CommandDocumentation { if (!outputConfig?.schema) { return docs; @@ -451,8 +451,8 @@ const ALWAYS_STRIP = new Set([LOG_LEVEL_KEY]); */ function mergeGlobalFlags( existingFlags: Record<string, unknown>, - // biome-ignore lint/suspicious/noExplicitAny: OutputConfig type is erased at the builder level - outputConfig?: OutputConfig<any> + // oxlint-disable-next-line typescript/no-explicit-any -- OutputConfig type is erased at the builder level + outputConfig?: OutputConfig<any>, ): { mergedFlags: Record<string, unknown>; commandOwnsOrg: boolean; @@ -494,7 +494,7 @@ export function buildCommand< const ARGS extends BaseArgs = [], const CONTEXT extends CommandContext = CommandContext, >( - builderArgs: LocalCommandBuilderArguments<FLAGS, ARGS, CONTEXT> + builderArgs: LocalCommandBuilderArguments<FLAGS, ARGS, CONTEXT>, ): Command<CONTEXT> { const originalFunc = builderArgs.func; const outputConfig = builderArgs.output; @@ -547,8 +547,8 @@ export function buildCommand< stdout: Writer, value: unknown, flags: Record<string, unknown>, - // biome-ignore lint/suspicious/noExplicitAny: Renderer type mirrors erased OutputConfig<T> - renderer?: HumanRenderer<any> + // oxlint-disable-next-line typescript/no-explicit-any -- Renderer type mirrors erased OutputConfig<T> + renderer?: HumanRenderer<any>, ): void { // ClearScreen token: defer until next render to avoid flash if (value instanceof ClearScreen) { @@ -577,7 +577,7 @@ export function buildCommand< * comma-string to string[] when output: { human: ... }. */ function cleanRawFlags( - raw: Record<string, unknown> + raw: Record<string, unknown>, ): Record<string, unknown> { const clean: Record<string, unknown> = {}; for (const [key, value] of Object.entries(raw)) { @@ -600,8 +600,8 @@ export function buildCommand< stdout: Writer, hint: string | undefined, json: unknown, - // biome-ignore lint/suspicious/noExplicitAny: Renderer type mirrors erased OutputConfig<T> - renderer?: HumanRenderer<any> + // oxlint-disable-next-line typescript/no-explicit-any -- Renderer type mirrors erased OutputConfig<T> + renderer?: HumanRenderer<any>, ): void { if (json) { return; @@ -641,7 +641,7 @@ export function buildCommand< err: unknown, stdout: Writer, ctx: { commandPrefix?: readonly string[]; stderr: Writer }, - args: unknown[] + args: unknown[], ): Promise<boolean> { if (!(err instanceof CliError) || err instanceof OutputError) { return false; @@ -664,8 +664,8 @@ export function buildCommand< } ctx.stderr.write( warning( - `Tip: use --help for help (e.g., sentry ${pathSegments.join(" ")} --help)\n\n` - ) + `Tip: use --help for help (e.g., sentry ${pathSegments.join(" ")} --help)\n\n`, + ), ); stdout.write(`${formatHelpHuman(result)}\n`); return true; @@ -673,7 +673,7 @@ export function buildCommand< // Wrap func to intercept logging flags, capture telemetry, then call original. // The wrapper is an async function that iterates the generator returned by func. - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Central framework wrapper — flag cleanup, env-based JSON, output rendering, and error handling are all tightly coupled. + // Central framework wrapper — flag cleanup, env-based JSON, output rendering, and error handling are all tightly coupled. const wrappedFunc = async function ( this: CONTEXT, flags: Record<string, unknown>, @@ -681,7 +681,7 @@ export function buildCommand< ) { applyLoggingFlags( flags[LOG_LEVEL_KEY] as LogLevelName | undefined, - flags.verbose as boolean + flags.verbose as boolean, ); // Map --org / --project compat flags to env vars before anything @@ -690,7 +690,7 @@ export function buildCommand< flags.org as string | undefined, flags.project as string | undefined, commandOwnsOrg, - commandOwnsProject + commandOwnsProject, ); const cleanFlags = cleanRawFlags(flags as Record<string, unknown>); @@ -715,7 +715,7 @@ export function buildCommand< cleanFlags.json === true || cleanFlags.yes === true || cleanFlags["dry-run"] === true - ) + ), ); const stdout = (this as unknown as { stdout: Writer }).stdout; @@ -742,7 +742,7 @@ export function buildCommand< stdout, new CommandOutput(err.data), cleanFlags, - renderer + renderer, ); } throw err; @@ -778,7 +778,7 @@ export function buildCommand< const generator = originalFunc.call( this, cleanFlags as FLAGS, - ...(args as unknown as ARGS) + ...(args as unknown as ARGS), ); let result = await generator.next(); while (!result.done) { @@ -786,7 +786,7 @@ export function buildCommand< result = await generator.next(); } return result.value as CommandReturn | undefined; - } + }, ); // Render phase: output finalization. @@ -816,7 +816,7 @@ export function buildCommand< commandPrefix?: readonly string[]; stderr: Writer; }, - args + args, ); if (recovered) { return; diff --git a/packages/cli/src/lib/complete.ts b/packages/cli/src/lib/complete.ts index a87435e92..3fec1796a 100644 --- a/packages/cli/src/lib/complete.ts +++ b/packages/cli/src/lib/complete.ts @@ -32,7 +32,7 @@ function getCompletionOrganizations() { return credential ? getCachedOrganizations( getConfiguredSentryUrl() ?? credential.host, - credential.identity + credential.identity, ) : []; } @@ -176,7 +176,7 @@ export const ORG_ONLY_COMMANDS = new Set([ */ export function getCompletions( precedingWords: string[], - partial: string + partial: string, ): Completion[] { // Build the command path from preceding words (e.g., "issue list") const cmdPath = @@ -334,7 +334,7 @@ function completeOrgSlugsWithSlash(partial: string): Completion[] { */ export function completeProjectSlugs( projectPartial: string, - orgSlug: string + orgSlug: string, ): Completion[] { const projects = getCachedProjectsForOrg(orgSlug); diff --git a/packages/cli/src/lib/completions.ts b/packages/cli/src/lib/completions.ts index cfe0a9e6f..13866aa21 100644 --- a/packages/cli/src/lib/completions.ts +++ b/packages/cli/src/lib/completions.ts @@ -66,7 +66,7 @@ export type CommandTree = { * metadata needed for completion. */ function extractFlagEntries( - flags: Record<string, FlagDef> | undefined + flags: Record<string, FlagDef> | undefined, ): FlagEntry[] { if (!flags) { return []; @@ -184,7 +184,7 @@ type BashFlagVars = { function collectCommandFlagVars( prefix: string, cmd: CommandEntry, - out: BashFlagVars + out: BashFlagVars, ): void { if (cmd.flags.length > 0) { const flagNames = cmd.flags.map((f) => `--${f.name}`).join(" "); @@ -194,7 +194,7 @@ function collectCommandFlagVars( if (flag.enumValues && flag.enumValues.length > 0) { const varName = `${prefix}_${shellVarName(flag.name)}_values`; out.enumVarLines.push( - ` local ${varName}="${flag.enumValues.join(" ")}"` + ` local ${varName}="${flag.enumValues.join(" ")}"`, ); out.enumFlagNames.push(`--${flag.name}`); } @@ -257,7 +257,7 @@ export function generateBashCompletion(binaryName: string): string { const caseBranches = groups .map( (g) => - ` ${g.name})\n COMPREPLY=($(compgen -W "\${${shellVarName(g.name)}_commands}" -- "\${cur}"))\n ;;` + ` ${g.name})\n COMPREPLY=($(compgen -W "\${${shellVarName(g.name)}_commands}" -- "\${cur}"))\n ;;`, ) .join("\n"); @@ -390,7 +390,7 @@ export function generateZshCompletion(binaryName: string): string { const caseBranches = groups .map( (g) => - ` ${g.name})\n _describe -t commands '${g.name} command' ${shellVarName(g.name)}_commands\n ;;` + ` ${g.name})\n _describe -t commands '${g.name} command' ${shellVarName(g.name)}_commands\n ;;`, ) .join("\n"); @@ -468,11 +468,11 @@ export function generateFishCompletion(binaryName: string): string { const topLevelLines = [ ...groups.map( (g) => - `complete -c ${binaryName} -n "__fish_use_subcommand" -a "${g.name}" -d "${escapeDblQuote(g.brief)}"` + `complete -c ${binaryName} -n "__fish_use_subcommand" -a "${g.name}" -d "${escapeDblQuote(g.brief)}"`, ), ...standalone.map( (s) => - `complete -c ${binaryName} -n "__fish_use_subcommand" -a "${s.name}" -d "${escapeDblQuote(s.brief)}"` + `complete -c ${binaryName} -n "__fish_use_subcommand" -a "${s.name}" -d "${escapeDblQuote(s.brief)}"`, ), ].join("\n"); @@ -482,7 +482,7 @@ export function generateFishCompletion(binaryName: string): string { const cmdLines = g.subcommands .map( (s) => - `complete -c ${binaryName} -n "__fish_seen_subcommand_from ${g.name}" -a "${s.name}" -d "${escapeDblQuote(s.brief)}"` + `complete -c ${binaryName} -n "__fish_seen_subcommand_from ${g.name}" -a "${s.name}" -d "${escapeDblQuote(s.brief)}"`, ) .join("\n"); @@ -491,8 +491,8 @@ export function generateFishCompletion(binaryName: string): string { .flatMap((s) => s.flags.map( (f) => - `complete -c ${binaryName} -n "__fish_seen_subcommand_from ${g.name}; and __fish_seen_subcommand_from ${s.name}" -l "${f.name}" -d "${escapeDblQuote(f.brief)}"` - ) + `complete -c ${binaryName} -n "__fish_seen_subcommand_from ${g.name}; and __fish_seen_subcommand_from ${s.name}" -l "${f.name}" -d "${escapeDblQuote(f.brief)}"`, + ), ) .join("\n"); @@ -507,7 +507,7 @@ export function generateFishCompletion(binaryName: string): string { const flags = cmd.flags .map( (f) => - `complete -c ${binaryName} -n "__fish_seen_subcommand_from ${cmd.name}" -l "${f.name}" -d "${escapeDblQuote(f.brief)}"` + `complete -c ${binaryName} -n "__fish_seen_subcommand_from ${cmd.name}" -l "${f.name}" -d "${escapeDblQuote(f.brief)}"`, ) .join("\n"); return `\n# ${cmd.name} flags\n${flags}`; @@ -569,7 +569,7 @@ function escapeDblQuote(s: string): string { */ export function getCompletionScript( shellType: ShellType, - binaryName = "sentry" + binaryName = "sentry", ): string | null { switch (shellType) { case "bash": @@ -593,7 +593,7 @@ export function getCompletionScript( export function getCompletionPath( shellType: ShellType, homeDir: string, - xdgDataHome?: string + xdgDataHome?: string, ): string | null { const dataHome = xdgDataHome || join(homeDir, ".local", "share"); @@ -632,7 +632,7 @@ export function getCompletionPath( export async function installCompletions( shellType: ShellType, homeDir: string, - xdgDataHome?: string + xdgDataHome?: string, ): Promise<CompletionLocation | null> { const script = getCompletionScript(shellType); if (!script) { diff --git a/packages/cli/src/lib/constants.ts b/packages/cli/src/lib/constants.ts index d45a7ae17..14612a29f 100644 --- a/packages/cli/src/lib/constants.ts +++ b/packages/cli/src/lib/constants.ts @@ -96,7 +96,7 @@ export function getConfiguredSentryUrl(): string | undefined { return normalized; } catch { throw new ConfigError( - "SENTRY_HOST/SENTRY_URL is not a valid URL; use a credential-free HTTP(S) URL." + "SENTRY_HOST/SENTRY_URL is not a valid URL; use a credential-free HTTP(S) URL.", ); } } @@ -166,12 +166,12 @@ export const SENTRY_CLI_DSN = * mechanism directly. */ if (typeof __SENTRY_DEBUG_ID__ !== "undefined") { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { - // biome-ignore lint/suspicious/useErrorMessage: stack trace capture only + // stack trace capture only const stack = new Error().stack; if (stack) { - // biome-ignore lint/suspicious/noExplicitAny: SDK reads this untyped global + // oxlint-disable-next-line typescript/no-explicit-any -- SDK reads this untyped global const g = globalThis as any; g._sentryDebugIds = g._sentryDebugIds || {}; g._sentryDebugIds[stack] = __SENTRY_DEBUG_ID__; diff --git a/packages/cli/src/lib/crc32.ts b/packages/cli/src/lib/crc32.ts index e8221137d..95bf5686e 100644 --- a/packages/cli/src/lib/crc32.ts +++ b/packages/cli/src/lib/crc32.ts @@ -14,7 +14,7 @@ const POLYNOMIAL = 0xed_b8_83_20; let table: Uint32Array | undefined; -// biome-ignore-start lint/suspicious/noBitwiseOperators: CRC-32 is defined in terms of bitwise operations +// oxlint-disable no-bitwise -- CRC-32 is defined in terms of bitwise operations function getTable(): Uint32Array { if (!table) { table = new Uint32Array(256); @@ -42,7 +42,7 @@ export function crc32Fallback(data: Uint8Array, value = 0): number { } return ~crc >>> 0; } -// biome-ignore-end lint/suspicious/noBitwiseOperators: CRC-32 is defined in terms of bitwise operations +// oxlint-enable no-bitwise /** * Compute the CRC-32 of `data`, optionally continuing from a previous diff --git a/packages/cli/src/lib/credential-redaction.ts b/packages/cli/src/lib/credential-redaction.ts index 445f59627..1765df0c5 100644 --- a/packages/cli/src/lib/credential-redaction.ts +++ b/packages/cli/src/lib/credential-redaction.ts @@ -18,11 +18,11 @@ const BEARER_PART = String.raw`(?:${ESCAPED_CONTROL}[ \t]*|\\+[^"'\s\\]|[^\s"'\\ // Quotes (including JSON-escaped quotes) delimit the diagnostic string. const BEARER_CREDENTIAL = new RegExp( String.raw`\bBearer[ \t]+${BEARER_PART}+(?:(?:\r\n|(?! )[\s\x00-\x1f\x7f-\x9f])[ \t]*${BEARER_PART}+)*`, - "gi" + "gi", ); const SENTRY_CREDENTIAL = new RegExp( String.raw`\bsntry[su]_[A-Za-z0-9._~+/=-]+(?:(?:\r\n|(?! )[\s\x00-\x1f\x7f-\x9f]|${ESCAPED_CONTROL})[ \t]*[A-Za-z0-9._~+/=-]+)*`, - "gi" + "gi", ); /** @@ -59,7 +59,7 @@ function redactInvalidBearerHeaders(text: string): string { } parts.push( text.slice(cursor, match.index), - `${prefix}${quote}Bearer [REDACTED]${quote}` + `${prefix}${quote}Bearer [REDACTED]${quote}`, ); cursor = end + quote.length; } @@ -73,7 +73,7 @@ export function redactCredentialText(text: string): string { .replace(QUOTED_CREDENTIAL, (_match, quote: string, prefix: string) => prefix.toLowerCase().startsWith("bearer") ? `${quote}Bearer [REDACTED]${quote}` - : `${quote}[REDACTED]${quote}` + : `${quote}[REDACTED]${quote}`, ) .replace(BEARER_CREDENTIAL, "Bearer [REDACTED]") .replace(SENTRY_CREDENTIAL, "[REDACTED]"); diff --git a/packages/cli/src/lib/custom-ca.ts b/packages/cli/src/lib/custom-ca.ts index 7e87e0ba1..aec8a466f 100644 --- a/packages/cli/src/lib/custom-ca.ts +++ b/packages/cli/src/lib/custom-ca.ts @@ -58,9 +58,10 @@ let warnedSaas = false; * the lazy loading in `resolve()` — single source of truth for PEM validation. */ export function readCaCertFile( - path: string + path: string, ): { ok: true; content: string } | { ok: false; reason: string } { let content: string; + // oxlint-disable-next-line sentry-cli/no-silent-catch -- A read failure is returned as a typed validation result. try { content = readFileSync(path, "utf-8"); } catch { @@ -196,7 +197,7 @@ export function warnIfSaasWithEnvCa(targetUrl: string): void { log.warn( `Using custom CA certificates from ${resolvedLabel} for sentry.io connections.\n` + " If you intended this (e.g. corporate proxy), silence this warning:\n" + - " sentry cli defaults ca-cert /path/to/cert.pem" + " sentry cli defaults ca-cert /path/to/cert.pem", ); } @@ -304,7 +305,7 @@ export function getCustomCaCerts(): string | undefined { */ export function customFetch( input: string | URL | Request, - init?: RequestInit + init?: RequestInit, ): Promise<Response> { const tlsOpts = getCustomTlsOptions(); if (!tlsOpts) { diff --git a/packages/cli/src/lib/custom-headers.ts b/packages/cli/src/lib/custom-headers.ts index 3d1057516..8cca5b9e2 100644 --- a/packages/cli/src/lib/custom-headers.ts +++ b/packages/cli/src/lib/custom-headers.ts @@ -97,13 +97,13 @@ const scopedHeadersOverride = new AsyncLocalStorage<{ function assertValidHeaderName(name: string, source: string): void { if (!VALID_HEADER_NAME_RE.test(name)) { throw new ConfigError( - `Invalid header name '${name}' in ${source}. Header names must contain only alphanumeric characters, hyphens, and RFC 7230 token characters.` + `Invalid header name '${name}' in ${source}. Header names must contain only alphanumeric characters, hyphens, and RFC 7230 token characters.`, ); } if (FORBIDDEN_HEADER_NAMES.has(name.toLowerCase())) { throw new ConfigError( - `Cannot override reserved header '${name}' in ${source}. This header is managed by the CLI.` + `Cannot override reserved header '${name}' in ${source}. This header is managed by the CLI.`, ); } } @@ -133,7 +133,7 @@ export function parseCustomHeaders(raw: string): readonly [string, string][] { const colonIndex = trimmed.indexOf(":"); if (colonIndex === -1) { throw new ConfigError( - `Invalid header in SENTRY_CUSTOM_HEADERS: '${trimmed}'. Expected 'Name: Value' format.` + `Invalid header in SENTRY_CUSTOM_HEADERS: '${trimmed}'. Expected 'Name: Value' format.`, ); } @@ -142,7 +142,7 @@ export function parseCustomHeaders(raw: string): readonly [string, string][] { if (!name) { throw new ConfigError( - `Invalid header in SENTRY_CUSTOM_HEADERS: empty header name in '${trimmed}'.` + `Invalid header in SENTRY_CUSTOM_HEADERS: empty header name in '${trimmed}'.`, ); } @@ -168,7 +168,7 @@ export function parseCustomHeaders(raw: string): readonly [string, string][] { * @throws {ConfigError} On invalid or reserved header names */ function validateCustomHeadersOverride( - headers: Record<string, string> | undefined + headers: Record<string, string> | undefined, ): readonly [string, string][] | undefined { if (headers === undefined) { return; @@ -179,7 +179,7 @@ function validateCustomHeadersOverride( const name = rawName.trim(); if (!name) { throw new ConfigError( - "Invalid header in SentryOptions.headers: empty header name." + "Invalid header in SentryOptions.headers: empty header name.", ); } assertValidHeaderName(name, "SentryOptions.headers"); @@ -189,18 +189,18 @@ function validateCustomHeadersOverride( } export function setCustomHeadersOverride( - headers: Record<string, string> | undefined + headers: Record<string, string> | undefined, ): void { overrideHeaders = validateCustomHeadersOverride(headers); } export function withCustomHeadersOverride<T>( headers: Record<string, string> | undefined, - callback: () => T + callback: () => T, ): T { return scopedHeadersOverride.run( { value: validateCustomHeadersOverride(headers) }, - callback + callback, ); } @@ -246,7 +246,7 @@ function passesSelfHostedGuard(): boolean { if (!saasWarningLogged) { saasWarningLogged = true; log.warn( - "Custom headers are set but no self-hosted Sentry instance is configured. Headers will be ignored." + "Custom headers are set but no self-hosted Sentry instance is configured. Headers will be ignored.", ); } return false; @@ -306,7 +306,7 @@ export function getCustomHeaders(): readonly [string, string][] { export function applyCustomHeaders( headers: Headers, requestUrl: string | URL | Request, - isTrusted = isRequestOriginTrustedForCustomHeaders(requestUrl) + isTrusted = isRequestOriginTrustedForCustomHeaders(requestUrl), ): void { const customHeaders = getCustomHeaders(); if (customHeaders.length === 0) { @@ -318,7 +318,7 @@ export function applyCustomHeaders( untrustedDestinationWarningLogged = true; log.warn( "Skipping custom headers for request to untrusted host. " + - "If this is legitimate, run 'sentry auth login --url <url>' against the intended instance." + "If this is legitimate, run 'sentry auth login --url <url>' against the intended instance.", ); } return; diff --git a/packages/cli/src/lib/db/auth.ts b/packages/cli/src/lib/db/auth.ts index 5c7d2cd33..65af35268 100644 --- a/packages/cli/src/lib/db/auth.ts +++ b/packages/cli/src/lib/db/auth.ts @@ -66,7 +66,7 @@ function migrateNullHost(row: AuthRow): string { const host = bootHost ? normalizeCredentialHost(bootHost) : DEFAULT_SENTRY_URL; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { withDbSpan("migrateAuthHost", () => { const db = getDatabase(); @@ -86,7 +86,7 @@ function normalizeCredentialHost(host: string): string { const origin = normalizeHttpOrigin(host); if (!origin) { throw new ConfigError( - "Stored credential host must be a credential-free HTTP(S) URL." + "Stored credential host must be a credential-free HTTP(S) URL.", ); } return origin; @@ -222,7 +222,7 @@ export function getAuthConfig(): AuthConfig | undefined { * falls back to the caller's default behavior. */ export function getStoredAuthHost(): string | undefined { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return withDbSpan("getStoredAuthHost", () => { const row = getAuthRow(); @@ -250,7 +250,7 @@ export function getStoredAuthHost(): string | undefined { * OAuth's host over the env-token snapshot. */ export function hasUsableStoredToken(): boolean { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return withDbSpan("hasUsableStoredToken", () => { const row = getAuthRow(); @@ -280,7 +280,7 @@ export function hasUsableStoredToken(): boolean { * first access, same as {@link getStoredAuthHost}). */ export function getUsableStoredTokenHost(): string | undefined { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return withDbSpan("getUsableStoredTokenHost", () => { const row = getAuthRow(); @@ -403,7 +403,7 @@ export function setAuthToken( token: string, expiresIn?: number, newRefreshToken?: string, - options?: SetAuthTokenOptions + options?: SetAuthTokenOptions, ): void { const normalizedToken = normalizeAuthToken(token); withDbSpan("setAuthToken", () => { @@ -442,7 +442,7 @@ export function setAuthToken( updated_at: now, host, }, - ["id"] + ["id"], ); }); // Auth row changed — drop memoized fingerprint, token, row, and @@ -475,7 +475,7 @@ export async function clearAuth(): Promise<void> { clearTrustedHostState(); // Dynamic import avoids the auth→response-cache→auth cycle. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { clearResponseCache } = await import("../response-cache.js"); await clearResponseCache(); @@ -614,7 +614,7 @@ export function getCredentialContext(): CredentialContext | undefined { const origin = normalizeHttpOrigin(host); if (!origin) { throw new ConfigError( - "Stored credential host must be a credential-free HTTP(S) URL." + "Stored credential host must be a credential-free HTTP(S) URL.", ); } return Object.freeze({ @@ -705,7 +705,7 @@ const MAX_REFRESH_IDENTITY_ALIASES = 128; function rememberRefreshIdentity( host: string, previous: string, - next: string + next: string, ): void { if (previous === next) { return; @@ -722,11 +722,11 @@ function rememberRefreshIdentity( function matchesRefreshIdentity( host: string, previous: string, - current: string + current: string, ): boolean { const seen = new Set<string>(); const start = `${host}\0${previous}`; - for (let key = start; !seen.has(key); ) { + for (let key = start; !seen.has(key);) { seen.add(key); const next = refreshIdentityAliases.get(key); if (!next) { @@ -742,7 +742,7 @@ function matchesRefreshIdentity( function rowMatchesCredential( row: AuthRow | undefined, - credential: StoredCredentialSnapshot + credential: StoredCredentialSnapshot, ): boolean { return ( row?.token === credential.token && @@ -756,7 +756,7 @@ function rowMatchesCredential( function assertExpectedCredential( result: Pick<RefreshTokenResult, "host" | "identity">, - expected: RefreshTokenOptions["expectedCredential"] + expected: RefreshTokenOptions["expectedCredential"], ): void { if ( expected && @@ -765,11 +765,11 @@ function assertExpectedCredential( !matchesRefreshIdentity( result.host, expected.identity, - result.identity + result.identity, ))) ) { throw new ConfigError( - "Active credentials changed while the request was in flight. Retry the request." + "Active credentials changed while the request was in flight. Retry the request.", ); } } @@ -778,7 +778,7 @@ function persistRefreshedCredential( credential: StoredCredentialSnapshot, token: string, nextRefreshToken: string, - expiresIn: number + expiresIn: number, ): boolean { const saved = withDbSpan("persistRefreshedCredential", () => { const db = getDatabase(); @@ -788,14 +788,14 @@ function persistRefreshedCredential( } const now = Date.now(); db.query( - "UPDATE auth SET token = ?, refresh_token = ?, expires_at = ?, issued_at = ?, updated_at = ?, host = ? WHERE id = 1" + "UPDATE auth SET token = ?, refresh_token = ?, expires_at = ?, issued_at = ?, updated_at = ?, host = ? WHERE id = 1", ).run( token, nextRefreshToken, now + expiresIn * 1000, now, now, - credential.host + credential.host, ); return true; })(); @@ -810,7 +810,7 @@ function persistRefreshedCredential( } async function performTokenRefresh( - credential: StoredCredentialSnapshot + credential: StoredCredentialSnapshot, ): Promise<RefreshTokenResult> { const { refreshAccessToken } = await import("../oauth.js"); const { AuthError } = await import("../errors.js"); @@ -844,11 +844,11 @@ async function performTokenRefresh( credential, token, nextRefreshToken, - tokenResponse.expires_in + tokenResponse.expires_in, ) ) { throw new ConfigError( - "Active credentials changed while the request was in flight. Retry the request." + "Active credentials changed while the request was in flight. Retry the request.", ); } @@ -868,7 +868,7 @@ async function performTokenRefresh( } function getEnvRefreshResult( - expected: RefreshTokenOptions["expectedCredential"] + expected: RefreshTokenOptions["expectedCredential"], ): RefreshTokenResult | undefined { const credential = envCredentialContext(); if (!credential) { @@ -879,7 +879,7 @@ function getEnvRefreshResult( } async function refreshStoredCredential( - credential: StoredCredentialSnapshot + credential: StoredCredentialSnapshot, ): Promise<RefreshTokenResult> { const key = `${credential.identity}\0${credential.host}\0${hashIdentity("oauth-access", credential.token)}\0${credential.updatedAt}`; const existing = refreshPromises.get(key); @@ -899,7 +899,7 @@ async function refreshStoredCredential( /** Get a valid token, refreshing if needed. Use force=true after 401 responses. */ export async function refreshToken( - options: RefreshTokenOptions = {} + options: RefreshTokenOptions = {}, ): Promise<RefreshTokenResult> { // With SENTRY_FORCE_ENV_TOKEN, env token takes priority (no refresh needed). const forced = getEnv().SENTRY_FORCE_ENV_TOKEN?.trim() @@ -929,7 +929,7 @@ export async function refreshToken( const origin = normalizeHttpOrigin(host); if (!origin) { throw new ConfigError( - "Stored credential host must be a credential-free HTTP(S) URL." + "Stored credential host must be a credential-free HTTP(S) URL.", ); } const identity = row.refresh_token @@ -972,7 +972,7 @@ export async function refreshToken( } throw new AuthError( "expired", - "Session expired and no refresh token available. Run 'sentry auth login'." + "Session expired and no refresh token available. Run 'sentry auth login'.", ); } diff --git a/packages/cli/src/lib/db/completion-telemetry.ts b/packages/cli/src/lib/db/completion-telemetry.ts index 24fe5d640..a558d021e 100644 --- a/packages/cli/src/lib/db/completion-telemetry.ts +++ b/packages/cli/src/lib/db/completion-telemetry.ts @@ -34,7 +34,7 @@ export function queueCompletionTelemetry(entry: { try { const db = getDatabase(); db.query( - "INSERT INTO completion_telemetry_queue (command_path, duration_ms, result_count) VALUES (?, ?, ?)" + "INSERT INTO completion_telemetry_queue (command_path, duration_ms, result_count) VALUES (?, ?, ?)", ).run(entry.commandPath, Math.round(entry.durationMs), entry.resultCount); } catch (error) { log.debug("Failed to queue completion telemetry", error); @@ -55,7 +55,7 @@ export function drainCompletionTelemetry(): CompletionTelemetryEntry[] { // Atomic read + delete — no race with concurrent __complete processes const rows = db .query( - "DELETE FROM completion_telemetry_queue RETURNING id, command_path, duration_ms, result_count" + "DELETE FROM completion_telemetry_queue RETURNING id, command_path, duration_ms, result_count", ) .all() as { id: number; diff --git a/packages/cli/src/lib/db/dsn-cache.ts b/packages/cli/src/lib/db/dsn-cache.ts index 37b46f141..63f57bb4b 100644 --- a/packages/cli/src/lib/db/dsn-cache.ts +++ b/packages/cli/src/lib/db/dsn-cache.ts @@ -119,10 +119,10 @@ function rowToCachedDsnEntry(row: DsnCacheRow): CachedDsnEntry { // Parse allResolved from all_dsns_json for inferred sources if (row.source === "inferred" && row.all_dsns_json) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { entry.allResolved = JSON.parse( - row.all_dsns_json + row.all_dsns_json, ) as CachedDsnEntry["allResolved"]; } catch { // Ignore parse errors, allResolved will be undefined @@ -163,7 +163,7 @@ export function getCachedDsn(directory: string): CachedDsnEntry | undefined { export function setCachedDsn( directory: string, - entry: Omit<CachedDsnEntry, "cachedAt"> + entry: Omit<CachedDsnEntry, "cachedAt">, ): void { const db = getDatabase(); const now = Date.now(); @@ -189,7 +189,7 @@ export function setCachedDsn( cached_at: now, last_accessed: now, }, - ["directory"] + ["directory"], ); maybeCleanupCaches(); @@ -198,7 +198,7 @@ export function setCachedDsn( /** Update resolved org/project info after API resolution. */ export function updateCachedResolution( directory: string, - resolved: ResolvedProjectInfo + resolved: ResolvedProjectInfo, ): void { const db = getDatabase(); @@ -223,7 +223,7 @@ export function updateCachedResolution( resolved.projectSlug, resolved.projectName, Date.now(), - directory + directory, ); } @@ -247,9 +247,9 @@ export function clearDsnCache(directory?: string): void { */ async function validateDirMtime( fullPath: string, - cachedMtime: number + cachedMtime: number, ): Promise<boolean> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stats = await stat(fullPath); return Math.floor(stats.mtimeMs) === cachedMtime; @@ -264,9 +264,9 @@ async function validateDirMtime( */ async function validateFileMtime( fullPath: string, - cachedMtime: number + cachedMtime: number, ): Promise<boolean> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stats = await stat(fullPath); if (!stats.isFile()) { @@ -287,7 +287,7 @@ async function validateFileMtime( */ async function validateSourceMtimes( projectRoot: string, - sourceMtimes: Record<string, number> + sourceMtimes: Record<string, number>, ): Promise<boolean> { for (const [relativePath, cachedMtime] of Object.entries(sourceMtimes)) { const fullPath = join(projectRoot, relativePath); @@ -308,7 +308,7 @@ async function validateSourceMtimes( */ async function validateDirMtimes( projectRoot: string, - dirMtimes: Record<string, number> + dirMtimes: Record<string, number>, ): Promise<boolean> { for (const [relativePath, cachedMtime] of Object.entries(dirMtimes)) { const fullPath = join(projectRoot, relativePath); @@ -326,7 +326,7 @@ function isMtimesRecord(value: unknown): value is Record<string, number> { value !== null && !Array.isArray(value) && Object.values(value as Record<string, unknown>).every( - (v) => typeof v === "number" + (v) => typeof v === "number", ) ); } @@ -349,7 +349,7 @@ function isArray(value: unknown): value is DetectedDsn[] { * @returns Cached detection or undefined if not cached/invalid */ export async function getCachedDetection( - projectRoot: string + projectRoot: string, ): Promise<CachedDetection | undefined> { if (dsnCacheDisabled) { return; @@ -440,7 +440,7 @@ export async function getCachedDetection( */ export function setCachedDetection( projectRoot: string, - entry: DetectionCacheEntry + entry: DetectionCacheEntry, ): void { const db = getDatabase(); const now = Date.now(); @@ -474,7 +474,7 @@ export function setCachedDetection( cached_at: now, last_accessed: now, }, - ["directory"] + ["directory"], ); maybeCleanupCaches(); diff --git a/packages/cli/src/lib/db/index.ts b/packages/cli/src/lib/db/index.ts index 2c7b66c0a..8fbfad802 100644 --- a/packages/cli/src/lib/db/index.ts +++ b/packages/cli/src/lib/db/index.ts @@ -116,7 +116,7 @@ export function resolveConfigDir(env: NodeJS.ProcessEnv, home: string): string { */ export function resolveXdgConfigDir( env: NodeJS.ProcessEnv, - home: string + home: string, ): string { const override = env[CONFIG_DIR_ENV_VAR]; if (override) { @@ -145,18 +145,18 @@ function ensureConfigDir(): void { function setDbPermissions(): void { const dbPath = getDbPath(); - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { chmodSync(dbPath, 0o600); // WAL mode creates -wal and -shm files that may contain sensitive data // Chmod them too if they exist (they may not exist on first run) - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { chmodSync(`${dbPath}-wal`, 0o600); } catch { // File may not exist yet } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { chmodSync(`${dbPath}-shm`, 0o600); } catch { diff --git a/packages/cli/src/lib/db/install-info.ts b/packages/cli/src/lib/db/install-info.ts index e9aca9232..911d1bca4 100644 --- a/packages/cli/src/lib/db/install-info.ts +++ b/packages/cli/src/lib/db/install-info.ts @@ -56,7 +56,7 @@ export function getInstallInfo(): StoredInstallInfo | null { * @param info - Installation info to store (recordedAt is auto-set to now) */ export function setInstallInfo( - info: Omit<StoredInstallInfo, "recordedAt"> + info: Omit<StoredInstallInfo, "recordedAt">, ): void { const db = getDatabase(); setMetadata(db, { diff --git a/packages/cli/src/lib/db/issue-org-cache.ts b/packages/cli/src/lib/db/issue-org-cache.ts index 276669a64..a501567de 100644 --- a/packages/cli/src/lib/db/issue-org-cache.ts +++ b/packages/cli/src/lib/db/issue-org-cache.ts @@ -77,7 +77,7 @@ export function setCachedIssueOrg(numericId: string, orgSlug: string): void { org_slug: orgSlug, cached_at: Date.now(), }, - ["issue_id"] + ["issue_id"], ); } diff --git a/packages/cli/src/lib/db/json.ts b/packages/cli/src/lib/db/json.ts index 8641e036d..21129686a 100644 --- a/packages/cli/src/lib/db/json.ts +++ b/packages/cli/src/lib/db/json.ts @@ -30,7 +30,7 @@ const log = logger.withTag("db-json"); */ export function safeParseJson<T>( raw: string | null | undefined, - validate?: (value: unknown) => value is T + validate?: (value: unknown) => value is T, ): T | undefined { if (raw === null || raw === undefined) { return; diff --git a/packages/cli/src/lib/db/migration.ts b/packages/cli/src/lib/db/migration.ts index 6ad40153e..55a4dbd25 100644 --- a/packages/cli/src/lib/db/migration.ts +++ b/packages/cli/src/lib/db/migration.ts @@ -34,7 +34,7 @@ function isMigrationCompleted(db: Database): boolean { function markMigrationCompleted(db: Database): void { db.query("INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)").run( MIGRATION_COMPLETED_KEY, - "true" + "true", ); } @@ -46,7 +46,7 @@ function oldConfigExists(): boolean { function readOldConfig(): OldConfig | null { const configPath = join(getConfigDir(), OLD_CONFIG_FILENAME); - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { readFileSync } = _require("node:fs"); const content = readFileSync(configPath, "utf-8"); @@ -122,7 +122,7 @@ type OldConfig = { }; /** Migrate once, retaining the original file and skipping auth if its access token is malformed. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one-time migration +// one-time migration export function migrateFromJson(db: Database): void { // Check SQLite metadata first - this is the authoritative source if (isMigrationCompleted(db)) { @@ -178,18 +178,18 @@ export function migrateFromJson(db: Database): void { oldConfig.auth.refreshToken ?? null, oldConfig.auth.expiresAt ?? null, oldConfig.auth.issuedAt ?? null, - Date.now() + Date.now(), ); } if (oldConfig.defaults?.organization) { db.query( - "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)" + "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)", ).run("defaults.org", oldConfig.defaults.organization); } if (oldConfig.defaults?.project) { db.query( - "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)" + "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)", ).run("defaults.project", oldConfig.defaults.project); } @@ -221,7 +221,7 @@ export function migrateFromJson(db: Database): void { entry.projectSlug, entry.projectName, entry.cachedAt, - entry.cachedAt // last_accessed = cachedAt for migrated entries + entry.cachedAt, // last_accessed = cachedAt for migrated entries ); } } @@ -256,7 +256,7 @@ export function migrateFromJson(db: Database): void { entry.resolved?.projectSlug ?? null, entry.resolved?.projectName ?? null, entry.cachedAt, - entry.cachedAt + entry.cachedAt, ); } } @@ -274,7 +274,7 @@ export function migrateFromJson(db: Database): void { // Skip all aliases if cachedAt is missing (required for NOT NULL column) if (cachedAt !== null && cachedAt !== undefined) { for (const [alias, entry] of Object.entries( - oldConfig.projectAliases.aliases + oldConfig.projectAliases.aliases, )) { // Skip malformed entries missing required fields if (!(entry?.orgSlug && entry?.projectSlug)) { @@ -286,7 +286,7 @@ export function migrateFromJson(db: Database): void { entry.projectSlug, fingerprint, cachedAt, - cachedAt + cachedAt, ); } } @@ -300,7 +300,7 @@ export function migrateFromJson(db: Database): void { if (invalidAuthToken) { log.warn( "Malformed authentication credentials were not migrated. The original config.json was kept. " + - "Run 'sentry auth login' to authenticate again, then remove the old file." + "Run 'sentry auth login' to authenticate again, then remove the old file.", ); } else { // Best-effort cleanup: SQLite metadata prevents re-import if it fails. diff --git a/packages/cli/src/lib/db/pagination.ts b/packages/cli/src/lib/db/pagination.ts index 40f7ab0bb..b56811e5d 100644 --- a/packages/cli/src/lib/db/pagination.ts +++ b/packages/cli/src/lib/db/pagination.ts @@ -66,12 +66,12 @@ export type PaginationState = { */ export function getPaginationState( commandKey: string, - contextKey: string + contextKey: string, ): PaginationState | undefined { const db = getDatabase(); const row = db .query( - "SELECT cursor_stack, page_index, expires_at FROM pagination_cursors WHERE command_key = ? AND context = ?" + "SELECT cursor_stack, page_index, expires_at FROM pagination_cursors WHERE command_key = ? AND context = ?", ) .get(commandKey, contextKey) as PaginationCursorRow | undefined; @@ -81,7 +81,7 @@ export function getPaginationState( if (row.expires_at <= Date.now()) { db.query( - "DELETE FROM pagination_cursors WHERE command_key = ? AND context = ?" + "DELETE FROM pagination_cursors WHERE command_key = ? AND context = ?", ).run(commandKey, contextKey); return; } @@ -91,11 +91,11 @@ export function getPaginationState( // first page by deleting the bad row and returning a cache miss. const stack = safeParseJson<string[]>( row.cursor_stack, - (value): value is string[] => Array.isArray(value) + (value): value is string[] => Array.isArray(value), ); if (!stack) { db.query( - "DELETE FROM pagination_cursors WHERE command_key = ? AND context = ?" + "DELETE FROM pagination_cursors WHERE command_key = ? AND context = ?", ).run(commandKey, contextKey); return; } @@ -115,7 +115,7 @@ function savePaginationState( commandKey: string, contextKey: string, state: PaginationState, - ttlMs = CURSOR_TTL_MS + ttlMs = CURSOR_TTL_MS, ): void { const db = getDatabase(); runUpsert( @@ -128,7 +128,7 @@ function savePaginationState( page_index: state.index, expires_at: Date.now() + ttlMs, }, - ["command_key", "context"] + ["command_key", "context"], ); } @@ -169,7 +169,7 @@ export type ResolvedCursor = { export function resolveCursor( cursorFlag: string | undefined, commandKey: string, - contextKey: string + contextKey: string, ): ResolvedCursor { if (!cursorFlag) { // No --cursor flag → fresh start. Use "first" so advancePaginationState @@ -194,7 +194,7 @@ export function resolveCursor( if (!state || state.index + 1 >= state.stack.length) { throw new ValidationError( "No next page saved for this query. Run without --cursor first.", - "cursor" + "cursor", ); } const nextCursor = state.stack[state.index + 1] as string; @@ -208,7 +208,7 @@ export function resolveCursor( if (!state || state.index <= 0) { throw new ValidationError( "Already on the first page — cannot go back further.", - "cursor" + "cursor", ); } const prevCursor = state.stack[state.index - 1] as string; @@ -244,7 +244,7 @@ export function advancePaginationState( commandKey: string, contextKey: string, direction: CursorDirection, - nextCursor: string | undefined + nextCursor: string | undefined, ): void { const state = getPaginationState(commandKey, contextKey); @@ -306,11 +306,11 @@ export function advancePaginationState( */ export function clearPaginationState( commandKey: string, - contextKey: string + contextKey: string, ): void { const db = getDatabase(); db.query( - "DELETE FROM pagination_cursors WHERE command_key = ? AND context = ?" + "DELETE FROM pagination_cursors WHERE command_key = ? AND context = ?", ).run(commandKey, contextKey); } @@ -325,7 +325,7 @@ export function clearPaginationState( */ export function hasPreviousPage( commandKey: string, - contextKey: string + contextKey: string, ): boolean { const state = getPaginationState(commandKey, contextKey); return !!state && state.index > 0; @@ -375,7 +375,7 @@ export function escapeContextKeyValue(value: string): string { export function buildPaginationContextKey( type: string, scope: string, - params?: Record<string, string | undefined> + params?: Record<string, string | undefined>, ): string { let key = `host:${getApiBaseUrl()}|type:${type}:${scope}`; if (params) { @@ -450,7 +450,7 @@ export function decodeCompoundCursor(raw: string): (string | null)[] { */ export function decodeTargetCursors( raw: string | undefined, - sortedKeys: readonly string[] + sortedKeys: readonly string[], ): { startCursors: Map<string, string>; exhausted: Set<string> } { const startCursors = new Map<string, string>(); const exhausted = new Set<string>(); @@ -479,7 +479,7 @@ export function decodeTargetCursors( */ export function buildMultiTargetContextKey( targets: ResolvedTarget[], - filters?: { sort?: string; query?: string; period?: string; limit?: number } + filters?: { sort?: string; query?: string; period?: string; limit?: number }, ): string { const host = getApiBaseUrl(); const targetFingerprint = targets @@ -523,7 +523,7 @@ export function buildMultiTargetContextKey( */ export function buildMultiOrgContextKey( orgs: string[], - query: string | undefined + query: string | undefined, ): string { const sortedOrgs = [...orgs].sort().map(escapeContextKeyValue).join(","); return buildPaginationContextKey("multi-org", sortedOrgs, { q: query }); diff --git a/packages/cli/src/lib/db/project-aliases.ts b/packages/cli/src/lib/db/project-aliases.ts index b0f38e790..3a768359e 100644 --- a/packages/cli/src/lib/db/project-aliases.ts +++ b/packages/cli/src/lib/db/project-aliases.ts @@ -17,14 +17,14 @@ type ProjectAliasRow = { function touchAliasEntries(): void { const db = getDatabase(); - // biome-ignore lint/plugin: global touch — updates ALL rows, not a single keyed entry + // oxlint-disable-next-line sentry-cli/no-inline-touch-cache -- global touch — updates ALL rows, not a single keyed entry db.query("UPDATE project_aliases SET last_accessed = ?").run(Date.now()); } /** Set project aliases, replacing all existing ones. */ export function setProjectAliases( aliases: Record<string, ProjectAliasEntry>, - dsnFingerprint?: string + dsnFingerprint?: string, ): void { const db = getDatabase(); const now = Date.now(); @@ -45,7 +45,7 @@ export function setProjectAliases( entry.projectSlug, dsnFingerprint ?? null, now, - now + now, ); } })(); @@ -82,7 +82,7 @@ export function getProjectAliases(): /** Get project by alias. Validates DSN fingerprint if both current and cached are present. */ export function getProjectByAlias( alias: string, - currentFingerprint?: string + currentFingerprint?: string, ): ProjectAliasEntry | undefined { const db = getDatabase(); diff --git a/packages/cli/src/lib/db/project-cache.ts b/packages/cli/src/lib/db/project-cache.ts index 1bb0ff1a1..b81082354 100644 --- a/packages/cli/src/lib/db/project-cache.ts +++ b/packages/cli/src/lib/db/project-cache.ts @@ -72,7 +72,7 @@ function setByKey(key: string, info: Omit<CachedProject, "cachedAt">): void { cached_at: now, last_accessed: now, }, - ["cache_key"] + ["cache_key"], ); maybeCleanupCaches(); @@ -80,7 +80,7 @@ function setByKey(key: string, info: Omit<CachedProject, "cachedAt">): void { export function getCachedProject( orgId: string, - projectId: string + projectId: string, ): CachedProject | undefined { return getByKey(projectCacheKey(orgId, projectId)); } @@ -88,14 +88,14 @@ export function getCachedProject( export function setCachedProject( orgId: string, projectId: string, - info: Omit<CachedProject, "cachedAt"> + info: Omit<CachedProject, "cachedAt">, ): void { setByKey(projectCacheKey(orgId, projectId), info); } /** Get cached project by DSN public key (for self-hosted or SaaS DSNs without org ID). */ export function getCachedProjectByDsnKey( - publicKey: string + publicKey: string, ): CachedProject | undefined { return getByKey(dsnCacheKey(publicKey)); } @@ -103,7 +103,7 @@ export function getCachedProjectByDsnKey( /** Cache project by DSN public key (for self-hosted or SaaS DSNs without org ID). */ export function setCachedProjectByDsnKey( publicKey: string, - info: Omit<CachedProject, "cachedAt"> + info: Omit<CachedProject, "cachedAt">, ): void { setByKey(dsnCacheKey(publicKey), info); } @@ -127,12 +127,12 @@ export function setCachedProjectByDsnKey( */ export function getCachedProjectBySlug( orgSlug: string, - projectSlug: string + projectSlug: string, ): CachedProject | undefined { const db = getDatabase(); const row = db .query( - "SELECT cache_key, org_slug, org_name, project_slug, project_name, project_id, MAX(cached_at) AS cached_at, last_accessed FROM project_cache WHERE org_slug = ? AND project_slug = ?" + "SELECT cache_key, org_slug, org_name, project_slug, project_name, project_id, MAX(cached_at) AS cached_at, last_accessed FROM project_cache WHERE org_slug = ? AND project_slug = ?", ) .get(orgSlug, projectSlug) as ProjectCacheRow | undefined; @@ -155,12 +155,12 @@ export function getCachedProjectBySlug( */ export function getCachedProjectById( orgSlug: string, - projectId: string + projectId: string, ): CachedProject | undefined { const db = getDatabase(); const row = db .query( - "SELECT * FROM project_cache WHERE org_slug = ? AND project_id = ? ORDER BY cached_at DESC, cache_key LIMIT 1" + "SELECT * FROM project_cache WHERE org_slug = ? AND project_id = ? ORDER BY cached_at DESC, cache_key LIMIT 1", ) .get(orgSlug, projectId) as ProjectCacheRow | undefined; @@ -182,7 +182,7 @@ export function getCachedProjectById( * @param orgSlug - The organization slug to filter by */ export function getCachedProjectsForOrg( - orgSlug: string + orgSlug: string, ): { projectSlug: string; projectName: string }[] { const db = getDatabase(); // Use MAX(cached_at) to deterministically pick the most recently cached @@ -192,7 +192,7 @@ export function getCachedProjectsForOrg( // produced the MAX/MIN aggregate value. const rows = db .query( - "SELECT project_slug, project_name, MAX(cached_at) FROM project_cache WHERE org_slug = ? GROUP BY project_slug" + "SELECT project_slug, project_name, MAX(cached_at) FROM project_cache WHERE org_slug = ? GROUP BY project_slug", ) .all(orgSlug) as Pick<ProjectCacheRow, "project_slug" | "project_name">[]; @@ -216,7 +216,7 @@ export function getCachedProjectsForOrg( export function cacheProjectsForOrg( orgSlug: string, orgName: string, - projects: Array<{ id: string; slug: string; name: string }> + projects: Array<{ id: string; slug: string; name: string }>, ): void { if (projects.length === 0) { return; @@ -240,7 +240,7 @@ export function cacheProjectsForOrg( cached_at: now, last_accessed: now, }, - ["cache_key"] + ["cache_key"], ); } })(); diff --git a/packages/cli/src/lib/db/project-root-cache.ts b/packages/cli/src/lib/db/project-root-cache.ts index a3329ea6b..af30afb4d 100644 --- a/packages/cli/src/lib/db/project-root-cache.ts +++ b/packages/cli/src/lib/db/project-root-cache.ts @@ -50,7 +50,7 @@ export type ProjectRootCacheEntry = { * @returns Cached project root or undefined if not cached/invalid */ export async function getCachedProjectRoot( - cwd: string + cwd: string, ): Promise<CachedProjectRoot | undefined> { const db = getDatabase(); @@ -106,14 +106,14 @@ export async function getCachedProjectRoot( */ export async function setCachedProjectRoot( cwd: string, - entry: ProjectRootCacheEntry + entry: ProjectRootCacheEntry, ): Promise<void> { const db = getDatabase(); const now = Date.now(); // Get current mtime of the cwd directory let cwdMtime: number; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stats = await stat(cwd); cwdMtime = Math.floor(stats.mtimeMs); @@ -133,7 +133,7 @@ export async function setCachedProjectRoot( cached_at: now, ttl_expires_at: now + CACHE_TTL_MS, }, - ["cwd"] + ["cwd"], ); maybeCleanupCaches(); diff --git a/packages/cli/src/lib/db/regions.ts b/packages/cli/src/lib/db/regions.ts index 5736b8042..ec45876fe 100644 --- a/packages/cli/src/lib/db/regions.ts +++ b/packages/cli/src/lib/db/regions.ts @@ -64,7 +64,7 @@ function requireRegionBaseUrl(url: string): string { const parsed = new URL(url); if (parsed.search || parsed.hash) { throw new Error( - "Organization region URL must not contain a query or fragment" + "Organization region URL must not contain a query or fragment", ); } const path = parsed.pathname.replace(TRAILING_SLASHES_RE, ""); @@ -74,7 +74,7 @@ function requireRegionBaseUrl(url: string): string { function registerTrustedOrigins( identity: string, sourceOrigin: string, - urls: readonly string[] + urls: readonly string[], ): void { const key = trustScopeKey(identity, sourceOrigin); if ( @@ -106,7 +106,7 @@ function seedTrustedOrigins(identity: string, sourceOrigin: string): void { try { const rows = getDatabase() .query( - `SELECT DISTINCT source_origin, response_origin, region_url FROM ${TABLE} WHERE credential_identity = ? AND (source_origin = ? OR response_origin = ?)` + `SELECT DISTINCT source_origin, response_origin, region_url FROM ${TABLE} WHERE credential_identity = ? AND (source_origin = ? OR response_origin = ?)`, ) .all(identity, sourceOrigin, sourceOrigin) as Pick< OrgRegionRow, @@ -137,12 +137,12 @@ function seedTrustedOrigins(identity: string, sourceOrigin: string): void { export function registerTrustedRegionUrls( urls: readonly string[], sourceOrigin = getActiveSourceOrigin(), - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): void { registerTrustedOrigins( identity, requireOrigin(sourceOrigin, "Region source origin"), - urls + urls, ); } @@ -153,7 +153,7 @@ export function registerTrustedRegionUrls( export function isTrustedRegionOrigin( origin: string, sourceOrigin = getActiveSourceOrigin(), - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): boolean { const candidate = normalizeHttpOrigin(origin); const source = normalizeHttpOrigin(sourceOrigin); @@ -247,7 +247,7 @@ export type OrgRegionEntry = { export function getOrgRegion( orgSlug: string, sourceOrigin = getActiveSourceOrigin(), - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): string | undefined { const source = normalizeHttpOrigin(sourceOrigin); if (!source) { @@ -257,7 +257,7 @@ export function getOrgRegion( const db = getDatabase(); const row = db .query( - `SELECT region_url FROM ${TABLE} WHERE org_slug = ? AND source_origin = ? AND credential_identity = ?` + `SELECT region_url FROM ${TABLE} WHERE org_slug = ? AND source_origin = ? AND credential_identity = ?`, ) .get(orgSlug, source, identity) as | Pick<OrgRegionRow, "region_url"> @@ -279,7 +279,7 @@ export function getOrgRegion( export function getOrgByNumericId( numericId: string, sourceOrigin = getActiveSourceOrigin(), - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): { slug: string; regionUrl: string } | undefined { const source = normalizeHttpOrigin(sourceOrigin); if (!source) { @@ -288,7 +288,7 @@ export function getOrgByNumericId( const db = getDatabase(); const row = db .query( - `SELECT org_slug, region_url FROM ${TABLE} WHERE org_id = ? AND source_origin = ? AND credential_identity = ?` + `SELECT org_slug, region_url FROM ${TABLE} WHERE org_id = ? AND source_origin = ? AND credential_identity = ?`, ) .get(numericId, source, identity) as | Pick<OrgRegionRow, "org_slug" | "region_url"> @@ -306,13 +306,12 @@ export function getOrgByNumericId( * @param orgSlug - The organization slug * @param regionUrl - The region URL (e.g., https://us.sentry.io) */ -// biome-ignore lint/nursery/useMaxParams: provenance fields are explicit at the persistence boundary. export function setOrgRegion( orgSlug: string, regionUrl: string, responseOrigin = getActiveSourceOrigin(), cacheOrigin = responseOrigin, - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): void { const db = getDatabase(); const now = Date.now(); @@ -331,7 +330,7 @@ export function setOrgRegion( response_origin: response, updated_at: now, }, - ["credential_identity", "source_origin", "org_slug"] + ["credential_identity", "source_origin", "org_slug"], ); registerTrustedOrigins(identity, source, [response]); registerTrustedOrigins(identity, response, [region]); @@ -354,11 +353,11 @@ export function setOrgRegions(entries: OrgRegionEntry[]): void { const normalized = entries.map((entry) => { const response = requireOrigin( entry.sourceOrigin ?? getActiveSourceOrigin(), - "Region response origin" + "Region response origin", ); const source = requireOrigin( entry.cacheOrigin ?? response, - "Region lookup origin" + "Region lookup origin", ); return { entry, @@ -407,12 +406,12 @@ export function setOrgRegions(entries: OrgRegionEntry[]): void { /** Keep trusted region routes but make an incomplete org list a cache miss. */ export function invalidateCachedOrganizations( sourceOrigin: string, - identity: string + identity: string, ): void { const source = requireOrigin(sourceOrigin, "Region lookup origin"); getDatabase() .query( - `UPDATE ${TABLE} SET org_id = NULL, org_name = NULL, org_role = NULL WHERE source_origin = ? AND credential_identity = ?` + `UPDATE ${TABLE} SET org_id = NULL, org_name = NULL, org_role = NULL WHERE source_origin = ? AND credential_identity = ?`, ) .run(source, identity); } @@ -435,7 +434,7 @@ export function clearOrgRegions(): void { */ export function getAllOrgRegions( sourceOrigin = getActiveSourceOrigin(), - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): Map<string, string> { const source = normalizeHttpOrigin(sourceOrigin); if (!source) { @@ -444,7 +443,7 @@ export function getAllOrgRegions( const db = getDatabase(); const rows = db .query( - `SELECT org_slug, region_url FROM ${TABLE} WHERE source_origin = ? AND credential_identity = ?` + `SELECT org_slug, region_url FROM ${TABLE} WHERE source_origin = ? AND credential_identity = ?`, ) .all(source, identity) as Pick<OrgRegionRow, "org_slug" | "region_url">[]; @@ -483,7 +482,7 @@ const ORG_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; */ export function getCachedOrganizations( sourceOrigin = getActiveSourceOrigin(), - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): CachedOrg[] { if (orgCacheDisabled) { return []; @@ -498,7 +497,7 @@ export function getCachedOrganizations( const cutoff = Date.now() - ORG_CACHE_TTL_MS; const rows = db .query( - `SELECT org_slug, org_id, org_name, org_role FROM ${TABLE} WHERE source_origin = ? AND credential_identity = ? AND org_id IS NOT NULL AND org_name IS NOT NULL AND updated_at > ?` + `SELECT org_slug, org_id, org_name, org_role FROM ${TABLE} WHERE source_origin = ? AND credential_identity = ? AND org_id IS NOT NULL AND org_name IS NOT NULL AND updated_at > ?`, ) .all(source, identity, cutoff) as Pick< OrgRegionRow, @@ -526,7 +525,7 @@ export function getCachedOrganizations( export function getCachedOrgRole( orgSlug: string, sourceOrigin = getActiveSourceOrigin(), - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): string | undefined { const source = normalizeHttpOrigin(sourceOrigin); if (!source) { @@ -536,7 +535,7 @@ export function getCachedOrgRole( const cutoff = Date.now() - ORG_CACHE_TTL_MS; const row = db .query( - `SELECT org_role FROM ${TABLE} WHERE org_slug = ? AND source_origin = ? AND credential_identity = ? AND org_role IS NOT NULL AND updated_at > ?` + `SELECT org_role FROM ${TABLE} WHERE org_slug = ? AND source_origin = ? AND credential_identity = ? AND org_role IS NOT NULL AND updated_at > ?`, ) .get(orgSlug, source, identity, cutoff) as | Pick<OrgRegionRow, "org_role"> diff --git a/packages/cli/src/lib/db/repo-cache.ts b/packages/cli/src/lib/db/repo-cache.ts index dcce5c99f..287089a37 100644 --- a/packages/cli/src/lib/db/repo-cache.ts +++ b/packages/cli/src/lib/db/repo-cache.ts @@ -73,7 +73,7 @@ export function getCachedRepos(orgSlug: string): SentryRepository[] | null { */ export function setCachedRepos( orgSlug: string, - repos: SentryRepository[] + repos: SentryRepository[], ): void { const db = getDatabase(); runUpsert( @@ -84,7 +84,7 @@ export function setCachedRepos( repos_json: JSON.stringify(repos), cached_at: Date.now(), }, - ["org_slug"] + ["org_slug"], ); } diff --git a/packages/cli/src/lib/db/schema.ts b/packages/cli/src/lib/db/schema.ts index 7c43eb3d3..2d2ac5df7 100644 --- a/packages/cli/src/lib/db/schema.ts +++ b/packages/cli/src/lib/db/schema.ts @@ -286,7 +286,7 @@ export const TABLE_SCHEMAS: Record<string, TableSchema> = { function columnDefsToDDL( tableName: string, columns: [string, ColumnDef][], - compositePrimaryKey?: string[] + compositePrimaryKey?: string[], ): string { const columnDefs = columns.map(([name, col]) => { const parts = [name, col.type]; @@ -315,12 +315,12 @@ function columnDefsToDDL( /** Generate CREATE TABLE DDL from a table schema */ export function generateTableDDL( tableName: string, - schema: TableSchema + schema: TableSchema, ): string { return columnDefsToDDL( tableName, Object.entries(schema.columns), - schema.compositePrimaryKey + schema.compositePrimaryKey, ); } @@ -337,12 +337,12 @@ export function generatePreMigrationTableDDL(tableName: string): string { } const baseColumns = Object.entries(schema.columns).filter( - ([, col]) => col.addedInVersion === undefined + ([, col]) => col.addedInVersion === undefined, ); if (baseColumns.length === 0) { throw new Error( - `Table ${tableName} has no base columns (all columns were added in migrations)` + `Table ${tableName} has no base columns (all columns were added in migrations)`, ); } @@ -354,7 +354,7 @@ export const EXPECTED_TABLES: Record<string, string> = Object.fromEntries( Object.entries(TABLE_SCHEMAS).map(([name, schema]) => [ name, generateTableDDL(name, schema), - ]) + ]), ); /** Column info for repair operations */ @@ -373,14 +373,14 @@ export const EXPECTED_COLUMNS: Record<string, RepairColumnDef[]> = .map(([name, col]) => ({ name, type: col.type })); return [tableName, migratedColumns] as const; }) - .filter(([, cols]) => cols.length > 0) + .filter(([, cols]) => cols.length > 0), ); /** Check if a table exists in the database */ export function tableExists(db: Database, table: string): boolean { const result = db .query( - "SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name=?" + "SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name=?", ) .get(table) as { count: number }; return result.count > 0; @@ -390,11 +390,11 @@ export function tableExists(db: Database, table: string): boolean { export function hasColumn( db: Database, table: string, - column: string + column: string, ): boolean { const result = db .query( - `SELECT COUNT(*) as count FROM pragma_table_info('${table}') WHERE name='${column}'` + `SELECT COUNT(*) as count FROM pragma_table_info('${table}') WHERE name='${column}'`, ) .get() as { count: number }; return result.count > 0; @@ -411,7 +411,7 @@ export function hasColumn( function hasCompositePrimaryKey( db: Database, table: string, - expectedColumns: string[] + expectedColumns: string[], ): boolean { const row = db .query("SELECT sql FROM sqlite_master WHERE type='table' AND name=?") @@ -430,7 +430,7 @@ function addColumnIfMissing( db: Database, table: string, column: string, - type: string + type: string, ): void { if (!hasColumn(db, table, column)) { db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`); @@ -524,7 +524,7 @@ function repairMissingColumns(db: Database, result: RepairResult): void { } catch (e) { const msg = stringifyUnknown(e); result.failed.push( - `Failed to add column ${tableName}.${col.name}: ${msg}` + `Failed to add column ${tableName}.${col.name}: ${msg}`, ); } } @@ -556,7 +556,7 @@ function repairWrongPrimaryKeys(db: Database, result: RepairResult): void { db.exec(`DROP TABLE ${tableName}`); db.exec(EXPECTED_TABLES[tableName] as string); result.fixed.push( - `Recreated table ${tableName} with correct primary key` + `Recreated table ${tableName} with correct primary key`, ); } catch (e) { const msg = stringifyUnknown(e); @@ -580,10 +580,10 @@ export function repairSchema(db: Database): RepairResult { repairWrongPrimaryKeys(db, result); if (result.fixed.length > 0) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { db.query("UPDATE schema_version SET version = ?").run( - CURRENT_SCHEMA_VERSION + CURRENT_SCHEMA_VERSION, ); } catch { // Ignore version update failures - schema is still fixed @@ -664,7 +664,7 @@ export type RepairAttemptResult<T> = */ export function tryRepairAndRetry<T>( operation: () => T, - error: unknown + error: unknown, ): RepairAttemptResult<T> { // Skip repair if disabled via environment variable if (getEnv()[NO_AUTO_REPAIR_ENV] === "1") { @@ -683,7 +683,7 @@ export function tryRepairAndRetry<T>( isRepairing = true; let repairSucceeded = false; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Dynamic imports to avoid circular dependencies with db/index.js const { getRawDatabase } = _require("./index.js") as { @@ -722,7 +722,7 @@ export function initSchema(db: Database): void { if (!versionRow) { db.query("INSERT OR IGNORE INTO schema_version (version) VALUES (?)").run( - CURRENT_SCHEMA_VERSION + CURRENT_SCHEMA_VERSION, ); } } @@ -743,7 +743,7 @@ function getSchemaVersion(db: Database): number { * - Column renames (requires data copy in SQLite) * - Complex constraints */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: sequential migration steps are inherently linear +// sequential migration steps are inherently linear export function runMigrations(db: Database): void { const currentVersion = getSchemaVersion(db); @@ -841,12 +841,12 @@ export function runMigrations(db: Database): void { .get() as { organization: string | null; project: string | null } | null; if (row?.organization) { db.query( - "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)" + "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)", ).run("defaults.org", row.organization); } if (row?.project) { db.query( - "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)" + "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)", ).run("defaults.project", row.project); } db.exec("DROP TABLE defaults"); @@ -885,7 +885,7 @@ export function runMigrations(db: Database): void { if (currentVersion < CURRENT_SCHEMA_VERSION) { db.query("UPDATE schema_version SET version = ?").run( - CURRENT_SCHEMA_VERSION + CURRENT_SCHEMA_VERSION, ); } } diff --git a/packages/cli/src/lib/db/sqlite.ts b/packages/cli/src/lib/db/sqlite.ts index efbc2880b..2ea808f79 100644 --- a/packages/cli/src/lib/db/sqlite.ts +++ b/packages/cli/src/lib/db/sqlite.ts @@ -121,7 +121,7 @@ function toNodeParams(params: SQLQueryBindings[]): SQLQueryBindings[] { * `node-sqlite3-wasm` Statement). * @param kind Which driver produced `stmt`. */ -// biome-ignore lint/suspicious/noExplicitAny: backing driver types vary +// oxlint-disable-next-line typescript/no-explicit-any -- backing driver types vary function wrapStatement(stmt: any, kind: DriverKind): StatementWrapper { return new Proxy(stmt, { get(target, prop) { @@ -170,7 +170,7 @@ function wrapStatement(stmt: any, kind: DriverKind): StatementWrapper { /** Backing-driver constructor plus which kind it is. */ type ResolvedDriver = { - // biome-ignore lint/suspicious/noExplicitAny: driver constructors differ + // oxlint-disable-next-line typescript/no-explicit-any -- driver constructors differ Ctor: any; kind: DriverKind; }; @@ -256,7 +256,7 @@ function resolveDriver(): ResolvedDriver { } const { Database: WasmDatabase } = _require("node-sqlite3-wasm") as { - // biome-ignore lint/suspicious/noExplicitAny: driver types loaded lazily + // oxlint-disable-next-line typescript/no-explicit-any -- driver types loaded lazily Database: any; }; resolvedDriver = { Ctor: WasmDatabase, kind: "wasm" }; @@ -389,11 +389,11 @@ function writeLockOwner(dbPath: string): void { /** Read the recorded owner PID, or null if absent/unreadable. */ function readLockOwner(dbPath: string): number | null { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const pid = Number.parseInt( readFileSync(lockOwnerPath(dbPath), "utf8"), - 10 + 10, ); return Number.isInteger(pid) && pid > 0 ? pid : null; } catch { @@ -422,7 +422,7 @@ function removeLockOwner(dbPath: string): void { * - `transaction(fn)` — wrap a function in BEGIN/COMMIT/ROLLBACK */ export class Database { - // biome-ignore lint/suspicious/noExplicitAny: backing driver resolved at runtime + // oxlint-disable-next-line typescript/no-explicit-any -- backing driver resolved at runtime private readonly db: any; /** Which backing driver this instance uses (for param normalisation). */ diff --git a/packages/cli/src/lib/db/user.ts b/packages/cli/src/lib/db/user.ts index b69209711..0d646fc83 100644 --- a/packages/cli/src/lib/db/user.ts +++ b/packages/cli/src/lib/db/user.ts @@ -63,6 +63,6 @@ export function setUserInfo(info: UserInfo): void { name: info.name ?? null, updated_at: now, }, - ["id"] + ["id"], ); } diff --git a/packages/cli/src/lib/db/utils.ts b/packages/cli/src/lib/db/utils.ts index 75cdb6d8f..0eeec49e1 100644 --- a/packages/cli/src/lib/db/utils.ts +++ b/packages/cli/src/lib/db/utils.ts @@ -60,7 +60,7 @@ export function upsert<T extends Record<string, SqlValue>>( table: string, data: T, conflictColumns: (keyof T)[], - options: UpsertOptions<T> = {} + options: UpsertOptions<T> = {}, ): SqlQuery { const columns = Object.keys(data); const values = Object.values(data) as SqlValue[]; @@ -79,7 +79,7 @@ export function upsert<T extends Record<string, SqlValue>>( const excludeSet = new Set((options.excludeFromUpdate ?? []) as string[]); const updateColumns = columns.filter( - (col) => !(conflictSet.has(col) || excludeSet.has(col)) + (col) => !(conflictSet.has(col) || excludeSet.has(col)), ); const updateClause = @@ -119,7 +119,7 @@ export function runUpsert<T extends Record<string, SqlValue>>( db: QueryRunner, table: string, data: T, - conflictColumns: (keyof T)[] + conflictColumns: (keyof T)[], ): void { const { sql, values } = upsert(table, data, conflictColumns); db.query(sql).run(...values); @@ -144,7 +144,7 @@ type MetadataRow = { key: string; value: string }; */ export function getMetadata( db: QueryRunner, - keys: string[] + keys: string[], ): Map<string, string> { if (keys.length === 0) { return new Map(); @@ -167,7 +167,7 @@ export function getMetadata( */ export function setMetadata( db: QueryRunner, - entries: Record<string, string> + entries: Record<string, string>, ): void { const pairs = Object.entries(entries); if (pairs.length === 0) { @@ -214,11 +214,11 @@ export function clearMetadata(db: QueryRunner, keys: string[]): void { export function touchCacheEntry( table: string, keyColumn: string, - keyValue: string + keyValue: string, ): void { const db = getDatabase(); db.query(`UPDATE ${table} SET last_accessed = ? WHERE ${keyColumn} = ?`).run( Date.now(), - keyValue + keyValue, ); } diff --git a/packages/cli/src/lib/delta-upgrade.ts b/packages/cli/src/lib/delta-upgrade.ts index 204875dce..6ca6355ea 100644 --- a/packages/cli/src/lib/delta-upgrade.ts +++ b/packages/cli/src/lib/delta-upgrade.ts @@ -1,7 +1,7 @@ /** Delta upgrade discovery and application backed by binpatch. */ import { join } from "node:path"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import { applyPatchChainInMemory, @@ -55,7 +55,7 @@ export type { PatchChain, StableChainInfo, } from "binpatch"; -// biome-ignore lint/performance/noBarrelFile: preserve the existing public API +// preserve the existing public API export { extractSha256, getPatchFromVersion, @@ -87,7 +87,7 @@ function upgradeSourceKey(source: UpgradeSource): string { /** Return whether a normalized release list belongs to the selected source. */ export function isNormalizedForSource( releases: GitHubRelease[], - source: UpgradeSource + source: UpgradeSource, ): boolean { return ( (releases as Partial<NormalizedGitHubReleases>)[ @@ -99,7 +99,7 @@ export function isNormalizedForSource( /** Filter and normalize raw stable GitHub releases for one upgrade source. */ export function normalizeStableReleases( releases: unknown[], - source: UpgradeSource + source: UpgradeSource, ): NormalizedGitHubReleases { const normalized = releases .filter(isGitHubRelease) @@ -112,7 +112,7 @@ export function normalizeStableReleases( .filter( (release) => semverValid(release.tag_name) !== null && - semverPrerelease(release.tag_name) === null + semverPrerelease(release.tag_name) === null, ) as NormalizedGitHubReleases; Object.defineProperty(normalized, NORMALIZED_RELEASE_SOURCE, { value: upgradeSourceKey(source), @@ -159,7 +159,7 @@ function instrumentCache(base: PatchCache): PatchCache { span.setAttribute("cache.key", [key]); span.setAttribute( "cache.item_size", - chain.patches.reduce((sum, patch) => sum + patch.size, 0) + chain.patches.reduce((sum, patch) => sum + patch.size, 0), ); await base.save(chain, steps); }); @@ -236,7 +236,7 @@ export function canAttemptDelta(targetVersion: string): boolean { export async function fetchRecentReleases( signal?: AbortSignal, - source: UpgradeSource = getPrimaryUpgradeSource() + source: UpgradeSource = getPrimaryUpgradeSource(), ): Promise<NormalizedGitHubReleases> { try { const response = await customFetch( @@ -247,7 +247,7 @@ export async function fetchRecentReleases( "User-Agent": `sentry-cli/${CLI_VERSION}`, }, signal, - } + }, ); if (!response.ok) { return normalizeStableReleases([], source); @@ -266,7 +266,7 @@ export async function fetchRecentReleases( export async function downloadStablePatch( url: string, - signal?: AbortSignal + signal?: AbortSignal, ): Promise<Uint8Array | null> { try { const response = await customFetch(url, { @@ -281,7 +281,7 @@ export async function downloadStablePatch( } export function extractStableChain( - opts: ExtractStableChainOpts + opts: ExtractStableChainOpts, ): StableChainInfo | null { const result = binpatchExtractStableChain(opts); return "failure" in result ? null : result; @@ -290,13 +290,13 @@ export function extractStableChain( export function filterAndSortChainTags( allTags: string[], currentVersion: string, - targetVersion: string + targetVersion: string, ): string[] { return binpatchFilterAndSortChainTags( allTags, currentVersion, targetVersion, - compareVersions + compareVersions, ); } @@ -316,7 +316,7 @@ type ChainStepResult = export function validateChainStep( manifest: OciManifest, - opts: { expectedFrom: string; patchLayerName: string; sizeLimit: number } + opts: { expectedFrom: string; patchLayerName: string; sizeLimit: number }, ): ChainStepResult { const fromVersion = getPatchFromVersion(manifest); if (fromVersion !== opts.expectedFrom) { @@ -336,7 +336,7 @@ export function validateChainStep( const layer = manifest.layers.find( (item) => item.annotations?.["org.opencontainers.image.title"] === - opts.patchLayerName + opts.patchLayerName, ); return layer ? { @@ -357,12 +357,12 @@ export function resolveStableChain( currentVersion: string, targetVersion: string, signal?: AbortSignal, - source: UpgradeSource = getPrimaryUpgradeSource() + source: UpgradeSource = getPrimaryUpgradeSource(), ): Promise<PatchChain | null> { return stableSource(source).resolveChain( currentVersion, targetVersion, - signal + signal, ); } @@ -388,17 +388,19 @@ export async function resolveNightlyChain(opts: { const chainTags = filterAndSortChainTags( tags, opts.currentVersion, - opts.targetVersion + opts.targetVersion, ); if (chainTags.length === 0 || chainTags.length > MAX_NIGHTLY_CHAIN_DEPTH) { return null; } let manifests: OciManifest[]; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { manifests = await Promise.all( - chainTags.map((tag) => client.fetchManifest(opts.token, tag, opts.signal)) + chainTags.map((tag) => + client.fetchManifest(opts.token, tag, opts.signal), + ), ); } catch { return null; @@ -428,7 +430,7 @@ export async function resolveNightlyChain(opts: { if (!result.ok) { Sentry.getActiveSpan()?.setAttribute( "telemetry_reason", - result.failure.reason + result.failure.reason, ); return null; } @@ -444,7 +446,7 @@ export async function resolveNightlyChain(opts: { if (previousVersion !== opts.targetVersion || !expectedSha256) { Sentry.getActiveSpan()?.setAttribute( "telemetry_reason", - "version-mismatch" + "version-mismatch", ); return null; } @@ -452,10 +454,10 @@ export async function resolveNightlyChain(opts: { const patches = await Promise.all( digests.map(async (digest) => { const data = new Uint8Array( - await client.downloadBlobBuffer(opts.token, digest, opts.signal) + await client.downloadBlobBuffer(opts.token, digest, opts.signal), ); return { data, size: data.byteLength }; - }) + }), ); return { patches, @@ -469,7 +471,7 @@ export function applyPatchChain( chain: PatchChain, oldBinaryPath: string, destPath: string, - onBytes?: (bytes: number) => void + onBytes?: (bytes: number) => void, ): Promise<string> { return withTracingSpan( "apply-patches", @@ -481,15 +483,15 @@ export function applyPatchChain( oldBinaryPath, chain.patches.map((patch) => patch.data), destPath, - onBytes + onBytes, ); if (sha256 !== chain.expectedSha256) { throw new Error( - `SHA-256 mismatch after patching: got ${sha256}, expected ${chain.expectedSha256}` + `SHA-256 mismatch after patching: got ${sha256}, expected ${chain.expectedSha256}`, ); } return sha256; - } + }, ); } @@ -515,7 +517,7 @@ function makeProgressHandler(setMessage?: SetMessage): ProgressHandler { `${isApply ? "Applying" : "Processing"} patch(es)`, event.total, setMessage, - { format: isApply ? "pct" : "bytes" } + { format: isApply ? "pct" : "bytes" }, ); } if (event.type === "bytes" && progress) { @@ -540,7 +542,7 @@ function telemetry(): DeltaTelemetry & { _source: { current?: string } } { const span = Sentry.getActiveSpan(); span?.setAttribute("delta.source", source); log.debug( - `Resolved patch chain from ${source}: ${chain.patches.length} patch(es), ${formatBytes(chain.totalSize)} total` + `Resolved patch chain from ${source}: ${chain.patches.length} patch(es), ${formatBytes(chain.totalSize)} total`, ); }, onOfflineMiss: () => { @@ -553,14 +555,14 @@ function telemetry(): DeltaTelemetry & { _source: { current?: string } } { }; } -// biome-ignore lint/nursery/useMaxParams: internal adapter mirrors the preserved public call shape +// oxlint-disable-next-line max-params -- internal adapter mirrors the preserved public call shape function resolveDelta( source: SourceStrategy, targetVersion: string, oldBinaryPath: string, destPath: string, offline?: boolean, - setMessage?: SetMessage + setMessage?: SetMessage, ): Promise<{ result: DeltaResult | null; source: string | undefined }> { const tel = telemetry(); return resolveAndApply({ @@ -576,14 +578,14 @@ function resolveDelta( }).then((result) => ({ result, source: tel._source.current })); } -// biome-ignore lint/nursery/useMaxParams: preserve the existing public API +// oxlint-disable-next-line max-params -- preserve the existing public API export function resolveStableDelta( targetVersion: string, oldBinaryPath: string, destPath: string, offline?: boolean, setMessage?: SetMessage, - source: UpgradeSource = getPrimaryUpgradeSource() + source: UpgradeSource = getPrimaryUpgradeSource(), ): Promise<DeltaResult | null> { return resolveDelta( stableSource(source), @@ -591,18 +593,18 @@ export function resolveStableDelta( oldBinaryPath, destPath, offline, - setMessage + setMessage, ).then(({ result }) => result); } -// biome-ignore lint/nursery/useMaxParams: preserve the existing public API +// oxlint-disable-next-line max-params -- preserve the existing public API export function resolveNightlyDelta( targetVersion: string, oldBinaryPath: string, destPath: string, offline?: boolean, setMessage?: SetMessage, - source: UpgradeSource = getPrimaryUpgradeSource() + source: UpgradeSource = getPrimaryUpgradeSource(), ): Promise<DeltaResult | null> { return resolveDelta( nightlySource(source), @@ -610,18 +612,18 @@ export function resolveNightlyDelta( oldBinaryPath, destPath, offline, - setMessage + setMessage, ).then(({ result }) => result); } -// biome-ignore lint/nursery/useMaxParams: preserve the existing public API +// oxlint-disable-next-line max-params -- preserve the existing public API export function attemptDeltaUpgrade( targetVersion: string, oldBinaryPath: string, destPath: string, offline?: boolean, setMessage?: SetMessage, - source: UpgradeSource = getPrimaryUpgradeSource() + source: UpgradeSource = getPrimaryUpgradeSource(), ): Promise<DeltaResult | null> { if (!canAttemptDelta(targetVersion)) { return Promise.resolve(null); @@ -641,7 +643,7 @@ export function attemptDeltaUpgrade( oldBinaryPath, destPath, offline, - setMessage + setMessage, ); chainSource = resolved.source; const result = resolved.result; @@ -651,12 +653,12 @@ export function attemptDeltaUpgrade( Sentry.metrics.distribution( "upgrade.delta.patch_bytes", result.patchBytes, - { attributes: { channel } } + { attributes: { channel } }, ); Sentry.metrics.distribution( "upgrade.delta.chain_length", result.chainLength, - { attributes: { channel } } + { attributes: { channel } }, ); } else { span.setAttribute("delta.result", "unavailable"); @@ -681,7 +683,7 @@ export function attemptDeltaUpgrade( } const message = error instanceof Error ? error.message : String(error); log.warn( - `Delta upgrade failed (${message}), falling back to full download` + `Delta upgrade failed (${message}), falling back to full download`, ); span.setStatus({ code: 2 }); span.setAttribute("delta.result", "error"); @@ -689,14 +691,14 @@ export function attemptDeltaUpgrade( return null; } }, - { "delta.channel": channel } + { "delta.channel": channel }, ); } async function prefetch( source: SourceStrategy, targetVersion: string, - signal?: AbortSignal + signal?: AbortSignal, ): Promise<void> { if (!canAttemptDelta(targetVersion) || signal?.aborted) { return; @@ -711,7 +713,7 @@ async function prefetch( export function prefetchNightlyPatches( targetVersion: string, signal?: AbortSignal, - source: UpgradeSource = getPrimaryUpgradeSource() + source: UpgradeSource = getPrimaryUpgradeSource(), ): Promise<void> { return prefetch(nightlySource(source), targetVersion, signal); } @@ -719,7 +721,7 @@ export function prefetchNightlyPatches( export function prefetchStablePatches( targetVersion: string, signal?: AbortSignal, - source: UpgradeSource = getPrimaryUpgradeSource() + source: UpgradeSource = getPrimaryUpgradeSource(), ): Promise<void> { return prefetch(stableSource(source), targetVersion, signal); } diff --git a/packages/cli/src/lib/detect-agent.ts b/packages/cli/src/lib/detect-agent.ts index b99b5a35b..80b34863f 100644 --- a/packages/cli/src/lib/detect-agent.ts +++ b/packages/cli/src/lib/detect-agent.ts @@ -318,7 +318,7 @@ export async function detectAgentFromProcessTree(): Promise< const log = logger.withTag("detect-agent"); export async function getProcessInfoFromOS( - pid: number + pid: number, ): Promise<ProcessInfo | undefined> { // Linux: /proc is an in-memory filesystem — fast even though async try { @@ -338,7 +338,7 @@ export async function getProcessInfoFromOS( const result = await execFileUnreffed( "ps", ["-p", String(pid), "-o", "ppid=,comm="], - { timeout: 500 } + { timeout: 500 }, ); const match = result.trim().match(PS_PPID_COMM_RE); if (match?.[1] && match?.[2]) { @@ -357,7 +357,7 @@ export async function getProcessInfoFromOS( function execFileUnreffed( cmd: string, args: readonly string[], - opts: { timeout?: number } + opts: { timeout?: number }, ): Promise<string> { return new Promise((resolve, reject) => { const child = execFile( @@ -370,7 +370,7 @@ function execFileUnreffed( } else { resolve(stdout); } - } + }, ); child.unref(); }); diff --git a/packages/cli/src/lib/dev-script.ts b/packages/cli/src/lib/dev-script.ts index bbb9fd234..95b663c25 100644 --- a/packages/cli/src/lib/dev-script.ts +++ b/packages/cli/src/lib/dev-script.ts @@ -39,7 +39,7 @@ const SHELL_FEATURES_RE = /^[A-Za-z_]\w*=\S|&&|\|\||[|><;$"'`]/; * @returns The detected command, or null if nothing was found */ export async function detectDevCommand( - cwd: string + cwd: string, ): Promise<DetectedCommand | null> { const result = (await tryPackageJson(cwd)) ?? @@ -100,9 +100,9 @@ async function tryPackageJson(cwd: string): Promise<DetectedCommand | null> { async function tryPythonFile( cwd: string, filename: string, - args: string[] + args: string[], ): Promise<DetectedCommand | null> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, filename)); return { args, source: filename }; @@ -113,7 +113,7 @@ async function tryPythonFile( /** Check for go.mod and return `go run .` */ async function tryGoMod(cwd: string): Promise<DetectedCommand | null> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, "go.mod")); return { args: ["go", "run", "."], source: "go.mod" }; @@ -125,7 +125,7 @@ async function tryGoMod(cwd: string): Promise<DetectedCommand | null> { /** Check for docker-compose.yml or compose.yml. */ async function tryDockerCompose(cwd: string): Promise<DetectedCommand | null> { for (const filename of ["docker-compose.yml", "compose.yml"]) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, filename)); return { args: ["docker", "compose", "up"], source: filename }; diff --git a/packages/cli/src/lib/dif/find.ts b/packages/cli/src/lib/dif/find.ts index d3329e17e..7ca4c1878 100644 --- a/packages/cli/src/lib/dif/find.ts +++ b/packages/cli/src/lib/dif/find.ts @@ -161,7 +161,7 @@ type SearchState = { /** Find the remaining id that matches `debugId`, or undefined. */ function matchRemaining( state: SearchState, - debugId: string + debugId: string, ): string | undefined { const normalized = normalizeDebugId(debugId); return state.remaining.has(normalized) ? normalized : undefined; @@ -171,7 +171,7 @@ function matchRemaining( async function tryProguard( path: string, size: number, - state: SearchState + state: SearchState, ): Promise<void> { if ( !state.wantProguard || @@ -190,7 +190,7 @@ async function tryProguard( } catch (error) { logger.debug( `Skipping ProGuard candidate ${path}: read/hash failed`, - error + error, ); return; } @@ -213,7 +213,7 @@ function satisfy(state: SearchState, id: string): void { async function tryObject( path: string, size: number, - state: SearchState + state: SearchState, ): Promise<void> { let format: string; try { @@ -304,7 +304,7 @@ export async function findDebugFiles(opts: FindOptions): Promise<FindResult> { formats: new Set( wantedTypes .map((t) => TYPE_TO_FORMAT[t.toLowerCase()]) - .filter((f): f is string => Boolean(f)) + .filter((f): f is string => Boolean(f)), ), sourcebundleType: sourcebundleDisplayType(wantedTypes), wantProguard: wantedTypes.some((t) => t.toLowerCase() === "proguard"), diff --git a/packages/cli/src/lib/dif/index.ts b/packages/cli/src/lib/dif/index.ts index 9091f7bcf..2aac914f7 100644 --- a/packages/cli/src/lib/dif/index.ts +++ b/packages/cli/src/lib/dif/index.ts @@ -95,7 +95,7 @@ function loadWasmBytes(): Uint8Array { const msg = err instanceof Error ? err.message : String(err); throw new Error( `Could not locate the DIF parser WASM (${SYMBOLIC_WASM_SUBPATH}). ` + - `Expected it next to the bundle or via @sentry/symbolic: ${msg}` + `Expected it next to the bundle or via @sentry/symbolic: ${msg}`, ); } } @@ -179,7 +179,7 @@ export type EmbeddedPpdbResult = { * @throws If the buffer cannot be parsed as a known object format. */ export function extractEmbeddedPpdb( - data: Uint8Array + data: Uint8Array, ): EmbeddedPpdbResult | null { ensureInitialized(); using archive = new Archive(data); @@ -196,7 +196,7 @@ export function extractEmbeddedPpdb( } catch (err) { log.debug( `Failed to extract embedded Portable PDB for ${object.debugId}`, - err + err, ); } } @@ -216,7 +216,7 @@ export function extractEmbeddedPpdb( * @returns The selected object, or `undefined` if the archive has no objects. */ export function selectBundledObject<T extends { hasDebugInfo: boolean }>( - objects: readonly T[] + objects: readonly T[], ): T | undefined { return objects.find((object) => object.hasDebugInfo) ?? objects[0]; } @@ -263,7 +263,7 @@ export function createSourceBundle( data: Uint8Array, objectName: string, readSource: (path: string) => Uint8Array | null, - options?: { collectIl2cppSources?: boolean } + options?: { collectIl2cppSources?: boolean }, ): SourceBundleResult { ensureInitialized(); using archive = new Archive(data); @@ -334,7 +334,7 @@ export type Il2cppMappingResult = { export function createIl2cppLineMapping( data: Uint8Array, readSource: (path: string) => Uint8Array | null, - targetDebugId?: string + targetDebugId?: string, ): Il2cppMappingResult | null { ensureInitialized(); using archive = new Archive(data); @@ -351,7 +351,7 @@ export function createIl2cppLineMapping( } const mapping = il2cppLineMapping( object, - (path: string) => readSource(path) ?? undefined + (path: string) => readSource(path) ?? undefined, ); if (!mapping) { return null; diff --git a/packages/cli/src/lib/dif/scan.ts b/packages/cli/src/lib/dif/scan.ts index 612d5e43d..1affe9ee6 100644 --- a/packages/cli/src/lib/dif/scan.ts +++ b/packages/cli/src/lib/dif/scan.ts @@ -138,7 +138,7 @@ function hasValidDebugId(obj: DifObjectInfo): boolean { /** Whether the object's format is one of the requested types (or no filter). */ function formatMatches( objFormat: string, - formats: Set<string> | undefined + formats: Set<string> | undefined, ): boolean { return !formats || formats.size === 0 || formats.has(objFormat); } @@ -200,7 +200,7 @@ export function buildDifFilters(options: DifFilterOptions): DifFilters { if (!format) { throw new ValidationError( `Unknown debug file type '${raw}'. Valid types: ${VALID_DIF_TYPES.join(", ")}`, - "type" + "type", ); } formats.add(format); @@ -234,7 +234,7 @@ export function buildDifFilters(options: DifFilterOptions): DifFilters { */ export function objectPassesFilters( obj: DifObjectInfo, - filters: DifFilters + filters: DifFilters, ): boolean { return ( hasValidDebugId(obj) && @@ -313,7 +313,7 @@ export async function scanPaths(paths: string[]): Promise<string[]> { * @throws {ValidationError} If the path does not exist or cannot be accessed. */ async function statExplicitPath( - path: string + path: string, ): Promise<Awaited<ReturnType<typeof stat>>> { try { return await stat(path); @@ -325,12 +325,12 @@ async function statExplicitPath( if (code === "EACCES" || code === "EPERM") { throw new ValidationError( `Path '${path}' is not readable: ${code}.`, - "path" + "path", ); } throw new ValidationError( `Cannot access path '${path}': ${code ?? "unknown error"}.`, - "path" + "path", ); } } @@ -468,7 +468,7 @@ export type PrepareDifsResult = { export async function prepareDifs( paths: string[], filters: DifFilters, - options: PrepareDifsOptions = {} + options: PrepareDifsOptions = {}, ): Promise<PrepareDifsResult> { const prepared: PreparedDif[] = []; const maxFileSize = options.maxFileSize ?? 0; @@ -486,7 +486,7 @@ export async function prepareDifs( path, filters, maxFileSize, - maxZipTotalSize + maxZipTotalSize, ); // Oversized zip entries are advisory only — they warn per entry and // intentionally do NOT feed `oversizedCount`. That counter gates the @@ -504,7 +504,7 @@ export async function prepareDifs( const { difs, oversized } = await prepareFileDif( path, filters, - maxFileSize + maxFileSize, ); if (oversized) { oversizedCount += 1; @@ -534,7 +534,7 @@ export async function prepareDifs( async function prepareFileDif( path: string, filters: DifFilters, - maxFileSize: number + maxFileSize: number, ): Promise<{ difs: PreparedDif[]; oversized: boolean }> { const peeked = await peekHeader(path); if (!peeked) { @@ -557,12 +557,12 @@ async function prepareFileDif( // match, so it must not inflate the oversized count. if (formatOk) { log.warn( - `Skipping ${path}: size ${peeked.size} exceeds maximum file size ${maxFileSize}` + `Skipping ${path}: size ${peeked.size} exceeds maximum file size ${maxFileSize}`, ); return { difs: [], oversized: true }; } log.debug( - `Skipping ${path} for embedded PPDB extraction: size ${peeked.size} exceeds maximum file size ${maxFileSize}` + `Skipping ${path} for embedded PPDB extraction: size ${peeked.size} exceeds maximum file size ${maxFileSize}`, ); return { difs: [], oversized: false }; } @@ -585,7 +585,7 @@ async function prepareFileDif( function matchedDif( displayPath: string, content: Buffer, - filters: DifFilters + filters: DifFilters, ): PreparedDif | null { if (content.length === 0) { return null; @@ -600,7 +600,7 @@ function matchedDif( } const matched = archive.objects.filter((obj) => - objectPassesFilters(obj, filters) + objectPassesFilters(obj, filters), ); if (matched.length === 0) { return null; @@ -640,7 +640,7 @@ function embeddedPpdbDif( displayPath: string, content: Buffer, filters: DifFilters, - maxFileSize: number + maxFileSize: number, ): { dif: PreparedDif | null; oversized: boolean } { // Only PE images can embed a Portable PDB. Cheap-gate on the header so the // vast majority of scanned files (ELF/Mach-O/Breakpad/...) skip the full @@ -658,7 +658,7 @@ function embeddedPpdbDif( } catch (err) { log.debug( `Could not extract embedded Portable PDB from ${displayPath}`, - err + err, ); return { dif: null, oversized: false }; } @@ -667,7 +667,7 @@ function embeddedPpdbDif( } if (maxFileSize > 0 && extracted.ppdb.byteLength > maxFileSize) { log.warn( - `Skipping embedded Portable PDB from ${displayPath}: size ${extracted.ppdb.byteLength} exceeds maximum file size ${maxFileSize}` + `Skipping embedded Portable PDB from ${displayPath}: size ${extracted.ppdb.byteLength} exceeds maximum file size ${maxFileSize}`, ); // Only count toward the oversized total when a Portable PDB is actually a // requested type; otherwise it would never have been uploaded anyway, so it @@ -703,7 +703,7 @@ function difFromBuffer( displayPath: string, content: Buffer, filters: DifFilters, - maxFileSize: number + maxFileSize: number, ): { difs: PreparedDif[]; oversized: boolean } { const difs: PreparedDif[] = []; const main = matchedDif(displayPath, content, filters); @@ -732,7 +732,7 @@ function difFromBuffer( async function readMatchedDif( path: string, filters: DifFilters, - maxFileSize: number + maxFileSize: number, ): Promise<{ difs: PreparedDif[]; oversized: boolean }> { let content: Buffer; try { @@ -763,7 +763,7 @@ function difFromCandidateBuffer( displayPath: string, content: Buffer, filters: DifFilters, - maxFileSize: number + maxFileSize: number, ): PreparedDif[] { if (content.length === 0) { return []; @@ -810,7 +810,7 @@ async function prepareZipDifs( path: string, filters: DifFilters, maxFileSize: number, - maxTotalSize: number + maxTotalSize: number, ): Promise<PreparedDif[] | null> { const zip = await readZipDifEntries(path, { maxFileSize, maxTotalSize }); if (!zip) { @@ -819,7 +819,12 @@ async function prepareZipDifs( const prepared: PreparedDif[] = []; for (const entry of zip.entries) { prepared.push( - ...difFromCandidateBuffer(entry.path, entry.content, filters, maxFileSize) + ...difFromCandidateBuffer( + entry.path, + entry.content, + filters, + maxFileSize, + ), ); } return prepared; diff --git a/packages/cli/src/lib/dif/zip.ts b/packages/cli/src/lib/dif/zip.ts index 5120b19c4..f1b59844b 100644 --- a/packages/cli/src/lib/dif/zip.ts +++ b/packages/cli/src/lib/dif/zip.ts @@ -117,7 +117,7 @@ function isDirectoryEntry(name: string): boolean { * cannot be opened or is shorter than the 2-byte magic. */ async function peekZipContainer( - path: string + path: string, ): Promise<{ isZip: boolean; size: number }> { try { const fd = await open(path, "r"); @@ -158,7 +158,7 @@ async function peekZipContainer( */ export async function readZipDifEntries( path: string, - options: ReadZipOptions = {} + options: ReadZipOptions = {}, ): Promise<ReadZipResult | null> { if (!path.toLowerCase().endsWith(".zip")) { return null; @@ -178,7 +178,7 @@ export async function readZipDifEntries( // header, finds no object format, and skips it without a full read. if (maxTotalSize > 0 && size > maxTotalSize) { log.warn( - `Skipping ${path}: archive size ${size} exceeds maximum total extraction size ${maxTotalSize}` + `Skipping ${path}: archive size ${size} exceeds maximum total extraction size ${maxTotalSize}`, ); return null; } @@ -204,7 +204,7 @@ export async function readZipDifEntries( // which would discard the whole archive and its valid siblings. Skip it. if (!SUPPORTED_COMPRESSION.has(file.compression)) { log.debug( - `Skipping ${path}/${file.name}: unsupported compression method ${file.compression}` + `Skipping ${path}/${file.name}: unsupported compression method ${file.compression}`, ); return false; } @@ -217,7 +217,7 @@ export async function readZipDifEntries( if (maxFileSize > 0 && file.originalSize > maxFileSize) { oversizedCount += 1; log.warn( - `Skipping ${path}/${file.name}: uncompressed size ${file.originalSize} exceeds maximum file size ${maxFileSize}` + `Skipping ${path}/${file.name}: uncompressed size ${file.originalSize} exceeds maximum file size ${maxFileSize}`, ); return false; } @@ -225,7 +225,7 @@ export async function readZipDifEntries( // cannot inflate unbounded (unzipSync holds all accepted entries at once). if (maxTotalSize > 0 && acceptedTotal + file.originalSize > maxTotalSize) { log.warn( - `Skipping ${path}/${file.name}: would exceed maximum total extraction size ${maxTotalSize}` + `Skipping ${path}/${file.name}: would exceed maximum total extraction size ${maxTotalSize}`, ); return false; } diff --git a/packages/cli/src/lib/docs-context.ts b/packages/cli/src/lib/docs-context.ts index 6f54bd172..5ce885daf 100644 --- a/packages/cli/src/lib/docs-context.ts +++ b/packages/cli/src/lib/docs-context.ts @@ -50,7 +50,7 @@ const FRAMEWORK_SIGNALS: ReadonlyArray<readonly [RegExp, string]> = [ export type DocsContextReader = { hasConfig: (name: (typeof DOCS_CONTEXT_CONFIGS)[number]) => Promise<boolean>; readManifest: ( - name: (typeof DOCS_CONTEXT_MANIFESTS)[number] + name: (typeof DOCS_CONTEXT_MANIFESTS)[number], ) => Promise<string | undefined>; }; @@ -64,7 +64,7 @@ function addFrameworkSignals(text: string, frameworks: Set<string>): void { function addLanguageSignal( manifest: (typeof DOCS_CONTEXT_MANIFESTS)[number], - languages: Set<string> + languages: Set<string>, ): void { const languageByManifest: Partial< Record<(typeof DOCS_CONTEXT_MANIFESTS)[number], string> @@ -98,14 +98,14 @@ function containsSentrySdk(text: string): boolean { * contains manifest/config contents, paths, DSNs, tokens, or package lists. */ export async function detectDocsContextFromReader( - reader: DocsContextReader + reader: DocsContextReader, ): Promise<DocsProjectContext> { const frameworks = new Set<string>(); const languages = new Set<string>(); let sentryConfigured = false; for (const manifest of DOCS_CONTEXT_MANIFESTS) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const contents = await reader.readManifest(manifest); if (contents === undefined) { @@ -120,7 +120,7 @@ export async function detectDocsContextFromReader( } for (const config of DOCS_CONTEXT_CONFIGS) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { sentryConfigured ||= await reader.hasConfig(config); } catch { @@ -139,7 +139,7 @@ export async function detectDocsContextFromReader( export function detectDocsContext(cwd: string): Promise<DocsProjectContext> { return detectDocsContextFromReader({ async readManifest(name) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return await readFile(join(cwd, name), "utf8"); } catch { @@ -147,7 +147,7 @@ export function detectDocsContext(cwd: string): Promise<DocsProjectContext> { } }, async hasConfig(name) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, name)); return true; diff --git a/packages/cli/src/lib/docs-service.ts b/packages/cli/src/lib/docs-service.ts index db0dc2b27..aa9bf22cf 100644 --- a/packages/cli/src/lib/docs-service.ts +++ b/packages/cli/src/lib/docs-service.ts @@ -19,7 +19,7 @@ export type DocsListResponse = { async function postDocs<T>( path: "/api/docs/query" | "/api/docs/list", - body: unknown + body: unknown, ): Promise<T> { assertHostedInitServiceAcceptsTokenHost(); const { token } = await refreshToken(); @@ -36,7 +36,7 @@ async function postDocs<T>( if (!response.ok) { let detail = text; let parsed: { code?: unknown; error?: unknown } | undefined; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { parsed = JSON.parse(text) as { code?: unknown; error?: unknown }; } catch { @@ -46,13 +46,13 @@ async function postDocs<T>( throw new CliError( "Could not produce a verified documentation answer.\n" + " Try rephrasing the question so it can be answered from current Sentry documentation.", - EXIT.API + EXIT.API, ); } if (parsed?.code === "DOCS_MODEL_UNAVAILABLE") { throw new CliError( "Sentry Docs AI is temporarily unavailable in this region. Please try again later.", - EXIT.API + EXIT.API, ); } if (typeof parsed?.error === "string") { @@ -62,7 +62,7 @@ async function postDocs<T>( "Docs service request failed", response.status, detail, - path + path, ); } try { @@ -72,21 +72,21 @@ async function postDocs<T>( "Docs service returned invalid JSON", response.status, undefined, - path + path, ); } } export function queryDocs( query: string, - context: DocsProjectContext + context: DocsProjectContext, ): Promise<DocsQueryResponse> { return postDocs("/api/docs/query", { context, query }); } export function listDocs( query: string, - limit: number + limit: number, ): Promise<DocsListResponse> { return postDocs("/api/docs/list", { limit, query }); } diff --git a/packages/cli/src/lib/dsn/ARCHITECTURE.md b/packages/cli/src/lib/dsn/ARCHITECTURE.md index 09b0bb63f..cc482d14b 100644 --- a/packages/cli/src/lib/dsn/ARCHITECTURE.md +++ b/packages/cli/src/lib/dsn/ARCHITECTURE.md @@ -53,19 +53,19 @@ DSNs are detected in this priority order (highest first): ## Files -| File | Purpose | -|------|---------| -| `index.ts` | Public API - re-exports for clean imports | -| `types.ts` | All TypeScript types and Zod schemas | +| File | Purpose | +| ------------- | ----------------------------------------- | +| `index.ts` | Public API - re-exports for clean imports | +| `types.ts` | All TypeScript types and Zod schemas | | `detector.ts` | Main detection orchestration with caching | -| `scanner.ts` | Shared file scanning utilities | -| `parser.ts` | DSN string parsing and validation | -| `cache.ts` | Cache read/write to config file | -| `resolver.ts` | DSN → org/project via Sentry API | -| `errors.ts` | User-friendly error messages | -| `env.ts` | `SENTRY_DSN` env var detection | +| `scanner.ts` | Shared file scanning utilities | +| `parser.ts` | DSN string parsing and validation | +| `cache.ts` | Cache read/write to config file | +| `resolver.ts` | DSN → org/project via Sentry API | +| `errors.ts` | User-friendly error messages | +| `env.ts` | `SENTRY_DSN` env var detection | | `env-file.ts` | `.env` file detection + monorepo scanning | -| `languages/` | Language-specific code detectors | +| `languages/` | Language-specific code detectors | ## Adding a New Language Detector @@ -118,6 +118,7 @@ The module uses a two-level caching strategy: 2. **Project Cache** - Stores resolved org/project info per DSN Cache invalidation: + - DSN cache is verified by re-reading the source file - If DSN changed, cache is updated - If file deleted, full scan is triggered diff --git a/packages/cli/src/lib/dsn/code-scanner.ts b/packages/cli/src/lib/dsn/code-scanner.ts index f2f81dd42..113315b2f 100644 --- a/packages/cli/src/lib/dsn/code-scanner.ts +++ b/packages/cli/src/lib/dsn/code-scanner.ts @@ -93,7 +93,7 @@ const HTTP_SCHEME_PROBE = /http/i; */ export function extractDsnsFromContent( content: string, - limit?: number + limit?: number, ): string[] { if (!HTTP_SCHEME_PROBE.test(content)) { return []; @@ -150,7 +150,7 @@ export function scanCodeForFirstDsn(cwd: string): Promise<DetectedDsn | null> { return withTracingSpan( "scanCodeForFirstDsn", "dsn.detect.code", - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: walker loop + read + extract + validate + span-status error branch is inherently branchy + // walker loop + read + extract + validate + span-status error branch is inherently branchy async (span) => { let filesScanned = 0; try { @@ -179,7 +179,7 @@ export function scanCodeForFirstDsn(cwd: string): Promise<DetectedDsn | null> { raw, "code", entry.relativePath, - inferPackagePath(entry.relativePath) + inferPackagePath(entry.relativePath), ); if (detected !== null) { span.setAttribute("dsn.files_scanned", filesScanned); @@ -203,7 +203,7 @@ export function scanCodeForFirstDsn(cwd: string): Promise<DetectedDsn | null> { "dsn.scan_dir": cwd, "dsn.stop_on_first": true, "dsn.max_depth": DSN_MAX_DEPTH, - } + }, ); } @@ -229,7 +229,7 @@ function getExpectedHost(): string { } catch { throw new ConfigError( `SENTRY_HOST/SENTRY_URL "${sentryUrl}" is not a valid URL`, - "Set SENTRY_HOST/SENTRY_URL to a valid URL (e.g., https://sentry.example.com) or unset it to use sentry.io" + "Set SENTRY_HOST/SENTRY_URL to a valid URL (e.g., https://sentry.example.com) or unset it to use sentry.io", ); } } @@ -316,7 +316,7 @@ function scanDirectory(cwd: string): Promise<CodeScanResult> { "dsn.scan_dir": cwd, "dsn.stop_on_first": false, "dsn.max_depth": DSN_MAX_DEPTH, - } + }, ); } @@ -338,7 +338,7 @@ function processMatch( line: string; mtime?: number; }, - ctx: MatchProcessingContext + ctx: MatchProcessingContext, ): void { if (isCommentedLine(match.line.trim())) { return; diff --git a/packages/cli/src/lib/dsn/detector.ts b/packages/cli/src/lib/dsn/detector.ts index 53b56b865..1ca948397 100644 --- a/packages/cli/src/lib/dsn/detector.ts +++ b/packages/cli/src/lib/dsn/detector.ts @@ -230,7 +230,7 @@ export async function detectAllDsns(cwd: string): Promise<DsnDetectionResult> { // Get project root directory mtime for quick invalidation // when files are added/removed at root level let rootDirMtime = 0; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stats = await stat(projectRoot); rootDirMtime = Math.floor(stats.mtimeMs); @@ -263,7 +263,7 @@ export async function detectAllDsns(cwd: string): Promise<DsnDetectionResult> { * Used to invalidate low-priority cached DSNs when code is added. */ function checkForHigherPriorityCodeDsn( - cwd: string + cwd: string, ): Promise<DetectedDsn | null> { return scanCodeForFirstDsn(cwd); } @@ -276,7 +276,7 @@ function checkForHigherPriorityCodeDsn( */ async function verifyEnvVarCache( cwd: string, - cached: CachedDsnEntry + cached: CachedDsnEntry, ): Promise<DetectedDsn | null> { // First check if a code DSN exists (highest priority) const codeDsn = await checkForHigherPriorityCodeDsn(cwd); @@ -303,7 +303,7 @@ async function verifyEnvVarCache( */ async function verifyFileDsnCache( cwd: string, - cached: CachedDsnEntry + cached: CachedDsnEntry, ): Promise<DetectedDsn | null> { if (!cached.sourcePath) { return null; @@ -311,7 +311,7 @@ async function verifyFileDsnCache( const filePath = join(cwd, cached.sourcePath); - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Guard: skip non-regular files (FIFOs, sockets, etc.) that would block. // 1Password streams secrets via symlinked named pipes; Bun.file().text() @@ -348,7 +348,7 @@ async function verifyFileDsnCache( */ async function verifyCachedDsn( cwd: string, - cached: CachedDsnEntry + cached: CachedDsnEntry, ): Promise<DetectedDsn | null> { // Env var source (lowest priority) - check for higher-priority sources if (cached.source === "env") { @@ -375,7 +375,7 @@ async function verifyCachedDsn( */ function extractDsnFromContent( content: string, - source: DsnSource + source: DsnSource, ): string | null { switch (source) { case "env_file": diff --git a/packages/cli/src/lib/dsn/env-file.ts b/packages/cli/src/lib/dsn/env-file.ts index 3b518c6de..e4409a560 100644 --- a/packages/cli/src/lib/dsn/env-file.ts +++ b/packages/cli/src/lib/dsn/env-file.ts @@ -56,10 +56,10 @@ export const ENV_FILES = [ */ const TRAILING_UNDERSCORE = /_$/; const ENV_DSN_PREFIXES = FRAMEWORK_ENV_PREFIXES.map((p) => - p.replace(TRAILING_UNDERSCORE, "") + p.replace(TRAILING_UNDERSCORE, ""), ).join("|"); const ENV_DSN_PATTERN = new RegExp( - `^(?:(?:${ENV_DSN_PREFIXES})_)?SENTRY_DSN\\s*=\\s*(['"]?)(.+?)\\1\\s*(?:#.*)?$` + `^(?:(?:${ENV_DSN_PREFIXES})_)?SENTRY_DSN\\s*=\\s*(['"]?)(.+?)\\1\\s*(?:#.*)?$`, ); /** @@ -112,7 +112,7 @@ export const extractDsnFromEnvFile = extractDsnFromEnvContent; * @returns First detected DSN or null if not found */ export async function detectFromEnvFiles( - cwd: string + cwd: string, ): Promise<DetectedDsn | null> { return await withTracingSpan( "detectFromEnvFiles", @@ -131,7 +131,7 @@ export async function detectFromEnvFiles( span.setAttribute("dsn.env_files_checked", ENV_FILES.length); span.setAttribute("dsn.env_dsn_found", dsns.length > 0); return dsns[0] ?? null; - } + }, ); } @@ -146,7 +146,7 @@ export async function detectFromEnvFiles( * @returns Object with all detected DSNs and source file mtimes */ export async function detectFromAllEnvFiles( - cwd: string + cwd: string, ): Promise<EnvFileScanResult> { return await withTracingSpan( "detectFromAllEnvFiles", @@ -183,7 +183,7 @@ export async function detectFromAllEnvFiles( span.setAttribute("dsn.env_files_checked", filesChecked); span.setAttribute("dsn.env_dsn_found", allDsns.length > 0); return { dsns: allDsns, sourceMtimes: allMtimes }; - } + }, ); } @@ -197,7 +197,7 @@ export async function detectFromAllEnvFiles( * @returns Object with detected DSNs (with packagePath set) and source file mtimes */ export async function detectFromMonorepoEnvFiles( - cwd: string + cwd: string, ): Promise<EnvFileScanResult> { const dsns: DetectedDsn[] = []; const sourceMtimes: Record<string, number> = {}; @@ -210,7 +210,7 @@ export async function detectFromMonorepoEnvFiles( // surfaces when iterating. Wrap the full open+iterate in one try/catch. // No explicit handle.close() needed: for-await-of auto-closes the Dir // handle when the loop exits (including early return or break). - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { for await (const entry of await opendir(rootDir)) { // Skip hidden dirs (.git, .cache) and non-directories. Accept @@ -256,7 +256,7 @@ type PackageDsnResult = { */ async function detectDsnInPackage( pkgDir: string, - packagePath: string + packagePath: string, ): Promise<PackageDsnResult> { const { dsns, sourceMtimes: rawMtimes } = await scanSpecificFiles( pkgDir, @@ -273,10 +273,10 @@ async function detectDsnInPackage( raw, "env_file", sourcePath, - metadata?.packagePath + metadata?.packagePath, ); }, - } + }, ); // Prefix mtimes with package path for uniqueness diff --git a/packages/cli/src/lib/dsn/env.ts b/packages/cli/src/lib/dsn/env.ts index 20199038a..748e9fcc5 100644 --- a/packages/cli/src/lib/dsn/env.ts +++ b/packages/cli/src/lib/dsn/env.ts @@ -30,7 +30,7 @@ export const FRAMEWORK_ENV_PREFIXES = [ * Checked in order after `SENTRY_DSN` (canonical name has priority). */ const FRAMEWORK_DSN_VARS = FRAMEWORK_ENV_PREFIXES.map( - (prefix) => `${prefix}SENTRY_DSN` + (prefix) => `${prefix}SENTRY_DSN`, ); /** diff --git a/packages/cli/src/lib/dsn/errors.ts b/packages/cli/src/lib/dsn/errors.ts index 9b91ff871..7f2e7d001 100644 --- a/packages/cli/src/lib/dsn/errors.ts +++ b/packages/cli/src/lib/dsn/errors.ts @@ -52,7 +52,7 @@ export function formatConflictError(result: DsnDetectionResult): string { */ export async function formatNoDsnError( cwd: string, - showProjects = true + showProjects = true, ): Promise<string> { const lines = [ `No Sentry DSN detected in ${cwd}\n`, @@ -65,7 +65,7 @@ export async function formatNoDsnError( // Try to fetch and show accessible projects if (showProjects) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const projects = await getAccessibleProjects(); @@ -88,7 +88,7 @@ export async function formatNoDsnError( lines.push(""); lines.push("2. Add SENTRY_DSN to a .env file:"); lines.push( - " echo 'SENTRY_DSN=https://key@o123.ingest.sentry.io/456' >> .env" + " echo 'SENTRY_DSN=https://key@o123.ingest.sentry.io/456' >> .env", ); lines.push(""); lines.push("3. Specify project explicitly:"); diff --git a/packages/cli/src/lib/dsn/fs-utils.ts b/packages/cli/src/lib/dsn/fs-utils.ts index e5ea340ac..eaf4838a9 100644 --- a/packages/cli/src/lib/dsn/fs-utils.ts +++ b/packages/cli/src/lib/dsn/fs-utils.ts @@ -5,7 +5,7 @@ */ import { stat } from "node:fs/promises"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; /** @@ -62,7 +62,7 @@ function isIgnorableFileError(error: unknown): boolean { */ export function handleFileError( error: unknown, - context: { operation: string; path?: string } + context: { operation: string; path?: string }, ): void { if (!isIgnorableFileError(error)) { Sentry.captureException(error, { @@ -91,7 +91,7 @@ export function handleFileError( */ export async function isRegularFile( filePath: string, - operation = "isRegularFile" + operation = "isRegularFile", ): Promise<boolean> { try { const stats = await stat(filePath); diff --git a/packages/cli/src/lib/dsn/index.ts b/packages/cli/src/lib/dsn/index.ts index 9a68770ce..bdd08aeba 100644 --- a/packages/cli/src/lib/dsn/index.ts +++ b/packages/cli/src/lib/dsn/index.ts @@ -1,4 +1,4 @@ -// biome-ignore-all lint/performance/noBarrelFile: intentional public API +// Intentional public API barrel. /** * DSN Detection Module * diff --git a/packages/cli/src/lib/dsn/parser.ts b/packages/cli/src/lib/dsn/parser.ts index 1f25406ca..9b92caf69 100644 --- a/packages/cli/src/lib/dsn/parser.ts +++ b/packages/cli/src/lib/dsn/parser.ts @@ -77,7 +77,7 @@ export function isPlaceholderNumericId(id: string): boolean { export function isPlaceholderPublicKey(publicKey: string): boolean { // URL parsers may percent-encode angle-bracket templates such as `%3Ckey%3E`. let decoded = publicKey; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { decoded = decodeURIComponent(publicKey); } catch { @@ -103,7 +103,7 @@ export function isPlaceholderPublicKey(publicKey: string): boolean { * // } */ export function parseDsn(dsn: string): ParsedDsn | null { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = new URL(dsn); @@ -174,7 +174,7 @@ export function createDetectedDsn( raw: string, source: DsnSource, sourcePath?: string, - packagePath?: string + packagePath?: string, ): DetectedDsn | null { const parsed = parseDsn(raw); if (!parsed) { diff --git a/packages/cli/src/lib/dsn/project-root.ts b/packages/cli/src/lib/dsn/project-root.ts index b9052af85..67616ff85 100644 --- a/packages/cli/src/lib/dsn/project-root.ts +++ b/packages/cli/src/lib/dsn/project-root.ts @@ -196,7 +196,7 @@ const statLimit = pLimit(STAT_CONCURRENCY); * Check if a path exists (file or directory) using stat. */ async function pathExists(filePath: string): Promise<boolean> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await stat(filePath); return true; @@ -233,13 +233,13 @@ function anyExists(dir: string, names: readonly string[]): Promise<boolean> { */ async function anyGlobMatches( dir: string, - patterns: readonly string[] + patterns: readonly string[], ): Promise<boolean> { // Bun's opendir() may not throw on a missing directory — the error // surfaces when iterating. Wrap the full open+iterate in one try/catch. // No explicit handle.close() needed: for-await-of auto-closes the Dir // handle when the loop exits (including early return or break). - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Pre-compile matchers outside the loop to avoid recompiling per entry. const matchers = patterns.map((p) => picomatch(p, { dot: true })); @@ -292,7 +292,7 @@ async function checkEditorConfigRoot(dir: string): Promise<boolean> { function getRepoRootType( hasVcs: boolean, hasCi: boolean, - hasEditorConfigRoot: boolean + hasEditorConfigRoot: boolean, ): "vcs" | "ci" | "editorconfig" | undefined { if (hasVcs) { return "vcs"; @@ -313,7 +313,7 @@ function getRepoRootType( * @returns Object with found status and marker type */ export function hasRepoRootMarker( - dir: string + dir: string, ): Promise<{ found: boolean; type?: "vcs" | "ci" | "editorconfig" }> { return withFsSpan("hasRepoRootMarker", async () => { // Check all marker types in parallel @@ -352,7 +352,7 @@ export function hasLanguageMarker(dir: string): Promise<boolean> { */ export function hasBuildSystemMarker(dir: string): Promise<boolean> { return withFsSpan("hasBuildSystemMarker", async () => - anyExists(dir, BUILD_SYSTEM_MARKERS) + anyExists(dir, BUILD_SYSTEM_MARKERS), ); } @@ -393,7 +393,7 @@ function checkEnvForDsn(dir: string): Promise<DetectedDsn | null> { * Returns home directory if it exists, otherwise filesystem root. */ export function getStopBoundary(): string { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return homedir(); } catch { @@ -429,7 +429,7 @@ export function isHomeOrAncestor(dir: string): boolean { async function processDirectoryLevel( currentDir: string, languageMarkerAt: string | null, - buildSystemAt: string | null + buildSystemAt: string | null, ): Promise<{ dsnResult: DetectedDsn | null; repoRootResult: { found: boolean; type?: "vcs" | "ci" | "editorconfig" }; @@ -453,7 +453,7 @@ async function processDirectoryLevel( function selectProjectRoot( languageMarkerAt: string | null, buildSystemAt: string | null, - fallback: string + fallback: string, ): { projectRoot: string; reason: ProjectRootReason } { if (languageMarkerAt) { return { projectRoot: languageMarkerAt, reason: "language" }; @@ -470,7 +470,7 @@ function selectProjectRoot( function createDsnFoundResult( currentDir: string, dsnResult: DetectedDsn, - levelsTraversed: number + levelsTraversed: number, ): ProjectRootResult { return { projectRoot: currentDir, @@ -492,7 +492,7 @@ function createRepoRootResult( currentDir: string, markerType: "vcs" | "ci" | "editorconfig" | undefined, levelsTraversed: number, - languageMarkerAt: string | null + languageMarkerAt: string | null, ): ProjectRootResult { // Prefer closer language marker over repo root for monorepo support if (languageMarkerAt) { @@ -515,7 +515,7 @@ function createRepoRootResult( */ async function finalizeSentryCliRc( cwd: string, - config: SentryCliRcConfig + config: SentryCliRcConfig, ): Promise<void> { await applyGlobalFallbacks(config); setSentryCliRcCache(cwd, config); @@ -535,7 +535,7 @@ async function finalizeSentryCliRc( */ async function walkUpDirectories( resolvedStart: string, - stopBoundary: string + stopBoundary: string, ): Promise<ProjectRootResult> { let levelsTraversed = 0; let languageMarkerAt: string | null = null; @@ -567,7 +567,7 @@ async function walkUpDirectories( currentDir, repoRootResult.type, levelsTraversed, - languageMarkerAt + languageMarkerAt, ); } @@ -594,7 +594,7 @@ async function walkUpDirectories( const selected = selectProjectRoot( languageMarkerAt, buildSystemAt, - resolvedStart + resolvedStart, ); return { @@ -633,6 +633,6 @@ export function findProjectRoot(startDir: string): Promise<ProjectRootResult> { return result; }, - { "dsn.start_dir": startDir } + { "dsn.start_dir": startDir }, ); } diff --git a/packages/cli/src/lib/dsn/resolver.ts b/packages/cli/src/lib/dsn/resolver.ts index 70649e97d..ae8645822 100644 --- a/packages/cli/src/lib/dsn/resolver.ts +++ b/packages/cli/src/lib/dsn/resolver.ts @@ -32,7 +32,7 @@ import type { */ export async function resolveProject( cwd: string, - dsn: DetectedDsn + dsn: DetectedDsn, ): Promise<ResolvedProject> { // Check if we have cached resolution if (dsn.resolved) { @@ -60,7 +60,7 @@ export async function resolveProject( if (!project?.organization) { throw new Error( "Cannot resolve project: DSN could not be matched to any accessible project. " + - "You may not have access, or specify the target explicitly: sentry <command> <org>/<project>" + "You may not have access, or specify the target explicitly: sentry <command> <org>/<project>", ); } @@ -68,7 +68,7 @@ export async function resolveProject( orgSlug: project.organization.slug, orgName: resolveOrgDisplayName( project.organization.slug, - project.organization.name + project.organization.name, ), projectSlug: project.slug, projectName: project.name, @@ -103,7 +103,7 @@ export async function resolveProject( */ async function fetchProjectInfo( orgId: string, - projectId: string + projectId: string, ): Promise<ResolvedProjectInfo> { // Fetch all orgs to find the one matching our orgId const orgs = await listOrganizations(); @@ -114,7 +114,7 @@ async function fetchProjectInfo( if (!org) { throw new Error( `Could not find organization with ID ${orgId}. ` + - "You may not have access to this organization." + "You may not have access to this organization.", ); } @@ -127,7 +127,7 @@ async function fetchProjectInfo( if (!project) { throw new Error( `Could not find project with ID ${projectId} in organization ${org.slug}. ` + - "You may not have access to this project." + "You may not have access to this project.", ); } @@ -158,12 +158,12 @@ type AccessibleProject = { export async function getAccessibleProjects(): Promise<AccessibleProject[]> { const results: AccessibleProject[] = []; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const orgs = await listOrganizations(); for (const org of orgs) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const projects = await listProjects(org.slug); diff --git a/packages/cli/src/lib/dsn/scanner.ts b/packages/cli/src/lib/dsn/scanner.ts index 7459ff503..e8d4e02e7 100644 --- a/packages/cli/src/lib/dsn/scanner.ts +++ b/packages/cli/src/lib/dsn/scanner.ts @@ -37,7 +37,7 @@ export type SpecificFileScanResult = { */ export type FileProcessor = ( relativePath: string, - content: string + content: string, ) => FileProcessResult | null; /** @@ -59,9 +59,9 @@ export async function scanSpecificFiles( createDsn: ( raw: string, relativePath: string, - metadata?: { packagePath?: string } + metadata?: { packagePath?: string }, ) => DetectedDsn | null; - } + }, ): Promise<SpecificFileScanResult> { const { stopOnFirst = false, processFile, createDsn } = options; const dsns: DetectedDsn[] = []; diff --git a/packages/cli/src/lib/env-token-host.ts b/packages/cli/src/lib/env-token-host.ts index a3e9701ee..ef655404a 100644 --- a/packages/cli/src/lib/env-token-host.ts +++ b/packages/cli/src/lib/env-token-host.ts @@ -49,7 +49,7 @@ const EXPLICIT_SCHEME_RE = /^([a-z][a-z\d+.-]*):\/\//i; function normalizeHost( input: string | undefined, - source: string + source: string, ): string | undefined { if (!input) { return; @@ -121,7 +121,7 @@ export function captureEnvTokenHost(): void { const configuredHost = normalizeHost( env.SENTRY_HOST?.trim() || env.SENTRY_URL?.trim(), - env.SENTRY_HOST?.trim() ? "SENTRY_HOST" : "SENTRY_URL" + env.SENTRY_HOST?.trim() ? "SENTRY_HOST" : "SENTRY_URL", ) ?? null; // Claim first: for sntrys_ tokens, the embedded url is authoritative. const claim = captureClaimHost(getRawEnvToken()); diff --git a/packages/cli/src/lib/envelope/checkin-builder.ts b/packages/cli/src/lib/envelope/checkin-builder.ts index cd9358446..dc2d366e7 100644 --- a/packages/cli/src/lib/envelope/checkin-builder.ts +++ b/packages/cli/src/lib/envelope/checkin-builder.ts @@ -44,7 +44,7 @@ type MonitorConfig = NonNullable<SerializedCheckIn["monitor_config"]>; * CLI's `requires("schedule")` constraint. */ export function buildMonitorConfig( - flags: CheckInConfigFlags + flags: CheckInConfigFlags, ): MonitorConfig | undefined { const dependentFlags: [keyof CheckInConfigFlags, string][] = [ ["check-in-margin", "--check-in-margin"], @@ -59,7 +59,7 @@ export function buildMonitorConfig( if (flags[key] !== undefined) { throw new ValidationError( `${flagName} requires --schedule to be set.`, - "schedule" + "schedule", ); } } diff --git a/packages/cli/src/lib/envelope/event-builder.ts b/packages/cli/src/lib/envelope/event-builder.ts index 8644f13e5..80f15832a 100644 --- a/packages/cli/src/lib/envelope/event-builder.ts +++ b/packages/cli/src/lib/envelope/event-builder.ts @@ -48,7 +48,7 @@ export function parseKeyValue(pair: string): [string, string] { if (idx <= 0) { throw new ValidationError( `Expected KEY:VALUE format, got: ${JSON.stringify(pair)}`, - "tag/extra" + "tag/extra", ); } return [pair.slice(0, idx), pair.slice(idx + 1)]; @@ -58,7 +58,7 @@ export function parseKeyValue(pair: string): [string, string] { * Parse an array of KEY:VALUE strings into a plain object. */ function parseKeyValuePairs( - pairs: string[] | undefined + pairs: string[] | undefined, ): Record<string, string> { if (!pairs?.length) { return {}; @@ -109,7 +109,7 @@ function parseTimestamp(ts: string | undefined): number | undefined { } throw new ValidationError( `Invalid --timestamp value: '${ts}'. Use a Unix epoch number, ISO 8601, or RFC 2822 date.`, - "timestamp" + "timestamp", ); } @@ -129,7 +129,7 @@ const CATEGORY_RE = /^([^:]+):\s*(.*)$/; */ export async function parseBreadcrumbsFromLogfile( logfilePath: string, - withCategories: boolean + withCategories: boolean, ): Promise<Breadcrumb[]> { let content: string; let mtimeSeconds: number; @@ -144,7 +144,7 @@ export async function parseBreadcrumbsFromLogfile( } throw new ValidationError( `Cannot read logfile ${logfilePath}: ${(err as Error).message}`, - "logfile" + "logfile", ); } @@ -181,7 +181,7 @@ export async function parseBreadcrumbsFromLogfile( * serialized for posting to the ingest endpoint. */ export async function buildEventFromFlags( - flags: SendEventFlags + flags: SendEventFlags, ): Promise<Event> { const tags = parseKeyValuePairs(flags.tag); // environ goes first so explicit --extra environ:val overrides it @@ -214,7 +214,7 @@ export async function buildEventFromFlags( breadcrumbs: flags.logfile ? await parseBreadcrumbsFromLogfile( flags.logfile, - flags["with-categories"] ?? false + flags["with-categories"] ?? false, ) : undefined, }; diff --git a/packages/cli/src/lib/envelope/event-send-dsn.ts b/packages/cli/src/lib/envelope/event-send-dsn.ts index a04457b80..d5027fb87 100644 --- a/packages/cli/src/lib/envelope/event-send-dsn.ts +++ b/packages/cli/src/lib/envelope/event-send-dsn.ts @@ -72,7 +72,7 @@ export function peelEventSendTarget(files: readonly string[]): { */ export async function resolveEventSendDsn( cwd: string, - target: EventSendTarget | undefined + target: EventSendTarget | undefined, ): Promise<string> { if (target?.kind === "dsn") { return target.dsn.trim(); @@ -117,18 +117,18 @@ function looksLikeFileArgument(value: string): boolean { */ async function dsnFromProjectTarget( target: string, - cwd: string + cwd: string, ): Promise<string> { if (!getAuthConfig()) { throw new ConfigError( `Cannot resolve project '${target}' without a logged-in session. Pass the DSN as the first argument or run sentry auth login.`, - "sentry auth login" + "sentry auth login", ); } const { org, project } = await resolveProjectBoundFromArg( target, cwd, - "event send" + "event send", ); const keys = await getProjectKeys(org, project, { status: "active" }); const activeDsns = [ @@ -136,20 +136,20 @@ async function dsnFromProjectTarget( keys .filter((key) => key.isActive) .map((key) => key.dsn.public) - .filter(Boolean) + .filter(Boolean), ), ]; if (activeDsns.length > 1) { throw new ConfigError( `Project ${org}/${project} has multiple active DSNs. Pass the desired DSN as the first argument.`, - EVENT_SEND_DSN_HINT + EVENT_SEND_DSN_HINT, ); } const dsn = activeDsns[0]; if (!dsn) { throw new ConfigError( `No active DSN found for ${org}/${project}. Pass a DSN as the first argument.`, - EVENT_SEND_DSN_HINT + EVENT_SEND_DSN_HINT, ); } log.debug(`Using DSN from ${org}/${project} client keys`); diff --git a/packages/cli/src/lib/envelope/transport.ts b/packages/cli/src/lib/envelope/transport.ts index 4b5ce98ce..a1affec29 100644 --- a/packages/cli/src/lib/envelope/transport.ts +++ b/packages/cli/src/lib/envelope/transport.ts @@ -78,7 +78,7 @@ export function resolveDsn(flags: DsnFlags): string | undefined { */ export async function resolveIngestDsn( flags: DsnFlags, - cwd: string + cwd: string, ): Promise<string | undefined> { const explicit = resolveDsn(flags); if (explicit) { @@ -107,7 +107,7 @@ export async function resolveIngestDsn( */ export function requireDsn( flags: DsnFlags, - usageHint = "sentry bash-hook --dsn <your-dsn>" + usageHint = "sentry bash-hook --dsn <your-dsn>", ): string { const dsn = resolveDsn(flags); if (dsn) { @@ -115,7 +115,7 @@ export function requireDsn( } throw new ConfigError( "No DSN found. Provide one via --dsn <dsn> or set the SENTRY_DSN environment variable.", - usageHint + usageHint, ); } @@ -136,7 +136,7 @@ export async function readFileBytes(file: string): Promise<Uint8Array> { } throw new ValidationError( `Cannot read file ${file}: ${(err as Error).message}`, - "path" + "path", ); } } @@ -149,7 +149,7 @@ export async function readFileBytes(file: string): Promise<Uint8Array> { */ export async function sendEnvelopeRequest( dsn: string, - body: string | Uint8Array + body: string | Uint8Array, ): Promise<void> { const url = buildEnvelopeUrl(dsn); @@ -158,7 +158,7 @@ export async function sendEnvelopeRequest( method: "POST", headers: { "Content-Type": "application/x-sentry-envelope" }, body, - }) + }), ); if (!response.ok) { diff --git a/packages/cli/src/lib/error-reporting.ts b/packages/cli/src/lib/error-reporting.ts index 3087e1815..9e7bf3ad4 100644 --- a/packages/cli/src/lib/error-reporting.ts +++ b/packages/cli/src/lib/error-reporting.ts @@ -25,7 +25,7 @@ * project's fingerprint rules for the active grouping policy. */ -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import { redactCredentialText } from "./credential-redaction.js"; import { @@ -165,7 +165,7 @@ function recordSilencedError(error: unknown, reason: SilenceReason): void { attributes.seer_reason = error.reason; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { Sentry.metrics.distribution("cli.error.silenced", 1, { attributes }); } catch { @@ -175,7 +175,7 @@ function recordSilencedError(error: unknown, reason: SilenceReason): void { // Structured log for user API errors — `detail` is often the most actionable // field and is searchable in Sentry Logs. if (reason === "api_user_error" && error instanceof ApiError) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { Sentry.logger.info("cli.api_error_silenced", { status: error.status, @@ -251,7 +251,7 @@ export function extractResourceKind(resource: string): string { // and headline values don't start with entity names. .replace( /\b(Organization|Dashboard|Dashboards|Project|Team)\s+[\w][\w-]*-[\w-]*/gi, - "$1" + "$1", ) .replace(/\b\d+\b/g, "") .replace(/\s+/g, " ") @@ -437,7 +437,7 @@ export function reportCliError(error: unknown): void { * `reportCliError` already have them). */ export function enrichEventWithGroupingTags( - event: Sentry.ErrorEvent + event: Sentry.ErrorEvent, ): Sentry.ErrorEvent { // Skip if reportCliError already set the tags via withScope. if (event.tags?.["cli_error.class"]) { diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts index 65a6e8e26..34d680fd2 100644 --- a/packages/cli/src/lib/errors.ts +++ b/packages/cli/src/lib/errors.ts @@ -141,7 +141,7 @@ export class HostScopeError extends CliError { constructor( sourceOrMessage: string, destinationUrl?: string, - tokenHost?: string | undefined + tokenHost?: string | undefined, ) { if (destinationUrl === undefined) { super(sourceOrMessage, EXIT.AUTH_HOST_SCOPE); @@ -150,7 +150,7 @@ export class HostScopeError extends CliError { `${sourceOrMessage}: ${destinationUrl}\n` + "Refusing to route requests to this host because no Sentry credentials are configured for it.\n" + `To use this host, run: sentry auth login --url ${destinationUrl}`, - EXIT.AUTH_HOST_SCOPE + EXIT.AUTH_HOST_SCOPE, ); } else { super( @@ -158,7 +158,7 @@ export class HostScopeError extends CliError { `Refusing to route requests here because it doesn't match the host your Sentry credentials are for (${tokenHost}).\n` + `To use this host, run: sentry auth login --url ${destinationUrl}\n` + "To keep using your current credentials, remove this URL override.", - EXIT.AUTH_HOST_SCOPE + EXIT.AUTH_HOST_SCOPE, ); } this.name = "HostScopeError"; @@ -185,13 +185,12 @@ export class ApiError extends CliError { */ readonly enriched403: boolean; - // biome-ignore lint/nursery/useMaxParams: established 4-param shape; enriched403 is a defaulted extension constructor( message: string, status: number, detail?: string, endpoint?: string, - enriched403 = false + enriched403 = false, ) { super(message, EXIT.API); this.name = "ApiError"; @@ -236,7 +235,7 @@ export class AuthError extends CliError { constructor( reason: AuthErrorReason, message?: string, - options?: AuthErrorOptions + options?: AuthErrorOptions, ) { const defaultMessages: Record<AuthErrorReason, string> = { not_authenticated: "Not authenticated. Run 'sentry auth login' first.", @@ -262,7 +261,7 @@ export class MalformedAuthTokenError extends AuthError { super( "invalid", "Invalid authentication token. Copy it again as a single line without spaces or control characters, " + - "or run 'sentry auth login' to replace stored credentials." + "or run 'sentry auth login' to replace stored credentials.", ); this.name = "MalformedAuthTokenError"; } @@ -337,7 +336,7 @@ function buildContextMessage( resource: string, command: string, alternatives: string[], - options?: { note?: string; isAutoDetect?: boolean } + options?: { note?: string; isAutoDetect?: boolean }, ): string { const { note, isAutoDetect } = options ?? {}; // Compound resources ("X and Y") need plural grammar @@ -383,7 +382,7 @@ function buildResolutionMessage( resource: string, headline: string, hint: string, - suggestions: string[] + suggestions: string[], ): string { const lines = [`${resource} ${headline}.`, "", "Try:", ` ${hint}`]; if (suggestions.length > 0) { @@ -427,7 +426,7 @@ export class ContextError extends CliError { resource: string, command: string, alternatives?: string[], - note?: string + note?: string, ) { // When alternatives is omitted, auto-detection was tried and failed const isAutoDetect = alternatives === undefined; @@ -441,7 +440,7 @@ export class ContextError extends CliError { note, isAutoDetect, }), - EXIT.CONTEXT_MISSING + EXIT.CONTEXT_MISSING, ); this.name = "ContextError"; this.resource = resource; @@ -454,7 +453,7 @@ export class ContextError extends CliError { if (command.includes("\n")) { throw new Error( "ContextError command must be a single-line CLI usage hint. " + - `Use ResolutionError for resolution failures. Got: "${command.slice(0, 80)}..."` + `Use ResolutionError for resolution failures. Got: "${command.slice(0, 80)}..."`, ); } } @@ -498,11 +497,11 @@ export class ResolutionError extends CliError { resource: string, headline: string, hint: string, - suggestions: string[] = [] + suggestions: string[] = [], ) { super( buildResolutionMessage(resource, headline, hint, suggestions), - EXIT.RESOLUTION + EXIT.RESOLUTION, ); this.name = "ResolutionError"; this.resource = resource; @@ -531,7 +530,7 @@ export class ResolutionError extends CliError { export function buildValidationMessage( headline: string, examples: string[], - note?: string + note?: string, ): string { const lines = [headline]; if (examples.length > 0) { @@ -554,11 +553,11 @@ export function validationError( headline: string, examples: string[], field?: string, - note?: string + note?: string, ): ValidationError { return new ValidationError( buildValidationMessage(headline, examples, note), - field + field, ); } @@ -740,7 +739,7 @@ export class WizardError extends CliError { constructor( message: string, - options?: { rendered?: boolean; exitCode?: number } + options?: { rendered?: boolean; exitCode?: number }, ) { super(message, options?.exitCode ?? EXIT.WIZARD); this.name = "WizardError"; @@ -785,7 +784,7 @@ export function stringifyUnknown(value: unknown): string { if (value && typeof value === "object") { // JSON.stringify can throw on circular references or BigInt values. // Fall back to String() which is always safe. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.stringify(value); } catch { @@ -804,7 +803,7 @@ export function stringifyUnknown(value: unknown): string { */ export function formatError(error: unknown): string { return redactCredentialText( - error instanceof CliError ? error.format() : stringifyUnknown(error) + error instanceof CliError ? error.format() : stringifyUnknown(error), ); } @@ -884,7 +883,7 @@ export const SEARCH_QUERY_HELP = export function toSearchQueryError( error: unknown, userQuery: string | undefined, - extraSuggestions: readonly string[] = [] + extraSuggestions: readonly string[] = [], ): unknown { if ( userQuery && @@ -1011,7 +1010,7 @@ export type AuthGuardResult<T> = AuthGuardSuccess<T> | AuthGuardFailure; * @throws {HostScopeError} Always re-thrown so host-scoping rejections surface to the user */ export async function withAuthGuard<T>( - fn: () => Promise<T> + fn: () => Promise<T>, ): Promise<AuthGuardResult<T>> { try { return { ok: true, value: await fn() }; diff --git a/packages/cli/src/lib/event-attachments.ts b/packages/cli/src/lib/event-attachments.ts index b046772fc..4580ddacb 100644 --- a/packages/cli/src/lib/event-attachments.ts +++ b/packages/cli/src/lib/event-attachments.ts @@ -70,7 +70,7 @@ function trimTrailingSlashes(value: string): string { function attachmentDownloadUrl( target: AttachmentTarget, - attachmentId: string + attachmentId: string, ): string { const origin = trimTrailingSlashes(target.apiBase); const path = [ @@ -110,7 +110,7 @@ async function resolveAttachmentApiBase(org: string): Promise<string> { export async function loadEventAttachments( org: string, project: string | undefined, - eventId: string + eventId: string, ): Promise<EventViewAttachment[]> { if (!project) { return []; @@ -152,13 +152,13 @@ function formatAttachment(attachment: EventAttachmentDetailsResponse): string { * Human-readable attachments section, or empty string when there are none. */ export function formatEventAttachments( - attachments: EventAttachmentDetailsResponse[] + attachments: EventAttachmentDetailsResponse[], ): string { if (attachments.length === 0) { return ""; } return renderMarkdown( - ["### Attachments", "", ...attachments.map(formatAttachment)].join("\n") + ["### Attachments", "", ...attachments.map(formatAttachment)].join("\n"), ); } @@ -166,7 +166,7 @@ export function formatEventAttachments( * Footer hint with a copy-pasteable download command for the first attachment. */ export function attachmentDownloadHint( - attachments: EventViewAttachment[] + attachments: EventViewAttachment[], ): string | undefined { const [first] = attachments; if (!first) { diff --git a/packages/cli/src/lib/formatters/chart-core.ts b/packages/cli/src/lib/formatters/chart-core.ts index 307233dab..7d1fe7d83 100644 --- a/packages/cli/src/lib/formatters/chart-core.ts +++ b/packages/cli/src/lib/formatters/chart-core.ts @@ -106,7 +106,7 @@ export type ChartModel = { /** Build a resolution-independent chart model from a timeseries result. */ export function buildChartModel( - data: TimeseriesResult + data: TimeseriesResult, ): ChartModel | undefined { if ( data.series.length === 0 || @@ -131,7 +131,7 @@ export function buildChartModel( /** Build a bar-per-category model from a categorical timeseries result. */ export function buildCategoricalChartModel( - data: TimeseriesResult + data: TimeseriesResult, ): ChartModel | undefined { const series = data.series .map((item) => ({ @@ -167,7 +167,7 @@ export function buildCategoricalChartModel( /** Build a heatmap grid model from grouped timeseries data. */ export function buildHeatmapModel( - data: TimeseriesResult + data: TimeseriesResult, ): HeatmapModel | undefined { if (data.series.length === 0) { return; @@ -181,7 +181,8 @@ export function buildHeatmapModel( const vals = s.values.map((v) => v.value); // pad shorter rows so every row has exactly `buckets` columns return vals.length < buckets - ? [...vals, ...new Array(buckets - vals.length).fill(0)] + ? // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. + [...vals, ...new Array(buckets - vals.length).fill(0)] : vals; }); const maxVal = Math.max(1, ...rows.flat()); @@ -190,6 +191,7 @@ export function buildHeatmapModel( /** Sum each bucket across every series. */ function bucketTotals(series: ChartSeries[], buckets: number): number[] { + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const totals = new Array<number>(buckets).fill(0); for (const s of series) { for (let i = 0; i < buckets; i++) { @@ -225,7 +227,7 @@ export type RasterizeOpts = { */ export function rasterizeChart( model: ChartModel, - opts: RasterizeOpts + opts: RasterizeOpts, ): DecodedImage | undefined { const width = Math.max(16, Math.floor(opts.width)); const height = Math.max(8, Math.floor(opts.height)); @@ -293,7 +295,7 @@ function drawBars( img: DecodedImage, model: ChartModel, height: number, - layout: BarLayout + layout: BarLayout, ): void { const series = model.series[0]; if (!series) { @@ -320,7 +322,7 @@ function drawStackedColumns( img: DecodedImage, model: ChartModel, height: number, - layout: BarLayout + layout: BarLayout, ): void { for (let b = 0; b < model.buckets; b++) { const x0 = b * (layout.barWidth + layout.gap); @@ -338,7 +340,7 @@ function drawStackedColumns( const segmentHeight = Math.min( yBottom, - Math.max(1, Math.round((value / model.maxVal) * height)) + Math.max(1, Math.round((value / model.maxVal) * height)), ); const yTop = Math.max(0, yBottom - segmentHeight); drawPixelRect(img, { @@ -358,7 +360,7 @@ function drawCategoricalBars( image: DecodedImage, model: ChartModel, height: number, - layout: BarLayout + layout: BarLayout, ): void { for (let index = 0; index < model.series.length; index += 1) { const series = model.series[index]; @@ -368,7 +370,7 @@ function drawCategoricalBars( const value = series.values[0] ?? 0; const barHeight = Math.min( height, - Math.max(0, Math.round((value / model.maxVal) * height)) + Math.max(0, Math.round((value / model.maxVal) * height)), ); drawPixelRect(image, { x: index * (layout.barWidth + layout.gap), @@ -396,7 +398,7 @@ const HEATMAP_CELL_COLORS: [number, number, number][] = [ /** Rasterize a heatmap grid into a pixel canvas (one colored cell per bucket). */ export function rasterizeHeatmap( model: HeatmapModel, - opts: RasterizeOpts + opts: RasterizeOpts, ): DecodedImage | undefined { if (model.buckets === 0 || model.rows.length === 0) { return; diff --git a/packages/cli/src/lib/formatters/conversation.ts b/packages/cli/src/lib/formatters/conversation.ts index de4ab64f4..1567c5229 100644 --- a/packages/cli/src/lib/formatters/conversation.ts +++ b/packages/cli/src/lib/formatters/conversation.ts @@ -193,7 +193,7 @@ function getOperationType(span: AgentConversationSpan): string | undefined { } function parseJson(value: string): unknown { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(value); } catch { @@ -310,13 +310,13 @@ function extractAssistantContent(span: AgentConversationSpan): string | null { } export function extractTurns( - spans: AgentConversationSpan[] + spans: AgentConversationSpan[], ): ConversationTurn[] { const sorted = [...spans].sort( - (a, b) => a["precise.start_ts"] - b["precise.start_ts"] + (a, b) => a["precise.start_ts"] - b["precise.start_ts"], ); const aiClientSpans = sorted.filter( - (s) => getOperationType(s) === "ai_client" + (s) => getOperationType(s) === "ai_client", ); const toolSpans = sorted.filter((s) => getOperationType(s) === "tool"); @@ -335,7 +335,7 @@ export function extractTurns( spanId: ts.span_id, timestamp: ts["precise.start_ts"], durationMs: Math.round( - (ts["precise.finish_ts"] - ts["precise.start_ts"]) * 1000 + (ts["precise.finish_ts"] - ts["precise.start_ts"]) * 1000, ), status: ts["span.status"], })); @@ -347,7 +347,7 @@ export function extractTurns( started: span["precise.start_ts"], ended: span["precise.finish_ts"], durationMs: Math.round( - (span["precise.finish_ts"] - span["precise.start_ts"]) * 1000 + (span["precise.finish_ts"] - span["precise.start_ts"]) * 1000, ), userContent: extractUserContent(span), assistantContent: extractAssistantContent(span), @@ -381,7 +381,7 @@ function formatEpoch(ts: number): string { function appendContentBlock( lines: string[], label: string, - content: string + content: string, ): void { lines.push(`**${label}**`); lines.push(""); @@ -425,7 +425,7 @@ function formatTurnHuman(turn: ConversationTurn): string { ? ` (${escapeMarkdownInline(tc.status)})` : ""; lines.push( - `- ${safeCodeSpan(tc.name)} — ${formatDuration(tc.durationMs)}${status}` + `- ${safeCodeSpan(tc.name)} — ${formatDuration(tc.durationMs)}${status}`, ); } lines.push(""); @@ -483,8 +483,8 @@ export function formatTranscriptResult(result: TranscriptResult): string { lines.push( colorTag( "yellow", - "⚠ Transcript truncated — the conversation exceeds the pagination limit." - ) + "⚠ Transcript truncated — the conversation exceeds the pagination limit.", + ), ); } return renderMarkdown(lines.join("\n")); @@ -494,7 +494,7 @@ export function buildTranscriptResult( conversationId: string, org: string, spans: AgentConversationSpan[], - title: string | null = null + title: string | null = null, ): TranscriptResult { const turns = extractTurns(spans); return { @@ -511,14 +511,14 @@ export function buildTranscriptResult( spans.length > 0 ? spans.reduce( (min, s) => Math.min(min, s["precise.start_ts"]), - Number.POSITIVE_INFINITY + Number.POSITIVE_INFINITY, ) : 0, endTimestamp: spans.length > 0 ? spans.reduce( (max, s) => Math.max(max, s["precise.finish_ts"]), - Number.NEGATIVE_INFINITY + Number.NEGATIVE_INFINITY, ) : 0, }; diff --git a/packages/cli/src/lib/formatters/dashboard.ts b/packages/cli/src/lib/formatters/dashboard.ts index ba4ce303f..ba14fecd5 100644 --- a/packages/cli/src/lib/formatters/dashboard.ts +++ b/packages/cli/src/lib/formatters/dashboard.ts @@ -304,7 +304,7 @@ const DIGIT_FONT_LG: Record<string, string[]> = { /** Build glyph row arrays from a formatted string and font. */ function buildGlyphRows( formatted: string, - font: Record<string, string[]> + font: Record<string, string[]>, ): string[][] { const sampleGlyph = font["0"]; const numRows = sampleGlyph?.length ?? 3; @@ -453,7 +453,7 @@ function buildTopBorder(title: string, width: number): string { /** Build the bottom border line: `╰──────────╯` */ function buildBottomBorder(width: number): string { return borderColor( - `${BORDER.bl}${BORDER.h.repeat(Math.max(0, width - 2))}${BORDER.br}` + `${BORDER.bl}${BORDER.h.repeat(Math.max(0, width - 2))}${BORDER.br}`, ); } @@ -508,7 +508,7 @@ function noDataLine(): string { */ function renderTimeseriesContent( data: TimeseriesResult, - innerWidth: number + innerWidth: number, ): string[] { if (data.series.length === 0) { return [noDataLine()]; @@ -533,7 +533,7 @@ function renderTimeseriesContent( lines.push(`${label} ${graph} ${formatted}`); } else { lines.push( - `${chalk.hex(COLORS.cyan)(label)} ${chalk.hex(COLORS.magenta)(graph)} ${chalk.bold(formatted)}` + `${chalk.hex(COLORS.cyan)(label)} ${chalk.hex(COLORS.magenta)(graph)} ${chalk.bold(formatted)}`, ); } } @@ -625,7 +625,7 @@ function buildYAxisSegment(opts: { */ function renderVerticalBarsContent( data: TimeseriesResult, - opts: { innerWidth: number; contentHeight: number } + opts: { innerWidth: number; contentHeight: number }, ): string[] { const { innerWidth, contentHeight } = opts; if (data.series.length === 0) { @@ -645,7 +645,7 @@ function renderVerticalBarsContent( const maxVal = Math.max( ...nonOther.map((i) => i.total), ...(nonOther.length === 0 ? items.map((i) => i.total) : []), - 1 + 1, ); // Y-axis gutter const gutterW = yAxisGutterWidth(maxVal); @@ -655,7 +655,7 @@ function renderVerticalBarsContent( const numItems = items.length; const barWidth = Math.max( 1, - Math.floor((chartWidth - (numItems - 1) * gap) / numItems) + Math.floor((chartWidth - (numItems - 1) * gap) / numItems), ); // Check if non-Other labels fit directly below bars @@ -722,7 +722,7 @@ function renderVBarRow( barWidth: number; gap: number; maxBarHeight: number; - } + }, ): string { const plain = isPlainOutput(); const parts: string[] = []; @@ -732,7 +732,7 @@ function renderVBarRow( continue; } const barHeight = Math.round( - (item.total / opts.maxVal) * opts.maxBarHeight + (item.total / opts.maxVal) * opts.maxBarHeight, ); if (barHeight >= opts.row) { const fill = plain ? seriesFill(item.label, i) : "█"; @@ -755,7 +755,7 @@ function renderVBarRow( */ function buildColorLegend( entries: { label: string; index: number }[], - maxWidth: number + maxWidth: number, ): string { const plain = isPlainOutput(); const parts: string[] = []; @@ -786,7 +786,7 @@ const TABLE_COL_GAP = 2; /** Measure the max visual width of each column across headers and rows. */ function measureTableColWidths( headers: string[], - cellRows: string[][] + cellRows: string[][], ): number[] { return headers.map((h, i) => { let maxW = stringWidth(h); @@ -872,7 +872,7 @@ function renderTableContent(data: TableResult, innerWidth: number): string[] { return formatWithUnit(val, col.unit); } return String(val); - }) + }), ); // Detect right-aligned (numeric) columns @@ -882,7 +882,7 @@ function renderTableContent(data: TableResult, innerWidth: number): string[] { cellRows.every((_, ri) => { const v = cellRows[ri]?.[ci] ?? ""; return v === "" || typeof data.rows[ri]?.[col.name] === "number"; - }) + }), ); const colWidths = measureTableColWidths(headers, cellRows); @@ -925,9 +925,9 @@ function renderTableContent(data: TableResult, innerWidth: number): string[] { const dataLines = cellRows.map((row) => row .map((cell, i) => - fitTableCell(cell, colWidths[i] ?? 3, rightAlign[i] ?? false) + fitTableCell(cell, colWidths[i] ?? 3, rightAlign[i] ?? false), ) - .join(gap) + .join(gap), ); return [ @@ -958,7 +958,7 @@ function centerVertically(lines: string[], contentHeight: number): string[] { */ function renderBigNumberContent( data: ScalarResult, - opts: { innerWidth: number; contentHeight: number } + opts: { innerWidth: number; contentHeight: number }, ): string[] { const { innerWidth, contentHeight } = opts; const formatted = formatBigNumberValue(data.value); @@ -987,7 +987,7 @@ function renderBigNumberContent( function tryBigNumberFonts( formatted: string, innerWidth: number, - contentHeight: number + contentHeight: number, ): string[] | undefined { // Font tiers: [font, glyphWidth, minContentHeight] const tiers: [Record<string, string[]>, number, number][] = [ @@ -1014,7 +1014,7 @@ function tryBigNumberFonts( */ function renderTimeseriesBarsContent( data: TimeseriesResult, - opts: { innerWidth: number; contentHeight: number } + opts: { innerWidth: number; contentHeight: number }, ): string[] { const { innerWidth, contentHeight } = opts; if (data.series.length === 0) { @@ -1056,7 +1056,7 @@ function renderTimeseriesBarsContent( label: s.label, values: downsample( s.values.map((v) => v.value), - maxBars + maxBars, ), })); lines.push( @@ -1065,7 +1065,7 @@ function renderTimeseriesBarsContent( barHeight, gutterWidth: gutterW, chartWidth, - }) + }), ); } else { const sampled = downsample(values, maxBars); @@ -1075,7 +1075,7 @@ function renderTimeseriesBarsContent( barHeight, gutterWidth: gutterW, chartWidth, - }) + }), ); } @@ -1085,7 +1085,7 @@ function renderTimeseriesBarsContent( timestamps: sampledTs, chartWidth, gutterWidth: gutterW, - }) + }), ); // Color legend for multi-series (below time labels) @@ -1115,7 +1115,7 @@ type AggregatedTimeseries = { }; function aggregateTimeseriesValues( - data: TimeseriesResult + data: TimeseriesResult, ): AggregatedTimeseries { const first = data.series[0]; if (!first || first.values.length === 0) { @@ -1139,6 +1139,7 @@ function aggregateTimeseriesValues( // Multiple series — sum per time bucket const bucketCount = first.values.length; + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const summed = new Array<number>(bucketCount).fill(0); for (const s of data.series) { for (let i = 0; i < s.values.length; i += 1) { @@ -1174,7 +1175,7 @@ const FRAC_BLOCKS = [" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" */ function buildTimeseriesBarColumn( fractionalHeight: number, - row: number + row: number, ): string { if (fractionalHeight >= row) { return "█"; @@ -1196,7 +1197,7 @@ function renderTimeBarRows( barHeight: number; gutterWidth: number; chartWidth: number; - } + }, ): string[] { const { maxVal, barHeight, gutterWidth, chartWidth } = opts; const plain = isPlainOutput(); @@ -1210,7 +1211,7 @@ function renderTimeBarRows( // so the total rendered width exactly equals chartWidth. const barWidth = Math.max( 1, - Math.floor(chartWidth / Math.max(1, sampled.length)) + Math.floor(chartWidth / Math.max(1, sampled.length)), ); const barRemainder = chartWidth - barWidth * sampled.length; @@ -1278,7 +1279,7 @@ function renderStackedTimeBarRows( barHeight: number; gutterWidth: number; chartWidth: number; - } + }, ): string[] { const { maxVal, barHeight, gutterWidth, chartWidth } = opts; const plain = isPlainOutput(); @@ -1288,7 +1289,7 @@ function renderStackedTimeBarRows( // Distribute the floor-division remainder across the first N bars. const barWidth = Math.max( 1, - Math.floor(chartWidth / Math.max(1, numBuckets)) + Math.floor(chartWidth / Math.max(1, numBuckets)), ); const barRemainder = chartWidth - barWidth * numBuckets; @@ -1346,7 +1347,7 @@ function buildStackedColumn( segments: { bottom: number; top: number; label: string; seriesIdx: number }[], row: number, plain: boolean, - barWidth: number + barWidth: number, ): string { // Walk segments top-down to find which series fills this row for (let s = segments.length - 1; s >= 0; s -= 1) { @@ -1369,7 +1370,7 @@ function buildStackedColumn( return fill.repeat(barWidth); } return chalk.hex(seriesColor(seg.label, seg.seriesIdx))( - ch.repeat(barWidth) + ch.repeat(barWidth), ); } return " ".repeat(barWidth); @@ -1381,7 +1382,7 @@ function buildStackedColumn( */ function downsampleTimestamps( timestamps: number[], - targetLen: number + targetLen: number, ): number[] { if (timestamps.length <= targetLen) { return timestamps; @@ -1442,13 +1443,14 @@ function formatTimestamp(ts: number, spanDays: number): string { /** Place labels onto a character array, centered on their positions. */ function placeLabelsOnLine( labels: { pos: number; text: string }[], - width: number + width: number, ): string { + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const line = new Array(width).fill(" "); for (const { pos, text } of labels) { const start = Math.min( width - text.length, - Math.max(0, pos - Math.floor(text.length / 2)) + Math.max(0, pos - Math.floor(text.length / 2)), ); for (let c = 0; c < text.length; c += 1) { const target = start + c; @@ -1501,6 +1503,7 @@ function buildTimeAxis(opts: { // Build axis line with ┬ at tick positions const tickPositions = new Set(labels.map((l) => l.pos)); + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const axisChars = new Array(chartWidth).fill("─"); for (const pos of tickPositions) { if (pos >= 0 && pos < chartWidth) { @@ -1586,7 +1589,7 @@ function heatmapCell(normalized: number, plain: boolean): string { */ function renderHeatmapContent( data: TimeseriesResult, - opts: { innerWidth: number; contentHeight: number } + opts: { innerWidth: number; contentHeight: number }, ): string[] { const { innerWidth, contentHeight } = opts; if (data.series.length === 0) { @@ -1602,7 +1605,7 @@ function renderHeatmapContent( const maxLabelLen = Math.min( 20, - Math.max(4, ...series.map((s) => s.label.length)) + Math.max(4, ...series.map((s) => s.label.length)), ); const gutterW = maxLabelLen + 1; // label + space const chartWidth = Math.max(1, innerWidth - gutterW); @@ -1616,10 +1619,11 @@ function renderHeatmapContent( const rows = series.map((s) => { const ds = downsample( s.values.map((v) => v.value), - bucketCount + bucketCount, ); return ds.length < bucketCount - ? [...ds, ...new Array(bucketCount - ds.length).fill(0)] + ? // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. + [...ds, ...new Array(bucketCount - ds.length).fill(0)] : ds; }); const globalMax = Math.max(1, ...rows.flat()); @@ -1644,7 +1648,7 @@ function renderHeatmapContent( // Find the longest series so its timestamps match the bucketCount. const longest = series.reduce( (a, b) => (b.values.length > a.values.length ? b : a), - series[0] ?? { values: [] } + series[0] ?? { values: [] }, ); const axisTs = longest.values.map((v) => v.timestamp); if (axisTs.length > 0) { @@ -1654,7 +1658,7 @@ function renderHeatmapContent( timestamps: dsTs, chartWidth: bucketCount, gutterWidth: gutterW, - }) + }), ); } @@ -1730,7 +1734,7 @@ function renderContentLines(opts: { */ function renderWidgetLines( widget: DashboardViewWidget, - width: number + width: number, ): string[] { const layout = widget.layout; const totalHeight = layout ? layout.h * LINES_PER_UNIT : LINES_PER_UNIT; @@ -1778,7 +1782,7 @@ function isAsciiLetter(ch: string): boolean { function advanceEscape( state: { type: EscapeType }, ch: string, - buffer: string + buffer: string, ): boolean { return advanceEscapeInner(state, ch, buffer.at(-1)); } @@ -1786,7 +1790,7 @@ function advanceEscape( function advanceEscapeInner( state: { type: EscapeType }, ch: string, - prev: string | undefined + prev: string | undefined, ): boolean { const stTerminator = ch === "\\" && prev === "\x1b"; switch (state.type) { @@ -1865,7 +1869,7 @@ function composeTermRow( active: DashboardViewWidget[], termRow: number, termWidth: number, - rendered: Map<DashboardViewWidget, string[]> + rendered: Map<DashboardViewWidget, string[]>, ): string { let line = ""; let currentCol = 0; @@ -1904,7 +1908,7 @@ function composeTermRow( */ function renderGrid( widgets: DashboardViewWidget[], - termWidth: number + termWidth: number, ): string[] { let maxGridBottom = 0; for (const w of widgets) { @@ -2058,17 +2062,17 @@ function logDashboardGraphicsRenderer(render: DashboardGraphicsRender): void { details.push(`effective pixel width=${render.pixelWidth}`); } details.push( - `graphics cap=${render.graphicsCapApplied ? "applied" : "not applied"}` + `graphics cap=${render.graphicsCapApplied ? "applied" : "not applied"}`, ); const reason = render.reason ? ` (reason: ${render.reason})` : ""; logger.debug( - `Dashboard graphics renderer: ${render.renderer}${reason}; ${details.join("; ")}` + `Dashboard graphics renderer: ${render.renderer}${reason}; ${details.join("; ")}`, ); } function renderCompleteDashboardAsGraphics( data: DashboardViewData, - termWidth: number | undefined + termWidth: number | undefined, ): DashboardGraphicsRender { const rendererPreference = data.rendererPreference ?? "auto"; const format = selectGraphicsFormat(rendererPreference); diff --git a/packages/cli/src/lib/formatters/feedback.ts b/packages/cli/src/lib/formatters/feedback.ts index 132c761ea..887592d20 100644 --- a/packages/cli/src/lib/formatters/feedback.ts +++ b/packages/cli/src/lib/formatters/feedback.ts @@ -35,27 +35,27 @@ const COMPACT_LIST_BREAKPOINT = 100; const LINE_BREAK_RE = /\n/g; const C1_CSI_RE = /\u009b[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g; const FEEDBACK_UNSAFE_TERMINAL_RE = - // biome-ignore lint/suspicious/noControlCharactersInRegex: terminal sanitization requires matching control characters + // oxlint-disable-next-line no-control-regex -- terminal sanitization requires matching control characters /[\x00-\x09\x0b\x0c\x0e-\x1f\x7f-\x9f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g; /** Confirm resolution using the updated Feedback's terminal-safe identifier. */ export function formatResolvedFeedback(feedback: SentryIssue): string { return renderMarkdown( - `Resolved feedback ${feedbackCodeSpan(feedback.shortId)}.` + `Resolved feedback ${feedbackCodeSpan(feedback.shortId)}.`, ); } /** Confirm reopening using the updated Feedback's terminal-safe identifier. */ export function formatReopenedFeedback(feedback: SentryIssue): string { return renderMarkdown( - `Reopened feedback ${feedbackCodeSpan(feedback.shortId)}.` + `Reopened feedback ${feedbackCodeSpan(feedback.shortId)}.`, ); } /** Confirm spam using the updated Feedback's terminal-safe identifier. */ export function formatSpamFeedback(feedback: SentryIssue): string { return renderMarkdown( - `Marked feedback ${feedbackCodeSpan(feedback.shortId)} as spam.` + `Marked feedback ${feedbackCodeSpan(feedback.shortId)} as spam.`, ); } @@ -96,7 +96,7 @@ function sanitizeFeedbackEventValue(value: unknown): unknown { Object.entries(value).map(([key, nestedValue]) => [ sanitizeFeedbackInline(key), sanitizeFeedbackEventValue(nestedValue), - ]) + ]), ); } return value; @@ -125,7 +125,7 @@ function feedbackMessage(feedback: SentryFeedback): string { feedback.metadata.message || feedback.metadata.value || feedback.metadata.title || - feedback.title + feedback.title, ); } @@ -208,7 +208,7 @@ const FEEDBACK_COLUMNS: Column<SentryFeedback>[] = [ function formatCompactFeedbackList( result: FeedbackListResult, scope: string, - width: number + width: number, ): string { const sections = result.feedback.map((feedback) => { const id = feedback.permalink @@ -260,7 +260,7 @@ function isFeedbackContext(value: unknown): value is Record<string, unknown> { } function feedbackContext( - event: SentryEvent | null + event: SentryEvent | null, ): Record<string, unknown> | undefined { const context = event?.contexts?.feedback; return isFeedbackContext(context) ? context : undefined; @@ -308,7 +308,7 @@ function formatFeedbackOverview(data: FeedbackViewResult): string { rows.push([ "SDK", feedbackCodeSpan( - feedback.metadata.sdk.name_normalized ?? feedback.metadata.sdk.name + feedback.metadata.sdk.name_normalized ?? feedback.metadata.sdk.name, ), ]); } @@ -349,7 +349,7 @@ function formatFeedbackOverview(data: FeedbackViewResult): string { "", "### Summary", "", - escapeFeedbackInline(feedback.metadata.summary) + escapeFeedbackInline(feedback.metadata.summary), ); } @@ -373,7 +373,7 @@ function formatRelatedReplays(data: FeedbackViewResult): string { ? `sentry replay view ${data.org}/${replayId}` : `sentry replay view ${replayId}`; lines.push( - `- ${feedbackCodeSpan(replayId)} (${feedbackCodeSpan(command)})` + `- ${feedbackCodeSpan(replayId)} (${feedbackCodeSpan(command)})`, ); } const remaining = additionalIds.length - MAX_REPLAY_IDS_SHOWN; @@ -391,13 +391,13 @@ function formatAttachment(attachment: EventAttachmentDetailsResponse): string { } function formatAttachments( - attachments: EventAttachmentDetailsResponse[] + attachments: EventAttachmentDetailsResponse[], ): string { if (attachments.length === 0) { return ""; } return renderMarkdown( - ["### Attachments", "", ...attachments.map(formatAttachment)].join("\n") + ["### Attachments", "", ...attachments.map(formatAttachment)].join("\n"), ); } @@ -409,8 +409,8 @@ export function formatFeedbackView(data: FeedbackViewResult): string { formatEventDetails( sanitizeFeedbackEvent(data.event), "Latest Feedback Event", - data.feedback.permalink - ) + data.feedback.permalink, + ), ); } const replays = formatRelatedReplays(data); diff --git a/packages/cli/src/lib/formatters/human.ts b/packages/cli/src/lib/formatters/human.ts index 4fdf01acc..c6038128e 100644 --- a/packages/cli/src/lib/formatters/human.ts +++ b/packages/cli/src/lib/formatters/human.ts @@ -7,7 +7,7 @@ * performance, while list tables are rendered via writeTable() → renderMarkdown(). */ -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import prettyMs from "pretty-ms"; import type { @@ -130,7 +130,7 @@ export function formatFixability(score: number | null | undefined): string { * @returns Formatted string like `"Med (50%)"`, or `""` when score is unavailable */ export function formatFixabilityDetail( - score: number | null | undefined + score: number | null | undefined, ): string { if (score === null || score === undefined) { return ""; @@ -153,7 +153,7 @@ type EntryTypeMap = { */ function extractEntry<T extends keyof EntryTypeMap>( event: SentryEvent, - type: T + type: T, ): EntryTypeMap[T] | null { if (!event.entries) { return null; @@ -270,7 +270,7 @@ export type FormatShortIdOptions = { */ function formatShortIdWithAlias( shortId: string, - projectAlias: string + projectAlias: string, ): string | null { // Extract the project part of the alias — cross-org collision aliases use // the format "o1/d" where only "d" should match against the short ID parts. @@ -322,7 +322,7 @@ function formatShortIdWithAlias( */ export function formatShortId( shortId: string, - options?: FormatShortIdOptions | string + options?: FormatShortIdOptions | string, ): string { const opts: FormatShortIdOptions = typeof options === "string" ? { projectSlug: options } : (options ?? {}); @@ -451,7 +451,7 @@ export function substatusLabel(substatus?: string | null): string { * @returns Subtitle string, or empty string if no relevant metadata */ export function formatIssueSubtitle( - metadata?: SentryIssue["metadata"] + metadata?: SentryIssue["metadata"], ): string { if (!metadata) { return ""; @@ -502,7 +502,7 @@ export function extractStatsPoints(stats?: Record<string, unknown>): number[] { return []; } return buckets.map((b: unknown) => - Array.isArray(b) && b.length >= 2 ? Number(b[1]) || 0 : 0 + Array.isArray(b) && b.length >= 2 ? Number(b[1]) || 0 : 0, ); } @@ -528,7 +528,7 @@ export type IssueTableRow = { function formatIdCell( issue: SentryIssue, formatOptions: FormatShortIdOptions, - compact = false + compact = false, ): string { const formatted = formatShortId(issue.shortId, formatOptions); const linked = issue.permalink @@ -536,7 +536,7 @@ function formatIdCell( : formatted; const alias = computeAliasShorthand( issue.shortId, - formatOptions.projectAlias + formatOptions.projectAlias, ); if (alias) { const sep = compact ? " " : "\n"; @@ -579,7 +579,7 @@ function formatIssueCell(issue: SentryIssue, compact = false): string { */ function formatTrendCell(issue: SentryIssue, compact = false): string { const points = extractStatsPoints( - issue.stats as Record<string, unknown> | undefined + issue.stats as Record<string, unknown> | undefined, ); const graph = points.length > 0 ? colorTag("muted", sparkline(points)) : ""; const status = substatusLabel(issue.substatus); @@ -611,7 +611,7 @@ function formatTrendCell(issue: SentryIssue, compact = false): string { export function writeIssueTable( stdout: Writer, rows: IssueTableRow[], - options?: { compact?: boolean } + options?: { compact?: boolean }, ): void { const compact = options?.compact ?? false; const showTrend = willShowTrend(); @@ -668,7 +668,7 @@ export function writeIssueTable( header: "TRIAGE", value: ({ issue }) => formatTriageCell(issue.priority, issue.seerFixabilityScore), - } + }, ); // Row separators colored with the muted palette color (#898294 → RGB 137,130,148) @@ -707,7 +707,7 @@ const IMPACT_RATIO = 0.6; */ function computeTriageScore( priority?: string | null, - fixabilityScore?: number | null + fixabilityScore?: number | null, ): number | null { const hasPriority = Boolean(priority); const hasFix = fixabilityScore !== null && fixabilityScore !== undefined; @@ -744,7 +744,7 @@ function computeTriageScore( */ function formatTriageCell( priority?: string | null, - fixabilityScore?: number | null + fixabilityScore?: number | null, ): string { const hasFix = fixabilityScore !== null && fixabilityScore !== undefined; const label = formatPriorityLabel(priority); @@ -799,7 +799,7 @@ function formatPriorityLabel(priority?: string | null): string { * @param issue - The Sentry issue to format * @returns Rendered terminal string */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: issue formatting logic +// issue formatting logic export function formatIssueDetails(issue: SentryIssue): string { const lines: string[] = []; @@ -895,7 +895,7 @@ export function formatIssueDetails(issue: SentryIssue): string { lines.push("**Message:**"); lines.push(""); lines.push( - `> ${escapeMarkdownInline(issue.metadata.value).replace(/\n/g, "\n> ")}` + `> ${escapeMarkdownInline(issue.metadata.value).replace(/\n/g, "\n> ")}`, ); } @@ -987,7 +987,7 @@ function buildStackTraceMarkdown(exceptionEntry: ExceptionEntry): string { /** * Build the breadcrumbs section as a markdown table. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: breadcrumb formatting logic +// breadcrumb formatting logic function buildBreadcrumbsMarkdown(breadcrumbsEntry: BreadcrumbsEntry): string { const breadcrumbs = breadcrumbsEntry.data.values ?? []; if (breadcrumbs.length === 0) { @@ -1164,7 +1164,7 @@ export function formatSimpleSpanTree( traceId: string, spans: TraceSpan[], maxDepth = Number.MAX_SAFE_INTEGER, - options: SpanTreeOptions = {} + options: SpanTreeOptions = {}, ): string[] { return withSerializeSpan("formatSimpleSpanTree", () => { // maxDepth = 0 means disabled (caller should skip, but handle gracefully) @@ -1210,7 +1210,7 @@ export function formatSimpleSpanTree( if (truncated) { const remaining = totalRootSpans - MAX_ROOT_SPANS; lines.push( - `└─ ${plainSafeMuted(`... ${remaining} more root span${remaining === 1 ? "" : "s"} (${totalRootSpans} total). Use --json to see all.`)}` + `└─ ${plainSafeMuted(`... ${remaining} more root span${remaining === 1 ? "" : "s"} (${totalRootSpans} total). Use --json to see all.`)}`, ); } @@ -1223,7 +1223,7 @@ export function formatSimpleSpanTree( /** * Build environment context section (browser, OS, device) as markdown. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: context formatting logic +// context formatting logic function buildEnvironmentMarkdown(event: SentryEvent): string { const contexts = event.contexts; if (!contexts) { @@ -1263,7 +1263,7 @@ function buildEnvironmentMarkdown(event: SentryEvent): string { /** * Build user information section as markdown. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: user formatting logic +// user formatting logic function buildUserMarkdown(event: SentryEvent): string { const user = event.user; if (!user) { @@ -1325,7 +1325,7 @@ function buildUserMarkdown(event: SentryEvent): string { */ function buildReplayMarkdown( event: SentryEvent, - issuePermalink?: string + issuePermalink?: string, ): string { const replayId = getReplayIdFromEvent(event); if (!replayId) { @@ -1360,9 +1360,9 @@ function buildReplayMarkdown( export function formatEventDetails( event: SentryEvent, header = "Latest Event", - issuePermalink?: string + issuePermalink?: string, ): string { - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Event formatting requires multiple conditional sections + // Event formatting requires multiple conditional sections return withSerializeSpan("formatEventDetails", () => { const sections: string[] = []; @@ -1468,7 +1468,7 @@ export function formatEventDetails( mdRow([ `\`${tag.key}\``, escapeMarkdownCell(String(tag.value)), - ]).trimEnd() + ]).trimEnd(), ); } } @@ -1489,7 +1489,7 @@ export function formatOrgDetails(org: SentryOrganization): string { const lines: string[] = []; lines.push( - `## ${escapeMarkdownInline(org.slug)}: ${escapeMarkdownInline(org.name || "(unnamed)")}` + `## ${escapeMarkdownInline(org.slug)}: ${escapeMarkdownInline(org.name || "(unnamed)")}`, ); lines.push(""); @@ -1529,12 +1529,12 @@ export function formatOrgDetails(org: SentryOrganization): string { */ export function formatProjectDetails( project: SentryProject, - dsn?: string | null + dsn?: string | null, ): string { const lines: string[] = []; lines.push( - `## ${escapeMarkdownInline(project.slug)}: ${escapeMarkdownInline(project.name || "(unnamed)")}` + `## ${escapeMarkdownInline(project.slug)}: ${escapeMarkdownInline(project.name || "(unnamed)")}`, ); lines.push(""); @@ -1551,7 +1551,7 @@ export function formatProjectDetails( if (project.organization) { const orgName = resolveOrgDisplayName( project.organization.slug, - project.organization.name + project.organization.name, ); kvRows.push([ "Organization", @@ -1674,7 +1674,7 @@ function durationPair( major: number, majorUnit: string, minor: number, - minorUnit: string + minorUnit: string, ): string { if (minor === 0) { return pluralUnit(major, majorUnit); @@ -1716,7 +1716,7 @@ export function formatExpiration(expiresAt: number): string { } const secondsRemaining = Math.round( - (expiresDate.getTime() - now.getTime()) / 1000 + (expiresDate.getTime() - now.getTime()) / 1000, ); return `${expiresDate.toLocaleString()} (${formatDuration(secondsRemaining)} remaining)`; } @@ -1735,11 +1735,11 @@ type FeedbackResult = import("../../commands/cli/feedback.js").FeedbackResult; export function formatFeedbackResult(data: FeedbackResult): string { if (data.sent) { return renderMarkdown( - `${colorTag("green", "✓")} Feedback submitted. Thank you!` + `${colorTag("green", "✓")} Feedback submitted. Thank you!`, ); } return renderMarkdown( - `${colorTag("yellow", "⚠")} Feedback may not have been sent (network timeout).` + `${colorTag("yellow", "⚠")} Feedback may not have been sent (network timeout).`, ); } @@ -1834,7 +1834,7 @@ function buildAuthDetailRows(data: AuthStatusData): [string, string][] { * Returns empty string when no defaults are set. */ function formatDefaultsSection( - defaults: NonNullable<AuthStatusData["defaults"]> + defaults: NonNullable<AuthStatusData["defaults"]>, ): string { const rows: [string, string][] = []; if (defaults.organization) { @@ -1856,20 +1856,20 @@ const MAX_VERIFY_DISPLAY = 5; * Build the credential verification section markdown. */ function formatVerificationSection( - verification: NonNullable<AuthStatusData["verification"]> + verification: NonNullable<AuthStatusData["verification"]>, ): string { const lines: string[] = [""]; if (verification.success) { const orgs = verification.organizations ?? []; lines.push( - `### ${colorTag("green", "✓")} Access verified — ${orgs.length} organization(s)` + `### ${colorTag("green", "✓")} Access verified — ${orgs.length} organization(s)`, ); if (orgs.length > 0) { lines.push(""); for (const org of orgs.slice(0, MAX_VERIFY_DISPLAY)) { lines.push( - `- ${escapeMarkdownInline(org.name)} (${safeCodeSpan(org.slug)})` + `- ${escapeMarkdownInline(org.name)} (${safeCodeSpan(org.slug)})`, ); } if (orgs.length > MAX_VERIFY_DISPLAY) { @@ -1929,7 +1929,7 @@ export function formatAuthStatus(data: AuthStatusData): string { * have been marked insufficient. */ function formatEnvTokenSection( - envToken: NonNullable<AuthStatusData["envToken"]> + envToken: NonNullable<AuthStatusData["envToken"]>, ): string { const status = envToken.active ? "active" @@ -1993,7 +1993,7 @@ export function formatProjectCreated(result: ProjectCreatedResult): string { // Slug divergence note (never applies in dry-run — we can't predict server renames) if (result.slugDiverged) { lines.push( - `> **Note:** Slug \`${result.project.slug}\` was assigned because \`${result.expectedSlug}\` is already taken.` + `> **Note:** Slug \`${result.project.slug}\` was assigned because \`${result.expectedSlug}\` is already taken.`, ); lines.push(""); } @@ -2003,14 +2003,14 @@ export function formatProjectCreated(result: ProjectCreatedResult): string { lines.push( dry ? `> **Note:** Would create team '${escapeMarkdownInline(result.teamSlug)}' (org has no teams).` - : `> **Note:** Created team '${escapeMarkdownInline(result.teamSlug)}' (org had no teams).` + : `> **Note:** Created team '${escapeMarkdownInline(result.teamSlug)}' (org had no teams).`, ); lines.push(""); } else if (result.teamSource === "auto-selected") { lines.push( dry ? `> **Note:** Would use team '${escapeMarkdownInline(result.teamSlug)}'. See all teams: \`sentry team list\`` - : `> **Note:** Using team '${escapeMarkdownInline(result.teamSlug)}'. See all teams: \`sentry team list\`` + : `> **Note:** Using team '${escapeMarkdownInline(result.teamSlug)}'. See all teams: \`sentry team list\``, ); lines.push(""); } @@ -2035,7 +2035,7 @@ export function formatProjectCreated(result: ProjectCreatedResult): string { if (!dry) { lines.push(""); lines.push( - `*Tip: Use \`sentry project view ${result.orgSlug}/${result.project.slug}\` for details*` + `*Tip: Use \`sentry project view ${result.orgSlug}/${result.project.slug}\` for details*`, ); } @@ -2085,12 +2085,12 @@ export function formatProjectDeleted(result: ProjectDeleteResult): string { if (result.dryRun) { return renderMarkdown( `Would delete project '${nameEsc}' (${safeCodeSpan(qualifiedSlug)}).\n\n` + - `URL: ${result.url}` + `URL: ${result.url}`, ); } return renderMarkdown( - `Deleted project '${nameEsc}' (${safeCodeSpan(qualifiedSlug)}).` + `Deleted project '${nameEsc}' (${safeCodeSpan(qualifiedSlug)}).`, ); } @@ -2167,7 +2167,7 @@ export function formatFixResult(data: FixResult): string { // Category sections const ownershipIssues = data.issues.filter((i) => i.category === "ownership"); const permissionIssues = data.issues.filter( - (i) => i.category === "permission" + (i) => i.category === "permission", ); const schemaIssues = data.issues.filter((i) => i.category === "schema"); @@ -2200,7 +2200,7 @@ export function formatFixResult(data: FixResult): string { lines.push(""); if (data.issues.length === 0 && !data.repairFailed) { lines.push( - `${colorTag("green", "✓")} No issues found. Database schema and permissions are correct.` + `${colorTag("green", "✓")} No issues found. Database schema and permissions are correct.`, ); } else if (data.dryRun && data.issues.length > 0 && !data.repairFailed) { lines.push("Run `sentry cli fix` to apply fixes."); @@ -2257,7 +2257,8 @@ function getMaxChangelogLines(): number { } return Math.max( MIN_CHANGELOG_LINES, - Math.floor(termHeight * CHANGELOG_HEIGHT_FACTOR) - CHANGELOG_HEADER_OVERHEAD + Math.floor(termHeight * CHANGELOG_HEIGHT_FACTOR) - + CHANGELOG_HEADER_OVERHEAD, ); } @@ -2287,7 +2288,7 @@ function formatChangelog(data: UpgradeResult): string { if (changelog.truncated) { const more = changelog.originalCount - changelog.totalItems; lines.push( - `<muted>...and ${more} more changes — https://github.com/getsentry/cli/releases</muted>` + `<muted>...and ${more} more changes — https://github.com/getsentry/cli/releases</muted>`, ); } @@ -2339,32 +2340,32 @@ export function formatUpgradeResult(data: UpgradeResult): string { const verb = ACTION_DESCRIPTIONS[data.action]; if (data.offline) { lines.push( - `${colorTag("green", "✓")} ${verb} to ${safeCodeSpan(data.targetVersion)}${escapeMarkdownInline(" (offline, from cache)")}` + `${colorTag("green", "✓")} ${verb} to ${safeCodeSpan(data.targetVersion)}${escapeMarkdownInline(" (offline, from cache)")}`, ); } else if (data.currentVersion !== data.targetVersion) { lines.push( - `${colorTag("green", "✓")} ${verb} to ${safeCodeSpan(data.targetVersion)} ${escapeMarkdownInline(`(from ${data.currentVersion})`)}` + `${colorTag("green", "✓")} ${verb} to ${safeCodeSpan(data.targetVersion)} ${escapeMarkdownInline(`(from ${data.currentVersion})`)}`, ); } else { lines.push( - `${colorTag("green", "✓")} ${verb} to ${safeCodeSpan(data.targetVersion)}` + `${colorTag("green", "✓")} ${verb} to ${safeCodeSpan(data.targetVersion)}`, ); } break; } case "up-to-date": lines.push( - `${colorTag("green", "✓")} Already up to date (${safeCodeSpan(data.currentVersion)})` + `${colorTag("green", "✓")} Already up to date (${safeCodeSpan(data.currentVersion)})`, ); break; case "checked": { if (data.currentVersion === data.targetVersion) { lines.push( - `${colorTag("green", "✓")} You are already on the target version (${safeCodeSpan(data.currentVersion)})` + `${colorTag("green", "✓")} You are already on the target version (${safeCodeSpan(data.currentVersion)})`, ); } else { lines.push( - `Latest: ${safeCodeSpan(data.targetVersion)} (current: ${safeCodeSpan(data.currentVersion)})` + `Latest: ${safeCodeSpan(data.targetVersion)} (current: ${safeCodeSpan(data.currentVersion)})`, ); } break; @@ -2373,7 +2374,7 @@ export function formatUpgradeResult(data: UpgradeResult): string { // Exhaustive check — all action types should be handled above const _: never = data.action; lines.push( - `${ACTION_DESCRIPTIONS[_ as UpgradeResult["action"]] ?? "Done"}` + `${ACTION_DESCRIPTIONS[_ as UpgradeResult["action"]] ?? "Done"}`, ); } } @@ -2521,7 +2522,7 @@ const DEFAULT_LABELS: Record<string, string> = { * Shows when an env var overrides the stored preference. */ function telemetryOverrideNote( - effective: DefaultsResult["telemetryEffective"] + effective: DefaultsResult["telemetryEffective"], ): string { if (!effective?.source.startsWith("env:")) { return ""; @@ -2565,7 +2566,7 @@ export function formatDefaultsResult(data: DefaultsResult): string { const label = DEFAULT_LABELS[data.changed?.key ?? ""] ?? data.changed?.key; return renderMarkdown( - `${colorTag("green", "✓")} Default ${escapeMarkdownInline(label ?? "setting")} set to ${safeCodeSpan(String(data.changed?.newValue))}` + `${colorTag("green", "✓")} Default ${escapeMarkdownInline(label ?? "setting")} set to ${safeCodeSpan(String(data.changed?.newValue))}`, ); } @@ -2573,7 +2574,7 @@ export function formatDefaultsResult(data: DefaultsResult): string { const label = DEFAULT_LABELS[data.changed?.key ?? ""] ?? data.changed?.key; return renderMarkdown( - `${colorTag("green", "✓")} Default ${escapeMarkdownInline(label ?? "setting")} cleared` + `${colorTag("green", "✓")} Default ${escapeMarkdownInline(label ?? "setting")} cleared`, ); } @@ -2636,7 +2637,7 @@ export function formatSentryStatus(data: SentryStatus): string { const indicatorTag = STATUS_INDICATOR_TAGS[data.indicator] ?? "yellow"; const icon = data.indicator === "none" ? "✓" : "●"; lines.push( - `## ${colorTag(indicatorTag, icon)} ${escapeMarkdownInline(data.description)}` + `## ${colorTag(indicatorTag, icon)} ${escapeMarkdownInline(data.description)}`, ); lines.push(""); @@ -2645,7 +2646,7 @@ export function formatSentryStatus(data: SentryStatus): string { lines.push(""); for (const incident of data.incidents) { lines.push( - `- **${escapeMarkdownInline(incident.name)}** (${escapeMarkdownInline(incident.impact)} impact, ${escapeMarkdownInline(incident.status)})` + `- **${escapeMarkdownInline(incident.name)}** (${escapeMarkdownInline(incident.impact)} impact, ${escapeMarkdownInline(incident.status)})`, ); lines.push(` ${safeCodeSpan(incident.shortlink)}`); } diff --git a/packages/cli/src/lib/formatters/index.ts b/packages/cli/src/lib/formatters/index.ts index a5390fe4a..5d06a1ca0 100644 --- a/packages/cli/src/lib/formatters/index.ts +++ b/packages/cli/src/lib/formatters/index.ts @@ -1,4 +1,4 @@ -// biome-ignore-all lint/performance/noBarrelFile: intentional public API +// Intentional public API barrel. /** * Formatters * diff --git a/packages/cli/src/lib/formatters/issue-links.ts b/packages/cli/src/lib/formatters/issue-links.ts index e033ec0a0..212b57903 100644 --- a/packages/cli/src/lib/formatters/issue-links.ts +++ b/packages/cli/src/lib/formatters/issue-links.ts @@ -13,17 +13,17 @@ export function formatIssueLinkResult(result: ExternalIssueLinkResult): string { return renderMarkdown( needsChange ? `Would ${result.action} ${external} ${result.action === "link" ? "to" : "from"} ${issue}. (dry run)` - : `Already ${result.linked ? "linked" : "unlinked"}: ${external}. (dry run)` + : `Already ${result.linked ? "linked" : "unlinked"}: ${external}. (dry run)`, ); } if (!result.changed) { return renderMarkdown( - `Already ${result.linked ? "linked" : "unlinked"}: ${external}.` + `Already ${result.linked ? "linked" : "unlinked"}: ${external}.`, ); } return renderMarkdown( result.linked ? `Linked ${external} to ${issue}.` - : `Unlinked ${external} from ${issue}. The external issue was not deleted.` + : `Unlinked ${external} from ${issue}. The external issue was not deleted.`, ); } diff --git a/packages/cli/src/lib/formatters/issue.ts b/packages/cli/src/lib/formatters/issue.ts index 37ef3d747..c53280928 100644 --- a/packages/cli/src/lib/formatters/issue.ts +++ b/packages/cli/src/lib/formatters/issue.ts @@ -45,7 +45,7 @@ function formatReplaySection(org: string | null, replayIds: string[]): string { for (const replayId of visibleReplayIds) { if (org) { lines.push( - `- \`${replayId}\` (view: \`sentry replay view ${org}/${replayId}\`)` + `- \`${replayId}\` (view: \`sentry replay view ${org}/${replayId}\`)`, ); } else { lines.push(`- \`${replayId}\``); @@ -55,7 +55,7 @@ function formatReplaySection(org: string | null, replayIds: string[]): string { const remainingCount = replayIds.length - visibleReplayIds.length; if (remainingCount > 0) { lines.push( - `- ${remainingCount} more related replay${remainingCount === 1 ? "" : "s"}` + `- ${remainingCount} more related replay${remainingCount === 1 ? "" : "s"}`, ); } @@ -70,7 +70,7 @@ function formatSingleIssueView(data: SingleIssueViewData): string { if (data.event) { parts.push( - formatEventDetails(data.event, "Latest Event", data.issue.permalink) + formatEventDetails(data.event, "Latest Event", data.issue.permalink), ); } @@ -101,7 +101,7 @@ export function formatIssueView(data: IssueViewData): string { function flattenIssueView( entry: SingleIssueViewData, - fields?: string[] + fields?: string[], ): Record<string, unknown> { const result: Record<string, unknown> = { ...entry.issue, @@ -128,7 +128,7 @@ function flattenIssueView( */ export function jsonTransformIssueView( data: IssueViewData, - fields?: string[] + fields?: string[], ): unknown { if (data.requestedCount <= 1) { const [first] = data.issues; diff --git a/packages/cli/src/lib/formatters/json.ts b/packages/cli/src/lib/formatters/json.ts index fe4d84928..5b678b3bd 100644 --- a/packages/cli/src/lib/formatters/json.ts +++ b/packages/cli/src/lib/formatters/json.ts @@ -24,7 +24,7 @@ import type { Writer } from "../../types/index.js"; */ function getNestedValue( obj: unknown, - path: string + path: string, ): { found: true; value: unknown } | { found: false } { if (obj === null || obj === undefined || typeof obj !== "object") { return { found: false }; @@ -69,7 +69,7 @@ function setNestedValue( target: Record<string, unknown>, path: string, value: unknown, - literalKey = false + literalKey = false, ): void { if (literalKey) { target[path] = value; @@ -116,7 +116,7 @@ function setNestedValue( export function filterFields<T>(data: T, fields: string[]): Partial<T> { if (Array.isArray(data)) { return data.map((item) => - filterFields(item, fields) + filterFields(item, fields), ) as unknown as Partial<T>; } @@ -158,7 +158,7 @@ export function parseFieldsList(input: string): string[] { input .split(",") .map((f) => f.trim()) - .filter(Boolean) + .filter(Boolean), ), ]; } @@ -221,7 +221,7 @@ export function writeJsonList<T>( fields?: string[]; /** Arbitrary extra metadata to include in the wrapper (e.g. `{ hint }`) */ extra?: Record<string, unknown>; - } + }, ): void { const { hasMore, nextCursor, errors, fields, extra } = options; const filtered = diff --git a/packages/cli/src/lib/formatters/local.ts b/packages/cli/src/lib/formatters/local.ts index 14eabb6f6..99d42be60 100644 --- a/packages/cli/src/lib/formatters/local.ts +++ b/packages/cli/src/lib/formatters/local.ts @@ -44,9 +44,9 @@ export function sanitize(text: string): string { const stripped = stripAnsi(text).replace(/[\r\n\x85]+/g, " "); // Strip C0 (0x00-0x1F, 0x7F) and C1 (0x80-0x9F) control characters. const noCtrl = stripped.replace( - // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping control chars from untrusted envelope data + // oxlint-disable-next-line no-control-regex -- stripping control chars from untrusted envelope data /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\x80-\x9f]/g, - "" + "", ); // Strip Unicode bidirectional override/isolate characters that can reorder terminal output. return noCtrl.replace(BIDI_RE, ""); @@ -100,7 +100,7 @@ const TRACE_ID_SHORT_LEN = 8; * rather than render garbage. */ export function extractTraceId( - event: Record<string, unknown> + event: Record<string, unknown>, ): string | undefined { const trace = (event.contexts as Record<string, unknown> | undefined) ?.trace as { trace_id?: unknown } | undefined; @@ -212,7 +212,7 @@ export function formatFrameHint(frames: StackFrame[]): string { const loc = sanitize( frame.colno ? `${frame.filename}:${frame.lineno}:${frame.colno}` - : `${frame.filename}:${frame.lineno}` + : `${frame.filename}:${frame.lineno}`, ); hint += ` ${muted(`[${loc}]`)}`; } @@ -229,7 +229,7 @@ export function formatFrameHint(frames: StackFrame[]): string { */ export function formatErrorItem( event: Record<string, unknown>, - header: Record<string, unknown> + header: Record<string, unknown>, ): string { const exception = event.exception as | { @@ -244,7 +244,7 @@ export function formatErrorItem( const first = exception?.values?.at(-1); const errorType = sanitize(String(first?.type ?? "Error")); const errorValue = sanitize( - String(first?.value ?? event.message ?? "Unknown error") + String(first?.value ?? event.message ?? "Unknown error"), ); let msg = `${errorType}: ${errorValue}`; @@ -273,7 +273,7 @@ export function formatErrorItem( */ export function formatTransactionItem( event: Record<string, unknown>, - header: Record<string, unknown> + header: Record<string, unknown>, ): string { const trace = (event.contexts as Record<string, unknown> | undefined) ?.trace as @@ -401,7 +401,7 @@ function formatAttrValue(value: unknown): string { * lower-signal for a top-level scan. */ function collectAllAttributes( - event: Record<string, unknown> + event: Record<string, unknown>, ): Map<string, unknown> { const merged = new Map<string, unknown>(); const sources: AttributeSource[] = [ @@ -425,7 +425,7 @@ function collectAllAttributes( */ function formatAttrGroup( title: string, - entries: [string, unknown][] + entries: [string, unknown][], ): string[] { if (entries.length === 0) { return []; @@ -437,7 +437,7 @@ function formatAttrGroup( const safeKey = sanitize(key); const padded = safeKey.padEnd(keyWidth); lines.push( - `${ATTR_INDENT}${ATTR_INDENT}${cyan(padded)} ${formatAttrValue(value)}` + `${ATTR_INDENT}${ATTR_INDENT}${cyan(padded)} ${formatAttrValue(value)}`, ); } return lines; @@ -520,7 +520,7 @@ export function formatSingleLog(logEntry: LogEntry, source: string): string { v !== null && v !== undefined && v.value !== null && - v.value !== undefined + v.value !== undefined, ) .sort(([a], [b]) => a.localeCompare(b)) .map(([k, v]) => muted(`[${sanitize(k)}=${sanitize(String(v.value))}]`)); @@ -546,7 +546,7 @@ export function formatSingleLog(logEntry: LogEntry, source: string): string { */ export function formatLogItem( event: Record<string, unknown>, - header: Record<string, unknown> + header: Record<string, unknown>, ): string[] { const items = event.items as LogEntry[] | undefined; if (!items?.length) { @@ -573,7 +573,7 @@ export type StreamedSpan = { * `Record<string, unknown>` compatible with the semantic display pipeline. */ function flattenSpanAttributes( - attrs: Record<string, { type?: string; value?: unknown }> | undefined + attrs: Record<string, { type?: string; value?: unknown }> | undefined, ): Record<string, unknown> { if (!attrs) { return {}; @@ -593,7 +593,7 @@ function flattenSpanAttributes( */ export function formatSingleSpan( span: StreamedSpan, - header: Record<string, unknown> + header: Record<string, unknown>, ): string { const spanName = sanitize(span.name ?? "unnamed span"); const flat = flattenSpanAttributes(span.attributes); @@ -616,7 +616,7 @@ export function formatSingleSpan( if (span.start_timestamp !== undefined && span.end_timestamp !== undefined) { const durationMs = Math.round( - (span.end_timestamp - span.start_timestamp) * 1000 + (span.end_timestamp - span.start_timestamp) * 1000, ); msg += ` ${muted(`[${durationMs}ms]`)}`; } @@ -640,7 +640,7 @@ export function formatSingleSpan( export function formatSpanItem( event: Record<string, unknown>, header: Record<string, unknown>, - showAttributes = false + showAttributes = false, ): string[] { const items = event.items as StreamedSpan[] | undefined; if (!items?.length) { @@ -687,7 +687,7 @@ export const ERROR_TYPES = new Set(["event", "error"]); * Returns undefined for item types that don't map to a filter category. */ export function itemTypeToFilterCategory( - itemType: string | undefined + itemType: string | undefined, ): FilterValue | undefined { if (!itemType) { return; @@ -762,7 +762,7 @@ function sanitizeJsonValue(value: unknown): unknown { Object.entries(value).map(([key, nestedValue]) => [ stripBidi(key), sanitizeJsonValue(nestedValue), - ]) + ]), ); } return value; @@ -772,7 +772,7 @@ function sanitizeJsonValue(value: unknown): unknown { function formatJsonObservation( observation: Record<string, unknown>, payload: Record<string, unknown>, - header: Record<string, unknown> + header: Record<string, unknown>, ): string { return JSON.stringify( sanitizeJsonValue({ @@ -780,14 +780,14 @@ function formatJsonObservation( envelope_id: jsonSafeIdentifier(header.__spotlight_envelope_id), event_id: jsonSafe(payload.event_id) ?? jsonSafe(header.event_id), ...observation, - }) + }), ); } /** Format an error item as a JSON object, including the best stack frame. */ function formatErrorJson( payload: Record<string, unknown>, - header: Record<string, unknown> + header: Record<string, unknown>, ): string { const exception = payload.exception as | { @@ -817,7 +817,7 @@ function formatErrorJson( source: inferSourceName(header), }, payload, - header + header, ); } @@ -834,7 +834,7 @@ function buildJsonAttributes(payload: Record<string, unknown>): { const user: Record<string, string> = {}; const sdk: Record<string, string> = {}; for (const [key, value] of [...merged].sort(([a], [b]) => - a.localeCompare(b) + a.localeCompare(b), )) { const target = isSdkAttribute(key) ? sdk : user; target[stripBidi(key)] = stripBidi(formatAttrValue(value)); @@ -854,14 +854,14 @@ function buildJsonAttributes(payload: Record<string, unknown>): { function formatTransactionJson( payload: Record<string, unknown>, header: Record<string, unknown>, - includeAttributes = false + includeAttributes = false, ): string { const trace = (payload.contexts as Record<string, unknown> | undefined) ?.trace as Record<string, unknown> | undefined; const attrs = mergeTransactionAttributes(payload); const semantic = formatSemanticSpanDisplay( attrs, - String(payload.transaction ?? trace?.description ?? "Transaction") + String(payload.transaction ?? trace?.description ?? "Transaction"), ); const start = payload.start_timestamp as number | undefined; const end = payload.timestamp as number | undefined; @@ -887,14 +887,14 @@ function formatTransactionJson( source: inferSourceName(header), }, payload, - header + header, ); } /** Format a log item as JSON objects (one per entry). */ function formatLogJson( payload: Record<string, unknown>, - header: Record<string, unknown> + header: Record<string, unknown>, ): string[] { const items = payload.items as LogEntry[] | undefined; if (!items?.length) { @@ -916,19 +916,19 @@ function formatLogJson( ([k, v]) => isUserLogAttribute(k) && v?.value !== null && - v?.value !== undefined + v?.value !== undefined, ) .map(([k, v]) => [ stripBidi(k), typeof v.value === "string" ? stripBidi(v.value) : v.value, - ]) + ]), ) : undefined, source, }, payload, - header - ) + header, + ), ); } @@ -936,7 +936,7 @@ function formatLogJson( function formatSpanJson( payload: Record<string, unknown>, header: Record<string, unknown>, - includeAttributes = false + includeAttributes = false, ): string[] { const items = payload.items as StreamedSpan[] | undefined; if (!items?.length) { @@ -947,7 +947,7 @@ function formatSpanJson( const flat = flattenSpanAttributes(span.attributes); const semantic = formatSemanticSpanDisplay( flat, - span.name ?? "unnamed span" + span.name ?? "unnamed span", ); const durationMs = span.start_timestamp !== undefined && span.end_timestamp !== undefined @@ -973,7 +973,7 @@ function formatSpanJson( source, }, payload, - header + header, ); }); } @@ -996,7 +996,7 @@ export function formatItemJson( itemType: string | undefined, payload: Record<string, unknown>, header: Record<string, unknown>, - showAttributes = false + showAttributes = false, ): string[] { if (itemType && ERROR_TYPES.has(itemType)) { return [formatErrorJson(payload, header)]; @@ -1017,7 +1017,7 @@ export function formatItemJson( timestamp: payload.timestamp, }, payload, - header + header, ), ]; } @@ -1044,13 +1044,12 @@ function inferSourceName(header: Record<string, unknown>): string { * When `showAttributes` is true, transaction items are followed by an indented * attribute table (see {@link formatAttributeTable}). */ -// biome-ignore lint/nursery/useMaxParams: established 4-param shape; showAttributes is a defaulted display toggle export function formatItem( itemType: string | undefined, payload: Record<string, unknown>, header: Record<string, unknown>, fallbackLabel: string, - showAttributes = false + showAttributes = false, ): string[] { if (itemType && ERROR_TYPES.has(itemType)) { return [formatErrorItem(payload, header)]; @@ -1080,7 +1079,7 @@ export function formatItem( export function isItemIncluded( itemType: string | undefined, activeFilters: ReadonlySet<FilterValue>, - payload?: Record<string, unknown> + payload?: Record<string, unknown>, ): boolean { if (activeFilters.size === 0) { return true; @@ -1141,7 +1140,7 @@ export function formatEnvelopeLinesJson( getEventTypes: () => string[] | null; }, activeFilters: ReadonlySet<FilterValue>, - showAttributes = false + showAttributes = false, ): string[] { const parsed = container.getParsedEnvelope(); if (!parsed) { @@ -1156,7 +1155,7 @@ export function formatEnvelopeLinesJson( continue; } lines.push( - ...formatItemJson(itemHeader.type, payload, header, showAttributes) + ...formatItemJson(itemHeader.type, payload, header, showAttributes), ); } return lines; @@ -1178,7 +1177,7 @@ export function formatEnvelopeLines( getEventTypes: () => string[] | null; }, activeFilters: ReadonlySet<FilterValue>, - showAttributes = false + showAttributes = false, ): string[] { const parsed = container.getParsedEnvelope(); if (!parsed) { @@ -1201,8 +1200,8 @@ export function formatEnvelopeLines( payload, header, itemHeader.type ?? container.getContentType(), - showAttributes - ) + showAttributes, + ), ); } diff --git a/packages/cli/src/lib/formatters/log.ts b/packages/cli/src/lib/formatters/log.ts index 7e5cbb1a3..ba351d230 100644 --- a/packages/cli/src/lib/formatters/log.ts +++ b/packages/cli/src/lib/formatters/log.ts @@ -138,7 +138,7 @@ export function buildLogRowCells( log: LogLike, padSeverity = true, includeTrace = true, - extraFields?: string[] + extraFields?: string[], ): string[] { const logId = getLogId(log); const shortId = logId ? colorTag("muted", logId.slice(0, 8)) : ""; @@ -154,7 +154,9 @@ export function buildLogRowCells( for (const field of extraFields) { const val = log[field]; cells.push( - escapeMarkdownCell(val !== null && val !== undefined ? String(val) : "") + escapeMarkdownCell( + val !== null && val !== undefined ? String(val) : "", + ), ); } } @@ -173,7 +175,7 @@ export function buildLogRowCells( export function formatLogRow( log: LogLike, includeTrace = true, - extraFields?: string[] + extraFields?: string[], ): string { return mdRow(buildLogRowCells(log, true, includeTrace, extraFields)); } @@ -201,7 +203,7 @@ const LOG_HINT_ROWS: string[][] = [ */ export function createLogStreamingTable( options: Partial<StreamingTableOptions> = {}, - extraColumns?: string[] + extraColumns?: string[], ): StreamingTable { const cols = [...LOG_TABLE_COLS, ...(extraColumns ?? [])]; // ID, Timestamp, Level are fixed-width; Message + extra columns are shrinkable @@ -261,13 +263,13 @@ export function formatLogsHeader(extraColumns?: string[]): string { export function formatLogTable( logs: LogLike[], includeTrace = true, - extraFields?: string[] + extraFields?: string[], ): string { const headers = [...LOG_TABLE_COLS, ...(extraFields ?? [])]; const rows = logs.map((log) => buildLogRowCells(log, false, includeTrace, extraFields).map((c) => - renderInlineMarkdown(c) - ) + renderInlineMarkdown(c), + ), ); return renderTextTable(headers, rows); } @@ -338,12 +340,12 @@ const REDUNDANT_LOG_DETAIL_ATTRS = new Set([ * @param extraFields - Optional --fields filter: limits which custom attributes are shown * @returns Rendered terminal string */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: log detail formatting requires multiple conditional sections +// log detail formatting requires multiple conditional sections export function formatLogDetails( log: DetailedSentryLog, orgSlug: string, allAttributes?: TraceItemAttribute[], - extraFields?: string[] + extraFields?: string[], ): string { const logId = log["sentry.item_id"]; const lines: string[] = []; @@ -357,7 +359,7 @@ export function formatLogDetails( ["ID", `\`${logId}\``], ["Timestamp", formatTimestamp(log.timestamp)], ["Severity", formatSeverityLabel(log.severity)], - ]) + ]), ); if (log.message) { @@ -448,7 +450,7 @@ export function formatLogDetails( // Custom Attributes — from trace-items detail endpoint (all non-standard attributes) if (allAttributes?.length) { let customAttrs = allAttributes.filter( - (a) => !REDUNDANT_LOG_DETAIL_ATTRS.has(a.name) + (a) => !REDUNDANT_LOG_DETAIL_ATTRS.has(a.name), ); if (extraFields?.length) { const wanted = new Set(extraFields); @@ -462,8 +464,8 @@ export function formatLogDetails( a.name, a.type === "array" ? JSON.stringify(a.value) : String(a.value), ]), - "Custom Attributes" - ) + "Custom Attributes", + ), ); } } else if (extraFields?.length) { diff --git a/packages/cli/src/lib/formatters/markdown.ts b/packages/cli/src/lib/formatters/markdown.ts index e523b55a4..807fd321e 100644 --- a/packages/cli/src/lib/formatters/markdown.ts +++ b/packages/cli/src/lib/formatters/markdown.ts @@ -98,7 +98,7 @@ export function mdRow(cells: readonly string[]): string { // Literal pipes are replaced with box-drawing │ to prevent breaking the // pipe-delimited table format. const out = cells.map((c) => - renderInlineMarkdown(c).replace(/\|/g, "\u2502") + renderInlineMarkdown(c).replace(/\|/g, "\u2502"), ); return `| ${out.join(" | ")} |\n`; } @@ -110,7 +110,7 @@ export function mdRow(cells: readonly string[]): string { */ export function mdKvTable( rows: ReadonlyArray<readonly [string, string]>, - heading?: string + heading?: string, ): string { const lines: string[] = []; if (heading) { @@ -126,7 +126,7 @@ export function mdKvTable( // Only replace structural characters that break table syntax. // Content escaping (<>, \, _*`) is the caller's responsibility. lines.push( - `| **${label}** | ${value.replace(/\n/g, " ").replace(/\|/g, "\u2502")} |` + `| **${label}** | ${value.replace(/\n/g, " ").replace(/\|/g, "\u2502")} |`, ); } return lines.join("\n"); @@ -166,7 +166,7 @@ const COLOR_TAGS: Record<string, (text: string) => string> = { /** Regex matching all supported color tag pairs — compiled once at module scope. */ const COLOR_TAG_RE = new RegExp( `<(${Object.keys(COLOR_TAGS).join("|")})>([\\s\\S]*?)<\\/\\1>`, - "gi" + "gi", ); /** @@ -237,7 +237,7 @@ function renderHtmlToken(raw: string): string { * language is unknown or highlighting fails. */ function highlightCode(code: string, language?: string): string { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return cliHighlight(code, { language, ignoreIllegals: true }); } catch { @@ -305,7 +305,7 @@ function renderOneInline(token: Token): string { } case "del": return chalk.dim.gray.strikethrough( - renderInline((token as Tokens.Del).tokens) + renderInline((token as Tokens.Del).tokens), ); case "br": return "\n"; @@ -334,7 +334,7 @@ function renderOneInline(token: Token): string { * * Also handles: strong, em, codespan, link, text, br, del, escape. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: paired color tag buffering +// paired color tag buffering function renderInline(tokens: Token[]): string { const parts: string[] = []; let i = 0; @@ -403,8 +403,8 @@ function renderBlocks(tokens: Token[]): string { parts.push(chalk.hex(COLORS.cyan).bold(text)); parts.push( chalk.hex(COLORS.cyan)( - "\u2501".repeat(Math.min(stringWidth(text), 30)) - ) + "\u2501".repeat(Math.min(stringWidth(text), 30)), + ), ); } else { // h3+ → bold cyan (less prominent, no divider) @@ -513,7 +513,7 @@ function renderList(list: Tokens.List, depth = 0): string { function renderTableToken(table: Tokens.Table): string { const headers = table.header.map((cell) => renderInline(cell.tokens)); const rows = table.rows.map((row) => - row.map((cell) => renderInline(cell.tokens)) + row.map((cell) => renderInline(cell.tokens)), ); const alignments: Alignment[] = table.align.map((a) => { diff --git a/packages/cli/src/lib/formatters/meta-table.ts b/packages/cli/src/lib/formatters/meta-table.ts index 206885104..d8fb69f56 100644 --- a/packages/cli/src/lib/formatters/meta-table.ts +++ b/packages/cli/src/lib/formatters/meta-table.ts @@ -33,7 +33,7 @@ export const NUMERIC_FIELD_TYPES = new Set([ export function formatCellValue( value: unknown, fieldType?: string, - unit?: string | null + unit?: string | null, ): string { if (value === null || value === undefined) { return "—"; @@ -66,7 +66,7 @@ export function formatCellValue( export function buildMetaColumns( fieldNames: string[], fieldTypes?: Record<string, string>, - fieldUnits?: Record<string, string | null> + fieldUnits?: Record<string, string | null>, ): Column<Record<string, unknown>>[] { return fieldNames.map((name) => { const fieldType = fieldTypes?.[name]; diff --git a/packages/cli/src/lib/formatters/numbers.ts b/packages/cli/src/lib/formatters/numbers.ts index 7aff44b62..28283d4f0 100644 --- a/packages/cli/src/lib/formatters/numbers.ts +++ b/packages/cli/src/lib/formatters/numbers.ts @@ -60,7 +60,7 @@ export function formatCompactCount(value: number): string { */ export function appendUnitSuffix( formatted: string, - unit?: string | null + unit?: string | null, ): string { if (!unit || unit === "none" || unit === "null") { return formatted; @@ -94,7 +94,7 @@ export function formatWithUnit(value: number, unit?: string | null): string { */ export function formatCompactWithUnit( value: number, - unit?: string | null + unit?: string | null, ): string { return appendUnitSuffix(compactFormatter.format(Math.round(value)), unit); } diff --git a/packages/cli/src/lib/formatters/output.ts b/packages/cli/src/lib/formatters/output.ts index cbe85de7d..d47b6f438 100644 --- a/packages/cli/src/lib/formatters/output.ts +++ b/packages/cli/src/lib/formatters/output.ts @@ -223,7 +223,7 @@ type RenderContext = { */ function applyJsonExclude( data: unknown, - excludeKeys: readonly string[] | undefined + excludeKeys: readonly string[] | undefined, ): unknown { if (!excludeKeys || excludeKeys.length === 0) { return data; @@ -281,15 +281,14 @@ function emitJsonObject(stdout: Writer, obj: unknown): void { * @param renderer - Per-invocation renderer (from `config.human()`) * @param ctx - Rendering context with flag values */ -// biome-ignore lint/nursery/useMaxParams: Framework function — config/renderer/ctx are all required for JSON vs human split. export function renderCommandOutput( stdout: Writer, data: unknown, - // biome-ignore lint/suspicious/noExplicitAny: Variance erasure — config/renderer are paired at build time, but the framework iterates over unknown yields. + // oxlint-disable-next-line typescript/no-explicit-any -- Variance erasure — config/renderer are paired at build time, but the framework iterates over unknown yields. config: OutputConfig<any>, - // biome-ignore lint/suspicious/noExplicitAny: Renderer type mirrors erased OutputConfig<T> + // oxlint-disable-next-line typescript/no-explicit-any -- Renderer type mirrors erased OutputConfig<T> renderer: HumanRenderer<any>, - ctx: RenderContext + ctx: RenderContext, ): void { // Binary bodies (Uint8Array) must bypass text formatters entirely — no // JSON pretty-print, no trailing newline, no string coercion. This is the diff --git a/packages/cli/src/lib/formatters/pixel-canvas.ts b/packages/cli/src/lib/formatters/pixel-canvas.ts index c1bc14a90..c55b8dd2f 100644 --- a/packages/cli/src/lib/formatters/pixel-canvas.ts +++ b/packages/cli/src/lib/formatters/pixel-canvas.ts @@ -78,7 +78,7 @@ export type PixelTextOptions = { /** Create an RGBA pixel canvas, optionally initialized to an opaque color. */ export function createPixelCanvas( - options: CreatePixelCanvasOptions + options: CreatePixelCanvasOptions, ): DecodedImage { const width = Math.max(1, Math.floor(options.width)); const height = Math.max(1, Math.floor(options.height)); @@ -98,7 +98,7 @@ export function createPixelCanvas( /** Draw a filled rectangle, clipping it to the canvas bounds. */ export function drawPixelRect( image: DecodedImage, - options: PixelRectOptions + options: PixelRectOptions, ): void { const x0 = Math.max(0, Math.floor(options.x)); const y0 = Math.max(0, Math.floor(options.y)); @@ -122,7 +122,7 @@ export function blitPixelImage( destination: DecodedImage, source: DecodedImage, x: number, - y: number + y: number, ): void { const destX = Math.floor(x); const destY = Math.floor(y); @@ -142,7 +142,7 @@ export function blitPixelImage( { x: targetX, y: targetY, - } + }, ); } } @@ -156,7 +156,7 @@ function copyOpaquePixel( destination: DecodedImage, source: DecodedImage, sourcePoint: PixelPoint, - targetPoint: PixelPoint + targetPoint: PixelPoint, ): void { const { x: sourceX, y: sourceY } = sourcePoint; const { x: targetX, y: targetY } = targetPoint; @@ -175,7 +175,7 @@ function copyOpaquePixel( export function drawPixelText( image: DecodedImage, text: string, - options: PixelTextOptions + options: PixelTextOptions, ): void { const cellWidth = Math.max(1, Math.floor(options.cellWidth)); const cellHeight = Math.max(1, Math.floor(options.cellHeight)); @@ -236,7 +236,7 @@ type SpleenGlyphOptions = { function drawSpleenGlyph( image: DecodedImage, glyph: Uint8Array, - options: SpleenGlyphOptions + options: SpleenGlyphOptions, ): void { for (let row = 0; row < SPLEEN_CELL_HEIGHT; row += 1) { const pattern = glyph[row] ?? 0; @@ -261,7 +261,7 @@ type SpleenPixelOptions = SpleenGlyphOptions & { function drawSpleenPixel( image: DecodedImage, - options: SpleenPixelOptions + options: SpleenPixelOptions, ): void { const x = options.x + @@ -288,7 +288,7 @@ function drawSpleenPixel( function scaleCoordinate( coordinate: number, cellSize: number, - glyphSize: number + glyphSize: number, ): number { return Math.floor((coordinate * cellSize) / glyphSize); } diff --git a/packages/cli/src/lib/formatters/plain-detect.ts b/packages/cli/src/lib/formatters/plain-detect.ts index 84426cef8..2f04a0334 100644 --- a/packages/cli/src/lib/formatters/plain-detect.ts +++ b/packages/cli/src/lib/formatters/plain-detect.ts @@ -85,17 +85,17 @@ export function stripAnsi(text: string): string { return ( text // CSI: \x1b[ + param bytes (0x30-0x3F) + intermediate bytes (0x20-0x2F) + final byte (0x40-0x7E) - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape detection requires matching \x1b + // oxlint-disable-next-line no-control-regex -- ANSI escape detection requires matching \x1b .replace(/\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, "") // OSC: \x1b] ... terminated by BEL (\x07) or ST (\x1b\) - // biome-ignore lint/suspicious/noControlCharactersInRegex: OSC sequences use \x1b and \x07 + // oxlint-disable-next-line no-control-regex -- OSC sequences use \x1b and \x07 .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "") // DCS: \x1bP ... terminated by ST (\x1b\) - // biome-ignore lint/suspicious/noControlCharactersInRegex: DCS sequences use \x1b + // oxlint-disable-next-line no-control-regex -- DCS sequences use \x1b .replace(/\x1bP[^\x1b]*\x1b\\/g, "") // Two-character ESC sequences (C1: 0x40-0x5F, Fs: 0x60-0x7E), e.g. \x1bc (terminal reset). // Applied last so CSI/OSC/DCS introducers (\x1b[, \x1b], \x1bP) are already stripped. - // biome-ignore lint/suspicious/noControlCharactersInRegex: ESC sequence detection requires matching \x1b + // oxlint-disable-next-line no-control-regex -- ESC sequence detection requires matching \x1b .replace(/\x1b[@-~]/g, "") ); } diff --git a/packages/cli/src/lib/formatters/replay.ts b/packages/cli/src/lib/formatters/replay.ts index 615db3232..b0eb1b7d5 100644 --- a/packages/cli/src/lib/formatters/replay.ts +++ b/packages/cli/src/lib/formatters/replay.ts @@ -39,7 +39,7 @@ type ReplayRecordingSegments = ListProjectReplayRecordingSegmentsResponse; type ReplayRecordingEvent = ReplayRecordingSegments[number][number]; function hasReplayTag( - value: unknown + value: unknown, ): value is { payload?: unknown; tag: string } { return ( typeof value === "object" && @@ -61,7 +61,7 @@ function hasReplayHref(value: unknown): value is { href: string } { } function hasPerformanceSpanFields( - value: unknown + value: unknown, ): value is { data?: unknown; description?: unknown; op?: unknown } { return ( typeof value === "object" && @@ -80,7 +80,7 @@ function hasNumericDuration(value: unknown): value is { duration: number } { } function hasClickFields( - value: unknown + value: unknown, ): value is { label?: unknown; selector?: unknown } { return ( typeof value === "object" && @@ -90,7 +90,7 @@ function hasClickFields( } function hasBreadcrumbFields( - value: unknown + value: unknown, ): value is { category?: unknown; message?: unknown } { return ( typeof value === "object" && @@ -119,7 +119,7 @@ function compactDetails(values: Array<string | null>): string[] { } function summarizePerformanceSpan( - payload: unknown + payload: unknown, ): Omit<ReplayActivityEvent, "timestampMs"> | null { if (!hasPerformanceSpanFields(payload)) { return null; @@ -147,7 +147,7 @@ function summarizePerformanceSpan( function summarizeClickLikeEvent( label: string, payload: unknown, - includeLabel = false + includeLabel = false, ): Omit<ReplayActivityEvent, "timestampMs"> { if (!hasClickFields(payload)) { return { label, details: [] }; @@ -166,7 +166,7 @@ function summarizeClickLikeEvent( } function summarizeBreadcrumb( - payload: unknown + payload: unknown, ): Omit<ReplayActivityEvent, "timestampMs"> | null { if (!hasBreadcrumbFields(payload)) { return null; @@ -199,20 +199,20 @@ const TAGGED_REPLAY_EVENT_SUMMARIZERS: Record< function summarizeTaggedReplayEvent( tag: string, - payload: unknown + payload: unknown, ): Omit<ReplayActivityEvent, "timestampMs"> | null { const summarize = TAGGED_REPLAY_EVENT_SUMMARIZERS[tag]; return summarize ? summarize(payload) : null; } function summarizeReplayEvent( - event: ReplayRecordingEvent + event: ReplayRecordingEvent, ): ReplayActivityEvent | null { const timestampMs = getEventTimestampMillis(event.timestamp); if (hasReplayTag(event.data)) { const replayEvent = summarizeTaggedReplayEvent( event.data.tag, - event.data.payload + event.data.payload, ); if (replayEvent) { return { timestampMs, ...replayEvent }; @@ -233,7 +233,7 @@ function summarizeReplayEvent( /** Extract a capped list of activity events from replay recording segments. */ export function extractReplayActivityEvents( segments: ReplayRecordingSegments | null, - maxEvents: number + maxEvents: number, ): ReplayActivityEvent[] { if (!segments) { return []; @@ -265,7 +265,7 @@ function formatList(values: string[] | undefined): string | undefined { function pushMarkdownRow( rows: MarkdownRow[], label: string, - value: string | undefined + value: string | undefined, ): void { if (!value) { return; @@ -281,7 +281,7 @@ function formatYesNo(value: boolean | null | undefined): string | undefined { } function formatNullableCount( - value: number | null | undefined + value: number | null | undefined, ): string | undefined { if (value === null || value === undefined) { return; @@ -290,7 +290,7 @@ function formatNullableCount( } function formatJoinedMarkdown( - values: Array<string | null | undefined> + values: Array<string | null | undefined>, ): string | undefined { const joined = values.filter(Boolean).join(" "); return joined ? escapeMarkdownCell(joined) : undefined; @@ -310,7 +310,7 @@ function formatReplayLocation(replay: ReplayDetails): string | undefined { function buildReplayOverviewRows( org: string, - replay: ReplayDetails + replay: ReplayDetails, ): MarkdownRow[] { const rows: MarkdownRow[] = [["Replay ID", `\`${replay.id}\``]]; pushMarkdownRow(rows, "Link", buildReplayUrl(org, replay.id)); @@ -318,43 +318,45 @@ function buildReplayOverviewRows( pushMarkdownRow( rows, "Started", - replay.started_at ? new Date(replay.started_at).toLocaleString() : undefined + replay.started_at + ? new Date(replay.started_at).toLocaleString() + : undefined, ); pushMarkdownRow( rows, "Finished", replay.finished_at ? new Date(replay.finished_at).toLocaleString() - : undefined + : undefined, ); pushMarkdownRow( rows, "Duration", replay.duration !== null && replay.duration !== undefined ? formatDurationVerbose(replay.duration) - : undefined + : undefined, ); pushMarkdownRow( rows, "Environment", - replay.environment ? escapeMarkdownCell(replay.environment) : undefined + replay.environment ? escapeMarkdownCell(replay.environment) : undefined, ); pushMarkdownRow( rows, "Platform", - replay.platform ? escapeMarkdownCell(replay.platform) : undefined + replay.platform ? escapeMarkdownCell(replay.platform) : undefined, ); pushMarkdownRow( rows, "Project ID", replay.project_id !== null && replay.project_id !== undefined ? String(replay.project_id) - : undefined + : undefined, ); pushMarkdownRow( rows, "Replay Type", - replay.replay_type ? escapeMarkdownCell(replay.replay_type) : undefined + replay.replay_type ? escapeMarkdownCell(replay.replay_type) : undefined, ); pushMarkdownRow(rows, "Archived", formatYesNo(replay.is_archived)); pushMarkdownRow(rows, "Viewed", formatYesNo(replay.has_viewed)); @@ -363,12 +365,12 @@ function buildReplayOverviewRows( pushMarkdownRow( rows, "Rage Clicks", - formatNullableCount(replay.count_rage_clicks) + formatNullableCount(replay.count_rage_clicks), ); pushMarkdownRow( rows, "Dead Clicks", - formatNullableCount(replay.count_dead_clicks) + formatNullableCount(replay.count_dead_clicks), ); return rows; @@ -380,17 +382,17 @@ function buildReplayUserRows(replay: ReplayDetails): MarkdownRow[] { pushMarkdownRow( rows, "User", - userLabel ? escapeMarkdownCell(userLabel) : undefined + userLabel ? escapeMarkdownCell(userLabel) : undefined, ); pushMarkdownRow( rows, "Email", - replay.user?.email ? escapeMarkdownCell(replay.user.email) : undefined + replay.user?.email ? escapeMarkdownCell(replay.user.email) : undefined, ); pushMarkdownRow( rows, "IP", - replay.user?.ip ? escapeMarkdownCell(replay.user.ip) : undefined + replay.user?.ip ? escapeMarkdownCell(replay.user.ip) : undefined, ); pushMarkdownRow(rows, "Location", formatReplayLocation(replay)); return rows; @@ -401,12 +403,12 @@ function buildReplayClientRows(replay: ReplayDetails): MarkdownRow[] { pushMarkdownRow( rows, "Browser", - formatJoinedMarkdown([replay.browser?.name, replay.browser?.version]) + formatJoinedMarkdown([replay.browser?.name, replay.browser?.version]), ); pushMarkdownRow( rows, "OS", - formatJoinedMarkdown([replay.os?.name, replay.os?.version]) + formatJoinedMarkdown([replay.os?.name, replay.os?.version]), ); pushMarkdownRow( rows, @@ -416,17 +418,17 @@ function buildReplayClientRows(replay: ReplayDetails): MarkdownRow[] { replay.device?.family, replay.device?.name, replay.device?.model_id, - ]) + ]), ); pushMarkdownRow( rows, "SDK", - formatJoinedMarkdown([replay.sdk?.name, replay.sdk?.version]) + formatJoinedMarkdown([replay.sdk?.name, replay.sdk?.version]), ); pushMarkdownRow( rows, "Dist", - replay.dist ? escapeMarkdownCell(replay.dist) : undefined + replay.dist ? escapeMarkdownCell(replay.dist) : undefined, ); return rows; } @@ -434,7 +436,7 @@ function buildReplayClientRows(replay: ReplayDetails): MarkdownRow[] { function pushKvSection( lines: string[], rows: MarkdownRow[], - title?: string + title?: string, ): void { if (rows.length === 0) { return; @@ -446,7 +448,7 @@ function pushKvSection( function pushListSection( lines: string[], title: string, - values: string[] | undefined + values: string[] | undefined, ): void { const content = formatList(values); if (!content) { @@ -467,10 +469,10 @@ function pushTagsSection(lines: string[], replay: ReplayDetails): void { lines.push("### Tags"); lines.push(""); for (const [key, values] of Object.entries(replay.tags).sort(([a], [b]) => - a.localeCompare(b) + a.localeCompare(b), )) { lines.push( - `- \`${escapeMarkdownInline(key)}\`: ${values.map((value) => `\`${escapeMarkdownInline(value)}\``).join(", ")}` + `- \`${escapeMarkdownInline(key)}\`: ${values.map((value) => `\`${escapeMarkdownInline(value)}\``).join(", ")}`, ); } } @@ -478,7 +480,7 @@ function pushTagsSection(lines: string[], replay: ReplayDetails): void { function pushActivitySection( lines: string[], replay: ReplayDetails, - activity: ReplayActivityEvent[] + activity: ReplayActivityEvent[], ): void { lines.push(""); lines.push("### Activity"); @@ -513,7 +515,7 @@ function pushActivitySection( function formatRelatedIssueLine( org: string, - issue: ReplayRelatedIssue + issue: ReplayRelatedIssue, ): string { if (!(issue.shortId && issue.title)) { return `- Event \`${issue.eventId}\``; @@ -542,7 +544,7 @@ function buildRelatedTraceStats(trace: ReplayRelatedTrace): string[] { function formatRelatedTraceLine( org: string, - trace: ReplayRelatedTrace + trace: ReplayRelatedTrace, ): string { const stats = buildRelatedTraceStats(trace); const suffix = stats.length > 0 ? ` (${stats.join(", ")})` : ""; @@ -553,7 +555,7 @@ function pushRelatedSection( lines: string[], org: string, relatedIssues: ReplayRelatedIssue[], - relatedTraces: ReplayRelatedTrace[] + relatedTraces: ReplayRelatedTrace[], ): void { if (relatedIssues.length === 0 && relatedTraces.length === 0) { return; @@ -635,6 +637,6 @@ export function formatReplayDownloadResult(data: ReplayDownloadData): string { ["Events", String(data.eventCount)], ["Duration", formatDurationCompactMs(data.durationMs)], ["Saved to", data.output], - ]) + ]), ); } diff --git a/packages/cli/src/lib/formatters/seer.ts b/packages/cli/src/lib/formatters/seer.ts index 30190b9ba..10a60e2d3 100644 --- a/packages/cli/src/lib/formatters/seer.ts +++ b/packages/cli/src/lib/formatters/seer.ts @@ -27,7 +27,7 @@ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", */ export function getSpinnerFrame(tick: number): string { const index = tick % SPINNER_FRAMES.length; - // biome-ignore lint/style/noNonNullAssertion: index is always valid due to modulo + // oxlint-disable-next-line typescript/no-non-null-assertion -- index is always valid due to modulo return SPINNER_FRAMES[index]!; } @@ -69,7 +69,7 @@ type BlockWithMessage = { message?: { content?: string | null } }; * Returns undefined if no block message is available. */ function getBlockProgressMessage( - blocks: BlockWithMessage[] + blocks: BlockWithMessage[], ): string | undefined { const lastBlock = blocks.at(-1); if (!lastBlock?.message?.content) { @@ -154,7 +154,7 @@ function buildRootCauseMarkdown(cause: RootCause, index: number): string { const lines: string[] = []; lines.push( - `### Cause #${index}: ${escapeMarkdownInline(cause.description ?? "")}` + `### Cause #${index}: ${escapeMarkdownInline(cause.description ?? "")}`, ); lines.push(""); @@ -224,7 +224,7 @@ export function formatIssueExplain(data: IssueExplainData): string { const sections = data.results.map( (result) => - `# ${escapeMarkdownInline(result.issue)}\n\n${buildRootCauseListMarkdown(result.rootCauses)}` + `# ${escapeMarkdownInline(result.issue)}\n\n${buildRootCauseListMarkdown(result.rootCauses)}`, ); return renderMarkdown(sections.join("\n\n---\n\n")); } @@ -242,7 +242,7 @@ export function formatIssueExplain(data: IssueExplainData): string { */ export function jsonTransformIssueExplain( data: IssueExplainData, - fields?: string[] + fields?: string[], ): unknown { const filterRootCauses = (rootCauses: RootCause[]): unknown => fields && fields.length > 0 ? filterFields(rootCauses, fields) : rootCauses; @@ -272,7 +272,7 @@ export function jsonTransformIssueExplain( export function createSeerError( status: number, detail?: string, - orgSlug?: string + orgSlug?: string, ): SeerError | null { if (status === 402) { return new SeerError("no_budget", orgSlug); @@ -307,7 +307,7 @@ export function createSeerError( export function handleSeerApiError( status: number, detail?: string, - orgSlug?: string + orgSlug?: string, ): Error { const seerError = createSeerError(status, detail, orgSlug); if (seerError) { @@ -362,7 +362,7 @@ export function formatSolution(solution: SolutionArtifact): string { lines.push(""); lines.push( - `**Summary:** ${escapeMarkdownInline(solution.data.one_line_summary ?? "")}` + `**Summary:** ${escapeMarkdownInline(solution.data.one_line_summary ?? "")}`, ); lines.push(""); diff --git a/packages/cli/src/lib/formatters/semantic-display.ts b/packages/cli/src/lib/formatters/semantic-display.ts index 6e86753aa..61d30cf36 100644 --- a/packages/cli/src/lib/formatters/semantic-display.ts +++ b/packages/cli/src/lib/formatters/semantic-display.ts @@ -25,7 +25,7 @@ export type SemanticSpanDisplay = { /** A function that attempts to render semantic display for an item. */ type SpanDisplayFormatter = ( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ) => SemanticSpanDisplay | null; const SPAN_LABEL_MAX_LENGTH = 120; @@ -93,7 +93,7 @@ export function hasAiAttributes(attrs: AttributeSource): boolean { function getAttr( attrs: AttributeSource, keys: string[], - maxLength = SPAN_METADATA_MAX_LENGTH + maxLength = SPAN_METADATA_MAX_LENGTH, ): string | undefined { for (const key of keys) { if (Object.hasOwn(attrs, key)) { @@ -115,7 +115,7 @@ function getAttr( */ export function formatSemanticSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay { for (const formatter of SEMANTIC_SPAN_FORMATTERS) { const result = formatter(attrs, fallbackLabel); @@ -190,7 +190,7 @@ export function inferSemanticOp(attrs: AttributeSource): string | undefined { function formatGenAiSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const operation = getAttr(attrs, ["gen_ai.operation.name"]); const toolName = getAttr(attrs, ["gen_ai.tool.name"]); @@ -219,13 +219,13 @@ function formatGenAiSpanDisplay( function formatMcpSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const method = getAttr(attrs, ["mcp.method.name"]); const resourceUri = getAttr( attrs, ["mcp.resource.uri"], - SPAN_ATTRIBUTE_MAX_LENGTH + SPAN_ATTRIBUTE_MAX_LENGTH, ); const target = getAttr(attrs, ["gen_ai.tool.name", "gen_ai.prompt.name"]) ?? @@ -245,7 +245,7 @@ function formatMcpSpanDisplay( function formatHttpSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const method = getAttr(attrs, ["http.request.method"])?.toUpperCase(); const statusCode = getAttr(attrs, ["http.response.status_code"]); @@ -268,7 +268,7 @@ function formatHttpSpanDisplay( function formatDatabaseSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const dbSystem = getAttr(attrs, ["db.system.name"]); const querySummary = getAttr(attrs, ["db.query.summary"]); @@ -280,7 +280,7 @@ function formatDatabaseSpanDisplay( const queryText = getAttr( attrs, ["db.query.text"], - SPAN_ATTRIBUTE_MAX_LENGTH + SPAN_ATTRIBUTE_MAX_LENGTH, ); if ( @@ -313,14 +313,14 @@ function formatDatabaseSpanDisplay( function formatGraphqlSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const operationType = getAttr(attrs, ["graphql.operation.type"]); const operationName = getAttr(attrs, ["graphql.operation.name"]); const document = getAttr( attrs, ["graphql.document"], - SPAN_ATTRIBUTE_MAX_LENGTH + SPAN_ATTRIBUTE_MAX_LENGTH, ); if (!(operationType || operationName || document)) { @@ -338,7 +338,7 @@ function formatGraphqlSpanDisplay( function formatRpcSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const rpcSystem = getAttr(attrs, ["rpc.system.name"]); const service = getAttr(attrs, ["rpc.service"]); @@ -362,7 +362,7 @@ function formatRpcSpanDisplay( function formatMessagingSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const messagingSystem = getAttr(attrs, ["messaging.system"]); const operation = getAttr(attrs, [ @@ -395,7 +395,7 @@ function formatMessagingSpanDisplay( function formatFaasSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const trigger = getAttr(attrs, ["faas.trigger"]); const name = getAttr(attrs, ["faas.invoked_name", "faas.name"]); @@ -444,7 +444,7 @@ function formatFaasSpanDisplay( function formatProcessSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const command = getAttr(attrs, [ "process.executable.name", @@ -468,14 +468,14 @@ function formatProcessSpanDisplay( function formatObjectStoreSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const bucket = getAttr(attrs, ["aws.s3.bucket"]); const key = getAttr(attrs, ["aws.s3.key"], SPAN_ATTRIBUTE_MAX_LENGTH); const copySource = getAttr( attrs, ["aws.s3.copy_source"], - SPAN_ATTRIBUTE_MAX_LENGTH + SPAN_ATTRIBUTE_MAX_LENGTH, ); const operation = getAttr(attrs, ["rpc.method"]); const region = getAttr(attrs, ["cloud.region"]); @@ -496,14 +496,14 @@ function formatObjectStoreSpanDisplay( function formatCloudEventsSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const eventType = getAttr(attrs, ["cloudevents.event_type"]); const eventSubject = getAttr(attrs, ["cloudevents.event_subject"]); const eventSource = getAttr( attrs, ["cloudevents.event_source"], - SPAN_ATTRIBUTE_MAX_LENGTH + SPAN_ATTRIBUTE_MAX_LENGTH, ); const specVersion = getAttr(attrs, ["cloudevents.event_spec_version"]); @@ -523,7 +523,7 @@ function formatCloudEventsSpanDisplay( function formatCicdSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const action = getAttr(attrs, ["cicd.pipeline.action.name"]); const pipeline = getAttr(attrs, ["cicd.pipeline.name"]); @@ -544,7 +544,7 @@ function formatCicdSpanDisplay( function formatFeatureFlagSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const flagKey = getAttr(attrs, ["feature_flag.key"]); const variant = getAttr(attrs, ["feature_flag.result.variant"]); @@ -565,13 +565,13 @@ function formatFeatureFlagSpanDisplay( function formatExceptionSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const exceptionType = getAttr(attrs, ["exception.type"]); const exceptionMessage = getAttr( attrs, ["exception.message"], - SPAN_ATTRIBUTE_MAX_LENGTH + SPAN_ATTRIBUTE_MAX_LENGTH, ); if (!(exceptionType || exceptionMessage)) { @@ -595,7 +595,7 @@ function formatExceptionSpanDisplay( function formatErrorSpanDisplay( attrs: AttributeSource, - fallbackLabel: string + fallbackLabel: string, ): SemanticSpanDisplay | null { const errorType = getErrorType(attrs); if (!errorType) { @@ -626,12 +626,12 @@ function getGenAiModelIdentifier(attrs: AttributeSource): string | undefined { function getHttpTarget( attrs: AttributeSource, - { includeServerTarget = false }: { includeServerTarget?: boolean } = {} + { includeServerTarget = false }: { includeServerTarget?: boolean } = {}, ): string | undefined { const route = getAttr( attrs, ["http.route", "url.template"], - SPAN_ATTRIBUTE_MAX_LENGTH + SPAN_ATTRIBUTE_MAX_LENGTH, ); const fullUrl = getAttr(attrs, ["url.full"], SPAN_ATTRIBUTE_MAX_LENGTH); const path = getAttr(attrs, ["url.path"], SPAN_ATTRIBUTE_MAX_LENGTH); @@ -712,7 +712,7 @@ function formatOperationLabel(operation: string, subject?: string): string { function formatObjectStoreTarget( bucket?: string, - key?: string + key?: string, ): string | undefined { if (bucket && key) { return truncate(`${bucket}/${key}`, SPAN_LABEL_MAX_LENGTH); @@ -733,7 +733,7 @@ function formatDbQueryText(value?: string): string | undefined { } return truncate( value.replace(/'([^']|'')*'/g, "?").replace(/\b\d+(\.\d+)?\b/g, "?"), - SPAN_LABEL_MAX_LENGTH + SPAN_LABEL_MAX_LENGTH, ); } @@ -777,7 +777,7 @@ function truncate(value: unknown, maxLength: number): string | undefined { /** Format a display part, same as truncate but exported for tests. */ export function formatDisplayPart( value: unknown, - maxLength: number + maxLength: number, ): string | undefined { return truncate(value, maxLength); } @@ -805,7 +805,7 @@ function dedupeMetadata(values: string[]): string[] { * Transaction-level attributes live in `contexts.trace.data`. */ export function mergeTransactionAttributes( - event: Record<string, unknown> + event: Record<string, unknown>, ): AttributeSource { const contexts = event.contexts as Record<string, unknown> | undefined; const trace = contexts?.trace as Record<string, unknown> | undefined; @@ -823,7 +823,7 @@ export function mergeTransactionAttributes( * returned by {@link mergeTransactionAttributes}. */ export function collectSpanAttributes( - event: Record<string, unknown> + event: Record<string, unknown>, ): AttributeSource[] { const spans = event.spans; if (!Array.isArray(spans)) { diff --git a/packages/cli/src/lib/formatters/sixel-dashboard.ts b/packages/cli/src/lib/formatters/sixel-dashboard.ts index b1e47be84..280ce5e61 100644 --- a/packages/cli/src/lib/formatters/sixel-dashboard.ts +++ b/packages/cli/src/lib/formatters/sixel-dashboard.ts @@ -45,7 +45,7 @@ const TEXT_COLOR: Rgb = [224, 224, 224]; /** ANSI CSI, OSC, and DCS escape sequences emitted by text formatters. */ const TERMINAL_ESCAPE_RE = new RegExp( `${String.fromCharCode(27)}(?:\\[[0-?]*[ -/]*[@-~]|\\][\\s\\S]*?(?:${String.fromCharCode(7)}|${String.fromCharCode(27)}\\\\)|P[\\s\\S]*?${String.fromCharCode(27)}\\\\)`, - "g" + "g", ); /** Aggregate function and its simple arguments in a raw query expression. */ @@ -93,7 +93,7 @@ export type RenderSixelDashboardOptions = { renderTextContent: ( widget: SixelDashboardWidget, innerWidth: number, - contentHeight: number + contentHeight: number, ) => string[]; /** * Encode the finished RGBA canvas to a terminal graphics escape string. @@ -116,7 +116,7 @@ export type RenderSixelDashboardResult = /** Render every dashboard widget into one terminal-positioned sixel image. */ export function renderDashboardAsSixel( data: SixelDashboardData, - options: RenderSixelDashboardOptions + options: RenderSixelDashboardOptions, ): RenderSixelDashboardResult { const widgets = positionWidgets(data.widgets); if (widgets.length === 0) { @@ -125,7 +125,7 @@ export function renderDashboardAsSixel( const pixelWidth = Math.max(1, Math.floor(options.pixelWidth)); const gridHeight = Math.max( - ...widgets.map((item) => item.layout.y + item.layout.h) + ...widgets.map((item) => item.layout.y + item.layout.h), ); const pixelHeight = gridHeight * LINES_PER_GRID_UNIT * options.cellHeight; const canvasPixels = pixelWidth * pixelHeight; @@ -157,8 +157,8 @@ function positionWidgets(widgets: SixelDashboardWidget[]): PositionedWidget[] { let nextY = Math.max( 0, ...widgets.flatMap((widget) => - widget.layout ? [widget.layout.y + widget.layout.h] : [] - ) + widget.layout ? [widget.layout.y + widget.layout.h] : [], + ), ); return widgets.map((widget) => { @@ -175,18 +175,18 @@ function positionWidgets(widgets: SixelDashboardWidget[]): PositionedWidget[] { function drawWidget( image: ReturnType<typeof createPixelCanvas>, positioned: PositionedWidget, - options: RenderSixelDashboardOptions + options: RenderSixelDashboardOptions, ): void { const { widget, layout } = positioned; const x = Math.floor((layout.x / GRID_COLUMNS) * image.width); const y = layout.y * LINES_PER_GRID_UNIT * options.cellHeight; const width = Math.max( 1, - Math.floor((layout.w / GRID_COLUMNS) * image.width) + Math.floor((layout.w / GRID_COLUMNS) * image.width), ); const height = Math.max( 1, - layout.h * LINES_PER_GRID_UNIT * options.cellHeight + layout.h * LINES_PER_GRID_UNIT * options.cellHeight, ); drawFrame(image, { x, y, width, height, title: widget.title, options }); @@ -235,7 +235,7 @@ function drawFrame( height: number; title: string; options: RenderSixelDashboardOptions; - } + }, ): void { const { x, y, width, height, title } = options; drawPixelRect(image, { x, y, width, height: 1, color: FRAME_COLOR }); @@ -267,13 +267,13 @@ function drawFrame( /** Early-out for empty data or heatmap (which has its own rasterizer). */ function tryRenderSpecialChart( image: ReturnType<typeof createPixelCanvas>, - options: Parameters<typeof drawChartContent>[1] + options: Parameters<typeof drawChartContent>[1], ): boolean { if (options.data.series.length === 0) { const label = "NO DATA"; const maxColumns = Math.max( 1, - Math.floor(options.width / options.cellWidth) + Math.floor(options.width / options.cellWidth), ); const labelColumns = Math.min(label.length, maxColumns); drawPixelText(image, label, { @@ -281,7 +281,7 @@ function tryRenderSpecialChart( options.x + Math.max( 0, - Math.floor((options.width - labelColumns * options.cellWidth) / 2) + Math.floor((options.width - labelColumns * options.cellWidth) / 2), ), y: options.y + @@ -323,7 +323,7 @@ function drawChartContent( height: number; cellWidth: number; cellHeight: number; - } + }, ): void { if (tryRenderSpecialChart(image, options)) { return; @@ -336,7 +336,7 @@ function drawChartContent( } const contentRows = Math.max( 1, - Math.floor(options.height / options.cellHeight) + Math.floor(options.height / options.cellHeight), ); const hasLegend = contentRows >= 4; const hasAxisLabels = contentRows >= 5; @@ -388,7 +388,7 @@ function drawChartContent( cellHeight: options.cellHeight, maxColumns: gutterColumns, color: FRAME_COLOR, - } + }, ); drawPixelText(image, "0", { x: options.x, @@ -438,7 +438,7 @@ function drawChartLabels( width: number; cellWidth: number; cellHeight: number; - } + }, ): void { const columns = Math.max(1, Math.floor(options.width / options.cellWidth)); const firstTimestamp = options.data.series[0]?.values[0]?.timestamp; @@ -484,7 +484,7 @@ function drawLegend( width: number; cellWidth: number; cellHeight: number; - } + }, ): void { let column = 0; const maxColumns = Math.max(1, Math.floor(options.width / options.cellWidth)); @@ -496,7 +496,7 @@ function drawLegend( const availableColumns = maxColumns - column - 2; const label = truncateLegendLabel( formatLegendLabel(series.label), - Math.min(18, availableColumns) + Math.min(18, availableColumns), ); const requiredColumns = Math.min(maxColumns, label.length + 2); if (column + requiredColumns > maxColumns) { @@ -551,7 +551,7 @@ function truncateLegendLabel(label: string, maxColumns: number): string { /** Format a number compactly enough for the chart-axis gutter. */ function formatChartValue( value: number, - unit: string | null | undefined + unit: string | null | undefined, ): string { const formatted = new Intl.NumberFormat("en", { notation: "compact", @@ -563,7 +563,7 @@ function formatChartValue( /** Format timestamps using the same span-aware form as the character dashboard. */ export function formatTimestamp( timestamp: number | undefined, - spanDays: number + spanDays: number, ): string { if (timestamp === undefined) { return ""; diff --git a/packages/cli/src/lib/formatters/sixel-timeseries.ts b/packages/cli/src/lib/formatters/sixel-timeseries.ts index 6df80358a..20875fc1a 100644 --- a/packages/cli/src/lib/formatters/sixel-timeseries.ts +++ b/packages/cli/src/lib/formatters/sixel-timeseries.ts @@ -32,7 +32,7 @@ const DEFAULT_HEIGHT = 120; */ export function renderTimeseriesAsSixel( data: TimeseriesResult, - opts: RenderSixelOpts = {} + opts: RenderSixelOpts = {}, ): string | undefined { const { maxPixelWidth = DEFAULT_WIDTH, diff --git a/packages/cli/src/lib/formatters/spleen-font.ts b/packages/cli/src/lib/formatters/spleen-font.ts index bcce8cc86..f05b3adb1 100644 --- a/packages/cli/src/lib/formatters/spleen-font.ts +++ b/packages/cli/src/lib/formatters/spleen-font.ts @@ -63,13 +63,13 @@ const DASHBOARD_GLYPH_INDEX = new Map<number, number>( DASHBOARD_CODE_POINTS.map((codePoint, index) => [ codePoint, LAST_ASCII_CODE_POINT - FIRST_ASCII_CODE_POINT + 1 + index, - ]) + ]), ); const SPLEEN_FONT_ASSET_KEY = "dist-build/spleen-8x16.bin"; const SPLEEN_FONT_ASSET_URL = new URL( "./assets/spleen-8x16.bin", - import.meta.url + import.meta.url, ); const EXPECTED_GLYPH_ROWS = (LAST_ASCII_CODE_POINT - @@ -120,7 +120,7 @@ function loadGlyphRows(): Uint8Array { new Uint8Array(readFileSync(SPLEEN_FONT_ASSET_URL)); if (glyphRows.length !== EXPECTED_GLYPH_ROWS) { throw new Error( - `Invalid Spleen dashboard font asset: expected ${EXPECTED_GLYPH_ROWS} bytes, got ${glyphRows.length}` + `Invalid Spleen dashboard font asset: expected ${EXPECTED_GLYPH_ROWS} bytes, got ${glyphRows.length}`, ); } return glyphRows; diff --git a/packages/cli/src/lib/formatters/sql.ts b/packages/cli/src/lib/formatters/sql.ts index 46caf3c65..2b73157da 100644 --- a/packages/cli/src/lib/formatters/sql.ts +++ b/packages/cli/src/lib/formatters/sql.ts @@ -88,7 +88,7 @@ export function colorizeSql(sql: string): string { return sql; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const tokens = parser.parse(sql); return tokens.map(colorizeToken).join(""); @@ -125,7 +125,7 @@ export function formatSqlBlock(sql: string): string { return `\n─── Query ───\n\n${sql}\n`; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const tokens = parser.parse(sql); // Use sqlish's string formatter for structural formatting (newlines at keywords) diff --git a/packages/cli/src/lib/formatters/table.ts b/packages/cli/src/lib/formatters/table.ts index 165d110f0..d9a655188 100644 --- a/packages/cli/src/lib/formatters/table.ts +++ b/packages/cli/src/lib/formatters/table.ts @@ -48,14 +48,14 @@ export type Column<T> = { */ export function buildMarkdownTable<T>( items: T[], - columns: Column<T>[] + columns: Column<T>[], ): string { const header = `| ${columns.map((c) => c.header).join(" | ")} |`; const separator = `| ${columns.map((c) => (c.align === "right" ? "---:" : "---")).join(" | ")} |`; const rows = items .map( (item) => - `| ${columns.map((c) => stripColorTags(c.value(item))).join(" | ")} |` + `| ${columns.map((c) => stripColorTags(c.value(item))).join(" | ")} |`, ) .join("\n"); return `${header}\n${separator}\n${rows}`; @@ -106,7 +106,7 @@ export type WriteTableOptions = { export function formatTable<T>( items: T[], columns: Column<T>[], - options?: WriteTableOptions + options?: WriteTableOptions, ): string { const headers = columns.map((c) => c.header); const alignments: Alignment[] = columns.map((c) => c.align ?? "left"); @@ -114,7 +114,7 @@ export function formatTable<T>( const shrinkable = columns.map((c) => c.shrinkable ?? true); const rows = items.map((item) => - columns.map((c) => renderInlineMarkdown(c.value(item))) + columns.map((c) => renderInlineMarkdown(c.value(item))), ); return renderTextTable(headers, rows, { @@ -140,7 +140,7 @@ export function writeTable<T>( stdout: Writer, items: T[], columns: Column<T>[], - options?: WriteTableOptions + options?: WriteTableOptions, ): void { stdout.write(formatTable(items, columns, options)); } diff --git a/packages/cli/src/lib/formatters/text-table.ts b/packages/cli/src/lib/formatters/text-table.ts index de953407d..aa50970ad 100644 --- a/packages/cli/src/lib/formatters/text-table.ts +++ b/packages/cli/src/lib/formatters/text-table.ts @@ -12,7 +12,7 @@ import stringWidth from "string-width"; /** Matches one or more trailing ANSI SGR escape sequences at end of string. */ -// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape detection requires matching \x1b +// oxlint-disable-next-line no-control-regex -- ANSI escape detection requires matching \x1b const TRAILING_ANSI_RE = /(?:\x1b\[[0-9;]*m)+$/; /** @@ -94,7 +94,7 @@ export type TextTableOptions = { export function renderTextTable( headers: string[], rows: string[][], - options: TextTableOptions = {} + options: TextTableOptions = {}, ): string { const { borderStyle = "rounded", @@ -170,7 +170,7 @@ function measureIntrinsicWidths( headers: string[], rows: string[][], colCount: number, - ctx: { cellPadding: number; minWidths: number[] } + ctx: { cellPadding: number; minWidths: number[] }, ): number[] { const { cellPadding, minWidths } = ctx; const pad = cellPadding * 2; @@ -210,7 +210,7 @@ function fitColumns( fitter: "proportional" | "balanced"; minWidths: number[]; shrinkable: boolean[]; - } + }, ): number[] { const { cellPadding, fitter, minWidths, shrinkable: shrinkFlags } = ctx; const totalIntrinsic = intrinsicWidths.reduce((s, w) => s + w, 0); @@ -224,7 +224,7 @@ function fitColumns( const isFixed = intrinsicWidths.map((_, i) => shrinkFlags[i] === false); const fixedTotal = intrinsicWidths.reduce( (s, w, i) => s + (isFixed[i] ? w : 0), - 0 + 0, ); const elasticTarget = maxContentWidth - fixedTotal; const elasticWidths = intrinsicWidths.filter((_, i) => !isFixed[i]); @@ -261,14 +261,14 @@ function fitProportional( widths: number[], target: number, cellPadding: number, - minWidths: number[] = [] + minWidths: number[] = [], ): number[] { const globalMin = 1 + cellPadding * 2; const colMins = widths.map((_, i) => - Math.max(globalMin, (minWidths[i] ?? 0) + cellPadding * 2) + Math.max(globalMin, (minWidths[i] ?? 0) + cellPadding * 2), ); const baseWidths = widths.map((w, i) => - Math.max(colMins[i] ?? globalMin, Math.floor(w)) + Math.max(colMins[i] ?? globalMin, Math.floor(w)), ); const totalBase = baseWidths.reduce((s, w) => s + w, 0); @@ -277,7 +277,7 @@ function fitProportional( } const floorWidths = baseWidths.map((w, i) => - Math.min(w, (colMins[i] ?? globalMin) + 1) + Math.min(w, (colMins[i] ?? globalMin) + 1), ); const floorTotal = floorWidths.reduce((s, w) => s + w, 0); const clampedTarget = Math.max(floorTotal, target); @@ -311,14 +311,14 @@ function fitBalanced( widths: number[], target: number, cellPadding: number, - minWidths: number[] = [] + minWidths: number[] = [], ): number[] { const globalMin = 1 + cellPadding * 2; const colMins = widths.map((_, i) => - Math.max(globalMin, (minWidths[i] ?? 0) + cellPadding * 2) + Math.max(globalMin, (minWidths[i] ?? 0) + cellPadding * 2), ); const baseWidths = widths.map((w, i) => - Math.max(colMins[i] ?? globalMin, Math.floor(w)) + Math.max(colMins[i] ?? globalMin, Math.floor(w)), ); const totalBase = baseWidths.reduce((s, w) => s + w, 0); @@ -328,7 +328,7 @@ function fitBalanced( const evenShare = Math.max(globalMin, Math.floor(target / baseWidths.length)); const floorWidths = baseWidths.map((w, i) => - Math.min(w, Math.max(evenShare, colMins[i] ?? globalMin)) + Math.min(w, Math.max(evenShare, colMins[i] ?? globalMin)), ); const floorTotal = floorWidths.reduce((s, w) => s + w, 0); const clampedTarget = Math.max(floorTotal, target); @@ -367,7 +367,7 @@ type ShrinkParams = { * * Ported from OpenTUI's allocateShrinkByWeight. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: ported algorithm +// ported algorithm function allocateShrink(params: ShrinkParams): number[] { const { baseWidths, floorWidths, shrinkable, targetShrink, mode } = params; const computeWeight = (v: number) => { @@ -383,7 +383,9 @@ function allocateShrink(params: ShrinkParams): number[] { return [...floorWidths]; } + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const shrink = new Array<number>(baseWidths.length).fill(0); + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const fractions = new Array<number>(baseWidths.length).fill(0); let usedShrink = 0; @@ -429,7 +431,7 @@ function allocateShrink(params: ShrinkParams): number[] { } return baseWidths.map((w, i) => - Math.max(floorWidths[i] ?? 0, w - (shrink[i] ?? 0)) + Math.max(floorWidths[i] ?? 0, w - (shrink[i] ?? 0)), ); } @@ -461,7 +463,7 @@ function wrapRow( cells: string[], columnWidths: number[], cellPadding: number, - truncate: boolean + truncate: boolean, ): string[][] { const wrappedCells: string[][] = []; for (let c = 0; c < columnWidths.length; c++) { @@ -502,7 +504,7 @@ function padCell( text: string, width: number, align: Alignment, - padding: number + padding: number, ): string { const contentWidth = width - padding * 2; const textWidth = stringWidth(text); @@ -552,7 +554,7 @@ type RowRenderContext = { */ function renderRowLines( wrappedCells: string[][], - ctx: RowRenderContext + ctx: RowRenderContext, ): string[] { const { columnWidths, alignments, cellPadding, vert } = ctx; const rowHeight = Math.max(1, ...wrappedCells.map((c) => c.length)); @@ -613,8 +615,8 @@ function renderGrid(params: GridParams): string { junction: border.topT, right: border.topRight, horizontal: hz, - }) - ) + }), + ), ); // Colored vertical border for row rendering @@ -655,8 +657,8 @@ function renderGrid(params: GridParams): string { junction: border.bottomT, right: border.bottomRight, horizontal: hz, - }) - ) + }), + ), ); return `${lines.join("\n")}\n`; @@ -665,7 +667,7 @@ function renderGrid(params: GridParams): string { /** Build a horizontal border line from column widths and junction characters. */ function horizontalLine( columnWidths: number[], - chars: { left: string; junction: string; right: string; horizontal: string } + chars: { left: string; junction: string; right: string; horizontal: string }, ): string { const segments = columnWidths.map((w) => chars.horizontal.repeat(w)); return `${chars.left}${segments.join(chars.junction)}${chars.right}`; @@ -742,7 +744,7 @@ export class StreamingTable { headers, hintRows, colCount, - { cellPadding, minWidths } + { cellPadding, minWidths }, ); const borderOverhead = 2 + (colCount - 1); @@ -780,7 +782,7 @@ export class StreamingTable { junction: border.topT, right: border.topRight, horizontal: hz, - }) + }), ); // Header cells @@ -796,7 +798,7 @@ export class StreamingTable { cellTexts.push(padCell(text, colW, align, cellPadding)); } lines.push( - `${border.vertical}${cellTexts.join(border.vertical)}${border.vertical}` + `${border.vertical}${cellTexts.join(border.vertical)}${border.vertical}`, ); } @@ -807,7 +809,7 @@ export class StreamingTable { junction: border.cross, right: border.rightT, horizontal: hz, - }) + }), ); return `${lines.join("\n")}\n`; @@ -833,7 +835,7 @@ export class StreamingTable { cellTexts.push(padCell(text, colW, align, cellPadding)); } lines.push( - `${border.vertical}${cellTexts.join(border.vertical)}${border.vertical}` + `${border.vertical}${cellTexts.join(border.vertical)}${border.vertical}`, ); } diff --git a/packages/cli/src/lib/formatters/time-utils.ts b/packages/cli/src/lib/formatters/time-utils.ts index 16529223a..f7771ca50 100644 --- a/packages/cli/src/lib/formatters/time-utils.ts +++ b/packages/cli/src/lib/formatters/time-utils.ts @@ -96,7 +96,7 @@ function pluralize(value: number, singular: string): string { * @returns Compact duration string (e.g., `"2m 5s"`, `"1d"`, `"—"`) */ export function formatDurationCompact( - seconds: number | null | undefined + seconds: number | null | undefined, ): string { if (seconds === null || seconds === undefined) { return "—"; diff --git a/packages/cli/src/lib/formatters/trace.ts b/packages/cli/src/lib/formatters/trace.ts index 6d9394fbd..865fb9a8d 100644 --- a/packages/cli/src/lib/formatters/trace.ts +++ b/packages/cli/src/lib/formatters/trace.ts @@ -72,7 +72,7 @@ const TRACE_TABLE_COLS = ["Trace ID", "Transaction", "Duration:", "When"]; * @returns `[traceId, transaction, duration, when]` markdown-safe strings */ export function buildTraceRowCells( - item: TransactionListItem + item: TransactionListItem, ): [string, string, string, string] { return [ `\`${item.trace}\``, @@ -121,13 +121,13 @@ export function formatTracesHeader(): string { */ export function formatTraceTable(items: TransactionListItem[]): string { const headers = TRACE_TABLE_COLS.map((c) => - c.endsWith(":") ? c.slice(0, -1) : c + c.endsWith(":") ? c.slice(0, -1) : c, ); const alignments = TRACE_TABLE_COLS.map((c) => - c.endsWith(":") ? ("right" as const) : ("left" as const) + c.endsWith(":") ? ("right" as const) : ("left" as const), ); const rows = items.map((item) => - buildTraceRowCells(item).map((c) => renderInlineMarkdown(c)) + buildTraceRowCells(item).map((c) => renderInlineMarkdown(c)), ); return renderTextTable(headers, rows, { alignments }); } @@ -172,7 +172,7 @@ function walkSpanTree( projects: Set<string>; rootTransaction?: string; rootOp?: string; - } + }, ): void { state.spanCount += 1; @@ -216,7 +216,7 @@ function walkSpanTree( */ export function computeTraceSummary( traceId: string, - spans: TraceSpan[] + spans: TraceSpan[], ): TraceSummary { const state = { spanCount: 0, @@ -317,12 +317,12 @@ export type FoundSpan = { */ export function findSpanById( spans: TraceSpan[], - spanId: string + spanId: string, ): FoundSpan | null { function search( span: TraceSpan, depth: number, - ancestors: TraceSpan[] + ancestors: TraceSpan[], ): FoundSpan | null { if (span.span_id?.toLowerCase() === spanId) { return { span, depth, ancestors }; @@ -392,7 +392,7 @@ export function translateSpanQuery(query: string): string { */ export function spanListItemToFlatSpan( item: SpanListItem, - extraFieldNames?: string[] + extraFieldNames?: string[], ): FlatSpan { const flat: FlatSpan = { span_id: item.id, @@ -493,7 +493,7 @@ function buildExtraColumns(extraColumns: string[]): Column<FlatSpan>[] { */ export function formatSpanTable( spans: FlatSpan[], - extraColumns?: string[] + extraColumns?: string[], ): string { // Auto-add Project column when spans come from multiple projects const projects = new Set(spans.map((s) => s.project_slug).filter(Boolean)); @@ -567,7 +567,7 @@ function formatAncestorChain(ancestors: TraceSpan[]): string { const aDesc = a.description || a.transaction || "(no description)"; const colorizedDesc = isDbSpanOp(aOp) ? colorizeSql(aDesc) : aDesc; lines.push( - `${indent}${colorTag("muted", aOp)} — ${escapeMarkdownInline(colorizedDesc)} ${colorTag("muted", `(${a.span_id})`)}` + `${indent}${colorTag("muted", aOp)} — ${escapeMarkdownInline(colorizedDesc)} ${colorTag("muted", `(${a.span_id})`)}`, ); } return `${renderMarkdown(lines.join("\n"))}\n`; @@ -585,7 +585,7 @@ export function formatSpanDetails( span: TraceSpan, ancestors: TraceSpan[], traceId: string, - detail?: TraceItemDetail + detail?: TraceItemDetail, ): string { const kvRows = buildSpanKvRows(span, traceId); @@ -619,10 +619,10 @@ const MAX_DISPLAY_ATTRS = 10; */ function appendAttributeRows( kvRows: [string, string][], - detail: TraceItemDetail + detail: TraceItemDetail, ): void { const attrs = detail.attributes.filter( - (a) => !(REDUNDANT_DETAIL_ATTRS.has(a.name) || a.name.startsWith("tags[")) + (a) => !(REDUNDANT_DETAIL_ATTRS.has(a.name) || a.name.startsWith("tags[")), ); if (attrs.length === 0) { return; diff --git a/packages/cli/src/lib/fuzzy.ts b/packages/cli/src/lib/fuzzy.ts index 38d977f61..63bd6e905 100644 --- a/packages/cli/src/lib/fuzzy.ts +++ b/packages/cli/src/lib/fuzzy.ts @@ -35,7 +35,7 @@ export function levenshtein(a: string, b: string): number { Math.min( dp.get(key(i - 1, j - 1)) ?? 0, dp.get(key(i - 1, j)) ?? 0, - dp.get(key(i, j - 1)) ?? 0 + dp.get(key(i, j - 1)) ?? 0, ); dp.set(key(i, j), cost); } @@ -79,7 +79,7 @@ export type FuzzyMatchOptions = { export function fuzzyMatch( partial: string, candidates: readonly string[], - opts?: FuzzyMatchOptions + opts?: FuzzyMatchOptions, ): string[] { if (partial === "") { const sorted = [...candidates].sort(); @@ -108,7 +108,7 @@ export function fuzzyMatch( } scored.sort( - (a, b) => a.score - b.score || a.candidate.localeCompare(b.candidate) + (a, b) => a.score - b.score || a.candidate.localeCompare(b.candidate), ); const results = scored.map((s) => s.candidate); diff --git a/packages/cli/src/lib/ghcr.ts b/packages/cli/src/lib/ghcr.ts index 5bca7c061..69312b589 100644 --- a/packages/cli/src/lib/ghcr.ts +++ b/packages/cli/src/lib/ghcr.ts @@ -68,7 +68,7 @@ function isRetryableError(error: Error): boolean { */ function buildSignal( timeout: number, - externalSignal?: AbortSignal + externalSignal?: AbortSignal, ): AbortSignal { const timeoutSignal = AbortSignal.timeout(timeout); return externalSignal @@ -78,7 +78,7 @@ function buildSignal( function rethrowExternalAbort( _error: unknown, - externalSignal?: AbortSignal + externalSignal?: AbortSignal, ): void { if (externalSignal?.aborted) { throw externalSignal?.reason; @@ -108,7 +108,7 @@ async function fetchWithRetry( url: string, init: RequestInit, context: string, - options?: RetryOptions + options?: RetryOptions, ): Promise<Response> { const timeout = options?.timeout ?? GHCR_REQUEST_TIMEOUT; const externalSignal = options?.signal; @@ -135,7 +135,7 @@ async function fetchWithRetry( } throw new UpgradeTransportError( - `${context}: ${lastError?.message ?? "unknown error"}` + `${context}: ${lastError?.message ?? "unknown error"}`, ); } @@ -159,7 +159,7 @@ export class GhcrManifestHttpError extends UpgradeError { constructor(tag: string, status: number) { super( "network_error", - `Failed to fetch manifest for tag "${tag}": HTTP ${status}` + `Failed to fetch manifest for tag "${tag}": HTTP ${status}`, ); this.name = "GhcrManifestHttpError"; this.status = status; @@ -257,7 +257,7 @@ function isOciManifest(value: unknown): value is OciManifest { */ export async function getAnonymousToken( sourceOrSignal: UpgradeSource | AbortSignal = PRIMARY_UPGRADE_SOURCE, - signal?: AbortSignal + signal?: AbortSignal, ): Promise<string> { const source = "ghcrRepo" in sourceOrSignal ? sourceOrSignal : PRIMARY_UPGRADE_SOURCE; @@ -267,20 +267,20 @@ export async function getAnonymousToken( url, { headers: { "User-Agent": getUserAgent() } }, "Failed to connect to GHCR", - { signal: externalSignal } + { signal: externalSignal }, ); if (!response.ok) { throw new UpgradeError( "network_error", - `GHCR token exchange failed: HTTP ${response.status}` + `GHCR token exchange failed: HTTP ${response.status}`, ); } const data = await parseUpgradeJson( response, externalSignal, - "GHCR token exchange returned invalid metadata" + "GHCR token exchange returned invalid metadata", ); if ( typeof data !== "object" || @@ -292,7 +292,7 @@ export async function getAnonymousToken( ) { throw new UpgradeError( "network_error", - "GHCR token exchange returned no token" + "GHCR token exchange returned no token", ); } @@ -311,7 +311,7 @@ export async function fetchManifest( token: string, tag: string, signal?: AbortSignal, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<OciManifest> { const url = `${GHCR_REGISTRY}/v2/${source.ghcrRepo}/manifests/${tag}`; const response = await fetchWithRetry( @@ -324,7 +324,7 @@ export async function fetchManifest( }, }, `Failed to fetch manifest for tag "${tag}"`, - { signal } + { signal }, ); if (!response.ok) { @@ -334,12 +334,12 @@ export async function fetchManifest( const data = await parseUpgradeJson( response, signal, - `Manifest for tag "${tag}" returned invalid metadata` + `Manifest for tag "${tag}" returned invalid metadata`, ); if (!isOciManifest(data)) { throw new UpgradeError( "network_error", - `Manifest for tag "${tag}" returned invalid metadata` + `Manifest for tag "${tag}" returned invalid metadata`, ); } return data; @@ -357,7 +357,7 @@ export async function fetchManifest( export async function fetchNightlyManifest( token: string, signal?: AbortSignal, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<OciManifest> { return await fetchManifest(token, GHCR_TAG, signal, source); } @@ -376,13 +376,13 @@ export function getNightlyVersion(manifest: OciManifest): string { if (!version) { throw new UpgradeError( "network_error", - "Nightly manifest has no version annotation" + "Nightly manifest has no version annotation", ); } if (semverValid(version) === null || !NIGHTLY_VERSION_REGEX.test(version)) { throw new UpgradeError( "network_error", - "Nightly manifest has invalid version annotation" + "Nightly manifest has invalid version annotation", ); } return version; @@ -401,15 +401,15 @@ export function getNightlyVersion(manifest: OciManifest): string { */ export function findLayerByFilename( manifest: OciManifest, - filename: string + filename: string, ): OciLayer { const layer = manifest.layers.find( - (l) => l.annotations?.["org.opencontainers.image.title"] === filename + (l) => l.annotations?.["org.opencontainers.image.title"] === filename, ); if (!layer) { throw new UpgradeError( "version_not_found", - `No nightly build found for ${filename}` + `No nightly build found for ${filename}`, ); } return layer; @@ -433,7 +433,7 @@ export async function downloadNightlyBlob( token: string, digest: string, signal?: AbortSignal, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<Response> { const blobUrl = `${GHCR_REGISTRY}/v2/${source.ghcrRepo}/blobs/${digest}`; @@ -454,7 +454,7 @@ export async function downloadNightlyBlob( const msg = error instanceof Error ? error.message : String(error); throw new UpgradeError( "network_error", - `Failed to connect to GHCR: ${msg}` + `Failed to connect to GHCR: ${msg}`, ); } @@ -473,7 +473,7 @@ export async function downloadNightlyBlob( if (!redirectUrl) { throw new UpgradeError( "network_error", - `GHCR blob redirect (${blobResponse.status}) had no Location header` + `GHCR blob redirect (${blobResponse.status}) had no Location header`, ); } @@ -496,14 +496,14 @@ export async function downloadNightlyBlob( const msg = error instanceof Error ? error.message : String(error); throw new UpgradeError( "network_error", - `Failed to download from blob storage: ${msg}` + `Failed to download from blob storage: ${msg}`, ); } if (!redirectResponse.ok) { throw new UpgradeError( "network_error", - `Blob storage download failed: HTTP ${redirectResponse.status}` + `Blob storage download failed: HTTP ${redirectResponse.status}`, ); } @@ -512,7 +512,7 @@ export async function downloadNightlyBlob( throw new UpgradeError( "network_error", - `Unexpected GHCR blob response: HTTP ${blobResponse.status}` + `Unexpected GHCR blob response: HTTP ${blobResponse.status}`, ); } @@ -531,7 +531,7 @@ async function fetchTagPage( token: string, lastTag?: string, signal?: AbortSignal, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<string[]> { let url = `${GHCR_REGISTRY}/v2/${source.ghcrRepo}/tags/list?n=${TAGS_PAGE_SIZE}`; if (lastTag) { @@ -547,25 +547,25 @@ async function fetchTagPage( }, }, "Failed to list GHCR tags", - { signal } + { signal }, ); if (!response.ok) { throw new UpgradeError( "network_error", - `Failed to list GHCR tags: HTTP ${response.status}` + `Failed to list GHCR tags: HTTP ${response.status}`, ); } const data = await parseUpgradeJson( response, signal, - "GHCR tag list returned invalid metadata" + "GHCR tag list returned invalid metadata", ); if (typeof data !== "object" || data === null) { throw new UpgradeError( "network_error", - "GHCR tag list returned invalid metadata" + "GHCR tag list returned invalid metadata", ); } if (!("tags" in data)) { @@ -579,7 +579,7 @@ async function fetchTagPage( ) { throw new UpgradeError( "network_error", - "GHCR tag list returned invalid metadata" + "GHCR tag list returned invalid metadata", ); } return data.tags; @@ -600,7 +600,7 @@ export async function listTags( token: string, prefix?: string, signal?: AbortSignal, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<string[]> { const allTags: string[] = []; const visitedCursors = new Set<string>(); @@ -626,7 +626,7 @@ export async function listTags( if (!nextTag || visitedCursors.has(nextTag)) { throw new UpgradeError( "network_error", - "GHCR tag pagination returned a repeated cursor" + "GHCR tag pagination returned a repeated cursor", ); } visitedCursors.add(nextTag); @@ -652,7 +652,7 @@ export async function downloadLayerBlob( token: string, digest: string, signal?: AbortSignal, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<ArrayBuffer> { const response = await downloadNightlyBlob(token, digest, signal, source); return response.arrayBuffer(); diff --git a/packages/cli/src/lib/git.ts b/packages/cli/src/lib/git.ts index 546c83280..bbc5634cb 100644 --- a/packages/cli/src/lib/git.ts +++ b/packages/cli/src/lib/git.ts @@ -91,7 +91,7 @@ function git(args: string[], cwd?: string): string { * @returns true if inside a git work tree */ export function isInsideGitWorkTree(cwd?: string): boolean { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { git(["rev-parse", "--is-inside-work-tree"], cwd); return true; @@ -110,7 +110,7 @@ export function isInsideGitWorkTree(cwd?: string): boolean { * @returns Array of formatted status lines (e.g., `["- M src/index.ts", "- ?? new-file.ts"]`) */ export function getUncommittedFiles(cwd?: string): string[] { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const raw = git(["status", "--porcelain=v1"], cwd); if (!raw) { @@ -142,7 +142,7 @@ export function getHeadCommit(cwd?: string): string { } catch { throw new ValidationError( "Not a git repository. Run this command from within a git working tree.", - "git" + "git", ); } } @@ -154,7 +154,7 @@ export function getHeadCommit(cwd?: string): string { * @returns true if the repository is shallow */ export function isShallowRepository(cwd?: string): boolean { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return git(["rev-parse", "--is-shallow-repository"], cwd) === "true"; } catch { @@ -186,7 +186,7 @@ const NUL = "\x00"; */ export function getCommitLog( cwd?: string, - options: { from?: string; depth?: number; paths?: string[] } = {} + options: { from?: string; depth?: number; paths?: string[] } = {}, ): GitCommit[] { const { from, depth = 20, paths } = options; @@ -199,7 +199,7 @@ export function getCommitLog( `--from must be a git ref, not a CLI flag (received '${from}').`, ["sentry release set-commits 1.0.0 --from v0.9.0"], "from", - "Git refs cannot start with '-'. Tokens like '--format=x' are CLI flags, not refs — use a tag or commit (e.g. v0.9.0)." + "Git refs cannot start with '-'. Tokens like '--format=x' are CLI flags, not refs — use a tag or commit (e.g. v0.9.0).", ); } @@ -225,7 +225,7 @@ export function getCommitLog( try { raw = git( ["log", `--format=${format}`, ...maxCount, range, ...pathspec], - cwd + cwd, ); } catch (error) { if (from && isUnknownGitRefError(error)) { @@ -236,7 +236,7 @@ export function getCommitLog( "sentry release set-commits <org>/<version> --from v0.9.0", ], "from", - "Range is always <ref>..HEAD — checkout the current release tag first." + "Range is always <ref>..HEAD — checkout the current release tag first.", ); } throw error; @@ -269,7 +269,7 @@ export function getCommitLog( * @returns `owner/repo` string, or undefined if no origin remote */ export function getRepositoryName(cwd?: string): string | undefined { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = git(["remote", "get-url", "origin"], cwd); return parseRemoteUrl(url); @@ -285,7 +285,7 @@ export function getRepositoryName(cwd?: string): string | undefined { * @returns The remote URL, or undefined when there is no origin remote */ export function getRemoteUrl(cwd?: string): string | undefined { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return git(["remote", "get-url", "origin"], cwd); } catch { @@ -300,7 +300,7 @@ export function getRemoteUrl(cwd?: string): string | undefined { * @returns The branch name, or undefined when detached (HEAD) or not a repo */ export function getCurrentBranch(cwd?: string): string | undefined { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const ref = git(["rev-parse", "--abbrev-ref", "HEAD"], cwd); return ref && ref !== "HEAD" ? ref : undefined; @@ -322,7 +322,7 @@ export function getMergeBase(ref: string, cwd?: string): string | undefined { if (ref.startsWith("-")) { return; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return git(["merge-base", "HEAD", ref], cwd) || undefined; } catch { @@ -340,7 +340,7 @@ export function getMergeBase(ref: string, cwd?: string): string | undefined { * (not set, shallow clone, or not a repo). */ export function getRemoteDefaultBranch(cwd?: string): string | undefined { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const ref = git(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd); const prefix = "refs/remotes/origin/"; @@ -372,7 +372,7 @@ const DOT_GIT_SUFFIX_RE = /\.git$/; export function parseRemoteUrl(url: string): string | undefined { // Try URL parsing first — handles https://, ssh://, git:// protocols // (including ssh://git@host:port/path which would confuse the SCP regex) - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = new URL(url); const path = parsed.pathname @@ -405,10 +405,10 @@ export function parseRemoteUrl(url: string): string | undefined { * @returns The repo name and which remote it came from, or undefined */ export function inferRepositoryName( - cwd?: string + cwd?: string, ): { name: string; remote: string } | undefined { for (const remote of ["upstream", "origin"]) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = git(["remote", "get-url", remote], cwd); const name = parseRemoteUrl(url); @@ -430,7 +430,7 @@ export function inferRepositoryName( * @returns The branch name, or "main" as fallback */ export function inferDefaultBranch(remote: string, cwd?: string): string { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const output = git(["symbolic-ref", `refs/remotes/${remote}/HEAD`], cwd); // refs/remotes/origin/main → main diff --git a/packages/cli/src/lib/help.ts b/packages/cli/src/lib/help.ts index 5be883026..e9d1e2227 100644 --- a/packages/cli/src/lib/help.ts +++ b/packages/cli/src/lib/help.ts @@ -169,7 +169,7 @@ function formatCommonFlags(): string { } const padding = 4; const labels = COMMON_FLAGS.map((f) => - f.short ? `${f.short}, ${f.long}` : ` ${f.long}` + f.short ? `${f.short}, ${f.long}` : ` ${f.long}`, ); const maxLabelLength = Math.max(...labels.map((l) => l.length)); return COMMON_FLAGS.map((f, i) => { @@ -190,7 +190,7 @@ function formatEnvVars(): string { } const padding = 4; const maxNameLength = Math.max( - ...TOP_LEVEL_ENV_VARS.map((v) => v.name.length) + ...TOP_LEVEL_ENV_VARS.map((v) => v.name.length), ); return TOP_LEVEL_ENV_VARS.map((v) => { const namePadded = v.name.padEnd(maxNameLength + padding); @@ -363,7 +363,7 @@ export function introspectAllCommands(): { * with optional fuzzy suggestions if the path doesn't resolve. */ export function introspectCommand( - commandPath: string[] + commandPath: string[], ): CommandInfo | RouteInfo | { error: string; suggestions?: string[] } { const routeMap = routes as unknown as RouteMap; const resolved = resolveCommandPath(routeMap, commandPath); @@ -405,7 +405,7 @@ export function introspectCommand( */ export function renderJsonHelp( prefix: readonly string[], - unprocessedInputs: readonly string[] + unprocessedInputs: readonly string[], ): string | undefined { const { hasJson, fields, extraPath } = parseHelpJsonFlags(unprocessedInputs); if (!hasJson) { @@ -498,7 +498,7 @@ export function isRecoverableUnknownCommand(argv: readonly string[]): boolean { * request */ export function rewriteHelpJsonToHelpCommand( - argv: readonly string[] + argv: readonly string[], ): string[] | undefined { let hasHelp = false; const { hasJson, fields, extraPath } = parseHelpJsonFlags(argv); @@ -591,12 +591,12 @@ function consumeValueFlag(inputs: readonly string[], index: number): number { */ const VALUE_FLAG_TOKENS: ReadonlySet<string> = new Set( GLOBAL_FLAGS.filter( - (flag) => flag.kind === "value" && flag.name !== "fields" + (flag) => flag.kind === "value" && flag.name !== "fields", ).flatMap((flag) => flag.short === null ? [`--${flag.name}`] - : [`--${flag.name}`, `-${flag.short}`] - ) + : [`--${flag.name}`, `-${flag.short}`], + ), ); /** @@ -616,7 +616,7 @@ function isValueFlagToken(token: string): boolean { function consumeFieldsFlag( inputs: readonly string[], index: number, - assign: (fields: string[]) => void + assign: (fields: string[]) => void, ): number { const token = inputs[index] ?? ""; if (token.startsWith("--fields=")) { @@ -666,7 +666,7 @@ function formatSuggestionList(items: string[]): string { */ function formatFlagHuman( flag: import("./introspect.js").FlagInfo, - aliases: Record<string, string> + aliases: Record<string, string>, ): string { const alias = Object.entries(aliases).find(([, v]) => v === flag.name)?.[0]; let syntax = `--${flag.name}`; diff --git a/packages/cli/src/lib/hex-id-recovery.ts b/packages/cli/src/lib/hex-id-recovery.ts index 4e7e746b0..6a1881e16 100644 --- a/packages/cli/src/lib/hex-id-recovery.ts +++ b/packages/cli/src/lib/hex-id-recovery.ts @@ -272,7 +272,7 @@ export function preNormalize(input: string): { */ export function stripTrailingNonHex( input: string, - expectedLen: 32 | 16 + expectedLen: 32 | 16, ): { hex: string; stripped: string } | null { if (input.length <= expectedLen) { return null; @@ -362,7 +362,7 @@ export function isOverNestedPath(input: string): boolean { * AuthError is re-thrown so the auto-login flow still triggers. */ export async function resolveRecoveryOrg( - parsed: ParsedOrgProject + parsed: ParsedOrgProject, ): Promise<{ org: string; project?: string } | null> { try { switch (parsed.type) { @@ -402,7 +402,7 @@ export async function resolveRecoveryOrg( */ function checkRetentionExpiry( input: string, - entityType: HexEntityType + entityType: HexEntityType, ): RecoveryResult | null { const retentionDays = RETENTION_DAYS[entityType]; if (retentionDays === null) { @@ -444,7 +444,7 @@ function filterByCandidate(ids: string[], candidate: HexCandidate): string[] { return unique.filter( (id) => id.startsWith(candidate.prefix) && - (!candidate.suffix || id.endsWith(candidate.suffix)) + (!candidate.suffix || id.endsWith(candidate.suffix)), ); } @@ -514,7 +514,7 @@ export const ADAPTERS: Record<HexEntityType, FuzzyLookupAdapter> = { */ async function findTraceBySpanId( spanId: string, - ctx: LookupContext + ctx: LookupContext, ): Promise<string | null> { if (!(ctx.org && ctx.project)) { return null; @@ -538,7 +538,7 @@ async function findTraceBySpanId( function buildSlugHint( input: string, entityType: HexEntityType, - org: string + org: string, ): string { const orgPart = org ? `${org}/` : "<org>/"; const cmd = SLUG_REDIRECT_COMMAND[entityType]; @@ -564,7 +564,7 @@ function buildSlugHint( */ function buildNoMatchHint( entityType: HexEntityType, - period: string | undefined + period: string | undefined, ): string { const window = period ?? SCAN_PERIODS[entityType]; const retention = RETENTION_DAYS[entityType]; @@ -608,7 +608,7 @@ function buildNoMatchHint( function buildRecoveryError( result: Extract<RecoveryResult, { kind: "failed" }>, fallbackError: Error, - options: HandleRecoveryOptions + options: HandleRecoveryOptions, ): Error { const entityCap = options.entityType.charAt(0).toUpperCase() + options.entityType.slice(1); @@ -620,7 +620,7 @@ function buildRecoveryError( `${entityCap} prefix '${result.original}'`, `matches ${candidates.length} ${options.entityType}s`, `Re-run with more characters or the full ID: ${options.canonicalCommand}`, - candidates.map((c) => options.canonicalCommand.replace("<id>", c)) + candidates.map((c) => options.canonicalCommand.replace("<id>", c)), ); } case "no-matches": @@ -630,7 +630,7 @@ function buildRecoveryError( `${entityCap} '${result.original}'`, failedReasonHeadline(result.reason), options.canonicalCommand, - result.hint ? [result.hint] : [] + result.hint ? [result.hint] : [], ); case "api-error": return fallbackError; @@ -650,7 +650,7 @@ function buildRecoveryError( /** Short headline describing why resolution failed, for ResolutionError. */ function failedReasonHeadline( - reason: "no-matches" | "past-retention" | "looks-like-slug" + reason: "no-matches" | "past-retention" | "looks-like-slug", ): string { switch (reason) { case "no-matches": @@ -675,7 +675,7 @@ function failedReasonHeadline( export async function recoverHexId( input: string, entityType: HexEntityType, - ctx: LookupContext + ctx: LookupContext, ): Promise<RecoveryResult> { const result = await recoverHexIdInternal(input, entityType, ctx); recordRecoveryOutcome(entityType, input, result); @@ -690,9 +690,9 @@ export async function recoverHexId( function recordRecoveryOutcome( entityType: HexEntityType, input: string, - result: RecoveryResult + result: RecoveryResult, ): void { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { addBreadcrumb({ category: "hex_id_recovery", @@ -724,7 +724,7 @@ function recordRecoveryOutcome( async function recoverHexIdInternal( input: string, entityType: HexEntityType, - ctx: LookupContext + ctx: LookupContext, ): Promise<RecoveryResult> { const expectedLen: 32 | 16 = entityType === "span" ? 16 : 32; const { cleaned, sentinel } = preNormalize(input); @@ -824,7 +824,7 @@ async function recoverHexIdInternal( function tryPreNormalizedValidId( input: string, cleaned: string, - entityType: HexEntityType + entityType: HexEntityType, ): RecoveryResult | null { const expectedRe = entityType === "span" ? SPAN_ID_RE : HEX_ID_RE; const lowered = input.trim().toLowerCase(); @@ -890,7 +890,7 @@ function describeStrippedParts(raw: string, cleaned: string): string { async function tryCrossEntityRedirect( cleaned: string, entityType: HexEntityType, - ctx: LookupContext + ctx: LookupContext, ): Promise<RecoveryResult | null> { // `SPAN_ID_RE` matches exactly 16 hex chars, so the earlier guard // already rules out 32-char trace IDs — no separate `HEX_ID_RE` check @@ -914,7 +914,7 @@ async function tryCrossEntityRedirect( throw err; } log.debug( - `Cross-entity lookup failed: ${err instanceof Error ? err.message : String(err)}` + `Cross-entity lookup failed: ${err instanceof Error ? err.message : String(err)}`, ); } return null; @@ -925,7 +925,7 @@ async function runFuzzyLookup( input: string, entityType: HexEntityType, candidate: HexCandidate, - ctx: LookupContext + ctx: LookupContext, ): Promise<RecoveryResult> { let raw: string[]; try { @@ -935,7 +935,7 @@ async function runFuzzyLookup( throw err; } log.debug( - `Fuzzy ${entityType} lookup failed: ${err instanceof Error ? err.message : String(err)}` + `Fuzzy ${entityType} lookup failed: ${err instanceof Error ? err.message : String(err)}`, ); return { kind: "failed", original: input, reason: "api-error" }; } @@ -959,7 +959,7 @@ async function runFuzzyLookup( } // `filtered.length === 1` by elimination; the non-null assertion here // is safe and keeps TypeScript's `noUncheckedIndexedAccess` happy. - // biome-ignore lint/style/noNonNullAssertion: exactly one element after the checks above + // oxlint-disable-next-line typescript/no-non-null-assertion -- exactly one element after the checks above const id = filtered[0]!; return { kind: "fuzzy", @@ -987,13 +987,13 @@ async function runFuzzyLookup( export function handleRecoveryResult( result: RecoveryResult, fallbackError: Error, - options: HandleRecoveryOptions + options: HandleRecoveryOptions, ): string { const scoped = logger.withTag(options.logTag); switch (result.kind) { case "stripped": scoped.warn( - `Stripped trailing '${result.stripped}' from ${options.entityType} ID. Using ${result.id}.` + `Stripped trailing '${result.stripped}' from ${options.entityType} ID. Using ${result.id}.`, ); return result.id; case "fuzzy": { @@ -1001,13 +1001,13 @@ export function handleRecoveryResult( ? `prefix ${result.prefix}…${result.suffix}` : `prefix ${result.prefix}`; scoped.warn( - `Interpreting '${result.original}' as ${options.entityType} ${result.id} (matched ${via}).` + `Interpreting '${result.original}' as ${options.entityType} ${result.id} (matched ${via}).`, ); return result.id; } case "redirect": scoped.warn( - `'${result.original}' is a ${result.fromEntity} ID, not a ${result.toEntity} ID. Using the associated ${result.toEntity} ${result.id}.` + `'${result.original}' is a ${result.fromEntity} ID, not a ${result.toEntity} ID. Using the associated ${result.toEntity} ${result.id}.`, ); return result.id; case "failed": diff --git a/packages/cli/src/lib/hex-id.ts b/packages/cli/src/lib/hex-id.ts index f720dca6b..fd530028b 100644 --- a/packages/cli/src/lib/hex-id.ts +++ b/packages/cli/src/lib/hex-id.ts @@ -108,7 +108,7 @@ export function normalizeHexId(value: string): string { * @returns Normalized 32-char lowercase hex ID, or undefined */ export function tryNormalizeHexId( - value: string | null | undefined + value: string | null | undefined, ): string | undefined { if (!value) { return; @@ -256,7 +256,7 @@ const UUID_V7 = "7"; * @returns `{ createdAt }` when the value is UUIDv7, else `null` */ export function decodeUuidV7Timestamp( - value: string + value: string, ): { createdAt: Date } | null { const normalized = normalizeHexId(value); if (!HEX_ID_RE.test(normalized)) { @@ -279,7 +279,7 @@ export function decodeUuidV7Timestamp( */ export function ageInDaysFromUuidV7( value: string, - now: Date = new Date() + now: Date = new Date(), ): number | null { const decoded = decodeUuidV7Timestamp(value); if (!decoded) { diff --git a/packages/cli/src/lib/ini.ts b/packages/cli/src/lib/ini.ts index 99c641d96..020652ebc 100644 --- a/packages/cli/src/lib/ini.ts +++ b/packages/cli/src/lib/ini.ts @@ -97,7 +97,7 @@ export function parseIni(content: string): IniData { const rawValue = line.slice(eqIndex + 1).trim(); const value = stripQuotes(rawValue); - // biome-ignore lint/style/noNonNullAssertion: section is always initialized above + // oxlint-disable-next-line typescript/no-non-null-assertion -- section is always initialized above data[currentSection]![key] = value; } diff --git a/packages/cli/src/lib/init/clack-plain.ts b/packages/cli/src/lib/init/clack-plain.ts index 58663cbec..b5ac270fc 100644 --- a/packages/cli/src/lib/init/clack-plain.ts +++ b/packages/cli/src/lib/init/clack-plain.ts @@ -14,7 +14,7 @@ * captured bindings) so that `spyOn` replacements take effect. */ -// biome-ignore lint/performance/noNamespaceImport: live binding required for test spyOn compatibility +// oxlint-disable-next-line sentry-cli/no-namespace-import -- live binding required for test spyOn compatibility import * as clack from "@clack/prompts"; import { isPlainOutput, stripAnsi } from "../formatters/plain-detect.js"; @@ -110,5 +110,5 @@ export const multiselect: typeof clack.multiselect = (...args) => clack.multiselect(...args); export const isCancel: typeof clack.isCancel = ( - value: unknown + value: unknown, ): value is symbol => clack.isCancel(value); diff --git a/packages/cli/src/lib/init/existing-project.ts b/packages/cli/src/lib/init/existing-project.ts index c6242b58f..13d565df7 100644 --- a/packages/cli/src/lib/init/existing-project.ts +++ b/packages/cli/src/lib/init/existing-project.ts @@ -12,7 +12,7 @@ import type { ExistingProjectData } from "./types.js"; */ export async function tryGetExistingProjectData( orgSlug: string, - projectSlug: string + projectSlug: string, ): Promise<ExistingProjectData | null> { try { const project = await getProject(orgSlug, projectSlug); diff --git a/packages/cli/src/lib/init/formatters.ts b/packages/cli/src/lib/init/formatters.ts index d310eaedc..6b55d6100 100644 --- a/packages/cli/src/lib/init/formatters.ts +++ b/packages/cli/src/lib/init/formatters.ts @@ -61,7 +61,7 @@ function deriveProjectName(output: WizardOutput): string { /** Derive a "start your app" command from the install command's package manager. */ function deriveStartCommand( - commands: string[] | undefined + commands: string[] | undefined, ): string | undefined { const first = commands?.[0]?.trim(); if (!first) { @@ -78,7 +78,7 @@ function deriveStartCommand( function buildCompletion( output: WizardOutput, verify: VerifyResult | undefined, - featureBlurbs: { label: string; blurb: string }[] + featureBlurbs: { label: string; blurb: string }[], ): WizardCompletion { // The server may not hand the org slug back directly (older deploys), so // fall back to recovering it from the settings URL it did send. This keeps @@ -126,7 +126,7 @@ function buildCompletion( */ function buildSummary( output: WizardOutput, - verify: VerifyResult | undefined + verify: VerifyResult | undefined, ): WizardSummary | null { // Resolve blurbs first so the Features row can check the *resolved* length. // If the agent returns blurbs with wrong IDs they all drop out here, and @@ -135,7 +135,7 @@ function buildSummary( (output.featureBlurbs ?? []).map(({ feature, blurb }) => [ feature, stripAnsi(blurb), - ]) + ]), ); const featureBlurbs = sortFeatures(output.features ?? []) .map((feature) => { @@ -203,7 +203,7 @@ function buildSummary( */ function mergeSentryProjectIdentity( output: WizardOutput, - identity: SentryProjectIdentity | undefined + identity: SentryProjectIdentity | undefined, ): WizardOutput { if (!identity) { return output; @@ -221,7 +221,7 @@ export function formatResult( result: WorkflowRunResult, ui: WizardUI, verify?: VerifyResult, - sentryProject?: SentryProjectIdentity + sentryProject?: SentryProjectIdentity, ): void { const output = mergeSentryProjectIdentity(result.result ?? {}, sentryProject); const summary = buildSummary(output, verify); @@ -241,7 +241,7 @@ export function formatResult( ui.outro( output.message ? stripAnsi(output.message) - : "Sentry SDK installed successfully!" + : "Sentry SDK installed successfully!", ); ui.feedback("success"); } @@ -256,18 +256,18 @@ export function formatError(result: WorkflowRunResult, ui: WizardUI): void { if (exitCode === EXIT_PLATFORM_NOT_DETECTED) { ui.log.warn( - "Hint: Could not detect your project's platform. Check that the directory contains a valid project." + "Hint: Could not detect your project's platform. Check that the directory contains a valid project.", ); } else if (exitCode === EXIT_DEPENDENCY_INSTALL_FAILED) { const commands = inner?.commands; if (commands?.length) { ui.log.warn( - `You can install dependencies manually:\n${commands.map((cmd) => ` $ ${cmd}`).join("\n")}` + `You can install dependencies manually:\n${commands.map((cmd) => ` $ ${cmd}`).join("\n")}`, ); } } else if (exitCode === EXIT_VERIFICATION_FAILED) { ui.log.warn( - "Hint: Fix the verification issues and run 'sentry init' again." + "Hint: Fix the verification issues and run 'sentry init' again.", ); } diff --git a/packages/cli/src/lib/init/git.ts b/packages/cli/src/lib/init/git.ts index 78e5b0138..31ec99f18 100644 --- a/packages/cli/src/lib/init/git.ts +++ b/packages/cli/src/lib/init/git.ts @@ -53,7 +53,7 @@ export async function checkGitStatus(opts: { if (!isInsideGitWorkTree({ cwd })) { if (yes) { ui.log.warn( - "You are not inside a git repository. Unable to revert changes if something goes wrong." + "You are not inside a git repository. Unable to revert changes if something goes wrong.", ); return true; } @@ -77,7 +77,7 @@ export async function checkGitStatus(opts: { const fileList = displayed.join("\n"); if (yes) { ui.log.warn( - `You have uncommitted or untracked files:\n${fileList}\nProceeding anyway (--yes).` + `You have uncommitted or untracked files:\n${fileList}\nProceeding anyway (--yes).`, ); return true; } diff --git a/packages/cli/src/lib/init/init-service-auth.ts b/packages/cli/src/lib/init/init-service-auth.ts index 493b10fe1..a1ed99732 100644 --- a/packages/cli/src/lib/init/init-service-auth.ts +++ b/packages/cli/src/lib/init/init-service-auth.ts @@ -23,7 +23,7 @@ const RETRYABLE_AUTH_FAILURE_CODES = new Set([ function classifyInitServiceAuthFailure( err: unknown, - endpoint: string + endpoint: string, ): ApiError | null { if (!(err instanceof Error && MASTRA_HTTP_401_RE.test(err.message))) { return null; @@ -33,7 +33,7 @@ function classifyInitServiceAuthFailure( INIT_SERVICE_REJECTED_TOKEN_MESSAGE, 401, enrich401Detail("Unauthorized: invalid token"), - endpoint + endpoint, ); } @@ -57,7 +57,7 @@ function isRetryableInitServiceAuthFailure(err: unknown): boolean { export async function withInitServiceAuthClassification<T>( operation: () => Promise<T>, - endpoint: string + endpoint: string, ): Promise<T> { let retried = false; @@ -87,7 +87,7 @@ export function assertHostedInitServiceAcceptsTokenHost(): void { throw new HostScopeError( "Hosted Sentry Init setup service", "https://sentry.io", - tokenHost + tokenHost, ); } } diff --git a/packages/cli/src/lib/init/interactive.ts b/packages/cli/src/lib/init/interactive.ts index eb98b930f..a4e40a69a 100644 --- a/packages/cli/src/lib/init/interactive.ts +++ b/packages/cli/src/lib/init/interactive.ts @@ -44,7 +44,7 @@ const DEFAULT_FEATURE_ORDER = [ ] as const; const DEFAULT_FEATURES = new Set<string>(DEFAULT_FEATURE_ORDER); const DEFAULT_FEATURE_RANK = new Map<string, number>( - DEFAULT_FEATURE_ORDER.map((feature, index) => [feature, index]) + DEFAULT_FEATURE_ORDER.map((feature, index) => [feature, index]), ); // These features need code-level choices this selector cannot configure yet. const UNSUPPORTED_INIT_FEATURES = new Set([ @@ -94,7 +94,7 @@ function buildFeatureReviewDetails(features: string[]): PromptDetail[] { export async function handleInteractive( payload: InteractivePayload, options: InteractiveContext, - ui: WizardUI + ui: WizardUI, ): Promise<Record<string, unknown>> { switch (payload.kind) { case "select": @@ -106,7 +106,7 @@ export async function handleInteractive( default: throw new WizardError( `Unsupported interactive prompt kind: "${(payload as { kind: string }).kind}"`, - { rendered: false } + { rendered: false }, ); } } @@ -143,7 +143,7 @@ function buildMultiAppMessage(apps: AppEntry[], items: string[]): string { function buildAppNotFoundMessage( requested: string, apps: AppEntry[], - items: string[] + items: string[], ): string { const exampleApp = items[0] ?? "<app>"; return [ @@ -160,7 +160,7 @@ function buildAppNotFoundMessage( async function handleSelect( payload: SelectPayload, options: InteractiveContext, - ui: WizardUI + ui: WizardUI, ): Promise<Record<string, unknown>> { const apps = payload.apps ?? []; const items = payload.options ?? apps.map((a) => a.name); @@ -173,7 +173,7 @@ async function handleSelect( if (options.app && payload.apps && payload.apps.length > 0) { const match = items.find( - (item) => item.toLowerCase() === options.app?.toLowerCase() + (item) => item.toLowerCase() === options.app?.toLowerCase(), ); if (!match) { const message = buildAppNotFoundMessage(options.app, apps, items); @@ -213,17 +213,17 @@ async function handleSelect( async function handleMultiSelect( payload: MultiSelectPayload, options: InteractiveContext, - ui: WizardUI + ui: WizardUI, ): Promise<Record<string, unknown>> { const available = prependRequiredFeature( (payload.availableFeatures ?? payload.options ?? []).filter( - (feature) => !UNSUPPORTED_INIT_FEATURES.has(feature) - ) + (feature) => !UNSUPPORTED_INIT_FEATURES.has(feature), + ), ); if (options.yes) { ui.log.info( - `Auto-selected all features: ${available.map(featureLabel).join(", ")}` + `Auto-selected all features: ${available.map(featureLabel).join(", ")}`, ); return { features: available }; } @@ -231,7 +231,7 @@ async function handleMultiSelect( const sorted = sortFeatureOptions(available); setTag("wizard.features.offered", available.join(",")); let initialValues: string[] = sorted.filter((feature) => - DEFAULT_FEATURES.has(feature) + DEFAULT_FEATURES.has(feature), ); while (true) { @@ -277,7 +277,7 @@ async function handleMultiSelect( async function handleConfirm( payload: ConfirmPayload, options: InteractiveContext, - ui: WizardUI + ui: WizardUI, ): Promise<Record<string, unknown>> { if (options.yes) { ui.log.info("Auto-confirmed: continuing"); diff --git a/packages/cli/src/lib/init/preflight.ts b/packages/cli/src/lib/init/preflight.ts index b4a29311b..8e8fdb5a7 100644 --- a/packages/cli/src/lib/init/preflight.ts +++ b/packages/cli/src/lib/init/preflight.ts @@ -44,7 +44,7 @@ type ProjectSelection = Pick< */ export async function resolveInitContext( initial: WizardOptions, - ui: WizardUI + ui: WizardUI, ): Promise<ResolvedInitContext | null> { return await withPreflightHandling(ui, async () => { const seed = await resolveInitContextSeed(initial, ui); @@ -57,7 +57,7 @@ export async function resolveInitContext( org, initial, seed, - ui + ui, ); if (!projectSelection) { return null; @@ -71,7 +71,7 @@ export async function resolveInitContext( async function withPreflightHandling( ui: WizardUI, - action: () => Promise<ResolvedInitContext | null> + action: () => Promise<ResolvedInitContext | null>, ): Promise<ResolvedInitContext | null> { try { return await action(); @@ -104,7 +104,7 @@ function buildResolvedInitContext( initial: WizardOptions, org: string, team: string | undefined, - selection: ProjectSelection + selection: ProjectSelection, ): ResolvedInitContext { return { directory: initial.directory, @@ -123,7 +123,7 @@ function buildResolvedInitContext( async function resolveInitContextSeed( initial: WizardOptions, - ui: WizardUI + ui: WizardUI, ): Promise<InitContextSeed | null> { const detected = await resolveDetectedProject(initial, ui); if (detected?.shouldAbort) { @@ -140,7 +140,7 @@ async function resolveInitContextSeed( async function ensureOrg( org: string | undefined, initial: WizardOptions, - ui: WizardUI + ui: WizardUI, ): Promise<string> { if (org) { return org; @@ -158,7 +158,7 @@ async function resolveProjectSelection( org: string, initial: WizardOptions, seed: InitContextSeed, - ui: WizardUI + ui: WizardUI, ): Promise<ProjectSelection | null> { if (!seed.project) { return { @@ -184,7 +184,7 @@ async function resolveProjectSelection( function mergeProjectSelection( seed: InitContextSeed, - resolved: ExistingProjectChoice + resolved: ExistingProjectChoice, ): ProjectSelection { const project = "project" in resolved ? resolved.project : seed.project; const clearedProject = @@ -200,7 +200,7 @@ function mergeProjectSelection( async function resolveDetectedProject( initial: WizardOptions, - ui: WizardUI + ui: WizardUI, ): Promise<{ org?: string; project?: string; @@ -212,7 +212,7 @@ async function resolveDetectedProject( } let detectedProject: { orgSlug: string; projectSlug: string } | null = null; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { detectedProject = await detectExistingProject(initial.directory); } catch { @@ -224,7 +224,7 @@ async function resolveDetectedProject( const existingProject = await tryGetExistingProjectData( detectedProject.orgSlug, - detectedProject.projectSlug + detectedProject.projectSlug, ).catch(() => null); if (initial.yes) { @@ -337,14 +337,14 @@ function toPreflightWizardError(error: unknown): WizardError { return new WizardError(error.format()); } return new WizardError( - error instanceof Error ? error.message : String(error) + error instanceof Error ? error.message : String(error), ); } async function resolveTeam( org: string, initial: WizardOptions, - ui: WizardUI + ui: WizardUI, ): Promise<string | undefined> { if (!initial.team) { return await resolveImplicitTeam(org, initial, ui); @@ -409,7 +409,7 @@ function canBypassMemberCreationRestriction(access: unknown): boolean { async function assertOrgScopedCreationCanProceed(org: string): Promise<void> { let organization: Awaited<ReturnType<typeof getOrganization>>; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { organization = await getOrganization(org); } catch { @@ -428,7 +428,7 @@ async function assertOrgScopedCreationCanProceed(org: string): Promise<void> { async function listTeamsForImplicitInit( org: string, - unattended: boolean + unattended: boolean, ): Promise<SentryTeam[] | undefined> { const scopeRecovery = captureOAuthScopeRecoveryGate(); try { @@ -457,11 +457,11 @@ async function listTeamsForImplicitInit( async function resolveImplicitTeam( org: string, initial: WizardOptions, - ui: WizardUI + ui: WizardUI, ): Promise<string | undefined> { const teams = await listTeamsForImplicitInit( org, - initial.yes || initial.dryRun + initial.yes || initial.dryRun, ); if (!teams) { return; @@ -508,7 +508,7 @@ function handleOrgListError(error: unknown): { ok: false; error: string } { } lines.push( "Specify the org on the command line: sentry init <org-slug>/", - "Or set an environment variable: SENTRY_ORG=<org-slug> sentry init" + "Or set an environment variable: SENTRY_ORG=<org-slug> sentry init", ); return { ok: false, error: lines.join("\n ") }; } @@ -527,7 +527,7 @@ function handleOrgListError(error: unknown): { ok: false; error: string } { async function resolveOrgSlug( cwd: string, yes: boolean, - ui: WizardUI + ui: WizardUI, ): Promise<string | { ok: false; error: string }> { const resolved = await resolveOrgPrefetched(cwd); if (resolved && !NUMERIC_ORG_ID_RE.test(resolved.org)) { @@ -546,7 +546,8 @@ async function resolveOrgSlug( } orgs.sort( (left, right) => - left.name.localeCompare(right.name) || left.slug.localeCompare(right.slug) + left.name.localeCompare(right.name) || + left.slug.localeCompare(right.slug), ); if (orgs.length === 0) { return { @@ -585,7 +586,7 @@ async function resolveOrgSlug( } async function detectExistingProject( - cwd: string + cwd: string, ): Promise<{ orgSlug: string; projectSlug: string } | null> { const { detectDsn } = await import("../dsn/index.js"); const dsn = await detectDsn(cwd); @@ -593,7 +594,7 @@ async function detectExistingProject( return null; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { resolveDsnByPublicKey } = await import("../resolve-target.js"); const resolved = await resolveDsnByPublicKey(dsn); diff --git a/packages/cli/src/lib/init/readiness.ts b/packages/cli/src/lib/init/readiness.ts index cbfdef3bd..0b9e2097f 100644 --- a/packages/cli/src/lib/init/readiness.ts +++ b/packages/cli/src/lib/init/readiness.ts @@ -30,7 +30,7 @@ export async function checkReadiness(ui: WizardUI): Promise<void> { } else { spin.stop("Warning", 2); ui.log.warn( - "Setup service may be slow or unreachable. The wizard will retry if needed." + "Setup service may be slow or unreachable. The wizard will retry if needed.", ); } } diff --git a/packages/cli/src/lib/init/spinner.ts b/packages/cli/src/lib/init/spinner.ts index 2491705a7..424b97634 100644 --- a/packages/cli/src/lib/init/spinner.ts +++ b/packages/cli/src/lib/init/spinner.ts @@ -29,7 +29,7 @@ type SpinnerOutput = NodeJS.WriteStream & { * file tree view needs to redraw an entire block in place as progress changes. */ export function createWizardSpinner( - output: SpinnerOutput = process.stdout + output: SpinnerOutput = process.stdout, ): WizardSpinner { let running = false; let frameIndex = 0; @@ -135,7 +135,7 @@ export function createWizardSpinner( clearRenderedBlock(); if (message) { output.write( - `${renderInlineMarkdown(formatStoppedBlock(message, code))}\n` + `${renderInlineMarkdown(formatStoppedBlock(message, code))}\n`, ); } if (output.isTTY) { diff --git a/packages/cli/src/lib/init/stdin-reopen.ts b/packages/cli/src/lib/init/stdin-reopen.ts index 466bdd112..9b9d33b15 100644 --- a/packages/cli/src/lib/init/stdin-reopen.ts +++ b/packages/cli/src/lib/init/stdin-reopen.ts @@ -144,7 +144,7 @@ function makeHandle(): TtyForwardingHandle { * Production callers pass no args — the defaults do the right thing. */ export function forwardFreshTtyToStdin( - deps: TtyDeps = {} + deps: TtyDeps = {}, ): TtyForwardingHandle { const { openTty = defaultOpenTty, isTty = defaultIsTty } = deps; @@ -160,7 +160,7 @@ export function forwardFreshTtyToStdin( } let fd: number; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { fd = openTty(); } catch { @@ -284,7 +284,7 @@ export function closeFreshTtyForwarding(): void { // `setRawMode(false)`), the TTY is still in raw mode — leaving it there // produces a shell with no echo after a crash. Best-effort: the fresh fd // may already be destroyed from a prior error, so swallow any throw. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { fresh.setRawMode(false); } catch { @@ -315,7 +315,7 @@ export function closeFreshTtyForwarding(): void { // user presses a key. Now that the original `.pause()` is restored, // invoke it directly so stock Node/Bun cleanup can finish. Idempotent: // safe when stdin was already paused. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { original.pause.call(process.stdin); } catch { diff --git a/packages/cli/src/lib/init/tools/apply-patchset.ts b/packages/cli/src/lib/init/tools/apply-patchset.ts index b5f7bb77a..e2f0b294c 100644 --- a/packages/cli/src/lib/init/tools/apply-patchset.ts +++ b/packages/cli/src/lib/init/tools/apply-patchset.ts @@ -28,7 +28,7 @@ function failedPreparation(failure: FileChangeFailure): ToolResult { */ export async function applyPatchset( input: unknown, - context: Pick<ToolContext, "dryRun" | "authToken"> + context: Pick<ToolContext, "dryRun" | "authToken">, ): Promise<ToolResult> { const parsed = parseFileChangesRequest(input); if (!parsed.ok) { @@ -37,7 +37,7 @@ export async function applyPatchset( const prepared = await prepareFileChanges( parsed.cwd, parsed.changes, - context.authToken + context.authToken, ); if (!prepared.ok) { return failedPreparation(prepared.failure); diff --git a/packages/cli/src/lib/init/tools/command-utils.ts b/packages/cli/src/lib/init/tools/command-utils.ts index a08205e15..acd40a902 100644 --- a/packages/cli/src/lib/init/tools/command-utils.ts +++ b/packages/cli/src/lib/init/tools/command-utils.ts @@ -146,7 +146,7 @@ function findBlockedExecutable(tokens: string[]): string | undefined { function isRecursiveSentrySetupToken( token: string, tokens: string[], - index: number + index: number, ): boolean { const executable = normalizeExecutableName(token); if ( @@ -203,7 +203,7 @@ function pushCurrentToken(state: TokenizeState): void { function appendEscapedUnquotedChar( state: TokenizeState, command: string, - index: number + index: number, ): number | undefined { const next = command[index + 1]; if ( @@ -221,7 +221,7 @@ function appendEscapedUnquotedChar( function handleUnquotedChar( state: TokenizeState, command: string, - index: number + index: number, ): number { const char = command[index]; if (!char) { @@ -263,7 +263,7 @@ function handleSingleQuotedChar(state: TokenizeState, char: string): void { function handleDoubleQuotedChar( state: TokenizeState, command: string, - index: number + index: number, ): number { const char = command[index]; if (!char) { @@ -318,7 +318,7 @@ export function tokenizeCommand(command: string): string[] { if (state.quote) { throw new Error( - `Invalid command: unterminated ${state.quote === '"' ? "double" : "single"} quote — "${command}"` + `Invalid command: unterminated ${state.quote === '"' ? "double" : "single"} quote — "${command}"`, ); } @@ -376,7 +376,7 @@ export function validateCommand(command: string): string | undefined { } async function readWebStream( - stream: ReadableStream<Uint8Array> + stream: ReadableStream<Uint8Array>, ): Promise<string> { const reader = stream.getReader(); const chunks: Buffer[] = []; @@ -430,7 +430,7 @@ async function readNodeStream(stream: NodeJS.ReadableStream): Promise<string> { * Drain a spawned stdout/stderr stream while enforcing output truncation. */ export async function readSpawnOutput( - stream: SpawnOutputStream + stream: SpawnOutputStream, ): Promise<string> { if (!stream) { return ""; diff --git a/packages/cli/src/lib/init/tools/create-sentry-project.ts b/packages/cli/src/lib/init/tools/create-sentry-project.ts index c75e2cc31..acd0f6591 100644 --- a/packages/cli/src/lib/init/tools/create-sentry-project.ts +++ b/packages/cli/src/lib/init/tools/create-sentry-project.ts @@ -78,7 +78,7 @@ async function resolveProjectCreation(opts: { const platform = opts.platform ?? undefined; const withPlatformFallback = async ( - fn: (p: string | undefined) => Promise<ProjectData> + fn: (p: string | undefined) => Promise<ProjectData>, ): Promise<ProjectData> => { try { return await fn(platform); @@ -164,7 +164,7 @@ async function resolveProjectCreation(opts: { async function validateTeamForDryRun( org: string, team: string | undefined, - autoCreateSlug: string + autoCreateSlug: string, ): Promise<void> { if (!team) { return; @@ -208,7 +208,7 @@ export async function createSentryProject( | "team" | "project" | "yes" - > + >, ): Promise<ToolResult> { const name = context.project ?? payload.params.name; const slug = slugify(name); @@ -314,7 +314,7 @@ export async function createSentryProject( * Tool definition for creating or ensuring a Sentry project exists for init. */ const describeCreateSentryProject = ( - payload: CreateSentryProjectPayload | EnsureSentryProjectPayload + payload: CreateSentryProjectPayload | EnsureSentryProjectPayload, ): string => payload.detail ?? `Ensuring project \`${payload.params.name}\` (${payload.params.platform})...`; diff --git a/packages/cli/src/lib/init/tools/file-changes/apply.ts b/packages/cli/src/lib/init/tools/file-changes/apply.ts index d8e6777b1..babb8e529 100644 --- a/packages/cli/src/lib/init/tools/file-changes/apply.ts +++ b/packages/cli/src/lib/init/tools/file-changes/apply.ts @@ -35,7 +35,7 @@ async function assertApplyPath(change: PreparedFileChange): Promise<void> { try { const currentRoot = await resolveCanonicalRoot(change.root); const currentDestination = await resolveCanonicalDestination( - change.absolutePath + change.absolutePath, ); const currentIdentity = await resolvePathIdentity(change.absolutePath); if ( @@ -49,13 +49,13 @@ async function assertApplyPath(change: PreparedFileChange): Promise<void> { } catch { throw new FileChangeApplyError( "stale_content", - `Cannot ${change.action} "${change.path}": path changed after validation` + `Cannot ${change.action} "${change.path}": path changed after validation`, ); } } async function applyPreparedFileChange( - change: PreparedFileChange + change: PreparedFileChange, ): Promise<void> { await assertApplyPath(change); if (change.action === "create") { @@ -70,7 +70,7 @@ async function applyPreparedFileChange( if ((error as NodeJS.ErrnoException).code === "EEXIST") { throw new FileChangeApplyError( "stale_content", - `Cannot create "${change.path}": target appeared after validation` + `Cannot create "${change.path}": target appeared after validation`, ); } throw error; @@ -81,18 +81,18 @@ async function applyPreparedFileChange( if (change.action === "modify") { const current = await safeReadFile( change.absolutePath, - "apply-file-changes.apply" + "apply-file-changes.apply", ); if (current === null) { throw new FileChangeApplyError( "stale_content", - `Cannot modify "${change.path}": target changed after validation` + `Cannot modify "${change.path}": target changed after validation`, ); } if (current !== change.expectedContent) { throw new FileChangeApplyError( "stale_content", - `Cannot modify "${change.path}": file changed after validation` + `Cannot modify "${change.path}": file changed after validation`, ); } await assertApplyPath(change); @@ -103,7 +103,7 @@ async function applyPreparedFileChange( if (!(await deleteSnapshotMatches(change.absolutePath, change.expected))) { throw new FileChangeApplyError( "stale_content", - `Cannot delete "${change.path}": target changed after validation` + `Cannot delete "${change.path}": target changed after validation`, ); } await assertApplyPath(change); @@ -114,7 +114,7 @@ async function applyPreparedFileChange( function failureFromError( change: PreparedFileChange, - error: unknown + error: unknown, ): FileChangeFailure { return { action: change.action, @@ -129,7 +129,7 @@ function failureFromError( function failedResult( failure: FileChangeFailure, - applied: AppliedFileChange[] + applied: AppliedFileChange[], ): ToolResult { return { data: { applied, failed: failure }, @@ -144,7 +144,7 @@ function failedResult( */ export async function applyPreparedFileChanges( changes: PreparedFileChange[], - dryRun: boolean + dryRun: boolean, ): Promise<ToolResult> { const applied = changes.map(({ action, path: filePath }) => ({ action, diff --git a/packages/cli/src/lib/init/tools/file-changes/contract.ts b/packages/cli/src/lib/init/tools/file-changes/contract.ts index 156a33631..22e678bd3 100644 --- a/packages/cli/src/lib/init/tools/file-changes/contract.ts +++ b/packages/cli/src/lib/init/tools/file-changes/contract.ts @@ -59,7 +59,7 @@ type FileChangesContractResult = * local implementation treats its payload as a batch of file changes. */ export function parseFileChangesRequest( - input: unknown + input: unknown, ): FileChangesContractResult { const result = safeParse(legacyApplyPatchsetPayloadSchema, input); if (!result.success) { diff --git a/packages/cli/src/lib/init/tools/file-changes/paths.ts b/packages/cli/src/lib/init/tools/file-changes/paths.ts index de6b88de8..56c31b882 100644 --- a/packages/cli/src/lib/init/tools/file-changes/paths.ts +++ b/packages/cli/src/lib/init/tools/file-changes/paths.ts @@ -10,7 +10,7 @@ import path from "node:path"; /** Return a stable device/inode identity for an existing path when available. */ export async function resolvePathIdentity( - absolutePath: string + absolutePath: string, ): Promise<string | undefined> { try { const stats = await lstat(absolutePath, { bigint: true }); @@ -35,7 +35,7 @@ export function resolveCanonicalRoot(root: string): Promise<string> { * missing suffix without mutating disk. */ export async function resolveCanonicalDestination( - absolutePath: string + absolutePath: string, ): Promise<string> { let existingPath = absolutePath; const missingSegments: string[] = []; diff --git a/packages/cli/src/lib/init/tools/file-changes/prepare.ts b/packages/cli/src/lib/init/tools/file-changes/prepare.ts index 90230d25d..40800444a 100644 --- a/packages/cli/src/lib/init/tools/file-changes/prepare.ts +++ b/packages/cli/src/lib/init/tools/file-changes/prepare.ts @@ -86,7 +86,7 @@ function validateFileChangePath(filePath: string): string | undefined { const invalidSegment = filePath .split("/") .some( - (segment) => segment.length === 0 || segment === "." || segment === ".." + (segment) => segment.length === 0 || segment === "." || segment === "..", ); return invalidSegment ? `Invalid file change path "${filePath}": path segments must not be empty, "." or ".."` @@ -105,7 +105,7 @@ function convertLineEndings(content: string, lineEnding: "\n" | "\r\n") { function replaceUnique( content: string, oldString: string, - newString: string + newString: string, ): MatchResult { const first = content.indexOf(oldString); if (first === -1) { @@ -126,7 +126,7 @@ function replaceUnique( function applyEdits( initialContent: string, filePath: string, - edits: Extract<FileChange, { action: "modify" }>["edits"] + edits: Extract<FileChange, { action: "modify" }>["edits"], ): ApplyEditsResult { const hasBom = initialContent.startsWith("\uFEFF"); let content = hasBom ? initialContent.slice(1) : initialContent; @@ -159,7 +159,7 @@ function applyEdits( function resolveCreateContent( change: Extract<FileChange, { action: "create" }>, - authToken?: string + authToken?: string, ): string { let content = change.path.endsWith(".json") ? prettyPrintJson(change.content) @@ -171,14 +171,14 @@ function resolveCreateContent( ) { content = content.replace( EMPTY_AUTH_TOKEN_RE, - (_, prefix) => `${prefix}${authToken}` + (_, prefix) => `${prefix}${authToken}`, ); } return content; } function prettyPrintJson(content: string): string { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return `${JSON.stringify(JSON.parse(content), null, 2)}\n`; } catch { @@ -192,7 +192,7 @@ function isEnvFile(filePath: string): boolean { } async function readDeleteSnapshot( - absolutePath: string + absolutePath: string, ): Promise<DeleteSnapshot | undefined> { try { const stats = await lstat(absolutePath); @@ -215,7 +215,7 @@ async function prepareFileChange( cwd: string, rootRealPath: string, change: FileChange, - authToken?: string + authToken?: string, ): Promise<PrepareFileChangesResult> { const pathError = validateFileChangePath(change.path); if (pathError) { @@ -247,7 +247,7 @@ async function prepareFileChange( let canonicalPath: string; let pathIdentity: string | undefined; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { canonicalPath = await resolveCanonicalDestination(absolutePath); if (!isCanonicalChild(rootRealPath, canonicalPath)) { @@ -287,7 +287,7 @@ async function prepareFileChange( async function prepareCreate( preparedPath: PreparedPath, change: Extract<FileChange, { action: "create" }>, - authToken?: string + authToken?: string, ): Promise<PrepareFileChangesResult> { try { await lstat(preparedPath.absolutePath); @@ -327,9 +327,9 @@ async function prepareCreate( async function prepareDelete( preparedPath: PreparedPath, - change: Extract<FileChange, { action: "delete" }> + change: Extract<FileChange, { action: "delete" }>, ): Promise<PrepareFileChangesResult> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const expected = await readDeleteSnapshot(preparedPath.absolutePath); if (!expected) { @@ -368,7 +368,7 @@ async function prepareDelete( async function prepareModify( preparedPath: PreparedPath, - change: Extract<FileChange, { action: "modify" }> + change: Extract<FileChange, { action: "modify" }>, ): Promise<PrepareFileChangesResult> { let initialContent: string; try { @@ -386,7 +386,7 @@ async function prepareModify( } const content = await safeReadFile( preparedPath.absolutePath, - "apply-file-changes.prepare" + "apply-file-changes.prepare", ); if (content === null) { return { @@ -433,7 +433,7 @@ async function prepareModify( } function normalizeFileChanges( - changes: FileChange[] + changes: FileChange[], ): NormalizeFileChangesResult { const normalized: FileChange[] = []; const targetIndexes = new Map<string, number>(); @@ -466,7 +466,7 @@ function normalizeFileChanges( (candidate) => candidate.path !== change.path && (candidate.path.startsWith(`${change.path}/`) || - change.path.startsWith(`${candidate.path}/`)) + change.path.startsWith(`${candidate.path}/`)), ); if (conflict) { return { @@ -491,7 +491,7 @@ function comparableCanonicalPath(filePath: string): string { function canonicalTargetsOverlap( first: PreparedFileChange, - second: PreparedFileChange + second: PreparedFileChange, ): boolean { const firstPath = comparableCanonicalPath(first.canonicalPath); const secondPath = comparableCanonicalPath(second.canonicalPath); @@ -510,7 +510,7 @@ function canonicalTargetsOverlap( export async function prepareFileChanges( cwd: string, changes: FileChange[], - authToken?: string + authToken?: string, ): Promise<PrepareFileChangesResult> { const prepared: PreparedFileChange[] = []; const [firstChange] = changes; @@ -518,7 +518,7 @@ export async function prepareFileChanges( return { changes: prepared, ok: true }; } let rootRealPath: string; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { rootRealPath = await resolveCanonicalRoot(cwd); } catch { @@ -542,7 +542,7 @@ export async function prepareFileChanges( cwd, rootRealPath, change, - authToken + authToken, ); if (!result.ok) { return result; @@ -570,7 +570,7 @@ export async function prepareFileChanges( /** Compare a prepared delete snapshot with the target's current state. */ export async function deleteSnapshotMatches( absolutePath: string, - expected: DeleteSnapshot + expected: DeleteSnapshot, ): Promise<boolean> { const current = await readDeleteSnapshot(absolutePath); if (!current || current.kind !== expected.kind) { diff --git a/packages/cli/src/lib/init/tools/file-exists-batch.ts b/packages/cli/src/lib/init/tools/file-exists-batch.ts index d14ffae12..b1fbe33a7 100644 --- a/packages/cli/src/lib/init/tools/file-exists-batch.ts +++ b/packages/cli/src/lib/init/tools/file-exists-batch.ts @@ -9,11 +9,11 @@ const PATH_SEGMENT_RE = /[/\\]/u; * Check whether a batch of paths exists inside the sandbox. */ export async function fileExistsBatch( - payload: FileExistsBatchPayload + payload: FileExistsBatchPayload, ): Promise<ToolResult> { const results = await Promise.all( payload.params.paths.map(async (filePath) => { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const absPath = safePath(payload.cwd, filePath); await fs.promises.access(absPath); @@ -21,7 +21,7 @@ export async function fileExistsBatch( } catch { return [filePath, false] as const; } - }) + }), ); const exists: Record<string, boolean> = {}; diff --git a/packages/cli/src/lib/init/tools/glob.ts b/packages/cli/src/lib/init/tools/glob.ts index d22f6d4d6..cde97878b 100644 --- a/packages/cli/src/lib/init/tools/glob.ts +++ b/packages/cli/src/lib/init/tools/glob.ts @@ -38,7 +38,7 @@ export async function glob(payload: GlobPayload): Promise<ToolResult> { maxResults, }); return { pattern, files, truncated }; - }) + }), ); return { ok: true, data: { results } }; } diff --git a/packages/cli/src/lib/init/tools/grep.ts b/packages/cli/src/lib/init/tools/grep.ts index c4a626148..7fa653211 100644 --- a/packages/cli/src/lib/init/tools/grep.ts +++ b/packages/cli/src/lib/init/tools/grep.ts @@ -27,7 +27,7 @@ type SearchResult = { async function runOneSearch( cwd: string, search: GrepSearch, - maxResults: number + maxResults: number, ): Promise<SearchResult> { // The scan engine trusts its `path` input (see // `GrepOptions.path`) — sandbox enforcement lives here. We only @@ -76,8 +76,8 @@ export async function grep(payload: GrepPayload): Promise<ToolResult> { payload.params.maxResultsPerSearch ?? MAX_GREP_RESULTS_PER_SEARCH; const results = await Promise.all( payload.params.searches.map((search) => - runOneSearch(payload.cwd, search, maxResults) - ) + runOneSearch(payload.cwd, search, maxResults), + ), ); return { ok: true, data: { results } }; } diff --git a/packages/cli/src/lib/init/tools/list-dir.ts b/packages/cli/src/lib/init/tools/list-dir.ts index f5bd2949e..a34befa1d 100644 --- a/packages/cli/src/lib/init/tools/list-dir.ts +++ b/packages/cli/src/lib/init/tools/list-dir.ts @@ -60,7 +60,7 @@ type WalkState = { }; async function readDirEntries(dir: string): Promise<fs.Dirent[] | undefined> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return await fs.promises.readdir(dir, { withFileTypes: true }); } catch { @@ -89,13 +89,13 @@ function shouldRecurseInto(entry: fs.Dirent, state: WalkState): boolean { function toDirEntry( state: WalkState, dir: string, - entry: fs.Dirent + entry: fs.Dirent, ): DirEntry | undefined { const abs = dir + NATIVE_SEP + entry.name; const relNative = abs.slice(state.cwdPrefixLen); if (entry.isSymbolicLink()) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { safePath(state.cwd, relNative); } catch { @@ -123,7 +123,7 @@ function toDirEntry( /** Return a regular file's byte size without opening or reading its contents. */ function fileSize(abs: string): { size?: number } { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stat = fs.lstatSync(abs); return stat.isFile() ? { size: stat.size } : {}; @@ -135,7 +135,7 @@ function fileSize(abs: string): { size?: number } { async function walkDirectory( dir: string, depth: number, - state: WalkState + state: WalkState, ): Promise<void> { if (depth > state.maxDepth || state.entries.length >= state.maxEntries) { state.truncated = true; diff --git a/packages/cli/src/lib/init/tools/project-file.ts b/packages/cli/src/lib/init/tools/project-file.ts index dc25a9c97..e3d23193c 100644 --- a/packages/cli/src/lib/init/tools/project-file.ts +++ b/packages/cli/src/lib/init/tools/project-file.ts @@ -51,7 +51,7 @@ export function normalizeProjectFilePath(filePath: string): string | undefined { segment.includes(":") || segment.endsWith(".") || segment.endsWith(" ") || - WINDOWS_RESERVED_SEGMENT_RE.test(segment) + WINDOWS_RESERVED_SEGMENT_RE.test(segment), ) ) { return; @@ -68,15 +68,15 @@ export function normalizeProjectFilePath(filePath: string): string | undefined { */ export async function openProjectFile( cwd: string, - filePath: string + filePath: string, ): Promise<OpenedProjectFile | { error: ReadFileErrorCode }> { let handle: fs.promises.FileHandle | undefined; try { const absPath = safePath(cwd, filePath); handle = await fs.promises.open( absPath, - // biome-ignore lint/suspicious/noBitwiseOperators: fs open flags are a bitmask. - fs.constants.O_RDONLY | fs.constants.O_NONBLOCK + // oxlint-disable-next-line no-bitwise -- fs open flags are a bitmask. + fs.constants.O_RDONLY | fs.constants.O_NONBLOCK, ); const stat = await handle.stat(); if (!stat.isFile()) { @@ -89,7 +89,7 @@ export async function openProjectFile( } return { handle, stat }; } catch (error) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await handle?.close().catch(() => { // Preserve the primary open error when cleanup fails. }); @@ -98,9 +98,9 @@ export async function openProjectFile( } export async function closeProjectFile( - handle: fs.promises.FileHandle + handle: fs.promises.FileHandle, ): Promise<void> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await handle.close().catch(() => { // Descriptor cleanup must not replace the primary read classification. }); @@ -108,7 +108,7 @@ export async function closeProjectFile( export function projectFileChanged( initial: fs.Stats, - final: fs.Stats + final: fs.Stats, ): boolean { return ( final.dev !== initial.dev || @@ -121,7 +121,7 @@ export function projectFileChanged( async function openedPathIsAllowed( cwd: string, absPath: string, - openedStat: fs.Stats + openedStat: fs.Stats, ): Promise<boolean> { const [realCwd, realPath] = await Promise.all([ fs.promises.realpath(cwd), @@ -149,7 +149,7 @@ async function openedPathIsAllowed( async function requestedPathContainsSymlink( cwd: string, - absPath: string + absPath: string, ): Promise<boolean> { let currentPath = path.resolve(cwd); const relativePath = path.relative(currentPath, absPath); @@ -184,7 +184,7 @@ function isSensitiveReadPath(filePath: string): boolean { SENSITIVE_PATH_TOKENS.has(token) || token === ".dev.vars" || token === ".env" || - token.startsWith(".env.") + token.startsWith(".env."), ); } diff --git a/packages/cli/src/lib/init/tools/read-files.ts b/packages/cli/src/lib/init/tools/read-files.ts index 76ad0d338..6499f3717 100644 --- a/packages/cli/src/lib/init/tools/read-files.ts +++ b/packages/cli/src/lib/init/tools/read-files.ts @@ -39,7 +39,7 @@ type LineScan = { * state between calls. */ export async function readFiles( - payload: ReadFilesPayload + payload: ReadFilesPayload, ): Promise<ToolResult> { const validated = validateReadRequest(payload); if ("error" in validated) { @@ -47,7 +47,7 @@ export async function readFiles( } const perFileOutputBytes = Math.max( 1, - Math.floor(MAX_READ_OUTPUT_BYTES / validated.paths.length) + Math.floor(MAX_READ_OUTPUT_BYTES / validated.paths.length), ); const window = { outputBytes: perFileOutputBytes, @@ -57,7 +57,7 @@ export async function readFiles( validated.paths.map(async (filePath) => { const result = await readSingleFileV2(payload.cwd, filePath, window); return [filePath, result] as const; - }) + }), ); return { @@ -72,7 +72,7 @@ export async function readFiles( async function readSingleFileV2( cwd: string, filePath: string, - window: ReadWindow + window: ReadWindow, ): Promise<ReadFileV2Result> { let opened: OpenedProjectFile | undefined; try { @@ -102,7 +102,7 @@ async function readSingleFileV2( } catch (error) { return { error: readErrorCode(error), status: "error" }; } finally { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await opened?.handle.close().catch(() => { // Preserve the primary read result when descriptor cleanup fails. }); @@ -112,7 +112,7 @@ async function readSingleFileV2( async function readTextPage( handle: fs.promises.FileHandle, fileSize: number, - window: ReadWindow + window: ReadWindow, ): Promise<ReadFileV2Result> { const located = await locateLineStart(handle, fileSize, window.startLine); if ("error" in located) { @@ -128,7 +128,7 @@ async function readTextPage( page, bytesRead, pageBytes - bytesRead, - located.offset + bytesRead + located.offset + bytesRead, ); if (result.bytesRead === 0) { return { error: "unreadable", status: "error" }; @@ -138,7 +138,7 @@ async function readTextPage( !isTextChunk( decoder, page.subarray(bytesRead, nextBytesRead), - located.offset + nextBytesRead < fileSize + located.offset + nextBytesRead < fileSize, ) ) { return { error: "not-text", status: "error" }; @@ -168,7 +168,7 @@ async function readTextPage( async function locateLineStart( handle: fs.promises.FileHandle, fileSize: number, - startLine: number + startLine: number, ): Promise< { offset: number } | { error: "invalid-range" | "not-text" | "unreadable" } > { @@ -185,7 +185,7 @@ async function locateLineStart( buffer, 0, Math.min(buffer.length, fileSize - position), - position + position, ); if (result.bytesRead === 0) { return { error: "unreadable" }; @@ -198,7 +198,7 @@ async function locateLineStart( !isTextChunk( decoder, chunk.subarray(0, scan.consumedBytes), - !scan.found && nextPosition < fileSize + !scan.found && nextPosition < fileSize, ) ) { return { error: "not-text" }; @@ -235,7 +235,7 @@ function scanLineBreaks(buffer: Buffer, linesToSkip: number): LineScan { } function validateReadRequest( - payload: ReadFilesPayload + payload: ReadFilesPayload, ): { error: string } | { paths: string[]; startLine: number } { const params = (payload as { params?: unknown }).params; if (typeof params !== "object" || params === null) { @@ -268,7 +268,7 @@ function validateReadRequest( } function normalizeReadPaths( - paths: unknown + paths: unknown, ): { error: string } | { paths: string[] } { if ( !Array.isArray(paths) || @@ -284,7 +284,7 @@ function normalizeReadPaths( (filePath) => typeof filePath !== "string" || filePath.length === 0 || - filePath.length > 1000 + filePath.length > 1000, ) ) { return { error: "read-files paths must be non-empty bounded strings" }; @@ -315,17 +315,17 @@ function isInvalidPositiveInteger(value: unknown): boolean { function isTextChunk( decoder: TextDecoder, buffer: Buffer, - hasMoreBytes: boolean + hasMoreBytes: boolean, ): boolean { if ( buffer.some( (byte) => - byte === 0x7f || (byte < 0x20 && ![0x09, 0x0a, 0x0d].includes(byte)) + byte === 0x7f || (byte < 0x20 && ![0x09, 0x0a, 0x0d].includes(byte)), ) ) { return false; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { decoder.decode(buffer, { stream: hasMoreBytes }); return true; diff --git a/packages/cli/src/lib/init/tools/registry.ts b/packages/cli/src/lib/init/tools/registry.ts index 340445a3d..8335efedd 100644 --- a/packages/cli/src/lib/init/tools/registry.ts +++ b/packages/cli/src/lib/init/tools/registry.ts @@ -31,7 +31,7 @@ const toolDefinitions = [ ] as const satisfies readonly AnyInitToolDefinition[]; const toolRegistry = new Map<ToolOperation, AnyInitToolDefinition>( - toolDefinitions.map((tool) => [tool.operation, tool] as const) + toolDefinitions.map((tool) => [tool.operation, tool] as const), ); /** Sentry API operations never inspect or mutate the local filesystem. */ @@ -54,7 +54,7 @@ export function describeTool(payload: ToolPayload): string { */ export async function executeTool( payload: ToolPayload, - context: ToolContext + context: ToolContext, ): Promise<ToolResult> { const tool = toolRegistry.get(payload.operation); if (!tool) { diff --git a/packages/cli/src/lib/init/tools/run-commands.ts b/packages/cli/src/lib/init/tools/run-commands.ts index 11bca9700..64a688375 100644 --- a/packages/cli/src/lib/init/tools/run-commands.ts +++ b/packages/cli/src/lib/init/tools/run-commands.ts @@ -63,7 +63,7 @@ function buildWindowsBatchCommand(executable: string, args: string[]): string { */ export async function runCommands( payload: RunCommandsPayload, - context: Pick<ToolContext, "dryRun"> + context: Pick<ToolContext, "dryRun">, ): Promise<ToolResult> { const timeoutMs = payload.params.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS; const parsedCommands: ReturnType<typeof parseCommand>[] = []; @@ -121,7 +121,7 @@ export async function runCommands( async function runSingleCommand( command: ReturnType<typeof parseCommand>, cwd: string, - timeoutMs: number + timeoutMs: number, ): Promise<{ command: string; exitCode: number; diff --git a/packages/cli/src/lib/init/tools/shared.ts b/packages/cli/src/lib/init/tools/shared.ts index 101ac2d99..4cfecf718 100644 --- a/packages/cli/src/lib/init/tools/shared.ts +++ b/packages/cli/src/lib/init/tools/shared.ts @@ -17,7 +17,7 @@ export function safePath(cwd: string, relative: string): string { } let realCwd: string; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { realCwd = fs.realpathSync(normalizedCwd); } catch { @@ -30,7 +30,7 @@ export function safePath(cwd: string, relative: string): string { const real = fs.realpathSync(checkPath); if (!real.startsWith(realCwd + path.sep) && real !== realCwd) { throw new Error( - `Path "${relative}" resolves outside project directory via symlink` + `Path "${relative}" resolves outside project directory via symlink`, ); } break; @@ -57,9 +57,9 @@ export function safePath(cwd: string, relative: string): string { */ export function validateToolSandbox( payload: Pick<ToolPayload, "cwd">, - directory: string + directory: string, ): { cwd: string } | ToolResult { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const realDirectory = fs.realpathSync(path.resolve(directory)); const realCwd = fs.realpathSync(path.resolve(payload.cwd)); diff --git a/packages/cli/src/lib/init/tools/types.ts b/packages/cli/src/lib/init/tools/types.ts index 0d608f101..2f487539a 100644 --- a/packages/cli/src/lib/init/tools/types.ts +++ b/packages/cli/src/lib/init/tools/types.ts @@ -18,12 +18,12 @@ export type InitToolDefinition<TOperation extends ToolOperation> = { operation: TOperation; /** Build a short spinner message for the current payload. */ describe: ( - payload: Extract<ToolPayload, { operation: TOperation }> + payload: Extract<ToolPayload, { operation: TOperation }>, ) => string; /** Execute the tool and return a resumable payload result. */ execute: ( payload: Extract<ToolPayload, { operation: TOperation }>, - context: ToolContext + context: ToolContext, ) => Promise<ToolResult>; }; diff --git a/packages/cli/src/lib/init/ui/factory.ts b/packages/cli/src/lib/init/ui/factory.ts index fb3a4d159..34066ae24 100644 --- a/packages/cli/src/lib/init/ui/factory.ts +++ b/packages/cli/src/lib/init/ui/factory.ts @@ -99,7 +99,7 @@ export async function getUIAsync(opts: UIFactoryOptions): Promise<WizardUI> { if (shouldUseLogging(opts)) { return new LoggingUI(); } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { createInkUI } = await import("./ink-ui.js"); return await createInkUI({ initialWelcome: opts.initialWelcome }); diff --git a/packages/cli/src/lib/init/ui/file-tree.ts b/packages/cli/src/lib/init/ui/file-tree.ts index 84790ee38..5b74537a9 100644 --- a/packages/cli/src/lib/init/ui/file-tree.ts +++ b/packages/cli/src/lib/init/ui/file-tree.ts @@ -159,7 +159,7 @@ export function flattenTree(root: FileTreeNode): FileTreeRow[] { function walk( nodes: FileTreeNode[], prefix: string, - rows: FileTreeRow[] + rows: FileTreeRow[], ): void { for (const [index, node] of nodes.entries()) { const isLast = index === nodes.length - 1; @@ -174,7 +174,7 @@ function walk( function rowFor( node: FileTreeNode, prefix: string, - isLast: boolean + isLast: boolean, ): FileTreeRow { // Files are leaves that carry either a change `action` (from // `buildFileTree`) or a read `status` (from `buildReadTree`). A diff --git a/packages/cli/src/lib/init/ui/ink-app.tsx b/packages/cli/src/lib/init/ui/ink-app.tsx index 07edafd50..b64e7fa6a 100644 --- a/packages/cli/src/lib/init/ui/ink-app.tsx +++ b/packages/cli/src/lib/init/ui/ink-app.tsx @@ -152,7 +152,7 @@ function formatBannerBrand(cliVersion: string | null): string { /** @internal Exported for testing. */ export function formatFeedbackBanner( width: number, - cliVersion: string | null + cliVersion: string | null, ): string { const brand = formatBannerBrand(cliVersion); const left = ` ${brand}`; @@ -169,7 +169,7 @@ export function formatFeedbackBanner( const spacerWidth = Math.max( 1, - width - left.length - clippedRight.length - rightPadding + width - left.length - clippedRight.length - rightPadding, ); return `${left}${" ".repeat(spacerWidth)}${clippedRight}${" ".repeat(rightPadding)}`; } @@ -192,7 +192,7 @@ function AppBody({ store }: AppProps): React.ReactNode { const snapshot = useSyncExternalStore( store.subscribe, store.getSnapshot, - store.getSnapshot + store.getSnapshot, ); const { columns, rows } = useInkFrameSize(); const [activeTab, setActiveTab] = useState(0); @@ -206,7 +206,7 @@ function AppBody({ store }: AppProps): React.ReactNode { { id: "status", label: "Status" }, { id: "files", label: "Files" }, ], - [] + [], ); const appShortcuts = useMemo<ShortcutBinding[]>(() => { @@ -404,7 +404,7 @@ function ActivityPane({ terminalRows: number; }): React.ReactNode { const promptLogs = logs.filter( - (log) => log.severity === "warn" || log.severity === "error" + (log) => log.severity === "warn" || log.severity === "error", ); // The shortest supported frame can keep one actionable log above a prompt; // roomier terminals retain the complete warning/error history. @@ -566,7 +566,7 @@ function useChoiceNavigation<T extends string>({ run: onCancel, }, ], - [choices, highlighted, onCancel, onChoose, totalCount] + [choices, highlighted, onCancel, onChoose, totalCount], ); useInkShortcuts(scope, shortcuts); @@ -664,7 +664,7 @@ function buildCompletionMenu({ value: "issues", label: "Open my Issues feed", hint: "see your first error", - } + }, ); } if (agentCommand) { @@ -726,7 +726,7 @@ function CompletionScreen({ completion, primaryUrl, }), - [agentCommand, agentQueued, completion, primaryUrl] + [agentCommand, agentQueued, completion, primaryUrl], ); const onMenuChoose = useCallback( @@ -759,7 +759,7 @@ function CompletionScreen({ break; } }, - [actions, agentCommand, agentQueued, onDismiss, primaryUrl, store] + [actions, agentCommand, agentQueued, onDismiss, primaryUrl, store], ); // `o` opens the first-error link directly (the hint next to it), separate @@ -782,7 +782,7 @@ function CompletionScreen({ }, ] : [], - [actions, primaryUrl] + [actions, primaryUrl], ); useInkShortcuts("completion-open", openShortcuts); @@ -883,7 +883,7 @@ function CompletionHeader({ /** "4 files changed" — the quiet footnote under the feature list. */ function completionSummaryText( - completion: WizardCompletion | null + completion: WizardCompletion | null, ): string | null { const n = completion?.changedFileCount ?? 0; if (n <= 0) { @@ -942,7 +942,7 @@ function CompletionWhatWeSetUp({ /** Copy for the first-error section, by state. */ function firstErrorInstruction( verified: boolean, - startCommand: string | undefined + startCommand: string | undefined, ): string { if (verified) { return "Your app is already sending events — here's the one you just sent:"; @@ -1083,7 +1083,7 @@ function IntroScreen({ width={bannerWidth} > {banner.map((row, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: positional banner rows + // positional banner rows <Text color={row.color} key={i}> {row.content} </Text> @@ -1121,7 +1121,7 @@ function WelcomeActions({ { value: "continue", label: "Continue" }, { value: "cancel", label: "Cancel" }, ], - [] + [], ); const onChoose = useCallback( (value: "continue" | "cancel") => { @@ -1131,7 +1131,7 @@ function WelcomeActions({ } prompt.resolve("continue"); }, - [prompt] + [prompt], ); const highlighted = useChoiceNavigation({ choices, @@ -1282,11 +1282,11 @@ function LearnPanel({ </Box> <Box height={1} /> {lines.map((line, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: positional content lines + // positional content lines <Text key={i}>{line || " "}</Text> ))} {Array.from({ length: padding }, (_, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: positional filler + // positional filler <Text key={`p${i}`}> </Text> ))} </Box> @@ -1297,7 +1297,7 @@ function LearnPanel({ function ProgressPanel({ steps }: { steps: StepEntry[] }): React.ReactNode { const completedCount = steps.filter( - (entry) => entry.status === "completed" + (entry) => entry.status === "completed", ).length; const totalCount = steps.length; @@ -1529,7 +1529,7 @@ function FilesPanel({ } const analyzedCount = filesRead.filter( - (entry) => entry.status === "analyzed" + (entry) => entry.status === "analyzed", ).length; const padding = Math.max(0, viewport - visible.length); @@ -1548,11 +1548,11 @@ function FilesPanel({ <Box flexDirection="row"> <Box flexDirection="column" flexGrow={1}> {visible.map((row, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: positional read-tree rows + // positional read-tree rows <ReadTreeLine key={`r${i}`} row={row} /> ))} {Array.from({ length: padding }, (_, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: positional filler + // positional filler <Text key={`p${i}`}> </Text> ))} </Box> @@ -1588,7 +1588,7 @@ function Scrollbar({ return ( <Box flexDirection="column" flexShrink={0} marginLeft={1}> {cells.map((cell, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: positional scrollbar + // positional scrollbar <Text color={MUTED_DIM} key={i}> {cell} </Text> @@ -1700,7 +1700,7 @@ function ChangedFilesTree({ Changed files </Text> {treeRows.map((row, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: positional tree rows + // positional tree rows <FileTreeLine key={i} row={row} /> ))} </Box> @@ -1753,15 +1753,15 @@ type CenteredSelectLayout = { }; function getCenteredSelectLayout( - options: SelectPromptOptionData[] + options: SelectPromptOptionData[], ): CenteredSelectLayout { const labelWidth = Math.max( 0, - ...options.map((option) => stringWidth(option.label)) + ...options.map((option) => stringWidth(option.label)), ); const hintWidth = Math.max( 0, - ...options.map((option) => stringWidth(option.hint ?? "")) + ...options.map((option) => stringWidth(option.hint ?? "")), ); return { labelWidth, @@ -1787,7 +1787,7 @@ const WORKFLOW_ACTIVITY_RESERVED_ROWS = 7; export function getOptionWindow( totalCount: number, highlighted: number, - maxVisible: number + maxVisible: number, ): readonly [number, number] { const normalizedTotal = Math.max(0, Math.floor(totalCount)); if (normalizedTotal === 0) { @@ -1796,16 +1796,16 @@ export function getOptionWindow( const viewportSize = Math.min( normalizedTotal, - Math.max(1, Math.floor(maxVisible)) + Math.max(1, Math.floor(maxVisible)), ); const normalizedHighlight = Math.min( normalizedTotal - 1, - Math.max(0, Math.floor(highlighted)) + Math.max(0, Math.floor(highlighted)), ); const centeredStart = normalizedHighlight - Math.floor(viewportSize / 2); const start = Math.min( normalizedTotal - viewportSize, - Math.max(0, centeredStart) + Math.max(0, centeredStart), ); return [start, start + viewportSize]; } @@ -1838,7 +1838,7 @@ function getPromptOptionCapacity({ } function getPromptOptionLimit( - args: Parameters<typeof getPromptOptionCapacity>[0] + args: Parameters<typeof getPromptOptionCapacity>[0], ): number { return Math.max(1, getPromptOptionCapacity(args)); } @@ -1861,7 +1861,7 @@ function getMultiSelectLayout({ const descriptionWidth = Math.max( 1, getPromptContentWidth(terminalColumns, alignment) - - (alignment === "center" ? 4 : 5) + (alignment === "center" ? 4 : 5), ); const optionRows = options.map((option) => { if (!option.description) { @@ -1876,10 +1876,10 @@ function getMultiSelectLayout({ }); const lockedRows = optionRows.reduce( (total, rows, index) => (options[index]?.locked ? total + rows : total), - 0 + 0, ); const selectableRows = optionRows.filter( - (_rows, index) => !options[index]?.locked + (_rows, index) => !options[index]?.locked, ); const tallestSelectable = Math.max(1, ...selectableRows); if (lockedRows + tallestSelectable > availableRows) { @@ -1892,7 +1892,7 @@ function getMultiSelectLayout({ return { maxVisibleOptions: Math.max( 1, - Math.floor(Math.max(0, availableRows - lockedRows) / tallestSelectable) + Math.floor(Math.max(0, availableRows - lockedRows) / tallestSelectable), ), showDescriptions: true, }; @@ -1900,7 +1900,7 @@ function getMultiSelectLayout({ function getPromptContentWidth( terminalColumns: number, - alignment: PromptAlignment + alignment: PromptAlignment, ): number { const frameWidth = getInkFrameWidth(terminalColumns); if (alignment === "center") { @@ -1919,22 +1919,22 @@ function getPromptTextRows(text: string, availableWidth: number): number { function getPromptDetailWidth( terminalColumns: number, - alignment: PromptAlignment + alignment: PromptAlignment, ): number { const leadingWidth = alignment === "start" ? 3 : 0; return Math.max( 1, - getPromptContentWidth(terminalColumns, alignment) - leadingWidth + getPromptContentWidth(terminalColumns, alignment) - leadingWidth, ); } function getPromptDetailsRows( details: readonly PromptDetail[], - availableWidth: number + availableWidth: number, ): number { return details.reduce( (rows, detail) => rows + getPromptTextRows(detail.text, availableWidth), - 0 + 0, ); } @@ -1971,7 +1971,7 @@ function getStructuredSelectLayout({ details.length > 0 && optionCount <= 2 ? optionRows : 1; const availableRows = Math.max( 1, - terminalRows - WORKFLOW_ACTIVITY_RESERVED_ROWS - occupiedRows + terminalRows - WORKFLOW_ACTIVITY_RESERVED_ROWS - occupiedRows, ); const naturalSpacingRows = 2 + (footer ? 1 : 0); const naturalRows = @@ -1991,7 +1991,7 @@ function getStructuredSelectLayout({ detailWidth, maxVisibleDetailRows: Math.max( 0, - availableRows - titleRows - minimumOptionRows + availableRows - titleRows - minimumOptionRows, ), maxVisibleOptions: minimumOptionRows, }; @@ -2015,7 +2015,7 @@ function getPromptMessageRows({ if (kind === "select") { const positionWidth = stringWidth( - `(${"9".repeat(countDigits)}/${"9".repeat(countDigits)})` + `(${"9".repeat(countDigits)}/${"9".repeat(countDigits)})`, ); availableWidth -= positionWidth + (alignment === "center" ? 1 : 3); } else if (alignment === "start") { @@ -2059,7 +2059,7 @@ function normalizeDetailWindowStart(start: number, itemCount: number): number { function getDetailWindowEnd( start: number, itemRows: readonly number[], - rowBudget: number + rowBudget: number, ): number { let usedRows = 0; let end = normalizeDetailWindowStart(start, itemRows.length); @@ -2077,7 +2077,7 @@ function getDetailWindowEnd( function getPreviousDetailWindowStart( start: number, itemRows: readonly number[], - rowBudget: number + rowBudget: number, ): number { let previousStart = normalizeDetailWindowStart(start, itemRows.length); let usedRows = 0; @@ -2095,7 +2095,7 @@ function getPreviousDetailWindowStart( function getNextDetailWindowStart( start: number, itemRows: readonly number[], - rowBudget: number + rowBudget: number, ): number { const currentStart = normalizeDetailWindowStart(start, itemRows.length); const windowEnd = getDetailWindowEnd(currentStart, itemRows, rowBudget); @@ -2111,7 +2111,7 @@ function usePromptDetailWindow( promptDetails: PromptDetail[], maxVisibleRows: number, detailWidth: number, - footer?: PromptDetail + footer?: PromptDetail, ): { detailShortcut: ShortcutBinding | null; isWindowed: boolean; @@ -2124,7 +2124,7 @@ function usePromptDetailWindow( header, headerRows: header ? getPromptTextRows(header.text, detailWidth) : 0, itemRows: items.map((detail) => - getPromptTextRows(detail.text, detailWidth) + getPromptTextRows(detail.text, detailWidth), ), items, }; @@ -2139,22 +2139,22 @@ function usePromptDetailWindow( const indicatorRows = isWindowed ? getPromptTextRows( `${detailLayout.items.length}-${detailLayout.items.length}/${detailLayout.items.length} · pgup/pgdn`, - detailWidth + detailWidth, ) : 0; const itemRowBudget = Math.max( 0, - maxVisibleRows - detailLayout.headerRows - footerRows - indicatorRows + maxVisibleRows - detailLayout.headerRows - footerRows - indicatorRows, ); const [windowStart, setWindowStart] = useState(0); const normalizedStart = normalizeDetailWindowStart( windowStart, - detailLayout.items.length + detailLayout.items.length, ); const windowEnd = getDetailWindowEnd( normalizedStart, detailLayout.itemRows, - itemRowBudget + itemRowBudget, ); const visibleDetails = isWindowed ? [ @@ -2180,7 +2180,7 @@ function usePromptDetailWindow( ? getPreviousDetailWindowStart : getNextDetailWindowStart; setWindowStart((start) => - moveWindow(start, detailLayout.itemRows, itemRowBudget) + moveWindow(start, detailLayout.itemRows, itemRowBudget), ); }, }; @@ -2334,7 +2334,7 @@ function SelectPromptArea({ : 1; const maxVisibleDetailRows = Math.max( 0, - messageRows - 1 - Math.max(0, minimumVisibleOptions - optionCapacity) + messageRows - 1 - Math.max(0, minimumVisibleOptions - optionCapacity), ); return ( <SelectPrompt @@ -2453,7 +2453,7 @@ function SelectPrompt({ promptDetails, maxVisibleDetailRows, detailWidth, - prompt.footer + prompt.footer, ); const { columns } = useInkFrameSize(); const centeredLayout = isCentered @@ -2464,12 +2464,12 @@ function SelectPrompt({ : undefined; const totalCount = prompt.options.length; const [highlighted, setHighlighted] = useState<number>(() => - Math.min(Math.max(prompt.initialIndex, 0), Math.max(0, totalCount - 1)) + Math.min(Math.max(prompt.initialIndex, 0), Math.max(0, totalCount - 1)), ); const [windowStart, windowEnd] = getOptionWindow( totalCount, highlighted, - maxVisibleOptions + maxVisibleOptions, ); const visibleOptions = prompt.options.slice(windowStart, windowEnd); const isWindowed = visibleOptions.length < totalCount; @@ -2510,7 +2510,7 @@ function SelectPrompt({ run: () => prompt.resolve(null), }, ], - [detailShortcut, highlighted, prompt, totalCount] + [detailShortcut, highlighted, prompt, totalCount], ); useInkShortcuts("select-prompt", shortcuts); @@ -2631,7 +2631,7 @@ function ConfirmPrompt({ run: () => prompt.resolve(null), }, ], - [prompt] + [prompt], ); useInkShortcuts("confirm-prompt", shortcuts); @@ -2686,11 +2686,11 @@ function MultiSelectPrompt({ const promptDetails = prompt.details ?? []; const lockedOptions = useMemo( () => prompt.options.filter((option) => option.locked), - [prompt.options] + [prompt.options], ); const selectableOptions = useMemo( () => prompt.options.filter((option) => !option.locked), - [prompt.options] + [prompt.options], ); const [selected, setSelected] = useState<Set<string>>( () => @@ -2699,11 +2699,11 @@ function MultiSelectPrompt({ ...prompt.options .filter((option) => option.locked) .map((option) => option.value), - ]) + ]), ); const [highlighted, setHighlighted] = useState<number>(() => { const firstUnselected = selectableOptions.findIndex( - (option) => !prompt.initialSelected.includes(option.value) + (option) => !prompt.initialSelected.includes(option.value), ); return Math.max(0, firstUnselected); }); @@ -2712,7 +2712,7 @@ function MultiSelectPrompt({ const [windowStart, windowEnd] = getOptionWindow( selectableCount, highlighted, - maxVisibleOptions + maxVisibleOptions, ); const visibleOptions = selectableOptions.slice(windowStart, windowEnd); const isWindowed = visibleOptions.length < selectableCount; @@ -2733,7 +2733,7 @@ function MultiSelectPrompt({ return next; }); }, - [selectableOptions] + [selectableOptions], ); const commit = useCallback(() => { @@ -2759,7 +2759,7 @@ function MultiSelectPrompt({ } if (key.upArrow) { setHighlighted((idx) => - idx === 0 ? selectableCount - 1 : idx - 1 + idx === 0 ? selectableCount - 1 : idx - 1, ); return; } @@ -2782,7 +2782,7 @@ function MultiSelectPrompt({ setSelected((prev) => { const lockedValues = lockedOptions.map((option) => option.value); const allSelectableSelected = selectableOptions.every((option) => - prev.has(option.value) + prev.has(option.value), ); if (allSelectableSelected) { return new Set(lockedValues); @@ -2814,7 +2814,7 @@ function MultiSelectPrompt({ selectableCount, selectableOptions, toggleAt, - ] + ], ); useInkShortcuts("multiselect-prompt", shortcuts); const shortcutText = `↑↓ move ${ICONS.bullet} space toggle ${ICONS.bullet} a all ${ICONS.bullet} enter continue`; @@ -2904,7 +2904,7 @@ function MultiSelectPrompt({ function getLockedOptionHint( option: MultiSelectPromptOptionData, - availableWidth: number + availableWidth: number, ): string { if (!option.locked) { return ""; @@ -3026,7 +3026,7 @@ export function mountApp( exitOnCtrlC: boolean; patchConsole: boolean; stdin?: import("node:tty").ReadStream; - } + }, ): { unmount: () => void; waitUntilExit: () => Promise<unknown>; diff --git a/packages/cli/src/lib/init/ui/ink-frame.tsx b/packages/cli/src/lib/init/ui/ink-frame.tsx index 91e462b22..14ff52449 100644 --- a/packages/cli/src/lib/init/ui/ink-frame.tsx +++ b/packages/cli/src/lib/init/ui/ink-frame.tsx @@ -20,7 +20,7 @@ export function getInkFrameWidth(terminalColumns: number): number { export function getInkFrameMargin( terminalColumns: number, - frameWidth: number + frameWidth: number, ): number { return Math.max(0, Math.floor((terminalColumns - frameWidth) / 2)); } @@ -65,7 +65,7 @@ export function StatusHistory({ color = currentColor; } return ( - // biome-ignore lint/suspicious/noArrayIndexKey: positional status messages + // positional status messages <Text color={color} key={index}> {glyph} {message} </Text> diff --git a/packages/cli/src/lib/init/ui/ink-report.ts b/packages/cli/src/lib/init/ui/ink-report.ts index ca2cbe7ef..2f89677fc 100644 --- a/packages/cli/src/lib/init/ui/ink-report.ts +++ b/packages/cli/src/lib/init/ui/ink-report.ts @@ -22,7 +22,7 @@ const ERROR_SPLIT_RE = /:\s+/; export function formatFailureReport( message: string, logs: readonly { severity: string; text: string }[], - feedbackHint?: string + feedbackHint?: string, ): string { const icon = chalk.hex(REPORT_ERROR)("\u2716"); const lines: string[] = [ @@ -32,7 +32,7 @@ export function formatFailureReport( (entry) => entry.severity === "error" && entry.text !== message && - entry.text !== "Failed" + entry.text !== "Failed", ); if (errorLogs.length > 0) { lines.push(""); @@ -47,14 +47,14 @@ export function formatFailureReport( export function formatSuccessReport( message: string, summary: WizardSummary | undefined, - feedbackHint?: string + feedbackHint?: string, ): string { const successIcon = chalk.hex(REPORT_SUCCESS)("✔"); const lines: string[] = ["", `${successIcon} ${chalk.bold(message)}`]; if (summary && summary.fields.length > 0) { lines.push(""); const labelWidth = Math.max( - ...summary.fields.map((field) => field.label.length) + ...summary.fields.map((field) => field.label.length), ); for (const field of summary.fields) { const label = chalk.hex(REPORT_MUTED)(field.label.padEnd(labelWidth)); @@ -94,7 +94,7 @@ export function formatSuccessReport( * to your errors (no key hints, since there's nothing to press anymore). */ export function formatSuccessExitLine( - summary: WizardSummary | undefined + summary: WizardSummary | undefined, ): string { const completion = summary?.completion; const project = completion?.projectName ?? "your project"; diff --git a/packages/cli/src/lib/init/ui/ink-shortcuts.tsx b/packages/cli/src/lib/init/ui/ink-shortcuts.tsx index c9761f684..e15b7826a 100644 --- a/packages/cli/src/lib/init/ui/ink-shortcuts.tsx +++ b/packages/cli/src/lib/init/ui/ink-shortcuts.tsx @@ -49,7 +49,7 @@ function shortcutSignature(hints: readonly ShortcutHint[]): string { } export function arrangeShortcutHints( - hints: readonly ShortcutHint[] + hints: readonly ShortcutHint[], ): ShortcutHint[] { const seen = new Set<string>(); const unique: Array<{ hint: ShortcutHint; index: number }> = []; @@ -107,7 +107,7 @@ export function ShortcutHintProvider({ } refresh(); }, - [refresh] + [refresh], ); const clearScope = useCallback( @@ -115,12 +115,12 @@ export function ShortcutHintProvider({ scopes.current.delete(scope); refresh(); }, - [refresh] + [refresh], ); const registry = useMemo( () => ({ setScope, clearScope }), - [setScope, clearScope] + [setScope, clearScope], ); return ( @@ -139,7 +139,7 @@ export function useShortcutHints(): ShortcutHint[] { export function useInkShortcuts( scope: string, bindings: readonly ShortcutBinding[], - options: { isActive?: boolean } = {} + options: { isActive?: boolean } = {}, ): void { const registry = useContext(ShortcutRegistryContext); const isActive = options.isActive ?? true; @@ -172,6 +172,6 @@ export function useInkShortcuts( } } }, - { isActive } + { isActive }, ); } diff --git a/packages/cli/src/lib/init/ui/ink-ui.ts b/packages/cli/src/lib/init/ui/ink-ui.ts index 4bc315786..7109657db 100644 --- a/packages/cli/src/lib/init/ui/ink-ui.ts +++ b/packages/cli/src/lib/init/ui/ink-ui.ts @@ -174,7 +174,7 @@ function runInheritedCommand(command: string): Promise<void> { function seedWelcomePrompt( store: WizardStore, opts: WelcomeOptions, - pending: PendingWelcome + pending: PendingWelcome, ): void { store.setLayout("intro"); store.setPrompt({ @@ -240,7 +240,7 @@ import inkAppPath from "./ink-app.tsx" with { type: "file" }; * broken in Bun-compiled binaries (see module docstring). */ function openFreshTtyForInk(): ReadStream | null { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const fd = openSync("/dev/tty", "r"); return new ReadStream(fd); @@ -256,7 +256,7 @@ function openFreshTtyForInk(): ReadStream | null { * deps). */ export async function createInkUI( - opts: CreateInkUIOptions = {} + opts: CreateInkUIOptions = {}, ): Promise<InkUI> { // Import the Ink App sidecar. Three runtime contexts: // @@ -277,9 +277,9 @@ export async function createInkUI( // Check if running inside a Node SEA binary let isSea = false; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { - // biome-ignore lint/suspicious/noExplicitAny: node:sea types not yet in @types/node + // oxlint-disable-next-line typescript/no-explicit-any -- node:sea types not yet in @types/node const sea = _require("node:sea") as any; isSea = sea.isSea?.() === true; } catch { @@ -289,7 +289,7 @@ export async function createInkUI( if (isSea) { // Extract the embedded sidecar to a temp file and import it. // The asset key matches what fossilize registered via --assets. - // biome-ignore lint/suspicious/noExplicitAny: node:sea types not yet in @types/node + // oxlint-disable-next-line typescript/no-explicit-any -- node:sea types not yet in @types/node const sea = _require("node:sea") as any; const { writeFileSync, mkdtempSync } = await import("node:fs"); const { join } = await import("node:path"); @@ -312,7 +312,7 @@ export async function createInkUI( // Clean up SEA temp file — module is cached in memory after import() if (seaTmpDir) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { rmSync } = await import("node:fs"); rmSync(seaTmpDir, { recursive: true, force: true }); @@ -391,7 +391,7 @@ export async function createInkUI( type InkInstance = { unmount: () => void; waitUntilExit: () => Promise<unknown>; - // biome-ignore lint/suspicious/noExplicitAny: dynamic-import boundary + // oxlint-disable-next-line typescript/no-explicit-any -- dynamic-import boundary rerender: (node: any) => void; /** * Clears Ink's last rendered output from the terminal. We call @@ -471,7 +471,7 @@ export class InkUI implements WizardUI { instance: InkInstance, store: WizardStore, freshStdin: ReadStream | null, - promptOptions: InkPromptOptions = {} + promptOptions: InkPromptOptions = {}, ) { this.instance = instance; this.store = store; @@ -483,7 +483,7 @@ export class InkUI implements WizardUI { const initialWelcome = this.initialWelcome; initialWelcome.tracedPromise = this.promptTelemetry.tracePrompt( "welcome", - () => initialWelcome.promise + () => initialWelcome.promise, ); } if (this.initialWelcome && !this.initialWelcome.settled) { @@ -533,7 +533,7 @@ export class InkUI implements WizardUI { actions: { openUrl: (url) => { // Best-effort; openBrowser never throws, catch keeps it non-blocking. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. openBrowser(url).catch(() => { // ignore }); @@ -589,7 +589,7 @@ export class InkUI implements WizardUI { setStep( stepId: string, - status: "in_progress" | "completed" | "failed" | "skipped" + status: "in_progress" | "completed" | "failed" | "skipped", ): void { this.promptTelemetry.setActiveStep(stepId, status === "in_progress"); this.store.setStepStatus(stepId, status); @@ -677,7 +677,7 @@ export class InkUI implements WizardUI { /** Measures only the interval in which the prompt can accept user input. */ private waitForPrompt<T>( kind: WizardPromptKind, - mount: (resolve: (value: T) => void) => void + mount: (resolve: (value: T) => void) => void, ): Promise<T> { return this.promptTelemetry.tracePrompt(kind, () => new Promise<T>(mount)); } @@ -689,7 +689,7 @@ export class InkUI implements WizardUI { private completePrompt<T>( prompt: ActivePrompt, value: T, - resolve: (resolvedValue: T) => void + resolve: (resolvedValue: T) => void, ): void { this.activePromptCancel = undefined; this.completedPrompt = prompt; @@ -711,8 +711,8 @@ export class InkUI implements WizardUI { ? Math.max( 0, opts.options.findIndex( - (option) => option.value === opts.initialValue - ) + (option) => option.value === opts.initialValue, + ), ) : 0; let settled = false; @@ -758,7 +758,7 @@ export class InkUI implements WizardUI { this.completePrompt( prompt, value === null ? CANCELLED : (value as T), - resolve + resolve, ); }, }; @@ -767,7 +767,7 @@ export class InkUI implements WizardUI { } multiselect<T extends string>( - opts: MultiSelectOptions<T> + opts: MultiSelectOptions<T>, ): Promise<T[] | Cancelled> { return this.waitForPrompt<T[] | Cancelled>("multiselect", (resolve) => { let settled = false; @@ -808,7 +808,7 @@ export class InkUI implements WizardUI { this.completePrompt( prompt, values === null ? CANCELLED : (values as T[]), - resolve + resolve, ); }, }; @@ -852,7 +852,7 @@ export class InkUI implements WizardUI { initialWelcome.tracedPromise ?? this.promptTelemetry.tracePrompt( "welcome", - () => initialWelcome.promise + () => initialWelcome.promise, ); return promptPromise.finally(() => { this.activePromptCancel = undefined; @@ -960,13 +960,13 @@ export class InkUI implements WizardUI { // Detach the cancel callback from the store so a stale Ctrl+C // routed through the App after teardown can't re-enter. this.store.setRequestCancel(undefined); - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { this.instance.clear(); } catch { // best-effort } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { this.instance.unmount(); } catch { @@ -985,22 +985,22 @@ export class InkUI implements WizardUI { // left untouched so its compact summary flows into scrollback as before. const hasPostExitActions = this.store.getSnapshot().postExitActions.length > 0; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { process.stdout.write( - hasPostExitActions ? "\x1b[?1049l\x1b[2J\x1b[H" : "\x1b[?1049l" + hasPostExitActions ? "\x1b[?1049l\x1b[2J\x1b[H" : "\x1b[?1049l", ); } catch { // best-effort — stdout may already be destroyed } if (this.freshStdin) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { this.freshStdin.setRawMode(false); } catch { // stream already torn down } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { this.freshStdin.pause(); this.freshStdin.destroy(); @@ -1108,7 +1108,7 @@ export class InkUI implements WizardUI { return formatFailureReport( this.failureMessage, this.store.getSnapshot().logs, - this.feedbackHint + this.feedbackHint, ); } if (!this.outroMessage) { diff --git a/packages/cli/src/lib/init/ui/logging-ui.ts b/packages/cli/src/lib/init/ui/logging-ui.ts index b2ec786b1..64ae5c3dd 100644 --- a/packages/cli/src/lib/init/ui/logging-ui.ts +++ b/packages/cli/src/lib/init/ui/logging-ui.ts @@ -46,11 +46,11 @@ import type { export class LoggingUIPromptError extends Error { constructor( promptKind: "select" | "multiselect" | "confirm", - message: string + message: string, ) { super( `Cannot show ${promptKind} prompt in non-interactive mode: ${message}. ` + - "Pass --yes or provide the value via CLI flags / environment variables." + "Pass --yes or provide the value via CLI flags / environment variables.", ); this.name = "LoggingUIPromptError"; } @@ -107,7 +107,7 @@ export class LoggingUI implements WizardUI { // the user's terminal even without a tabulated renderer. const labelWidth = Math.max( ...summary.fields.map((field) => field.label.length), - 0 + 0, ); this.writeLine(this.stdout, ""); for (const field of summary.fields) { @@ -206,7 +206,7 @@ export class LoggingUI implements WizardUI { multiselect<T extends string>(opts: MultiSelectOptions<T>): Promise<T[]> { return Promise.reject( - new LoggingUIPromptError("multiselect", opts.message) + new LoggingUIPromptError("multiselect", opts.message), ); } diff --git a/packages/cli/src/lib/init/ui/types.ts b/packages/cli/src/lib/init/ui/types.ts index 1b762346d..d42eda662 100644 --- a/packages/cli/src/lib/init/ui/types.ts +++ b/packages/cli/src/lib/init/ui/types.ts @@ -32,7 +32,7 @@ import type { InitFeedbackOutcome } from "../feedback.js"; /** Sentinel symbol returned by prompt methods when the user cancels. */ export const CANCELLED: unique symbol = Symbol.for( - "sentry-cli:wizard-ui:cancelled" + "sentry-cli:wizard-ui:cancelled", ); export type Cancelled = typeof CANCELLED; @@ -323,7 +323,7 @@ export type WizardUI = AsyncDisposable & { */ setStep?( stepId: string, - status: "in_progress" | "completed" | "failed" | "skipped" + status: "in_progress" | "completed" | "failed" | "skipped", ): void; /** @@ -372,7 +372,7 @@ export type WizardUI = AsyncDisposable & { * if the user aborted. */ multiselect<T extends string>( - opts: MultiSelectOptions<T> + opts: MultiSelectOptions<T>, ): Promise<T[] | Cancelled>; /** diff --git a/packages/cli/src/lib/init/ui/wizard-store.ts b/packages/cli/src/lib/init/ui/wizard-store.ts index 71aea1bf8..2f0584dd7 100644 --- a/packages/cli/src/lib/init/ui/wizard-store.ts +++ b/packages/cli/src/lib/init/ui/wizard-store.ts @@ -307,7 +307,7 @@ export class WizardStore { // Only keys the caller actually set override the baseline (undefined values // are dropped so they don't clobber a default). const overrides = Object.fromEntries( - Object.entries(initial).filter(([, value]) => value !== undefined) + Object.entries(initial).filter(([, value]) => value !== undefined), ) as Partial<WizardSnapshot>; this.snapshot = { ...baseSnapshot(), ...overrides }; } @@ -426,7 +426,7 @@ export class WizardStore { return; } const byPath = new Map( - this.snapshot.filesRead.map((entry) => [entry.path, entry]) + this.snapshot.filesRead.map((entry) => [entry.path, entry]), ); for (const path of paths) { const existing = byPath.get(path); @@ -489,7 +489,7 @@ export class WizardStore { return; } const byPath = new Map( - this.snapshot.filesRead.map((entry) => [entry.path, entry]) + this.snapshot.filesRead.map((entry) => [entry.path, entry]), ); for (const path of paths) { byPath.set(path, { path, status: "analyzed" }); @@ -542,7 +542,7 @@ export class WizardStore { } this.update({ postExitActions: this.snapshot.postExitActions.filter( - (c) => c !== command + (c) => c !== command, ), }); } @@ -637,7 +637,7 @@ export class WizardStore { */ function backfillSkippedSteps( steps: StepEntry[], - startedIndex: number + startedIndex: number, ): StepEntry[] { let changed = false; const candidate = steps.map((entry) => { @@ -671,7 +671,7 @@ function backfillSkippedSteps( function applyStepStatus( steps: StepEntry[], id: string, - status: StepStatus + status: StepStatus, ): StepEntry[] { const targetIndex = steps.findIndex((entry) => entry.id === id); if (targetIndex === -1) { diff --git a/packages/cli/src/lib/init/verify-setup.ts b/packages/cli/src/lib/init/verify-setup.ts index e52bf3f11..e77a4bd22 100644 --- a/packages/cli/src/lib/init/verify-setup.ts +++ b/packages/cli/src/lib/init/verify-setup.ts @@ -102,7 +102,7 @@ function findFatalError(line: string): boolean { /** Scan collected lines for fatal errors, returning the first match. */ function scanLinesForError( - lines: readonly string[] + lines: readonly string[], ): StartupOutcome & { kind: "errored" | "started" } { for (const line of lines) { if (findFatalError(line)) { @@ -121,7 +121,7 @@ function scanLinesForError( */ function watchChildOutput( child: ChildProcess, - timeoutMs: number + timeoutMs: number, ): { promise: Promise<StartupOutcome>; getLines: () => string[] } { const lines: string[] = []; let hasOutput = false; @@ -183,7 +183,7 @@ function watchChildOutput( function buildVerifyEnv( spotlightUrl: string, detected: { source: string }, - cwd: string + cwd: string, ): Record<string, string | undefined> { let env: Record<string, string | undefined> = { ...process.env, @@ -206,7 +206,7 @@ function buildVerifyEnv( /** Signal the POSIX process group or force-terminate the Windows process tree. */ function terminateProcessTree( child: ChildProcess, - signal: NodeJS.Signals + signal: NodeJS.Signals, ): boolean { const pid = child.pid; if (pid === undefined) { @@ -227,13 +227,13 @@ function terminateProcessTree( if (taskkill.error) { logger.debug( `Failed to terminate Windows verification process tree while handling ${signal}`, - taskkill.error + taskkill.error, ); return false; } if (taskkill.status !== 0) { logger.debug( - `taskkill exited with status ${taskkill.status} while handling ${signal} for the verification process tree` + `taskkill exited with status ${taskkill.status} while handling ${signal} for the verification process tree`, ); return false; } @@ -280,7 +280,7 @@ function isPosixProcessGroupAlive(child: ChildProcess): boolean { /** Wait for a POSIX verification process group to exit, bounded by a timeout. */ async function waitForPosixProcessGroupExit( child: ChildProcess, - timeoutMs: number + timeoutMs: number, ): Promise<boolean> { const deadline = Date.now() + timeoutMs; while (isPosixProcessGroupAlive(child)) { @@ -308,7 +308,7 @@ async function cleanupProcessTree(child: ChildProcess): Promise<void> { terminateProcessTree(child, "SIGTERM"); const exited = await waitForPosixProcessGroupExit( child, - PROCESS_SHUTDOWN_GRACE_MS + PROCESS_SHUTDOWN_GRACE_MS, ); if (!exited) { terminateProcessTree(child, "SIGKILL"); @@ -366,7 +366,7 @@ export type VerifyResult = { export async function verifySetup( result: WorkflowRunResult, ui: WizardUI, - cwd: string + cwd: string, ): Promise<VerifyResult> { const detected = await detectDevCommand(cwd); if (!detected) { @@ -401,7 +401,7 @@ export async function verifySetup( const envelopeReceived = new Promise<void>((r) => { subscriptionId = buffer.subscribe((container) => { if (firstEventId === undefined) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = container.getParsedEnvelope(); const header = parsed?.envelope?.[0] as @@ -459,11 +459,11 @@ export async function verifySetup( const { promise: startupPromise, getLines } = watchChildOutput( child, - VERIFY_TIMEOUT_S * 1000 + VERIFY_TIMEOUT_S * 1000, ); const childExited = new Promise<{ kind: "exited"; code: number }>((r) => { child.on("close", (code) => - r({ kind: "exited" as const, code: code ?? 1 }) + r({ kind: "exited" as const, code: code ?? 1 }), ); }); const childErrored = new Promise<{ kind: "spawn_error"; error: Error }>( @@ -471,7 +471,7 @@ export async function verifySetup( child.once("error", (error) => { resolveError({ kind: "spawn_error", error }); }); - } + }, ); let timeoutHandle: ReturnType<typeof setTimeout> | undefined; @@ -483,7 +483,7 @@ export async function verifySetup( new Promise<{ kind: "timeout" }>((r) => { timeoutHandle = setTimeout( () => r({ kind: "timeout" as const }), - VERIFY_TIMEOUT_S * 1000 + VERIFY_TIMEOUT_S * 1000, ); }), ]); @@ -597,7 +597,7 @@ function reportOutcome(outcome: VerifyOutcome, ctx: ReportContext): void { return; } ui.log.warn( - `Could not verify — dev server exited with code ${outcome.code}` + `Could not verify — dev server exited with code ${outcome.code}`, ); logger.debug(`Last output: ${getLines().slice(-3).join(" | ")}`); captureException(new Error("init verification failed"), { diff --git a/packages/cli/src/lib/init/wizard-runner.ts b/packages/cli/src/lib/init/wizard-runner.ts index e70f50ccb..8ee7b9de2 100644 --- a/packages/cli/src/lib/init/wizard-runner.ts +++ b/packages/cli/src/lib/init/wizard-runner.ts @@ -125,7 +125,7 @@ const SENTRY_PROJECT_OPERATIONS = new Set<ToolPayload["operation"]>([ /** Narrow a tool result's `data` to a Sentry project identity, if it has one. */ function extractSentryProjectIdentity( - data: unknown + data: unknown, ): SentryProjectIdentity | undefined { if (!data || typeof data !== "object") { return; @@ -150,7 +150,7 @@ function extractSentryProjectIdentity( function nextPhase( stepPhases: Map<string, number>, stepId: string, - names: string[] + names: string[], ): string { const phase = (stepPhases.get(stepId) ?? 0) + 1; stepPhases.set(stepId, phase); @@ -158,7 +158,7 @@ function nextPhase( } function hasFileMap( - value: unknown + value: unknown, ): value is { files: Record<string, unknown> } { return ( typeof value === "object" && @@ -206,14 +206,14 @@ function hasActiveStepsPath(value: Record<string, unknown>): value is Record< } function filePathMarkersForHistory( - data: unknown + data: unknown, ): Record<string, null> | undefined { if (!hasFileMap(data)) { return; } return Object.fromEntries( - Object.keys(data.files).map((path) => [path, null]) + Object.keys(data.files).map((path) => [path, null]), ); } @@ -224,7 +224,7 @@ function filePathMarkersForHistory( function summarizeToolPhaseForHistory( payload: ToolPayload, phase: string, - result: ToolResult + result: ToolResult, ): CompactPhaseHistoryEntry { const summary: CompactPhaseHistoryEntry = { ok: result.ok, @@ -342,7 +342,7 @@ const NOOP_ROTATION: ProgressRotationHandle = { function startProgressRotation( stepId: string, spin: SpinnerHandle, - spinState: SpinState + spinState: SpinState, ): ProgressRotationHandle { const messages = STEP_PROGRESS_MESSAGES[stepId]; if (!messages || messages.length === 0) { @@ -377,11 +377,11 @@ function startProgressRotation( }; } -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: suspend handling needs to branch across tool and interactive payload kinds +// suspend handling needs to branch across tool and interactive payload kinds async function handleSuspendedStep( ctx: StepContext, stepPhases: Map<string, number>, - stepHistory: Map<string, CompactPhaseHistoryEntry[]> + stepHistory: Map<string, CompactPhaseHistoryEntry[]>, ): Promise<Record<string, unknown>> { const { payload, stepId, spin, spinState, context, ui, sentryProject } = ctx; @@ -430,14 +430,14 @@ async function handleSuspendedStep( // stopping the spinner to persist a ✔ line that just duplicates the // checklist. spin.message( - renderInlineMarkdown(truncateForTerminal(toolResult.message)) + renderInlineMarkdown(truncateForTerminal(toolResult.message)), ); } else { const followUpMessage = toolResult.ok === false ? undefined : describePostTool(payload); if (followUpMessage) { spin.message( - renderInlineMarkdown(truncateForTerminal(followUpMessage)) + renderInlineMarkdown(truncateForTerminal(followUpMessage)), ); } } @@ -492,7 +492,7 @@ async function handleSuspendedStep( if (interactiveResult.cancelled === true) { throw new WizardError( "Setup could not complete: interactive step was not resolved.", - { rendered: false } + { rendered: false }, ); } @@ -501,7 +501,7 @@ async function handleSuspendedStep( spin.start( stepId === "select-features" ? STEP_ACTIVE_LABELS["plan-codemods"] - : "Processing..." + : "Processing...", ); spinState.running = true; @@ -576,7 +576,7 @@ function assertSuspendPayload(raw: unknown): SuspendPayload { /** Keep transport identity at the wire boundary, outside local tool and UI contracts. */ function localSuspendPayload( - payload: SuspendPayload + payload: SuspendPayload, ): InteractivePayload | ToolPayload { const { protocolVersion: _protocolVersion, @@ -589,12 +589,12 @@ function localSuspendPayload( function withTimeout<T>( promise: Promise<T>, ms: number, - label: string + label: string, ): Promise<T> { return new Promise((resolve, reject) => { const timer = setTimeout( () => reject(new Error(`${label} timed out after ${ms / 1000}s`)), - ms + ms, ); promise.then( (val) => { @@ -604,7 +604,7 @@ function withTimeout<T>( (err) => { clearTimeout(timer); reject(err); - } + }, ); }); } @@ -622,7 +622,7 @@ function buildWelcomeOptions(): WelcomeOptions { async function confirmExperimental( options: WizardOptions, - ui: WizardUI + ui: WizardUI, ): Promise<boolean> { if (options.yes || options.dryRun) { return true; @@ -661,12 +661,12 @@ async function confirmExperimental( async function preamble( options: WizardOptions, - ui: WizardUI + ui: WizardUI, ): Promise<boolean> { if (!(options.yes || options.dryRun || process.stdin.isTTY)) { throw new WizardError( "Interactive mode requires a terminal. Use --yes for non-interactive mode.", - { rendered: false } + { rendered: false }, ); } @@ -693,7 +693,7 @@ async function preamble( if (err instanceof LoggingUIPromptError) { throw new WizardError( "The interactive UI failed to load. Run with --yes for non-interactive mode.", - { rendered: false } + { rendered: false }, ); } throw err; @@ -782,7 +782,7 @@ function runStateRecoveryBackoffMs(): number[] { while (totalWaitMs < RUN_STATE_RECOVERY_MAX_WAIT_MS) { const delayMs = Math.min( RUN_STATE_RECOVERY_POLL_MS, - RUN_STATE_RECOVERY_MAX_WAIT_MS - totalWaitMs + RUN_STATE_RECOVERY_MAX_WAIT_MS - totalWaitMs, ); delays.push(delayMs); totalWaitMs += delayMs; @@ -794,7 +794,7 @@ function runStateRecoveryBackoffMs(): number[] { function isRecoverableRunState( result: WorkflowRunResult, resumedStepId: string, - resumedPayload: SuspendPayload + resumedPayload: SuspendPayload, ): boolean { if (result.status !== "suspended") { return true; @@ -817,7 +817,7 @@ async function tryRecoverCurrentRunState( workflow: ResumeRetryArgs["workflow"], runId: string, resumedStepId: string, - resumedPayload: SuspendPayload + resumedPayload: SuspendPayload, ): Promise<WorkflowRunResult | null> { const deadlineAt = Date.now() + RUN_STATE_RECOVERY_MAX_WAIT_MS; for (const delayMs of runStateRecoveryBackoffMs()) { @@ -827,17 +827,17 @@ async function tryRecoverCurrentRunState( } if (delayMs > 0) { await new Promise((resolve) => - setTimeout(resolve, Math.min(delayMs, remainingMs)) + setTimeout(resolve, Math.min(delayMs, remainingMs)), ); } const timeoutMs = Math.min( RUN_STATE_RECOVERY_TIMEOUT_MS, - deadlineAt - Date.now() + deadlineAt - Date.now(), ); if (timeoutMs <= 0) { return null; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const raw = await withTimeout( workflow.runById(runId, { @@ -852,7 +852,7 @@ async function tryRecoverCurrentRunState( ], }), timeoutMs, - "Run state recovery" + "Run state recovery", ); const result = assertWorkflowResult(raw); if (isRecoverableRunState(result, resumedStepId, resumedPayload)) { @@ -872,7 +872,7 @@ async function tryRecoverCurrentRunState( * re-executes the local tool when a response was lost or its request is stale. */ async function resumeWithRecovery( - args: ResumeRetryArgs + args: ResumeRetryArgs, ): Promise<WorkflowRunResult> { const { run, @@ -898,10 +898,10 @@ async function resumeWithRecovery( resumeData: wireResumeData, tracingOptions, }), - WORKFLOW_RESUME_ASYNC_ENDPOINT + WORKFLOW_RESUME_ASYNC_ENDPOINT, ), API_TIMEOUT_MS, - "Workflow resume" + "Workflow resume", ); return assertWorkflowResult(raw); } catch (err) { @@ -912,7 +912,7 @@ async function resumeWithRecovery( workflow, run.runId, stepId, - payload + payload, ); if (recovered) { addBreadcrumb({ @@ -949,7 +949,7 @@ async function resumeWithRecovery( workflow, run.runId, stepId, - payload + payload, ); ui.clearOverlay?.(); if (recovered) { @@ -966,7 +966,7 @@ async function resumeWithRecovery( } /** Run the wizard while negotiating v1 and echoing each suspended request ID. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: sequential wizard orchestration with error handling branches +// sequential wizard orchestration with error handling branches export async function runWizard(initialOptions: WizardOptions): Promise<void> { // Note: a previous `forwardFreshTtyToStdin()` call lived here as a // macOS-only workaround for clack reading from a broken inherited @@ -1091,7 +1091,7 @@ export async function runWizard(initialOptions: WizardOptions): Promise<void> { spin.message("Connecting to wizard..."); run = await withInitServiceAuthClassification( () => workflow.createRun(), - WORKFLOW_CREATE_RUN_ENDPOINT + WORKFLOW_CREATE_RUN_ENDPOINT, ); // Large shared context (dirListing, fileCache, existingSentry) // travels via Mastra's workflow `initialState` instead of `inputData`. @@ -1123,11 +1123,11 @@ export async function runWizard(initialOptions: WizardOptions): Promise<void> { }, tracingOptions, }), - WORKFLOW_START_ASYNC_ENDPOINT + WORKFLOW_START_ASYNC_ENDPOINT, ), API_TIMEOUT_MS, - "Workflow start" - ) + "Workflow start", + ), ); } catch (err) { if (err instanceof ApiError && err.status === 401) { @@ -1206,13 +1206,13 @@ export async function runWizard(initialOptions: WizardOptions): Promise<void> { sentryProject: sentryProjectRef, }, stepPhases, - stepHistory + stepHistory, ); const progressRotation = startProgressRotation( extracted.stepId, spin, - spinState + spinState, ); try { result = await resumeWithRecovery({ @@ -1300,7 +1300,7 @@ export async function runWizard(initialOptions: WizardOptions): Promise<void> { spinState, ui, directory, - sentryProjectRef.current + sentryProjectRef.current, ); setTag("wizard.outcome", "completed"); if (result.result?.platform) { @@ -1312,7 +1312,7 @@ export async function runWizard(initialOptions: WizardOptions): Promise<void> { "wizard.features", Array.isArray(resultFeatures) ? resultFeatures.join(",") - : String(resultFeatures) + : String(resultFeatures), ); } } @@ -1327,7 +1327,7 @@ export async function runWizard(initialOptions: WizardOptions): Promise<void> { */ function syncWorkflowStepStatuses( result: WorkflowRunResult, - ui: WizardUI + ui: WizardUI, ): void { for (const [stepId, step] of Object.entries(result.steps ?? {})) { if (step.status === "success") { @@ -1345,7 +1345,7 @@ type WorkflowFailure = { }; function getWorkflowFailure( - result: WorkflowRunResult + result: WorkflowRunResult, ): WorkflowFailure | undefined { const workflowCode = result.result?.exitCode; if (result.status === "success" && workflowCode === 0) { @@ -1371,14 +1371,14 @@ function getWorkflowFailure( }; } -// biome-ignore lint/nursery/useMaxParams: cwd and sentryProject are optional trailing extensions +// oxlint-disable-next-line max-params -- cwd and sentryProject are optional trailing extensions export async function handleFinalResult( result: WorkflowRunResult, spin: SpinnerHandle, spinState: SpinState, ui: WizardUI, cwd?: string, - sentryProject?: SentryProjectIdentity + sentryProject?: SentryProjectIdentity, ): Promise<void> { const failure = getWorkflowFailure(result); @@ -1465,7 +1465,7 @@ function activeStepIdsFor(result: WorkflowRunResult, stepId: string): string[] { function extractSuspendPayloadFromStep( result: WorkflowRunResult, - stepId: string + stepId: string, ): { payload: SuspendPayload; stepId: string } | undefined { const stepPayload = result.steps?.[stepId]?.suspendPayload; if (!stepPayload) { @@ -1476,7 +1476,7 @@ function extractSuspendPayloadFromStep( function extractSuspendPayload( result: WorkflowRunResult, - stepId: string + stepId: string, ): { payload: SuspendPayload; stepId: string } | undefined { const activeStepIds = activeStepIdsFor(result, stepId); if (activeStepIds.length > 0) { @@ -1500,7 +1500,7 @@ function extractSuspendPayload( } const payloadEntries = Object.entries(result.steps ?? {}).filter( - ([, entry]) => entry.suspendPayload + ([, entry]) => entry.suspendPayload, ); if (payloadEntries.length !== 1) { return; diff --git a/packages/cli/src/lib/init/workflow-inputs.ts b/packages/cli/src/lib/init/workflow-inputs.ts index 45dc8f9ed..dda88e83f 100644 --- a/packages/cli/src/lib/init/workflow-inputs.ts +++ b/packages/cli/src/lib/init/workflow-inputs.ts @@ -89,7 +89,7 @@ const MAX_PREREAD_TOTAL_BYTES = 512 * 1024; * Pre-compute the initial directory listing before the first workflow call. */ export async function precomputeDirListing( - directory: string + directory: string, ): Promise<DirEntry[]> { const result = await listDir({ type: "tool", @@ -105,14 +105,14 @@ export async function precomputeDirListing( */ export async function preReadCommonFiles( directory: string, - dirListing: DirEntry[] + dirListing: DirEntry[], ): Promise<Record<string, string | null>> { // `listDir` emits POSIX-normalized paths regardless of host OS, // so `COMMON_CONFIG_FILES` (POSIX) membership checks don't need // any per-path separator translation. const listingPaths = new Set(dirListing.map((entry) => entry.path)); const toRead = COMMON_CONFIG_FILES.filter((filePath) => - listingPaths.has(filePath) + listingPaths.has(filePath), ); const cache: Record<string, string | null> = {}; @@ -145,10 +145,10 @@ type CommonConfigRead = async function readCommonConfigFile( directory: string, - filePath: string + filePath: string, ): Promise<CommonConfigRead> { let opened: OpenedProjectFile | undefined; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const result = await openProjectFile(directory, filePath); if ("error" in result) { @@ -166,7 +166,7 @@ async function readCommonConfigFile( buffer, bytesRead, buffer.length - bytesRead, - bytesRead + bytesRead, ); if (readResult.bytesRead === 0) { return { status: "unreadable" }; diff --git a/packages/cli/src/lib/input-validation.ts b/packages/cli/src/lib/input-validation.ts index 62b1ccdac..b073a8927 100644 --- a/packages/cli/src/lib/input-validation.ts +++ b/packages/cli/src/lib/input-validation.ts @@ -41,7 +41,7 @@ const PRE_ENCODED_PATTERN = /%[0-9a-fA-F]{2}/; * Built via RegExp constructor to avoid lint warnings about literal * control characters in regex patterns. */ -// biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally matching control chars for input validation +// oxlint-disable-next-line no-control-regex -- intentionally matching control chars for input validation const CONTROL_CHAR_PATTERN = /[\x00-\x1f]/; /** @@ -105,7 +105,7 @@ export function rejectControlChars(input: string, label: string): void { const capitalizedLabel = label.charAt(0).toUpperCase() + label.slice(1); throw new ValidationError( `Invalid ${label}: contains ${describeForbiddenChar(match[0])}.\n` + - ` ${capitalizedLabel} must not contain control characters.` + ` ${capitalizedLabel} must not contain control characters.`, ); } } @@ -126,7 +126,7 @@ export function rejectPreEncoded(input: string, label: string): void { if (match) { throw new ValidationError( `Invalid ${label}: contains URL-encoded sequence "${match[0]}".\n` + - ` Use plain text instead of percent-encoding (e.g., "my project" not "my%20project").` + ` Use plain text instead of percent-encoding (e.g., "my project" not "my%20project").`, ); } } @@ -155,7 +155,7 @@ export function validateResourceId(input: string, label: string): void { if (match) { throw new ValidationError( `Invalid ${label}: contains ${describeForbiddenChar(match[0])}.\n` + - " Slugs and IDs must contain only letters, numbers, hyphens, and underscores." + " Slugs and IDs must contain only letters, numbers, hyphens, and underscores.", ); } } @@ -183,7 +183,7 @@ export function validateEndpoint(endpoint: string): void { if (PATH_TRAVERSAL_PATTERN.test(endpoint)) { throw new ValidationError( 'Invalid API endpoint: contains ".." path traversal.\n' + - " Use absolute API paths (e.g., /api/0/organizations/my-org/issues/)." + " Use absolute API paths (e.g., /api/0/organizations/my-org/issues/).", ); } } diff --git a/packages/cli/src/lib/interactive-login.ts b/packages/cli/src/lib/interactive-login.ts index 585f7dcf2..8354fd164 100644 --- a/packages/cli/src/lib/interactive-login.ts +++ b/packages/cli/src/lib/interactive-login.ts @@ -5,7 +5,7 @@ * Used by both the `auth login` command and auto-auth in bin.ts. */ -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import { openBrowser } from "./browser.js"; import { setupCopyKeyListener } from "./clipboard.js"; @@ -46,7 +46,7 @@ export function toLoginUser(user: { username?: string | null; }): NonNullable<LoginResult["user"]> { return Object.fromEntries( - Object.entries(user).filter(([k, v]) => k === "id" || Boolean(v)) + Object.entries(user).filter(([k, v]) => k === "id" || Boolean(v)), ) as NonNullable<LoginResult["user"]>; } @@ -80,7 +80,7 @@ export function buildDeviceFlowDisplay( userCode: string, verificationUriComplete: string, browserOpened: boolean, - isTTY: boolean + isTTY: boolean, ): string[] { const lines: string[] = []; @@ -117,7 +117,7 @@ export function buildDeviceFlowDisplay( * @returns Structured login result on success, or null on failure/cancellation */ export async function runInteractiveLogin( - options?: InteractiveLoginOptions + options?: InteractiveLoginOptions, ): Promise<LoginResult | null> { const timeout = options?.timeout ?? 900_000; // 15 minutes default const scope = options?.scope; @@ -134,7 +134,7 @@ export async function runInteractiveLogin( onUserCode: async ( userCode, _verificationUri, - verificationUriComplete + verificationUriComplete, ) => { urlToCopy = verificationUriComplete; @@ -155,7 +155,7 @@ export async function runInteractiveLogin( userCode, verificationUriComplete, browserOpened, - process.stdin.isTTY ?? false + process.stdin.isTTY ?? false, ); for (const line of displayLines) { log.log(line); @@ -168,13 +168,13 @@ export async function runInteractiveLogin( if (process.stdin.isTTY) { keyListener.cleanup = setupCopyKeyListener( process.stdin as NodeJS.ReadStream & { fd: 0 }, - () => urlToCopy + () => urlToCopy, ); } }, }, timeout, - scope + scope, ); // Stop the spinner diff --git a/packages/cli/src/lib/introspect.ts b/packages/cli/src/lib/introspect.ts index bf8c94f25..a6040ffc9 100644 --- a/packages/cli/src/lib/introspect.ts +++ b/packages/cli/src/lib/introspect.ts @@ -219,7 +219,7 @@ export function getPositionalString(params?: PositionalParams): string { * @returns Array of positional info objects */ export function extractPositionals( - params?: PositionalParams + params?: PositionalParams, ): PositionalInfo[] { if (!params) { return []; @@ -253,7 +253,7 @@ export function extractPositionals( * @returns Normalized flag info array */ export function extractFlags( - flags: Record<string, FlagDef> | undefined + flags: Record<string, FlagDef> | undefined, ): FlagInfo[] { if (!flags) { return []; @@ -280,7 +280,7 @@ export function extractFlags( export function buildCommandInfo( cmd: Command, path: string, - examples: string[] = [] + examples: string[] = [], ): CommandInfo { const jsonFields = cmd.__jsonSchema ? extractSchemaFields(cmd.__jsonSchema) @@ -310,7 +310,7 @@ export function buildCommandInfo( export function extractRouteGroupCommands( routeMap: RouteMap, routeName: string, - docExamples: Map<string, string[]> = new Map() + docExamples: Map<string, string[]> = new Map(), ): CommandInfo[] { const commands: CommandInfo[] = []; @@ -327,7 +327,7 @@ export function extractRouteGroupCommands( } else if (isRouteMap(subTarget)) { const nestedPrefix = `${routeName} ${subEntry.name.original}`; commands.push( - ...extractRouteGroupCommands(subTarget, nestedPrefix, docExamples) + ...extractRouteGroupCommands(subTarget, nestedPrefix, docExamples), ); } } @@ -396,7 +396,7 @@ const MAX_SUGGESTIONS = 3; */ export function resolveCommandPath( routeMap: RouteMap, - path: string[] + path: string[], ): ResolvedPath | UnresolvedPath | null { if (path.length === 0) { return null; diff --git a/packages/cli/src/lib/issue-id.ts b/packages/cli/src/lib/issue-id.ts index 628fc2e1d..5306bc6af 100644 --- a/packages/cli/src/lib/issue-id.ts +++ b/packages/cli/src/lib/issue-id.ts @@ -53,7 +53,7 @@ export function isShortSuffix(input: string): boolean { * @returns Parsed alias (lowercase) and suffix (uppercase), or null */ export function parseAliasSuffix( - input: string + input: string, ): { alias: string; suffix: string } | null { const match = ALIAS_SUFFIX_PATTERN.exec(input); if (!(match?.[1] && match[2])) { @@ -72,7 +72,7 @@ export function parseAliasSuffix( */ export function expandToFullShortId( suffix: string, - projectSlug: string + projectSlug: string, ): string { return `${projectSlug.toUpperCase()}-${suffix.toUpperCase()}`; } diff --git a/packages/cli/src/lib/issue-links.ts b/packages/cli/src/lib/issue-links.ts index 39c4f89a5..004caf57e 100644 --- a/packages/cli/src/lib/issue-links.ts +++ b/packages/cli/src/lib/issue-links.ts @@ -93,31 +93,31 @@ type StoredLink = { /** Validate the URL and return the Sentry App slug, or undefined for a native integration. */ function selectSentryApp( - options: ExternalIssueLinkOptions + options: ExternalIssueLinkOptions, ): string | undefined { const url = parseHttpUrl(options.url); if (!url) { throw new ValidationError( "External issue must be an absolute HTTP(S) URL without credentials.", - "url" + "url", ); } const appSlug = options.appSlug || (url.hostname === "linear.app" ? "linear" : undefined); if (appSlug && options.integrationId) { throw new ValidationError( - "--integration selects a native integration. Use --app for a Sentry App." + "--integration selects a native integration. Use --app for a Sentry App.", ); } if (appSlug && options.appSlug === "") { throw new ValidationError( "Specify --app for this external issue URL", - "app" + "app", ); } if (!appSlug && options.fields && Object.keys(options.fields).length > 0) { throw new ValidationError( - "--field requires a Sentry App selected with --app." + "--field requires a Sentry App selected with --app.", ); } return appSlug; @@ -143,7 +143,7 @@ function nativeRef(link: NativeIssueLink): ExternalIssueRef { async function planLink( options: ExternalIssueLinkOptions, - appSlug: string | undefined + appSlug: string | undefined, ): Promise<LinkPlan> { if (appSlug) { const prepared = await resolveAppIssueLink({ ...options, appSlug }); @@ -179,7 +179,7 @@ async function planLink( async function findStoredLink( options: ExternalIssueLinkOptions, - appSlug: string | undefined + appSlug: string | undefined, ): Promise<StoredLink | undefined> { const { orgSlug, issueId, url } = options; if (appSlug) { @@ -208,7 +208,10 @@ async function findStoredLink( function toResult( options: ExternalIssueLinkOptions, action: ExternalIssueLinkResult["action"], - outcome: Pick<ExternalIssueLinkResult, "linked" | "changed" | "externalIssue"> + outcome: Pick< + ExternalIssueLinkResult, + "linked" | "changed" | "externalIssue" + >, ): ExternalIssueLinkResult { return { org: options.orgSlug, @@ -221,7 +224,7 @@ function toResult( /** App callbacks run on the control silo, so invalidate the issue's regional cache too. */ async function invalidateIssueLinks( - options: ExternalIssueLinkOptions + options: ExternalIssueLinkOptions, ): Promise<void> { const regionUrl = await resolveOrgRegion(options.orgSlug); const base = getApiBaseUrl(); @@ -231,14 +234,14 @@ async function invalidateIssueLinks( invalidateCachedResponsesMatching(new URL(issuePath, base).href), invalidateCachedResponsesMatching( new URL(`/api/0/issues/${encodeURIComponent(options.issueId)}/`, base) - .href + .href, ), ]); } /** Associate an existing ticket; a dry run performs only discovery and validation. */ export async function linkExternalIssue( - options: ExternalIssueLinkOptions + options: ExternalIssueLinkOptions, ): Promise<ExternalIssueLinkResult> { const plan = await planLink(options, selectSentryApp(options)); if (options.dryRun) { @@ -261,7 +264,7 @@ export async function linkExternalIssue( /** Remove a stored association without contacting or deleting the remote ticket. */ export async function unlinkExternalIssue( - options: ExternalIssueLinkOptions + options: ExternalIssueLinkOptions, ): Promise<ExternalIssueLinkResult> { const appSlug = selectSentryApp(options); const link = await findStoredLink(options, appSlug); diff --git a/packages/cli/src/lib/jvm-bundle.ts b/packages/cli/src/lib/jvm-bundle.ts index 257da5022..3ae98d165 100644 --- a/packages/cli/src/lib/jvm-bundle.ts +++ b/packages/cli/src/lib/jvm-bundle.ts @@ -223,7 +223,7 @@ function isJvmSourceFile(name: string, relPath: string): boolean { */ async function resolveEntryKind( entry: import("node:fs").Dirent, - fullPath: string + fullPath: string, ): Promise<"dir" | "file" | "other"> { if (entry.isDirectory()) { return "dir"; @@ -263,7 +263,7 @@ async function resolveEntryKind( async function collectJvmSources( rootDir: string, userExcludes: Set<string>, - rootIsSrc: boolean + rootIsSrc: boolean, ): Promise<Map<string, string>> { const files = new Map<string, string>(); // Track canonicalized directory paths already visited so that symlink @@ -293,7 +293,7 @@ async function collectJvmSources( /** Process a single directory entry: recurse into dirs, collect source files. */ async function processEntry( entry: import("node:fs").Dirent, - dir: string + dir: string, ): Promise<void> { const fullPath = join(dir, entry.name); const kind = await resolveEntryKind(entry, fullPath); @@ -350,7 +350,7 @@ type ValidatedEntry = { * @returns Array of validated entries suitable for ZIP inclusion. */ async function readAndValidateFiles( - urlToAbsPath: Map<string, string> + urlToAbsPath: Map<string, string>, ): Promise<ValidatedEntry[]> { const validated: ValidatedEntry[] = []; @@ -382,13 +382,13 @@ async function readAndValidateFiles( async function writeBundle( outputPath: string, manifest: SourceBundleManifest, - validatedFiles: ValidatedEntry[] + validatedFiles: ValidatedEntry[], ): Promise<void> { const zip = await ZipWriter.create(outputPath); try { await zip.addEntry( "manifest.json", - Buffer.from(JSON.stringify(manifest, null, 2), "utf-8") + Buffer.from(JSON.stringify(manifest, null, 2), "utf-8"), ); for (const { bundlePath, content } of validatedFiles) { @@ -414,7 +414,7 @@ async function writeBundle( * @returns Result with file count and output path */ export async function buildJvmBundle( - options: JvmBundleOptions + options: JvmBundleOptions, ): Promise<JvmBundleResult> { const { sourcePath, outputPath, debugId, excludePatterns = [] } = options; @@ -432,7 +432,7 @@ export async function buildJvmBundle( const sourceFiles = await collectJvmSources( sourcePath, userExcludes, - prependSrc + prependSrc, ); log.debug(`Found ${sourceFiles.size} JVM source files`); @@ -450,7 +450,7 @@ export async function buildJvmBundle( if (existingRel !== undefined) { log.warn( `URL collision on ${url}: skipping '${relPath}' (already bundled from '${existingRel}'). ` + - "Use --exclude to drop the unwanted source set." + "Use --exclude to drop the unwanted source set.", ); collisionCount += 1; continue; diff --git a/packages/cli/src/lib/kitty-image.ts b/packages/cli/src/lib/kitty-image.ts index 709c6c30e..167247299 100644 --- a/packages/cli/src/lib/kitty-image.ts +++ b/packages/cli/src/lib/kitty-image.ts @@ -48,7 +48,7 @@ const CHUNK_SIZE = 4096; export function encodeImageToKitty( img: DecodedImage, maxWidth?: number, - preserveDimensions = false + preserveDimensions = false, ): string | undefined { const effectiveMaxWidth = preserveDimensions ? (maxWidth ?? DEFAULT_MAX_WIDTH) @@ -56,7 +56,7 @@ export function encodeImageToKitty( const scaled = downscale( img, effectiveMaxWidth, - preserveDimensions ? img.height : DEFAULT_MAX_HEIGHT + preserveDimensions ? img.height : DEFAULT_MAX_HEIGHT, ); const { width, height } = scaled; if (width <= 0 || height <= 0) { @@ -66,7 +66,7 @@ export function encodeImageToKitty( const payload = Buffer.from( scaled.data.buffer, scaled.data.byteOffset, - width * height * 4 + width * height * 4, ).toString("base64"); // a=T transmit+display, f=32 RGBA, s/v pixel dimensions of the raw buffer. @@ -95,7 +95,7 @@ export function encodeImageToKitty( export function imageBytesToKitty( body: Uint8Array, contentType?: string | null, - maxWidth?: number + maxWidth?: number, ): string | undefined { const format = detectImageFormat(body, contentType); if (!format) { diff --git a/packages/cli/src/lib/list-command.ts b/packages/cli/src/lib/list-command.ts index 261bba429..8b70e9b22 100644 --- a/packages/cli/src/lib/list-command.ts +++ b/packages/cli/src/lib/list-command.ts @@ -214,7 +214,7 @@ export function parseCursorFlag(value: string): string { } if (ALL_DIGITS_RE.test(value)) { throw new Error( - `'${value}' is not a valid cursor. Cursors look like "1735689600:0:0". Use "next" / "prev" to navigate pages.` + `'${value}' is not a valid cursor. Cursors look like "1735689600:0:0". Use "next" / "prev" to navigate pages.`, ); } return value; @@ -281,7 +281,7 @@ export function paginationHint(opts: { */ export function appendQueryHint( parts: string[], - query: string | undefined + query: string | undefined, ): void { if (query) { parts.push(`-q "${query}"`); @@ -298,7 +298,7 @@ export function appendQueryHint( export function appendSortHint( parts: string[], sort: string | undefined, - defaultSort?: string + defaultSort?: string, ): void { if (sort && sort !== defaultSort) { parts.push(`--sort "${sort}"`); @@ -315,7 +315,7 @@ export function appendSortHint( */ export function buildListLimitFlag( entityPlural: string, - defaultValue = String(LIST_DEFAULT_LIMIT) + defaultValue = String(LIST_DEFAULT_LIMIT), ): { kind: "parsed"; parse: typeof numberParser; @@ -429,8 +429,8 @@ function getSubcommandsForRoute(routeName: string): Set<string> { _subcommandsByRoute.set( entry.name.original, collectChildNames( - target as { getAllEntries: () => readonly RouteEntry[] } - ) + target as { getAllEntries: () => readonly RouteEntry[] }, + ), ); } } @@ -463,7 +463,7 @@ function getSubcommandsForRoute(routeName: string): Set<string> { export function interceptSubcommand( target: string | undefined, stderr: { write(s: string): void }, - routeName: string + routeName: string, ): string | undefined { if (!target) { return target; @@ -472,8 +472,8 @@ export function interceptSubcommand( if (trimmed && getSubcommandsForRoute(routeName).has(trimmed)) { stderr.write( warning( - `Tip: "${trimmed}" is a subcommand. Running: sentry ${routeName} ${trimmed}\n` - ) + `Tip: "${trimmed}" is a subcommand. Running: sentry ${routeName} ${trimmed}\n`, + ), ); return; } @@ -504,7 +504,7 @@ type ListCommandFunction< this: CONTEXT, flags: FLAGS, ...args: ARGS - // biome-ignore lint/suspicious/noConfusingVoidType: void is required here — generators that don't return a value have implicit void return, which is distinct from undefined in TypeScript's type system + // void is required here — generators that don't return a value have implicit void return, which is distinct from undefined in TypeScript's type system ) => AsyncGenerator<unknown, CommandReturn | void, undefined>; /** @@ -551,11 +551,11 @@ export function buildListCommand< readonly fullDescription?: string; }; readonly func: ListCommandFunction<FLAGS, ARGS, CONTEXT>; - // biome-ignore lint/suspicious/noExplicitAny: OutputConfig is generic but type is erased at the builder level + // oxlint-disable-next-line typescript/no-explicit-any -- OutputConfig is generic but type is erased at the builder level readonly output?: OutputConfig<any>; readonly auth?: boolean; }, - options?: ListCommandOptions + options?: ListCommandOptions, ): Command<CONTEXT> { const originalFunc = builderArgs.func; @@ -593,7 +593,7 @@ export function buildListCommand< aliases: mergedAliases, }; - // biome-ignore lint/suspicious/noExplicitAny: Stricli's CommandFunction type is complex + // oxlint-disable-next-line typescript/no-explicit-any -- Stricli's CommandFunction type is complex const wrappedFunc = function (this: CONTEXT, flags: FLAGS, ...args: any[]) { // Auto-apply fresh flag before command runs applyFreshFlag(flags as unknown as { readonly fresh: boolean }); @@ -609,7 +609,7 @@ export function buildListCommand< args[0] = interceptSubcommand( args[0] as string | undefined, ctx.stderr, - routeName + routeName, ); } return originalFunc.call(this, flags, ...(args as unknown as ARGS)); @@ -645,7 +645,7 @@ export type OrgListCommandDocs = { */ function formatListHuman<TEntity, TWithOrg>( result: ListResult<TWithOrg>, - config: OrgListConfig<TEntity, TWithOrg> + config: OrgListConfig<TEntity, TWithOrg>, ): string { const parts: string[] = []; @@ -691,7 +691,7 @@ function formatListHuman<TEntity, TWithOrg>( export function buildOrgListCommand<TEntity, TWithOrg>( config: OrgListConfig<TEntity, TWithOrg>, docs: OrgListCommandDocs, - routeName: string + routeName: string, ): Command<SentryContext> { return buildListCommand(routeName, { docs, @@ -717,7 +717,7 @@ export function buildOrgListCommand<TEntity, TWithOrg>( readonly fresh: boolean; readonly fields?: string[]; }, - target?: string + target?: string, ) { const { cwd } = this; const parsed = parseOrgProjectArg(target); diff --git a/packages/cli/src/lib/logger.ts b/packages/cli/src/lib/logger.ts index 544a24a7b..f9f8f409f 100644 --- a/packages/cli/src/lib/logger.ts +++ b/packages/cli/src/lib/logger.ts @@ -116,7 +116,7 @@ const DEFAULT_LOG_LEVEL = 3; */ export function parseLogLevel(name: string): number { const idx = LOG_LEVEL_NAMES.indexOf( - name.toLowerCase().trim() as LogLevelName + name.toLowerCase().trim() as LogLevelName, ); return idx === -1 ? DEFAULT_LOG_LEVEL : idx; } @@ -277,11 +277,10 @@ export function attachSentryReporter(): void { return; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Dynamic import to avoid pulling in Sentry at module load time. // The reporter is exported from @sentry/node-core/light (via @sentry/node → @sentry/core). - // eslint-disable-next-line @typescript-eslint/no-require-imports const Sentry = _require("@sentry/node-core/light") as { createConsolaReporter: (options?: Record<string, unknown>) => { log: (logObj: unknown) => void; diff --git a/packages/cli/src/lib/login-host-guard.ts b/packages/cli/src/lib/login-host-guard.ts index 54caa928f..913238575 100644 --- a/packages/cli/src/lib/login-host-guard.ts +++ b/packages/cli/src/lib/login-host-guard.ts @@ -84,7 +84,7 @@ export function isAutoLoginHostTrusted(host: string): boolean { */ export function buildHostRefusalMessage( host: string, - opts?: { tokenFlag?: boolean; rcSource?: string } + opts?: { tokenFlag?: boolean; rcSource?: string }, ): string { const tokenFlag = opts?.tokenFlag ? " --token <your-token>" : ""; const sourceClause = opts?.rcSource diff --git a/packages/cli/src/lib/metrics-transform.ts b/packages/cli/src/lib/metrics-transform.ts index 3fb5ca389..bb3fd712c 100644 --- a/packages/cli/src/lib/metrics-transform.ts +++ b/packages/cli/src/lib/metrics-transform.ts @@ -30,7 +30,7 @@ export function makeTracemetricsAggregate( agg: string, name: string, type: string, - unit: string + unit: string, ): string { return `${agg}(value,${name},${type},${unit})`; } @@ -44,20 +44,20 @@ export function makeTracemetricsAggregate( export function resolveMetricField( metricName: string, agg: string, - metrics: MetricMeta[] + metrics: MetricMeta[], ): string { if (!VALID_AGGS.has(agg)) { throw new ResolutionError( `Aggregation '${agg}'`, `not recognized. Valid aggregations: ${[...VALID_AGGS].join(", ")}`, - `sentry explore my-org/ -m ${metricName} --agg sum --dataset metrics` + `sentry explore my-org/ -m ${metricName} --agg sum --dataset metrics`, ); } const match = metrics.find((m) => m.name === metricName); if (!match) { const candidateNames = Array.from( - new Set(metrics.map((m) => m.name).filter((n) => n.length > 0)) + new Set(metrics.map((m) => m.name).filter((n) => n.length > 0)), ); const suggestions = fuzzyMatch(metricName, candidateNames, { maxResults: 5, @@ -69,7 +69,7 @@ export function resolveMetricField( `sentry explore my-org/ -m ${metricName} --dataset metrics --period 7d`, suggestions.length > 0 ? [`Similar metrics: ${suggestions.join(", ")}`] - : ["Use a wider --period to search for older metrics"] + : ["Use a wider --period to search for older metrics"], ); } diff --git a/packages/cli/src/lib/mutate-command.ts b/packages/cli/src/lib/mutate-command.ts index 976f2890f..14332540e 100644 --- a/packages/cli/src/lib/mutate-command.ts +++ b/packages/cli/src/lib/mutate-command.ts @@ -165,7 +165,7 @@ export function guardNonInteractive(flags: { if (!isatty(0)) { throw new CliError( "Destructive operation refused in non-interactive mode. " + - "Use --yes or --force to confirm." + "Use --yes or --force to confirm.", ); } } @@ -199,7 +199,7 @@ export function guardNonInteractive(flags: { export async function confirmByTyping( expected: string, promptMessage: string, - opts?: { logger?: { prompt: typeof logger.prompt } } + opts?: { logger?: { prompt: typeof logger.prompt } }, ): Promise<boolean> { const log = opts?.logger ?? logger; @@ -231,7 +231,7 @@ export async function confirmByTyping( export function requireExplicitTarget( parsed: ParsedOrgProject, entityType: string, - usageHint: string + usageHint: string, ): void { if (parsed.type === "auto-detect") { throw new ContextError(entityType, usageHint, [ @@ -263,7 +263,7 @@ type DeleteCommandFunction< this: CONTEXT, flags: FLAGS, ...args: ARGS - // biome-ignore lint/suspicious/noConfusingVoidType: void is required here — generators that don't return a value have implicit void return, which is distinct from undefined in TypeScript's type system + // void is required here — generators that don't return a value have implicit void return, which is distinct from undefined in TypeScript's type system ) => AsyncGenerator<unknown, CommandReturn | void, undefined>; /** @@ -337,11 +337,11 @@ export function buildDeleteCommand< readonly fullDescription?: string; }; readonly func: DeleteCommandFunction<FLAGS, ARGS, CONTEXT>; - // biome-ignore lint/suspicious/noExplicitAny: OutputConfig is generic but type is erased at the builder level + // oxlint-disable-next-line typescript/no-explicit-any -- OutputConfig is generic but type is erased at the builder level readonly output?: import("./formatters/output.js").OutputConfig<any>; readonly auth?: boolean; }, - options?: DeleteCommandOptions + options?: DeleteCommandOptions, ): Command<CONTEXT> { const originalFunc = builderArgs.func; @@ -389,7 +389,7 @@ export function buildDeleteCommand< aliases: mergedAliases, }; - // biome-ignore lint/suspicious/noExplicitAny: Stricli's CommandFunction type is complex + // oxlint-disable-next-line typescript/no-explicit-any -- Stricli's CommandFunction type is complex const wrappedFunc = function (this: CONTEXT, flags: FLAGS, ...args: any[]) { // Pre-hook: non-interactive safety guard if (!options?.noNonInteractiveGuard) { @@ -398,7 +398,7 @@ export function buildDeleteCommand< yes?: boolean; force?: boolean; "dry-run"?: boolean; - } + }, ); } diff --git a/packages/cli/src/lib/oauth.ts b/packages/cli/src/lib/oauth.ts index 234bc9370..20732b702 100644 --- a/packages/cli/src/lib/oauth.ts +++ b/packages/cli/src/lib/oauth.ts @@ -112,7 +112,7 @@ const SCOPES = OAUTH_SCOPES.join(" "); * `OAUTH_SCOPES` (e.g. `org:integrations`), update this filter explicitly. */ const OAUTH_SCOPES_READ_ONLY: readonly string[] = OAUTH_SCOPES.filter((scope) => - scope.endsWith(":read") + scope.endsWith(":read"), ); /** Lookup set of all canonical Sentry scopes for `--scope` validation. */ @@ -142,7 +142,7 @@ export type OAuthScopeSelection = { * contains a value that is not a known Sentry scope. */ export function resolveOAuthScopeString( - selection: OAuthScopeSelection = {} + selection: OAuthScopeSelection = {}, ): string { if (selection.scopes !== undefined) { return normalizeExplicitScopes(selection.scopes); @@ -168,7 +168,7 @@ function normalizeExplicitScopes(scopes: readonly string[]): string { if (!KNOWN_SCOPE_SET.has(scope)) { throw new ValidationError( `Invalid scope "${raw}". Must be one of: ${SENTRY_SCOPES.join(", ")}`, - "scope" + "scope", ); } if (!seen.has(scope)) { @@ -186,7 +186,7 @@ type DeviceFlowCallbacks = { onUserCode: ( userCode: string, verificationUri: string, - verificationUriComplete: string + verificationUriComplete: string, ) => void | Promise<void>; }; @@ -201,7 +201,7 @@ function sleep(ms: number): Promise<void> { async function fetchWithConnectionError( url: string, init: RequestInit, - customHeadersTrusted?: boolean + customHeadersTrusted?: boolean, ): Promise<Response> { // Inject custom headers for self-hosted proxies (IAP, mTLS, etc.) — // URL-scoped so they don't leak to untrusted hosts. @@ -227,7 +227,7 @@ async function fetchWithConnectionError( throw new ApiError( `TLS certificate error connecting to ${targetOrigin}`, 0, - buildTlsErrorDetail(error) + buildTlsErrorDetail(error), ); } @@ -240,7 +240,7 @@ async function fetchWithConnectionError( throw new ApiError( `Cannot connect to Sentry at ${targetOrigin}`, 0, - "Check your network connection and SENTRY_URL configuration" + "Check your network connection and SENTRY_URL configuration", ); } throw error; @@ -258,7 +258,7 @@ function assertRefreshHostTrusted(refreshUrl: string): string { throw new HostScopeError( "OAuth refresh token", "<unknown host>", - getActiveTokenHost() + getActiveTokenHost(), ); } return origin; @@ -279,7 +279,7 @@ function requestDeviceCode(scope: string = SCOPES) { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: deviceCodeRequestBody(clientId, scope), - } + }, ); if (!response.ok) { @@ -288,7 +288,7 @@ function requestDeviceCode(scope: string = SCOPES) { "Failed to initiate device flow", response.status, errorText, - "/oauth/device/code/" + "/oauth/device/code/", ); } @@ -301,7 +301,7 @@ function requestDeviceCode(scope: string = SCOPES) { "Invalid response from device authorization endpoint", response.status, "The server returned a non-JSON response body.", - "/oauth/device/code/" + "/oauth/device/code/", ); } @@ -311,7 +311,7 @@ function requestDeviceCode(scope: string = SCOPES) { "Invalid response from device authorization endpoint", response.status, result.issues.map((i) => i.message).join(", "), - "/oauth/device/code/" + "/oauth/device/code/", ); } @@ -330,7 +330,7 @@ function pollForToken(deviceCode: string): Promise<TokenResponse> { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: deviceTokenRequestBody(getClientId(), deviceCode), - } + }, ); let data: unknown; @@ -342,7 +342,7 @@ function pollForToken(deviceCode: string): Promise<TokenResponse> { "Unexpected response from token endpoint", response.status, "The server returned a non-JSON response body (possible proxy or CDN issue).", - "/oauth/token/" + "/oauth/token/", ); } @@ -357,7 +357,7 @@ function pollForToken(deviceCode: string): Promise<TokenResponse> { if (errorResult.success) { throw new DeviceFlowError( errorResult.output.error, - errorResult.output.error_description + errorResult.output.error_description, ); } @@ -366,7 +366,7 @@ function pollForToken(deviceCode: string): Promise<TokenResponse> { "Unexpected response from token endpoint", response.status, JSON.stringify(data), - "/oauth/token/" + "/oauth/token/", ); }); } @@ -427,7 +427,7 @@ async function attemptPoll(deviceCode: string): Promise<PollResult> { export async function performDeviceFlow( callbacks: DeviceFlowCallbacks, timeout = 600_000, // 10 minutes default (matches Sentry's expires_in) - scope: string = SCOPES + scope: string = SCOPES, ): Promise<TokenResponse> { // Step 1: Request device code const { @@ -443,7 +443,7 @@ export async function performDeviceFlow( await callbacks.onUserCode( user_code, verification_uri, - verification_uri_complete ?? `${verification_uri}?user_code=${user_code}` + verification_uri_complete ?? `${verification_uri}?user_code=${user_code}`, ); // Calculate absolute timeout @@ -474,7 +474,7 @@ export async function performDeviceFlow( throw new DeviceFlowError( "expired_token", - "Authentication timed out. Please try again." + "Authentication timed out. Please try again.", ); } @@ -486,13 +486,13 @@ export async function performDeviceFlow( * @param tokenResponse - The token response from performDeviceFlow */ export async function completeOAuthFlow( - tokenResponse: TokenResponse + tokenResponse: TokenResponse, ): Promise<void> { await setAuthToken( tokenResponse.access_token, tokenResponse.expires_in, tokenResponse.refresh_token, - { host: getSentryUrl() } + { host: getSentryUrl() }, ); } @@ -510,7 +510,7 @@ export async function setApiToken(token: string): Promise<void> { /** Refresh an access token using a refresh token. */ export function refreshAccessToken( refreshToken: string, - options: { credentialHost: string } + options: { credentialHost: string }, ): Promise<TokenResponse> { const clientId = getClientId(); const credentialHost = assertRefreshHostTrusted(options.credentialHost); @@ -529,7 +529,7 @@ export function refreshAccessToken( refresh_token: refreshToken, }), }, - true + true, ); if (!response.ok) { @@ -537,7 +537,7 @@ export function refreshAccessToken( try { const errorResult = safeParse( TokenErrorResponseSchema, - await response.json() + await response.json(), ); rejected = errorResult.success && errorResult.output.error === "invalid_grant"; @@ -547,14 +547,14 @@ export function refreshAccessToken( if (rejected) { throw new AuthError( "expired", - "Session expired because the refresh credential was rejected. Run 'sentry auth login' to re-authenticate." + "Session expired because the refresh credential was rejected. Run 'sentry auth login' to re-authenticate.", ); } throw new ApiError( "Token refresh failed", response.status, "The refresh endpoint returned an unexpected failure.", - "/oauth/token/" + "/oauth/token/", ); } @@ -567,7 +567,7 @@ export function refreshAccessToken( "Unexpected response from token refresh endpoint", response.status, "The server returned a non-JSON response body (possible proxy or CDN issue).", - "/oauth/token/" + "/oauth/token/", ); } @@ -578,7 +578,7 @@ export function refreshAccessToken( "Invalid response from token refresh endpoint", response.status, result.issues.map((i) => i.message).join(", "), - "/oauth/token/" + "/oauth/token/", ); } diff --git a/packages/cli/src/lib/objectstore.ts b/packages/cli/src/lib/objectstore.ts index 2c6442c0a..2dce1c1eb 100644 --- a/packages/cli/src/lib/objectstore.ts +++ b/packages/cli/src/lib/objectstore.ts @@ -58,7 +58,7 @@ function scopeSegment(scopes: [string, string][]): string { export function buildObjectUrl(config: ObjectstoreConfig, key: string): string { const base = config.url.replace(TRAILING_SLASHES, ""); return `${base}/v1/objects/${config.usecase}/${scopeSegment( - config.scopes + config.scopes, )}/${key}`; } @@ -77,7 +77,7 @@ function authHeaders(config: ObjectstoreConfig): Record<string, string> { */ export async function objectExists( config: ObjectstoreConfig, - key: string + key: string, ): Promise<boolean> { const url = buildObjectUrl(config, key); const response = await customFetch(url, { @@ -93,7 +93,7 @@ export async function objectExists( "Objectstore HEAD failed", response.status, response.statusText || "HEAD failed", - url + url, ); } return true; @@ -111,7 +111,7 @@ export async function objectExists( export async function putObject( config: ObjectstoreConfig, key: string, - body: Uint8Array + body: Uint8Array, ): Promise<void> { const url = buildObjectUrl(config, key); const response = await customFetch(url, { @@ -128,7 +128,7 @@ export async function putObject( "Objectstore upload failed", response.status, response.statusText || "PUT failed", - url + url, ); } } diff --git a/packages/cli/src/lib/org-list.ts b/packages/cli/src/lib/org-list.ts index 13142173c..d82737b6d 100644 --- a/packages/cli/src/lib/org-list.ts +++ b/packages/cli/src/lib/org-list.ts @@ -101,7 +101,7 @@ export type ListResult<T> = { */ export function jsonTransformListResult<T>( result: ListResult<T>, - fields?: string[] + fields?: string[], ): unknown { const items = fields && fields.length > 0 @@ -160,7 +160,7 @@ export type FetchResult<T> = export function distributeFetchBudget( limit: number, groupCount: number, - options: { minimumPerGroup?: boolean } = {} + options: { minimumPerGroup?: boolean } = {}, ): number[] { if (groupCount <= 0) { return []; @@ -172,7 +172,7 @@ export function distributeFetchBudget( const minimum = options.minimumPerGroup ? 1 : 0; return Array.from({ length: groupCount }, (_, i) => - Math.max(minimum, base + (i < remainder ? 1 : 0)) + Math.max(minimum, base + (i < remainder ? 1 : 0)), ); } @@ -203,7 +203,7 @@ export type FetchGroupsOptions<TGroup, TItem, TPage extends ListFetchPage> = { /** Fetch bounded items, capturing non-auth failures in FetchResult. */ fetchGroup: ( group: TGroup, - options: GroupFetchOptions + options: GroupFetchOptions, ) => Promise<FetchResult<TPage>>; /** Returns the page's mutable item array; surplus results are appended to it. */ getItems: (page: TPage) => TItem[]; @@ -213,17 +213,17 @@ export type FetchGroupsOptions<TGroup, TItem, TPage extends ListFetchPage> = { function countFetchedItems<TItem, TPage>( results: FetchResult<TPage>[], - getItems: (page: TPage) => TItem[] + getItems: (page: TPage) => TItem[], ): number { return results.reduce( (total, result) => total + (result.success ? getItems(result.data).length : 0), - 0 + 0, ); } function hasMorePages<TPage extends ListFetchPage>( - results: FetchResult<TPage>[] + results: FetchResult<TPage>[], ): boolean { return results.some((result) => result.success && result.data.hasMore); } @@ -243,7 +243,7 @@ export async function fetchGroupsWithBudget< TPage extends ListFetchPage, >( groups: TGroup[], - options: FetchGroupsOptions<TGroup, TItem, TPage> + options: FetchGroupsOptions<TGroup, TItem, TPage>, ): Promise<{ results: FetchResult<TPage>[]; hasMore: boolean }> { const { limit, startCursors, getGroupKey, fetchGroup, getItems, onProgress } = options; @@ -255,8 +255,8 @@ export async function fetchGroupsWithBudget< fetchGroup(group, { limit: quotas[index] ?? 1, startCursor: startCursors?.get(getGroupKey(group)), - }) - ) + }), + ), ); let totalFetched = countFetchedItems(phase1, getItems); @@ -282,8 +282,8 @@ export async function fetchGroupsWithBudget< const phase2 = await Promise.all( requests.map(({ group, limit: requestLimit, cursor }) => - fetchGroup(group, { limit: requestLimit, startCursor: cursor }) - ) + fetchGroup(group, { limit: requestLimit, startCursor: cursor }), + ), ); for (let index = 0; index < requests.length; index++) { const request = requests[index]; @@ -323,7 +323,7 @@ export async function fetchGroupsWithBudget< export function trimWithGroupGuarantee<T>( rows: T[], limit: number, - getGroupKey: (row: T) => string + getGroupKey: (row: T) => string, ): T[] { if (rows.length <= limit) { return rows; @@ -334,7 +334,7 @@ export function trimWithGroupGuarantee<T>( // Pass 1: pick one representative per group from the list for (let i = 0; i < rows.length && guaranteed.size < limit; i++) { - // biome-ignore lint/style/noNonNullAssertion: i is within bounds + // oxlint-disable-next-line typescript/no-non-null-assertion -- i is within bounds const key = getGroupKey(rows[i]!); if (!seenGroups.has(key)) { seenGroups.add(key); @@ -394,7 +394,7 @@ export type OrgListConfig<TEntity, TWithOrg> = ListCommandMeta & { */ listPaginated: ( orgSlug: string, - opts: { cursor?: string; perPage: number } + opts: { cursor?: string; perPage: number }, ) => Promise<PaginatedResponse<TEntity[]>>; /** @@ -485,7 +485,7 @@ export type ModeHandler< * {@link ParsedVariant} and returns a {@link ListResult}. */ export type ModeHandlerMap = { - // biome-ignore lint/suspicious/noExplicitAny: item type varies per command; erased at dispatch + // oxlint-disable-next-line typescript/no-explicit-any -- item type varies per command; erased at dispatch [K in ParsedOrgProject["type"]]: ModeHandler<K, any>; }; @@ -496,7 +496,7 @@ export type ModeHandlerMap = { * the default handlers from {@link buildDefaultHandlers}. */ export type ModeOverrides = { - // biome-ignore lint/suspicious/noExplicitAny: item type varies per command; erased at dispatch + // oxlint-disable-next-line typescript/no-explicit-any -- item type varies per command; erased at dispatch [K in ParsedOrgProject["type"]]?: ModeHandler<K, any>; }; @@ -505,7 +505,7 @@ export type ModeOverrides = { * Checks for the presence of `listForOrg` which only the full config has. */ export function isOrgListConfig<TEntity, TWithOrg>( - config: ListCommandMeta | OrgListConfig<TEntity, TWithOrg> + config: ListCommandMeta | OrgListConfig<TEntity, TWithOrg>, ): config is OrgListConfig<TEntity, TWithOrg> { return "listForOrg" in config; } @@ -516,7 +516,7 @@ export function isOrgListConfig<TEntity, TWithOrg>( */ export async function fetchOrgSafe<TEntity, TWithOrg>( config: OrgListConfig<TEntity, TWithOrg>, - orgSlug: string + orgSlug: string, ): Promise<TWithOrg[]> { const result = await withAuthGuard(async () => { const items = await config.listForOrg(orgSlug); @@ -530,11 +530,11 @@ export async function fetchOrgSafe<TEntity, TWithOrg>( * Skips orgs where the user lacks access (non-auth errors are swallowed). */ export async function fetchAllOrgs<TEntity, TWithOrg>( - config: OrgListConfig<TEntity, TWithOrg> + config: OrgListConfig<TEntity, TWithOrg>, ): Promise<TWithOrg[]> { const orgs = await listOrganizations(); const results = await Promise.all( - orgs.map((org) => fetchOrgSafe(config, org.slug)) + orgs.map((org) => fetchOrgSafe(config, org.slug)), ); return results.flat(); } @@ -559,7 +559,7 @@ type OrgAllOptions<TEntity, TWithOrg> = { function runOrgAll<TEntity, TWithOrg>( config: OrgListConfig<TEntity, TWithOrg>, org: string, - flags: BaseListFlags + flags: BaseListFlags, ): Promise<ListResult<TWithOrg>> { const contextKey = buildOrgContextKey(org); return handleOrgAll({ @@ -585,7 +585,7 @@ function runOrgAll<TEntity, TWithOrg>( * inside the handler so callers don't need to manage them. */ export async function handleOrgAll<TEntity, TWithOrg>( - options: OrgAllOptions<TEntity, TWithOrg> + options: OrgAllOptions<TEntity, TWithOrg>, ): Promise<ListResult<TWithOrg>> { const { config, org, flags, contextKey, cursor, direction } = options; @@ -599,8 +599,8 @@ export async function handleOrgAll<TEntity, TWithOrg>( config.listPaginated(org, { cursor: pageCursor, perPage, - }) - ) + }), + ), ); const { data: rawItems, nextCursor } = response; @@ -611,7 +611,7 @@ export async function handleOrgAll<TEntity, TWithOrg>( config.paginationKey, contextKey, direction, - nextCursor ?? undefined + nextCursor ?? undefined, ); const hasPrev = hasPreviousPage(config.paginationKey, contextKey); @@ -661,7 +661,7 @@ export async function handleOrgAll<TEntity, TWithOrg>( export async function handleAutoDetect<TEntity, TWithOrg>( config: OrgListConfig<TEntity, TWithOrg>, cwd: string, - flags: BaseListFlags + flags: BaseListFlags, ): Promise<ListResult<TWithOrg>> { const { orgs: orgsToFetch, @@ -677,12 +677,12 @@ export async function handleAutoDetect<TEntity, TWithOrg>( async () => { if (orgsToFetch.length > 0) { const results = await Promise.all( - orgsToFetch.map((org) => fetchOrgSafe(config, org)) + orgsToFetch.map((org) => fetchOrgSafe(config, org)), ); return results.flat(); } return fetchAllOrgs(config); - } + }, ); const limitCount = @@ -701,7 +701,7 @@ export async function handleAutoDetect<TEntity, TWithOrg>( if (allItems.length > limited.length) { hintParts.push( - `Showing ${limited.length} of ${allItems.length} ${config.entityPlural}` + `Showing ${limited.length} of ${allItems.length} ${config.entityPlural}`, ); } @@ -712,13 +712,13 @@ export async function handleAutoDetect<TEntity, TWithOrg>( if (skippedSelfHosted) { hintParts.push( `Note: ${skippedSelfHosted} DSN(s) could not be resolved. ` + - `Specify the organization explicitly: ${config.commandPrefix} <org>/` + `Specify the organization explicitly: ${config.commandPrefix} <org>/`, ); } if (limited.length > 0) { hintParts.push( - `Tip: Use '${config.commandPrefix} <org>/' to filter by organization` + `Tip: Use '${config.commandPrefix} <org>/' to filter by organization`, ); } @@ -748,7 +748,7 @@ type FetchedItemsOptions<TEntity, TWithOrg> = { * Shared by handleExplicitOrg and handleExplicitProject. */ function buildFetchedItemsResult<TEntity, TWithOrg>( - opts: FetchedItemsOptions<TEntity, TWithOrg> + opts: FetchedItemsOptions<TEntity, TWithOrg>, ): ListResult<TWithOrg> { const { config, items, flags, contextLabel, orgSlugForHint } = opts; const limited = items.slice(0, flags.limit); @@ -766,7 +766,7 @@ function buildFetchedItemsResult<TEntity, TWithOrg>( ? ` Use '${config.commandPrefix} ${orgSlugForHint}/' for paginated results.` : ""; hintParts.push( - `Showing ${limited.length} of ${items.length} ${config.entityPlural}.${paginateTip}` + `Showing ${limited.length} of ${items.length} ${config.entityPlural}.${paginateTip}`, ); } else { hintParts.push(`Showing ${limited.length} ${config.entityPlural}`); @@ -797,7 +797,7 @@ type ExplicitOrgOptions<TEntity, TWithOrg> = { * Returns a {@link ListResult} with items and contextual hints. */ export async function handleExplicitOrg<TEntity, TWithOrg>( - options: ExplicitOrgOptions<TEntity, TWithOrg> + options: ExplicitOrgOptions<TEntity, TWithOrg>, ): Promise<ListResult<TWithOrg>> { const { config, org, flags, noteOrgScoped = false } = options; const items = await withProgress( @@ -805,7 +805,7 @@ export async function handleExplicitOrg<TEntity, TWithOrg>( message: `Fetching ${config.entityPlural} (up to ${flags.limit})...`, json: flags.json, }, - () => fetchOrgSafe(config, org) + () => fetchOrgSafe(config, org), ); const result = buildFetchedItemsResult({ @@ -846,13 +846,13 @@ type ExplicitProjectOptions<TEntity, TWithOrg> = { * Returns a {@link ListResult} with items and hint text. */ export async function handleExplicitProject<TEntity, TWithOrg>( - options: ExplicitProjectOptions<TEntity, TWithOrg> + options: ExplicitProjectOptions<TEntity, TWithOrg>, ): Promise<ListResult<TWithOrg>> { const { config, org, project, flags } = options; const listForProject = config.listForProject; if (!listForProject) { throw new Error( - "handleExplicitProject called but config.listForProject is not defined" + "handleExplicitProject called but config.listForProject is not defined", ); } const raw = await withProgress( @@ -860,7 +860,7 @@ export async function handleExplicitProject<TEntity, TWithOrg>( message: `Fetching ${config.entityPlural} (up to ${flags.limit})...`, json: flags.json, }, - () => listForProject(org, project) + () => listForProject(org, project), ); const items = raw.map((entity) => config.withOrg(entity, org)); @@ -897,7 +897,7 @@ export async function handleExplicitProject<TEntity, TWithOrg>( * * Returns a {@link ListResult} with items and hint text. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: multi-mode dispatch with recovery is inherently branchy +// multi-mode dispatch with recovery is inherently branchy export async function handleProjectSearch<TEntity, TWithOrg>( config: OrgListConfig<TEntity, TWithOrg>, projectSlug: string, @@ -910,7 +910,7 @@ export async function handleProjectSearch<TEntity, TWithOrg>( org?: string; /** Classification supplied by the dispatcher after its target pre-check. */ projectSearchResolution?: ProjectSearchTargetResolution; - } + }, ): Promise<ListResult<TWithOrg>> { const { flags, @@ -932,14 +932,14 @@ export async function handleProjectSearch<TEntity, TWithOrg>( message: `Fetching ${config.entityPlural} (up to ${flags.limit})...`, json: flags.json, }, - () => classifyProjectSearchTarget(parsed) + () => classifyProjectSearchTarget(parsed), )); if (resolution.kind === "organization") { if (orgAllFallback) { log.warn( `'${projectSlug}' is an organization, not a project. ` + - `Listing all ${config.entityPlural} in '${projectSlug}'.` + `Listing all ${config.entityPlural} in '${projectSlug}'.`, ); return orgAllFallback(resolution.org); } @@ -950,7 +950,7 @@ export async function handleProjectSearch<TEntity, TWithOrg>( [ `List projects: sentry project list ${projectSlug}/`, `Specify a project: ${config.commandPrefix} ${projectSlug}/<project>`, - ] + ], ); } @@ -963,7 +963,7 @@ export async function handleProjectSearch<TEntity, TWithOrg>( `Project '${resolution.displaySlug}'`, "not found", `${config.commandPrefix} <org>/${projectSlug}`, - projectSearchNotFoundSuggestions(resolution) + projectSearchNotFoundSuggestions(resolution), ); } @@ -982,8 +982,8 @@ export async function handleProjectSearch<TEntity, TWithOrg>( withAuthGuard(async () => { const raw = await listForProject(m.orgSlug, m.slug); return raw.map((entity) => config.withOrg(entity, m.orgSlug)); - }) - ) + }), + ), ); allItems = results .filter((r): r is AuthGuardSuccess<TWithOrg[]> => r.ok) @@ -991,7 +991,7 @@ export async function handleProjectSearch<TEntity, TWithOrg>( } else { const uniqueOrgs = [...new Set(matches.map((m) => m.orgSlug))]; const results = await Promise.all( - uniqueOrgs.map((org) => fetchOrgSafe(config, org)) + uniqueOrgs.map((org) => fetchOrgSafe(config, org)), ); allItems = results.flat(); } @@ -1008,7 +1008,7 @@ export async function handleProjectSearch<TEntity, TWithOrg>( const hintParts: string[] = []; if (allItems.length > limited.length) { hintParts.push( - `Showing ${limited.length} of ${allItems.length} ${config.entityPlural}. Use --limit to show more.` + `Showing ${limited.length} of ${allItems.length} ${config.entityPlural}. Use --limit to show more.`, ); } else { hintParts.push(`Showing ${limited.length} ${config.entityPlural}`); @@ -1016,7 +1016,7 @@ export async function handleProjectSearch<TEntity, TWithOrg>( if (matches.length > 1) { hintParts.push( - `Found '${resolvedProjectSlug}' in ${matches.length} organizations` + `Found '${resolvedProjectSlug}' in ${matches.length} organizations`, ); } @@ -1037,17 +1037,17 @@ export async function handleProjectSearch<TEntity, TWithOrg>( * covered by the caller's overrides, which would be a programming error. */ function buildDefaultHandlers<TEntity, TWithOrg>( - config: ListCommandMeta | OrgListConfig<TEntity, TWithOrg> + config: ListCommandMeta | OrgListConfig<TEntity, TWithOrg>, ): ModeHandlerMap { function notSupported<T extends ParsedOrgProject["type"]>( - mode: string + mode: string, ): ModeHandler<T> { return () => Promise.reject( new Error( `No handler for '${mode}' mode in '${config.commandPrefix}'. ` + - "Provide a full OrgListConfig or an override for this mode." - ) + "Provide a full OrgListConfig or an override for this mode.", + ), ); } @@ -1094,7 +1094,7 @@ function buildDefaultHandlers<TEntity, TWithOrg>( const { cursor, direction } = resolveCursor( ctx.flags.cursor, config.paginationKey, - contextKey + contextKey, ); return handleOrgAll({ config, @@ -1146,7 +1146,7 @@ type OrgSlugMatchResult = { async function resolveOrgSlugMatch( parsed: ParsedOrgProject & { type: "project-search" }, config: ListCommandMeta, - fuzzy: boolean + fuzzy: boolean, ): Promise<OrgSlugMatchResult> { if (parsed.org !== undefined || parsed.originalSlug !== undefined) { return { parsed }; @@ -1160,7 +1160,7 @@ async function resolveOrgSlugMatch( if (resolution.kind === "organization") { log.warn( `'${slug}' is an organization, not a project. ` + - `Listing all ${config.entityPlural} in '${slug}'.` + `Listing all ${config.entityPlural} in '${slug}'.`, ); return { parsed: { type: "org-all", org: resolution.org } }; } @@ -1176,7 +1176,7 @@ async function resolveOrgSlugMatch( * that carry an org field. Returns the (possibly updated) parsed object. */ async function resolveOrgInParsed( - parsed: ParsedOrgProject + parsed: ParsedOrgProject, ): Promise<ParsedOrgProject> { if (!("org" in parsed && parsed.org)) { return parsed; @@ -1187,7 +1187,7 @@ async function resolveOrgInParsed( if (resolved.type === "explicit" || resolved.type === "org-all") { setOrgProjectContext( [effectiveOrg], - resolved.type === "explicit" ? [resolved.project] : [] + resolved.type === "explicit" ? [resolved.project] : [], ); } return resolved; @@ -1206,8 +1206,8 @@ async function resolveOrgInParsed( * This is the single entry point for all org-scoped list commands. */ export async function dispatchOrgScopedList<TEntity, TWithOrg>( - options: DispatchOptions<TEntity, TWithOrg> - // biome-ignore lint/suspicious/noExplicitAny: TWithOrg varies per command; callers narrow the return type + options: DispatchOptions<TEntity, TWithOrg>, + // oxlint-disable-next-line typescript/no-explicit-any -- TWithOrg varies per command; callers narrow the return type ): Promise<ListResult<any>> { const { config, cwd, flags, parsed, overrides } = options; @@ -1223,7 +1223,7 @@ export async function dispatchOrgScopedList<TEntity, TWithOrg>( effectiveParsed, config, flags.cursor === undefined || - cursorAllowedModes.includes("project-search") + cursorAllowedModes.includes("project-search"), ); effectiveParsed = resolution.parsed; projectSearchResolution = resolution.projectSearchResolution; @@ -1239,7 +1239,7 @@ export async function dispatchOrgScopedList<TEntity, TWithOrg>( "The --cursor flag requires the <org>/ pattern " + `(e.g., ${config.commandPrefix} my-org/).` + hint, - "cursor" + "cursor", ); } @@ -1256,6 +1256,6 @@ export async function dispatchOrgScopedList<TEntity, TWithOrg>( projectSearchResolution, }; - // biome-ignore lint/suspicious/noExplicitAny: safe — dispatch guarantees type match + // oxlint-disable-next-line typescript/no-explicit-any -- safe — dispatch guarantees type match return (handler as ModeHandler<any>)(ctx); } diff --git a/packages/cli/src/lib/patch-cache.ts b/packages/cli/src/lib/patch-cache.ts index ca10d76a3..ae0a211b6 100644 --- a/packages/cli/src/lib/patch-cache.ts +++ b/packages/cli/src/lib/patch-cache.ts @@ -3,7 +3,7 @@ import { makeCache, type PatchCache, type PatchChain } from "binpatch"; import { getConfigDir } from "./db/index.js"; export type { ChainMeta, PatchStepMeta } from "binpatch"; -// biome-ignore lint/performance/noBarrelFile: preserve the existing cache API +// preserve the existing cache API export { chainFileName, patchFileName } from "binpatch"; function cache(): PatchCache { @@ -12,14 +12,14 @@ function cache(): PatchCache { export function savePatchesToCache( chain: Pick<PatchChain, "patches" | "expectedSha256">, - steps: { fromVersion: string; toVersion: string }[] + steps: { fromVersion: string; toVersion: string }[], ): Promise<void> { return cache().save(chain, steps); } export async function loadCachedChain( currentVersion: string, - targetVersion: string + targetVersion: string, ): ReturnType<PatchCache["load"]> { const result = await cache().load(currentVersion, targetVersion); if (!result) { diff --git a/packages/cli/src/lib/platform-grid.ts b/packages/cli/src/lib/platform-grid.ts index 2cbfbcbfe..177258b5b 100644 --- a/packages/cli/src/lib/platform-grid.ts +++ b/packages/cli/src/lib/platform-grid.ts @@ -31,7 +31,7 @@ export function renderPlatformGrid(items: readonly string[]): string { (_, ci) => ({ header: " ", value: (row: string[]) => row[ci] ?? "", - }) + }), ); return buildMarkdownTable(rows, columns); } diff --git a/packages/cli/src/lib/polling.ts b/packages/cli/src/lib/polling.ts index 2aaa670bb..e1a3ec3be 100644 --- a/packages/cli/src/lib/polling.ts +++ b/packages/cli/src/lib/polling.ts @@ -219,7 +219,7 @@ export type WithProgressOptions = { */ export async function withProgress<T>( options: WithProgressOptions, - fn: (setMessage: (msg: string) => void) => Promise<T> + fn: (setMessage: (msg: string) => void) => Promise<T>, ): Promise<T> { if ( options.json || diff --git a/packages/cli/src/lib/progress.ts b/packages/cli/src/lib/progress.ts index b7e1a9db0..2ce928ff2 100644 --- a/packages/cli/src/lib/progress.ts +++ b/packages/cli/src/lib/progress.ts @@ -83,7 +83,7 @@ export function makeByteProgress( label: string, totalBytes: number | null, setMessage: SetMessage | undefined, - options: MakeByteProgressOptions = {} + options: MakeByteProgressOptions = {}, ): ByteProgress { const { format = "bytes", nowMs = Date.now } = options; let written = 0; @@ -106,7 +106,7 @@ export function makeByteProgress( const emit = (): void => { // Cosmetic only — a formatting or callback failure must never propagate. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { setMessage?.(render()); } catch { diff --git a/packages/cli/src/lib/proguard.ts b/packages/cli/src/lib/proguard.ts index 442b0e8b9..4f70d11a4 100644 --- a/packages/cli/src/lib/proguard.ts +++ b/packages/cli/src/lib/proguard.ts @@ -85,7 +85,7 @@ function uuidV5(name: Buffer, namespaceBytes: Buffer): string { */ export const PROGUARD_NAMESPACE = uuidV5( Buffer.from("guardsquare.com", "utf-8"), - NAMESPACE_DNS_BYTES + NAMESPACE_DNS_BYTES, ); /** diff --git a/packages/cli/src/lib/promises.ts b/packages/cli/src/lib/promises.ts index cc000d822..5a3bc29fa 100644 --- a/packages/cli/src/lib/promises.ts +++ b/packages/cli/src/lib/promises.ts @@ -30,7 +30,7 @@ */ export function anyTrue<T>( items: readonly T[], - predicate: (item: T) => Promise<boolean> + predicate: (item: T) => Promise<boolean>, ): Promise<boolean> { if (items.length === 0) { return Promise.resolve(false); diff --git a/packages/cli/src/lib/qrcode.ts b/packages/cli/src/lib/qrcode.ts index fe2c8350a..26d5088b0 100644 --- a/packages/cli/src/lib/qrcode.ts +++ b/packages/cli/src/lib/qrcode.ts @@ -40,7 +40,7 @@ export type QRCodeOptions = InferOutput<typeof QRCodeOptionsSchema>; */ export function generateQRCode( data: string, - options?: Partial<QRCodeOptions> + options?: Partial<QRCodeOptions>, ): Promise<string> { const opts = parse(QRCodeOptionsSchema, options ?? {}); diff --git a/packages/cli/src/lib/react-native/wrap-call.ts b/packages/cli/src/lib/react-native/wrap-call.ts index 42d52117a..01a27229b 100644 --- a/packages/cli/src/lib/react-native/wrap-call.ts +++ b/packages/cli/src/lib/react-native/wrap-call.ts @@ -30,7 +30,7 @@ export type SourceMapReport = { /** Whether this process is a Node Single Executable Application. */ export function isSea(): boolean { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const req = createRequire(import.meta.url); const sea = req("node:sea") as { isSea?: () => boolean }; @@ -54,7 +54,7 @@ function loadReport(path: string): SourceMapReport { if (!existsSync(path)) { return {}; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(readFileSync(path, "utf-8")) as SourceMapReport; } catch { @@ -162,7 +162,7 @@ function isComposeCommand(args: string[], env: NodeJS.ProcessEnv): boolean { function classifyInvocation( args: string[], env: NodeJS.ProcessEnv, - report: SourceMapReport + report: SourceMapReport, ): { executeHermes: boolean; shouldCopyDebugId: boolean } { if (isBundleCommand(args, env.SENTRY_RN_BUNDLE_COMMAND)) { handleBundle(args, report); @@ -191,7 +191,7 @@ export function wrapCall(env: NodeJS.ProcessEnv = process.env): number { const { executeHermes, shouldCopyDebugId } = classifyInvocation( args, env, - report + report, ); const executable = executeHermes @@ -199,7 +199,7 @@ export function wrapCall(env: NodeJS.ProcessEnv = process.env): number { : env.SENTRY_RN_REAL_NODE_BINARY; if (!executable) { throw new Error( - "Missing SENTRY_RN_REAL_NODE_BINARY / SENTRY_RN_REAL_HERMES_CLI_PATH" + "Missing SENTRY_RN_REAL_NODE_BINARY / SENTRY_RN_REAL_HERMES_CLI_PATH", ); } diff --git a/packages/cli/src/lib/react-native/xcode-env.ts b/packages/cli/src/lib/react-native/xcode-env.ts index 26b215ca8..5b1c2fbe8 100644 --- a/packages/cli/src/lib/react-native/xcode-env.ts +++ b/packages/cli/src/lib/react-native/xcode-env.ts @@ -162,7 +162,7 @@ function toPartialPlist(raw: Record<string, string>): Partial<InfoPlist> { */ function preprocessPlist( path: string, - vars: Env + vars: Env, ): Record<string, string> | null { const args = ["-xc", "-P", "-E"]; const other = vars.INFOPLIST_OTHER_PREPROCESSOR_FLAGS; @@ -191,7 +191,7 @@ function preprocessPlist( async function loadAndProcess( path: string, vars: Env, - allowPreprocessing: boolean + allowPreprocessing: boolean, ): Promise<InfoPlist | null> { const shouldPreprocess = allowPreprocessing && vars.INFOPLIST_PREPROCESS === "YES"; @@ -279,7 +279,7 @@ function getXcodeProjectInfo(cwd: string): XcodeProjectInfo | null { function getBuildVars( projectPath: string, target: string, - configuration: string + configuration: string, ): Record<string, string> { const out = runCapture("xcodebuild", [ "-showBuildSettings", @@ -310,7 +310,7 @@ function getBuildVars( /** Case-insensitive lookup of a configuration name. */ function findConfiguration( pi: XcodeProjectInfo, - name: string + name: string, ): string | undefined { const lower = name.toLowerCase(); return pi.configurations.find((cfg) => cfg.toLowerCase() === lower); @@ -319,7 +319,7 @@ function findConfiguration( /** Resolve the Info.plist for the project's first target (release, else debug). */ async function fromProjectInfo( pi: XcodeProjectInfo, - allowPreprocessing: boolean + allowPreprocessing: boolean, ): Promise<InfoPlist | null> { const config = findConfiguration(pi, "release") ?? findConfiguration(pi, "debug"); @@ -336,7 +336,7 @@ async function fromProjectInfo( return await loadAndProcess( resolve(base, infoPlistFile), vars, - allowPreprocessing + allowPreprocessing, ); } @@ -354,7 +354,7 @@ async function fromProjectInfo( export async function discoverInfoPlist( env: Env, cwd: string, - allowPreprocessing = false + allowPreprocessing = false, ): Promise<InfoPlist | null> { if (env.XCODE_VERSION_ACTUAL) { const filename = env.INFOPLIST_FILE; @@ -391,7 +391,7 @@ export async function resolveReleaseAndDist( env: Env, cwd: string, noAutoRelease: boolean, - allowPreprocessing = false + allowPreprocessing = false, ): Promise<ReleaseAndDist> { const distEnv = env.SENTRY_DIST; const releaseEnv = env.SENTRY_RELEASE; @@ -407,7 +407,7 @@ export async function resolveReleaseAndDist( if (!plist) { throw new Error( "Could not determine release: set SENTRY_RELEASE/SENTRY_DIST, run from " + - "Xcode, or pass --no-auto-release." + "Xcode, or pass --no-auto-release.", ); } return { diff --git a/packages/cli/src/lib/region.ts b/packages/cli/src/lib/region.ts index 05583150f..94867c288 100644 --- a/packages/cli/src/lib/region.ts +++ b/packages/cli/src/lib/region.ts @@ -74,7 +74,7 @@ export function resolveOrgRegion(orgSlug: string): Promise<string> { regionCache.delete(key); } }, - () => regionCache.delete(key) + () => regionCache.delete(key), ); return promise.then((resolution) => resolution.url); } @@ -89,7 +89,7 @@ export function resolveOrgRegion(orgSlug: string): Promise<string> { */ function normalizeRegionUrl( raw: string, - responseOrigin: string + responseOrigin: string, ): string | undefined { return normalizeRegionBaseUrl(raw, responseOrigin); } @@ -97,7 +97,7 @@ function normalizeRegionUrl( function getResolvedRegionUrl( raw: string | undefined, responseOrigin: string | undefined, - baseUrl: string + baseUrl: string, ): string | undefined { if (!responseOrigin) { return; @@ -117,7 +117,7 @@ function getResolvedRegionUrl( async function resolveOrgRegionUncached( orgSlug: string, credential: CredentialContext, - baseUrl: string + baseUrl: string, ): Promise<RegionResolution> { // 1. Check SQLite cache first const cached = getOrgRegion(orgSlug, baseUrl, credential.identity); @@ -158,7 +158,7 @@ async function resolveOrgRegionUncached( const regionUrl = getResolvedRegionUrl( rawRegionUrl, responseOrigin, - baseUrl + baseUrl, ); if (!(responseOrigin && regionUrl)) { return { cacheable: false, url: responseOrigin ?? baseUrl }; @@ -175,7 +175,7 @@ async function resolveOrgRegionUncached( regionUrl, responseOrigin, baseUrl, - credential.identity + credential.identity, ); return { cacheable: true, url: regionUrl }; @@ -223,7 +223,7 @@ function resolveOrgFromCache(orgSlug: string): string | undefined { const match = getOrgByNumericId( numericId, sourceOrigin, - credential.identity + credential.identity, ); if (match) { return match.slug; @@ -279,7 +279,7 @@ export async function resolveEffectiveOrg(orgSlug: string): Promise<string> { // boundary. Log here so --verbose shows why we used the raw slug. logger.debug( `resolveOrgRegion failed for '${orgSlug}', using raw slug`, - error + error, ); return orgSlug; } @@ -294,7 +294,7 @@ export async function resolveEffectiveOrg(orgSlug: string): Promise<string> { // Same as above: the command fails downstream and is reported there. logger.debug( `Failed to refresh org list for numeric ID '${orgSlug}'`, - error + error, ); return orgSlug; } diff --git a/packages/cli/src/lib/release-notes.ts b/packages/cli/src/lib/release-notes.ts index 903ca51f6..28cd6deb7 100644 --- a/packages/cli/src/lib/release-notes.ts +++ b/packages/cli/src/lib/release-notes.ts @@ -38,7 +38,7 @@ const log = logger.withTag("release-notes"); * Covers Dingbats (✨ U+2728), Misc Symbols (⚡ U+26A1), variation * selectors, and Supplemental Pictographics (🐛, 🔧, 📚). */ -// biome-ignore lint/suspicious/noMisleadingCharacterClass: intentional — stripping emoji ranges that include combining marks +// oxlint-disable-next-line no-misleading-character-class -- intentional — stripping emoji ranges that include combining marks const EMOJI_RE = /[\u{2000}-\u{2BFF}\u{FE00}-\u{FE0F}\u{1F000}-\u{1FFFF}]/gu; /** Strips ` by @author in [#123](url)` or ` by @author in #123` suffixes */ @@ -256,7 +256,7 @@ function stripAttributions(md: string): string { */ function truncateSectionMarkdown( md: string, - maxItems: number + maxItems: number, ): { markdown: string; removed: number } { const tokens = marked.lexer(md); let remaining = maxItems; @@ -301,7 +301,7 @@ function truncateSectionMarkdown( function applySectionTruncation( sections: ChangeSection[], maxItems: number, - originalCount: number + originalCount: number, ): { totalItems: number; truncated: boolean } { let budget = maxItems; @@ -320,7 +320,7 @@ function applySectionTruncation( ? Math.max(0, budget) : Math.min( Math.max(0, budget), - Math.max(1, Math.floor((sectionItems / originalCount) * maxItems)) + Math.max(1, Math.floor((sectionItems / originalCount) * maxItems)), ); if (sectionItems > sectionBudget) { @@ -334,7 +334,7 @@ function applySectionTruncation( const totalItems = sections.reduce( (sum, s) => sum + countMarkdownListItems(s.markdown), - 0 + 0, ); return { totalItems, truncated: totalItems < originalCount }; } @@ -357,7 +357,7 @@ function buildSummaryFromSections( sections: ChangeSection[], fromVersion: string, toVersion: string, - maxItems?: number + maxItems?: number, ): ChangelogSummary | null { if (sections.length === 0) { return null; @@ -365,7 +365,7 @@ function buildSummaryFromSections( const originalCount = sections.reduce( (sum, s) => sum + countMarkdownListItems(s.markdown), - 0 + 0, ); let totalItems = originalCount; @@ -430,7 +430,7 @@ type ChangelogBuildOptions = { function normalizeChangelogReleases( releases: GitHubRelease[], - source: UpgradeSource + source: UpgradeSource, ): GitHubRelease[] { if (isNormalizedForSource(releases, source)) { return releases; @@ -443,7 +443,7 @@ function buildChangelogSummaryForSource( releases: GitHubRelease[], fromVersion: string, toVersion: string, - options: ChangelogBuildOptions + options: ChangelogBuildOptions, ): ChangelogSummary | null { const { maxItems } = options; const inRange = releases.filter((release) => { @@ -463,7 +463,7 @@ function buildChangelogSummaryForSource( mergedSections, fromVersion, toVersion, - maxItems + maxItems, ); } @@ -480,7 +480,7 @@ export function buildChangelogSummary( releases: GitHubRelease[], fromVersion: string, toVersion: string, - maxItems?: number + maxItems?: number, ): ChangelogSummary | null { return buildChangelogSummaryForSource(releases, fromVersion, toVersion, { maxItems, @@ -592,13 +592,13 @@ const CHANGELOG_MAX_RELEASES = 30; * @returns Array of releases (newest first), or empty array on failure */ async function fetchReleasesForChangelog( - source: UpgradeSource + source: UpgradeSource, ): Promise<GitHubRelease[]> { let response: Response; try { response = await customFetch( `${getGitHubReleasesUrl(source)}?per_page=${CHANGELOG_MAX_RELEASES}`, - { headers: getGitHubHeaders() } + { headers: getGitHubHeaders() }, ); } catch (error) { log.debug("Failed to fetch releases for changelog", error); @@ -633,7 +633,7 @@ async function fetchReleasesForChangelog( * @returns Changelog summary, or null on failure */ async function fetchStableChangelog( - options: FetchChangelogOptions & { source: UpgradeSource } + options: FetchChangelogOptions & { source: UpgradeSource }, ): Promise<ChangelogSummary | null> { const { fromVersion, toVersion, maxItems, prefetchedReleases, source } = options; @@ -658,7 +658,7 @@ function buildNightlyChangelogSummary( commits: GitHubCommit[], fromVersion: string, toVersion: string, - maxItems?: number + maxItems?: number, ): ChangelogSummary | null { const sections = parseCommitMessages(commits); return buildSummaryFromSections(sections, fromVersion, toVersion, maxItems); @@ -681,7 +681,7 @@ async function fetchNightlyChangelog( fromVersion: string, toVersion: string, source: UpgradeSource, - maxItems?: number + maxItems?: number, ): Promise<ChangelogSummary | null> { const fromTs = extractNightlyTimestamp(fromVersion); const toTs = extractNightlyTimestamp(toVersion); @@ -728,7 +728,7 @@ async function fetchNightlyChangelog( commits as GitHubCommit[], fromVersion, toVersion, - maxItems + maxItems, ); } @@ -759,7 +759,7 @@ export type FetchChangelogOptions = { * already fetched the same data. */ export async function fetchChangelog( - opts: FetchChangelogOptions + opts: FetchChangelogOptions, ): Promise<ChangelogSummary | null> { try { const { @@ -775,7 +775,7 @@ export async function fetchChangelog( fromVersion, toVersion, source, - maxItems + maxItems, ); } return await fetchStableChangelog({ diff --git a/packages/cli/src/lib/replay-search.ts b/packages/cli/src/lib/replay-search.ts index 53b01305c..ae5665d39 100644 --- a/packages/cli/src/lib/replay-search.ts +++ b/packages/cli/src/lib/replay-search.ts @@ -45,7 +45,7 @@ export const DEFAULT_REPLAY_EXPLORE_FIELDS = [ /** Parse repeatable and comma-separated replay environment filters. */ export function parseReplayEnvironmentFilter( - values: readonly string[] | undefined + values: readonly string[] | undefined, ): string[] | undefined { const parsed = values ? [...values] @@ -219,7 +219,7 @@ export function getReplayRequestFields(fields: string[]): string[] { /** Extract a replay field value for CLI search/display output. */ export function getReplayFieldValue( replay: ReplayLike, - field: string + field: string, ): unknown { const resolver = REPLAY_FIELD_RESOLVERS[field]; if (!resolver) { @@ -236,7 +236,7 @@ export function getReplayFieldValue( * Extract the replay ID from the event's `contexts.replay` object. */ function getReplayIdFromReplayContext( - event: Pick<SentryEvent, "contexts"> + event: Pick<SentryEvent, "contexts">, ): string | undefined { const replayContext = event.contexts?.replay; return typeof replayContext?.replay_id === "string" @@ -251,10 +251,10 @@ function getReplayIdFromReplayContext( * Returns the first valid, normalized replay ID found. */ export function getReplayIdFromEvent( - event: Pick<SentryEvent, "contexts" | "tags"> + event: Pick<SentryEvent, "contexts" | "tags">, ): string | undefined { const tagReplayId = event.tags?.find( - (tag) => tag.key === "replayId" || tag.key === "replay.id" + (tag) => tag.key === "replayId" || tag.key === "replay.id", )?.value; return collectReplayIds([ @@ -270,7 +270,7 @@ export function getReplayIdFromEvent( * duplicate IDs are silently dropped. */ export function collectReplayIds( - values: Iterable<string | null | undefined> + values: Iterable<string | null | undefined>, ): string[] { const seen = new Set<string>(); const replayIds: string[] = []; diff --git a/packages/cli/src/lib/resolve-issue.ts b/packages/cli/src/lib/resolve-issue.ts index 790fff230..2fd4a32f4 100644 --- a/packages/cli/src/lib/resolve-issue.ts +++ b/packages/cli/src/lib/resolve-issue.ts @@ -57,7 +57,7 @@ export type ResolvedIssue = { async function resolveAliasSuffix( alias: string, suffix: string, - cwd: string + cwd: string, ): Promise<ResolvedIssue | null> { // Detect DSNs to get fingerprint for validation const detection = await detectAllDsns(cwd); @@ -82,7 +82,7 @@ async function resolveAliasSuffix( */ async function resolveShortSuffix( suffix: string, - options: ResolveIssueOptions + options: ResolveIssueOptions, ): Promise<ResolvedIssue | null> { const { org, project, cwd } = options; @@ -107,7 +107,7 @@ async function resolveShortSuffix( async function resolveWithOrgContext( issueId: string, options: ResolveIssueOptions, - commandHint: string + commandHint: string, ): Promise<ResolvedIssue> { const { org, cwd } = options; @@ -157,7 +157,7 @@ async function resolveWithOrgContext( export async function resolveIssueId( input: string, options: ResolveIssueOptions, - commandHint: string + commandHint: string, ): Promise<ResolvedIssue> { const { cwd } = options; @@ -167,7 +167,7 @@ export async function resolveIssueId( const resolved = await resolveAliasSuffix( aliasSuffix.alias, aliasSuffix.suffix, - cwd + cwd, ); if (resolved) { return resolved; diff --git a/packages/cli/src/lib/resolve-target.ts b/packages/cli/src/lib/resolve-target.ts index 811672e99..a63c83ab8 100644 --- a/packages/cli/src/lib/resolve-target.ts +++ b/packages/cli/src/lib/resolve-target.ts @@ -86,7 +86,7 @@ const log = logger.withTag("resolve-target"); * Eliminates boilerplate — every resolution function can call this on success. */ function withTelemetryContext<T extends { org: string; project?: string }>( - result: T + result: T, ): T { setOrgProjectContext([result.org], result.project ? [result.project] : []); return result; @@ -100,7 +100,7 @@ function withTelemetryContext<T extends { org: string; project?: string }>( * values are treated as absent rather than valid IDs. */ export function toNumericId( - id: string | number | null | undefined + id: string | number | null | undefined, ): number | undefined { if (id === null || id === undefined) { return; @@ -140,7 +140,7 @@ export type ResolvedTarget = { * failures propagate so callers cannot accidentally display an unresolved ID. */ export async function resolveTargetSlugs( - target: ResolvedTarget + target: ResolvedTarget, ): Promise<ResolvedTarget> { const numericOrg = isAllDigits(target.org); const numericProject = isAllDigits(target.project); @@ -161,7 +161,7 @@ export async function resolveTargetSlugs( project, orgDisplay: resolveOrgDisplayName( org, - target.projectData?.organization?.name + target.projectData?.organization?.name, ), projectDisplay: target.projectData?.name ?? target.projectDisplay, }; @@ -184,7 +184,7 @@ export async function resolveTargetSlugs( throw new ResolutionError( "Organization", "could not be resolved to a slug", - "sentry org list" + "sentry org list", ); } cacheResolvedProject(info, org, project); @@ -201,7 +201,7 @@ export async function resolveTargetSlugs( /** Look up project identity across numeric-key and discovery caches. */ function getCachedTargetProject( target: ResolvedTarget, - org: string + org: string, ): CachedProject | undefined { const projectId = target.projectData?.id ?? @@ -279,7 +279,7 @@ export type ResolveOrgOptions = { * @returns Resolved target with org/project info, or null if DSN not found */ export async function resolveFromDsn( - cwd: string + cwd: string, ): Promise<ResolvedTarget | null> { const dsn = await detectDsn(cwd); if (!(dsn?.orgId && dsn.projectId)) { @@ -329,7 +329,7 @@ function dsnTargetFromNumericIds( orgId: string, projectId: string, detectedFrom: string, - packagePath?: string + packagePath?: string, ): ResolvedTarget { const org = getOrgByNumericId(orgId)?.slug ?? orgId; return { @@ -350,7 +350,7 @@ function dsnTargetFromNumericIds( * @returns Resolved org info, or null if DSN not found */ export async function resolveOrgFromDsn( - cwd: string + cwd: string, ): Promise<ResolvedOrg | null> { const dsn = await detectDsn(cwd); if (!dsn?.orgId) { @@ -407,7 +407,7 @@ async function normalizeNumericOrg(orgId: string): Promise<string> { // Slow path: fetch org list to populate numeric ID → slug mapping. // resolveEffectiveOrg doesn't handle bare numeric IDs (only o-prefixed), // so we do a targeted refresh via listOrganizationsUncached(). - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { listOrganizationsUncached } = await import("./api-client.js"); await listOrganizationsUncached(); @@ -428,7 +428,7 @@ async function normalizeNumericOrg(orgId: string): Promise<string> { * @returns Resolved target or null if resolution failed */ export async function resolveDsnByPublicKey( - dsn: DetectedDsn + dsn: DetectedDsn, ): Promise<ResolvedTarget | null> { const detectedFrom = getDsnSourceDescription(dsn); @@ -457,7 +457,7 @@ export async function resolveDsnByPublicKey( if (projectInfo.organization) { const orgName = resolveOrgDisplayName( projectInfo.organization.slug, - projectInfo.organization.name + projectInfo.organization.name, ); setCachedProjectByDsnKey(dsn.publicKey, { orgSlug: projectInfo.organization.slug, @@ -497,7 +497,7 @@ export async function resolveDsnByPublicKey( * @returns Resolved target or null if resolution failed */ async function resolveDsnToTarget( - dsn: DetectedDsn + dsn: DetectedDsn, ): Promise<ResolvedTarget | null> { // For DSNs without orgId (self-hosted or some SaaS patterns), // resolve by searching for the project via DSN public key @@ -542,7 +542,7 @@ async function resolveDsnToTarget( orgId, dsnProjectId, detectedFrom, - packagePath + packagePath, ); } @@ -626,7 +626,7 @@ async function inferFromDirectoryName(cwd: string): Promise<ResolvedTargets> { // Search for matching projects using word-boundary matching let matches: Awaited<ReturnType<typeof findProjectsByPattern>>; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { matches = await findProjectsByPattern(dirName); } catch { @@ -740,9 +740,9 @@ function resolveFromEnvVars(): { */ async function findSimilarProjects( org: string, - slug: string + slug: string, ): Promise<string[]> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const projects = await listProjects(org); const slugs = projects.map((p) => p.slug); @@ -772,9 +772,9 @@ async function findSimilarProjectsAcrossOrgs( * the search also fuzzy-matches against project display names, making * it possible to resolve display-name input to the correct slug even * when the slug convention differs (e.g. underscores vs dashes). */ - displayName?: string + displayName?: string, ): Promise<ProjectWithOrg[]> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const concurrency = pLimit(5); const orgProjects = await Promise.all( @@ -788,8 +788,8 @@ async function findSimilarProjectsAcrossOrgs( ...project, orgSlug: org.slug, })); - }) - ) + }), + ), ); const allProjects = orgProjects.flat(); @@ -823,13 +823,13 @@ async function findSimilarProjectsAcrossOrgs( // Merge slug matches and name matches, preferring slug matches. const mergedSlugs = Array.from( - new Set(slugMatches.concat(nameMatchedSlugs)) + new Set(slugMatches.concat(nameMatchedSlugs)), ); // Expand matched slugs back to org-qualified entries (may include // the same slug from multiple orgs — that's correct for suggestions). return mergedSlugs.flatMap((matched) => - allProjects.filter((p) => p.slug === matched) + allProjects.filter((p) => p.slug === matched), ); } catch { return []; @@ -862,7 +862,7 @@ export async function tryFuzzyProjectRecovery( slug: string, orgs: { slug: string }[], /** Optional display name for name-based matching (see {@link findSimilarProjectsAcrossOrgs}). */ - displayName?: string + displayName?: string, ): Promise<FuzzyRecoveryResult> { const similar = await findSimilarProjectsAcrossOrgs(slug, orgs, displayName); if (similar.length === 1) { @@ -936,7 +936,7 @@ type ClassifyProjectSearchTargetOptions = { */ export async function classifyProjectSearchTarget( parsed: ParsedProjectSearch, - options: ClassifyProjectSearchTargetOptions = {} + options: ClassifyProjectSearchTargetOptions = {}, ): Promise<ProjectSearchTargetResolution> { const displaySlug = parsed.originalSlug ?? parsed.projectSlug; const scopedOrg = parsed.org @@ -954,7 +954,7 @@ export async function classifyProjectSearchTarget( scopedOrg === undefined ? searchResult.projects : searchResult.projects.filter( - (project) => project.orgSlug === scopedOrg + (project) => project.orgSlug === scopedOrg, ); const context: ProjectSearchContext = { displaySlug, scopedOrg }; @@ -976,11 +976,11 @@ export async function classifyProjectSearchTarget( const fuzzy = await tryFuzzyProjectRecovery( parsed.projectSlug, orgs, - parsed.originalSlug + parsed.originalSlug, ); if (fuzzy.kind === "match") { log.warn( - `No project matching '${displaySlug}'. Using '${fuzzy.project}' in org '${fuzzy.org}'.` + `No project matching '${displaySlug}'. Using '${fuzzy.project}' in org '${fuzzy.org}'.`, ); return { ...context, @@ -1004,7 +1004,7 @@ export async function classifyProjectSearchTarget( /** Return actionable suggestions for a classified project-search miss. */ export function projectSearchNotFoundSuggestions( - resolution: Extract<ProjectSearchTargetResolution, { kind: "not-found" }> + resolution: Extract<ProjectSearchTargetResolution, { kind: "not-found" }>, ): string[] { if (resolution.suggestions.length > 0) { return resolution.suggestions; @@ -1028,21 +1028,21 @@ export function projectSearchNotFoundSuggestions( function buildProjectNotFoundSuggestions( org: string, project: string, - similar: string[] + similar: string[], ): string[] { const suggestions: string[] = []; if (isAllDigits(project)) { suggestions.push( - "Project targets use slugs (e.g. 'frontend'), not numeric project IDs" + "Project targets use slugs (e.g. 'frontend'), not numeric project IDs", ); } if (similar.length > 0) { suggestions.push( - `Similar projects: ${similar.map((s) => `'${s}'`).join(", ")}` + `Similar projects: ${similar.map((s) => `'${s}'`).join(", ")}`, ); } suggestions.push( - `Check the project slug at https://sentry.io/organizations/${org}/projects/` + `Check the project slug at https://sentry.io/organizations/${org}/projects/`, ); return suggestions; } @@ -1065,7 +1065,7 @@ function buildProjectNotFoundSuggestions( */ export async function fetchProjectId( org: string, - project: string + project: string, ): Promise<number | undefined> { // Cache-first: avoid a round trip when `listProjects()` or DSN resolution // has already populated the entry for this (org, project) slug pair. @@ -1090,7 +1090,7 @@ export async function fetchProjectId( `Project '${project}'`, `not found in organization '${org}'`, `sentry project list ${org}/`, - buildProjectNotFoundSuggestions(org, project, similar) + buildProjectNotFoundSuggestions(org, project, similar), ); } return; @@ -1106,7 +1106,7 @@ export async function fetchProjectId( function cacheResolvedProject( project: SentryProject, orgIdentifier: string, - projectIdentifier: string + projectIdentifier: string, ): void { if (project.organization) { try { @@ -1114,7 +1114,7 @@ function cacheResolvedProject( orgSlug: project.organization.slug, orgName: resolveOrgDisplayName( project.organization.slug, - project.organization.name + project.organization.name, ), projectSlug: project.slug, projectName: project.name, @@ -1122,7 +1122,7 @@ function cacheResolvedProject( }); } catch (cacheErr) { log.debug( - `Failed to cache project '${orgIdentifier}/${projectIdentifier}': ${String(cacheErr)}` + `Failed to cache project '${orgIdentifier}/${projectIdentifier}': ${String(cacheErr)}`, ); } } @@ -1150,7 +1150,7 @@ function cacheResolvedProject( */ export async function resolveLogProjectId( org: string, - project: string + project: string, ): Promise<number | undefined> { if (isAllDigits(project)) { return Number(project); @@ -1163,7 +1163,7 @@ export async function resolveLogProjectId( } log.debug( `Failed to resolve project ID for '${org}/${project}'; falling back to slug scoping`, - error + error, ); return; } @@ -1199,13 +1199,14 @@ const DSN_RESOLVE_TIMEOUT_MS = 15_000; * @returns Array of resolved targets (null for failures/timeouts) */ async function resolveDsnsWithTimeout( - dsns: DetectedDsn[] + dsns: DetectedDsn[], ): Promise<(ResolvedTarget | null)[]> { const limit = pLimit(DSN_RESOLVE_CONCURRENCY); const signal = AbortSignal.timeout(DSN_RESOLVE_TIMEOUT_MS); // Shared results array — tasks write their result as they complete, // so partial results survive timeout. + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const results: (ResolvedTarget | null)[] = new Array(dsns.length).fill(null); const mapDone = limit.map(dsns, (dsn, i) => { @@ -1230,7 +1231,7 @@ async function resolveDsnsWithTimeout( if (raceResult === "timeout") { log.warn( - `DSN resolution timed out after ${DSN_RESOLVE_TIMEOUT_MS / 1000}s, returning partial results` + `DSN resolution timed out after ${DSN_RESOLVE_TIMEOUT_MS / 1000}s, returning partial results`, ); } @@ -1256,12 +1257,12 @@ async function resolveDsnsWithTimeout( * @throws Error if only one of org/project is provided */ export async function resolveAllTargets( - options: ResolveOptions + options: ResolveOptions, ): Promise<ResolvedTargets> { return await withTracingSpan( "resolveAllTargets", "resolve", - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Priority-based resolution cascade requires sequential checks. + // Priority-based resolution cascade requires sequential checks. async (span) => { const { org, project, cwd } = options; @@ -1286,7 +1287,7 @@ export async function resolveAllTargets( throw new ContextError( "Organization and project", options.usageHint ?? "sentry <command> <org>/<project>", - [] + [], ); } @@ -1311,7 +1312,7 @@ export async function resolveAllTargets( } log.debug( - `No SENTRY_ORG/SENTRY_PROJECT env vars, trying ${CONFIG_FILENAME} config file` + `No SENTRY_ORG/SENTRY_PROJECT env vars, trying ${CONFIG_FILENAME} config file`, ); // 3. .sentryclirc config file (walked up from cwd, merged with global) @@ -1359,14 +1360,14 @@ export async function resolveAllTargets( if (detection.all.length === 0) { log.debug( - "No DSNs found in source code or env files, trying directory name inference" + "No DSNs found in source code or env files, trying directory name inference", ); // 6. Fallback: infer from directory name const result = await inferFromDirectoryName(cwd); if (result.targets.length === 0) { span.setAttribute("resolve.method", "none"); log.debug( - "Directory name inference found no matching projects — auto-detection failed" + "Directory name inference found no matching projects — auto-detection failed", ); } else { span.setAttribute("resolve.method", "inference"); @@ -1382,7 +1383,7 @@ export async function resolveAllTargets( span.setAttribute("resolve.method", "dsn"); return resolveDetectedDsns(detection); }, - { "resolve.mode": "multi" } + { "resolve.mode": "multi" }, ); } @@ -1396,7 +1397,7 @@ export async function resolveAllTargets( * @returns Resolved targets with optional footer message */ async function resolveDetectedDsns( - detection: DsnDetectionResult + detection: DsnDetectionResult, ): Promise<ResolvedTargets> { // Deduplicate DSNs by (orgId, projectId) or publicKey before resolution. // Multiple DSNs in test fixtures or monorepos can share the same org+project @@ -1413,7 +1414,7 @@ async function resolveDetectedDsns( const uniqueDsns = [...uniqueDsnMap.values()]; log.debug( - `Resolving ${uniqueDsns.length} unique DSN targets (${detection.all.length} total detected)` + `Resolving ${uniqueDsns.length} unique DSN targets (${detection.all.length} total detected)`, ); // Resolve with concurrency limit to avoid overwhelming the Sentry API. @@ -1480,12 +1481,12 @@ async function resolveDetectedDsns( * @throws Error if only one of org/project is provided */ export async function resolveOrgAndProject( - options: ResolveOptions + options: ResolveOptions, ): Promise<ResolvedTarget | null> { return await withTracingSpan( "resolveOrgAndProject", "resolve", - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Priority-based resolution cascade requires sequential checks. + // Priority-based resolution cascade requires sequential checks. async (span) => { const { org, project, cwd } = options; @@ -1505,7 +1506,7 @@ export async function resolveOrgAndProject( throw new ContextError( "Organization and project", options.usageHint ?? "sentry <command> <org>/<project>", - [] + [], ); } @@ -1549,7 +1550,7 @@ export async function resolveOrgAndProject( } // 5. DSN auto-detection - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const dsnResult = await resolveFromDsn(cwd); if (dsnResult) { @@ -1590,7 +1591,7 @@ export async function resolveOrgAndProject( : first.detectedFrom, }); }, - { "resolve.mode": "single" } + { "resolve.mode": "single" }, ); } @@ -1601,7 +1602,7 @@ export async function resolveOrgAndProject( function toAccountTarget( org: SentryOrganization, project: SentryProject, - detectedFrom: string + detectedFrom: string, ): ResolvedTarget { return { org: org.slug, @@ -1670,7 +1671,7 @@ function canPromptForTarget(override?: boolean): boolean { */ async function promptSelect( message: string, - options: { label: string; value: string }[] + options: { label: string; value: string }[], ): Promise<string | null> { const sole = options[0]; if (options.length === 1 && sole) { @@ -1704,7 +1705,7 @@ async function promptForOrgProject(): Promise<ResolvedTarget | null> { const orgSlug = await promptSelect( "Select an organization:", - orgs.map((o) => ({ label: o.name || o.slug, value: o.slug })) + orgs.map((o) => ({ label: o.name || o.slug, value: o.slug })), ); if (!orgSlug) { return null; @@ -1725,13 +1726,13 @@ async function promptForOrgProject(): Promise<ResolvedTarget | null> { throw new ResolutionError( `Organization '${orgSlug}'`, "has no accessible projects", - `sentry project list ${orgSlug}/` + `sentry project list ${orgSlug}/`, ); } const projectSlug = await promptSelect( "Select a project:", - projects.map((p) => ({ label: p.name || p.slug, value: p.slug })) + projects.map((p) => ({ label: p.name || p.slug, value: p.slug })), ); if (!projectSlug) { return null; @@ -1747,7 +1748,7 @@ async function promptForOrgProject(): Promise<ResolvedTarget | null> { setDefaultOrganization(orgSlug); setDefaultProject(projectSlug); log.info( - `Saved ${orgSlug}/${projectSlug} as your default. Change it with: sentry cli defaults org <slug>` + `Saved ${orgSlug}/${projectSlug} as your default. Change it with: sentry cli defaults org <slug>`, ); } catch (error) { log.debug("Failed to persist selected org/project as default", error); @@ -1763,7 +1764,7 @@ async function promptForOrgProject(): Promise<ResolvedTarget | null> { * guidance shown to logged-out users. */ async function buildAccountContextError( - usageHint: string + usageHint: string, ): Promise<ContextError> { try { const orgs = await listOrganizations(); @@ -1808,7 +1809,7 @@ async function buildAccountContextError( * accessible projects. */ export async function guideOrgProjectFailure( - options: Pick<ResolveOptions, "usageHint" | "interactive"> + options: Pick<ResolveOptions, "usageHint" | "interactive">, ): Promise<ResolvedTarget> { const usageHint = options.usageHint ?? "sentry <command> <org>/<project>"; @@ -1835,7 +1836,7 @@ export async function guideOrgProjectFailure( * @throws {ContextError} When the target cannot be resolved or chosen. */ export async function resolveOrgProjectOrGuide( - options: ResolveOptions + options: ResolveOptions, ): Promise<ResolvedTarget> { const resolved = await resolveOrgAndProject(options); return resolved ?? (await guideOrgProjectFailure(options)); @@ -1843,7 +1844,7 @@ export async function resolveOrgProjectOrGuide( /** Resolve an explicitly configured org, without inferring one from a DSN. */ export async function resolveConfiguredOrg( - options: ResolveOrgOptions + options: ResolveOrgOptions, ): Promise<ResolvedOrg | null> { const { org, cwd } = options; @@ -1885,7 +1886,7 @@ export async function resolveConfiguredOrg( * Configured context takes precedence over automatic detection. */ export async function resolveOrg( - options: ResolveOrgOptions + options: ResolveOrgOptions, ): Promise<ResolvedOrg | null> { const configured = await resolveConfiguredOrg(options); if (configured) { @@ -1893,7 +1894,7 @@ export async function resolveOrg( } // 5. DSN auto-detection - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const result = await resolveOrgFromDsn(options.cwd); if (result) { @@ -1914,10 +1915,10 @@ export async function resolveOrg( async function resolveFuzzyProjectBoundSlug( resolution: Extract<ProjectSearchTargetResolution, { kind: "fuzzy-project" }>, projectSlug: string, - usageHint: string + usageHint: string, ): Promise<{ org: string; project: string; projectData: SentryProject }> { const project = await withAuthGuard(() => - getProject(resolution.org, resolution.project) + getProject(resolution.org, resolution.project), ); if (project.ok) { return withTelemetryContext({ @@ -1936,7 +1937,7 @@ async function resolveFuzzyProjectBoundSlug( [ `Similar project '${resolution.org}/${resolution.project}' was found but could not be accessed`, defaultHint, - ] + ], ); } @@ -1960,7 +1961,7 @@ export async function resolveProjectBoundSlug( usageHint: string, disambiguationExample?: string, /** Original user input before normalization — used for clearer messages. */ - originalSlug?: string + originalSlug?: string, ): Promise<{ org: string; project: string; projectData: SentryProject }> { const parsed: ParsedProjectSearch = { type: "project-search", @@ -1977,7 +1978,7 @@ export async function resolveProjectBoundSlug( [ `List projects: sentry project list ${projectSlug}/`, `Specify a project: ${projectSlug}/<project>`, - ] + ], ); } @@ -1999,7 +2000,7 @@ export async function resolveProjectBoundSlug( `Project "${resolution.displaySlug}"`, "not found", usageHint, - suggestions + suggestions, ); } @@ -2012,7 +2013,7 @@ export async function resolveProjectBoundSlug( throw new ValidationError( `Project "${resolution.displaySlug}" exists in multiple organizations.\n\n` + `Specify the organization:\n${orgList}${example}`, - "project.ambiguous_org" + "project.ambiguous_org", ); } const foundProject = projects[0]; @@ -2021,7 +2022,7 @@ export async function resolveProjectBoundSlug( if (isAllDigits(projectSlug) && foundProject.slug !== projectSlug) { log.warn( `Tip: Resolved project ID ${projectSlug} to ${foundProject.orgSlug}/${foundProject.slug}. ` + - "Use the slug form for faster lookups." + "Use the slug form for faster lookups.", ); } @@ -2062,7 +2063,7 @@ export type OrgListResolution = { */ export async function resolveOrgsForListing( orgFlag: string | undefined, - cwd: string + cwd: string, ): Promise<OrgListResolution> { if (orgFlag) { setOrgProjectContext([orgFlag], []); @@ -2151,7 +2152,7 @@ export async function resolveProjectBoundTarget( parsed: ParsedOrgProject, cwd: string, commandName: string, - options: ResolveProjectBoundTargetOptions = {} + options: ResolveProjectBoundTargetOptions = {}, ): Promise<ResolvedProjectBoundTarget> { const usageHint = options.usageHint ?? `sentry ${commandName} <org>/<project>`; @@ -2166,7 +2167,7 @@ export async function resolveProjectBoundTarget( throw new ContextError( "Project", `sentry ${commandName} ${parsed.org}/<project>`, - [] + [], ); case "project-search": { @@ -2179,7 +2180,7 @@ export async function resolveProjectBoundTarget( `'${parsed.projectSlug}'`, "is an organization, not a project", `sentry ${commandName} ${parsed.projectSlug}/<project>`, - [`List projects: sentry project list ${parsed.projectSlug}/`] + [`List projects: sentry project list ${parsed.projectSlug}/`], ); } @@ -2195,7 +2196,7 @@ export async function resolveProjectBoundTarget( `Project '${resolution.displaySlug}'`, "not found", usageHint, - projectSearchNotFoundSuggestions(resolution) + projectSearchNotFoundSuggestions(resolution), ); } @@ -2210,7 +2211,7 @@ export async function resolveProjectBoundTarget( `sentry ${commandName} <org>/${parsed.projectSlug}`, [ `Found in ${projects.length} organizations. Specify one:\n${projectOptions}`, - ] + ], ); } @@ -2253,12 +2254,12 @@ export async function resolveProjectBoundTarget( export function resolveProjectBoundFromArg( target: string | undefined, cwd: string, - commandName: string + commandName: string, ): Promise<ResolvedProjectBoundTarget> { return resolveProjectBoundTarget( parseOrgProjectArg(target), cwd, - commandName + commandName, ); } @@ -2311,10 +2312,10 @@ export type ResolveProjectBoundTargetsOptions = { * (issue list, alert issue list, …). Pass `opts.enrichProjectIds` or * `opts.checkIssueShortId` to enable command-specific behaviour. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: inherent multi-mode target resolution with per-mode error handling +// inherent multi-mode target resolution with per-mode error handling export async function resolveProjectBoundTargets( parsed: ReturnType<typeof parseOrgProjectArg>, - opts: ResolveProjectBoundTargetsOptions + opts: ResolveProjectBoundTargetsOptions, ): Promise<MultiTargetResolutionResult> { const { cwd, @@ -2351,11 +2352,11 @@ export async function resolveProjectBoundTargets( } catch (error) { logger.debug( `Failed to enrich project ID for ${t.org}/${t.project}`, - error + error, ); return t; } - }) + }), ); } return result; @@ -2397,7 +2398,7 @@ export async function resolveProjectBoundTargets( `Organization '${org}'`, "has no accessible projects", `sentry project list ${org}/`, - ["Check that you have access to projects in this organization"] + ["Check that you have access to projects in this organization"], ); } @@ -2421,7 +2422,7 @@ export async function resolveProjectBoundTargets( `'${displaySlug}'`, "looks like an issue short ID, not a project slug", `sentry issue view ${displaySlug}`, - ["To list issues in a project: sentry issue list <org>/<project>"] + ["To list issues in a project: sentry issue list <org>/<project>"], ); } @@ -2437,14 +2438,14 @@ export async function resolveProjectBoundTargets( [ `List projects: sentry project list ${parsed.projectSlug}/`, `Specify a project: ${prefix} ${parsed.projectSlug}/<project>`, - ] + ], ); } if (resolution.kind === "fuzzy-project") { const projectId = await fetchProjectId( resolution.org, - resolution.project + resolution.project, ); const targets: ResolvedTarget[] = [ { @@ -2464,7 +2465,7 @@ export async function resolveProjectBoundTargets( `Project '${displaySlug}'`, "not found", usageHint, - projectSearchNotFoundSuggestions(resolution) + projectSearchNotFoundSuggestions(resolution), ); } @@ -2493,7 +2494,7 @@ export async function resolveProjectBoundTargets( default: { const _exhaustive: never = parsed; throw new Error( - `Unexpected parsed type: ${(_exhaustive as { type: string }).type}` + `Unexpected parsed type: ${(_exhaustive as { type: string }).type}`, ); } } @@ -2532,7 +2533,7 @@ export async function resolveOrgOptionalTarget( parsed: ParsedOrgProject, cwd: string, commandName: string, - usageHint = `sentry ${commandName} <target>` + usageHint = `sentry ${commandName} <target>`, ): Promise<ResolvedOrgOptionalTarget> { // org-all: resolve the org slug only if (parsed.type === "org-all") { @@ -2562,7 +2563,7 @@ export async function resolveOrgOptionalTarget( const resolution = await classifyProjectSearchTarget(parsed); if (resolution.kind === "organization") { log.warn( - `'${resolution.org}' is an organization, not a project. Using organization '${resolution.org}'.` + `'${resolution.org}' is an organization, not a project. Using organization '${resolution.org}'.`, ); return withTelemetryContext({ org: resolution.org }); } @@ -2587,7 +2588,7 @@ export async function resolveOrgOnlyTarget( parsed: ParsedOrgProject, cwd: string, commandName: string, - usageHint = `sentry ${commandName} <target>` + usageHint = `sentry ${commandName} <target>`, ): Promise<string> { if (parsed.type === "auto-detect") { const resolved = await resolveOrg({ cwd }); @@ -2600,7 +2601,7 @@ export async function resolveOrgOnlyTarget( parsed, cwd, commandName, - usageHint + usageHint, ); return resolved.org; } @@ -2620,7 +2621,7 @@ export async function resolveOrgOnlyTarget( export function resolveOrgOptionalFromArg( target: string | undefined, cwd: string, - commandName: string + commandName: string, ): Promise<ResolvedOrgOptionalTarget> { return resolveOrgOptionalTarget(parseOrgProjectArg(target), cwd, commandName); } @@ -2630,12 +2631,12 @@ export function resolveOrgOnlyFromArg( target: string | undefined, cwd: string, commandName: string, - usageHint?: string + usageHint?: string, ): Promise<string> { return resolveOrgOnlyTarget( parseOrgProjectArg(target), cwd, commandName, - usageHint + usageHint, ); } diff --git a/packages/cli/src/lib/resolve-team.ts b/packages/cli/src/lib/resolve-team.ts index 5a55d6ad5..99b7c6ed0 100644 --- a/packages/cli/src/lib/resolve-team.ts +++ b/packages/cli/src/lib/resolve-team.ts @@ -129,14 +129,14 @@ export type ResolvedTeam = ResolvedConcreteTeam | DeferredResolvedTeam; async function handleListTeamsError( error: unknown, orgSlug: string, - options: ResolveTeamOptions + options: ResolveTeamOptions, ): Promise<never> { if (error instanceof ApiError) { if (error.status === 404) { return await buildOrgNotFoundError( orgSlug, options.usageHint, - options.detectedFrom + options.detectedFrom, ); } if (error.status === 403) { @@ -149,7 +149,7 @@ async function handleListTeamsError( `Organization '${orgSlug}'`, `could not be accessed (${error.status})`, `${options.usageHint} --team <team-slug>`, - ["The organization may not exist, or you may lack access"] + ["The organization may not exist, or you may lack access"], ); } throw error; @@ -159,15 +159,15 @@ export async function resolveOrCreateTeam( orgSlug: string, options: ResolveTeamOptions & { deferAutoCreateOnEmptyOrg?: false | undefined; - } + }, ): Promise<ResolvedConcreteTeam>; export async function resolveOrCreateTeam( orgSlug: string, - options: ResolveTeamOptions & { deferAutoCreateOnEmptyOrg: true } + options: ResolveTeamOptions & { deferAutoCreateOnEmptyOrg: true }, ): Promise<ResolvedTeam>; export async function resolveOrCreateTeam( orgSlug: string, - options: ResolveTeamOptions + options: ResolveTeamOptions, ): Promise<ResolvedTeam> { if (options.team) { return { slug: options.team, source: "explicit" }; @@ -217,7 +217,7 @@ export async function resolveOrCreateTeam( [ `${label}. Specify one with --team`, ...candidates.map((t) => `Available: ${t.slug}`), - ] + ], ); } @@ -228,7 +228,7 @@ export async function resolveOrCreateTeam( */ function resolveEmptyTeams( orgSlug: string, - options: ResolveTeamOptions + options: ResolveTeamOptions, ): Promise<ResolvedTeam> | ResolvedTeam { if (options.deferAutoCreateOnEmptyOrg) { return { source: "deferred" }; @@ -252,7 +252,7 @@ function resolveEmptyTeams( */ async function autoCreateTeam( orgSlug: string, - slug: string + slug: string, ): Promise<ResolvedTeam> { try { const team = await createTeam(orgSlug, slug); @@ -274,7 +274,7 @@ async function autoCreateTeam( `Create a team manually at ${getSentryBaseUrl()}/settings/${orgSlug}/teams/` + (error instanceof ApiError ? `\n\nAPI error (${error.status}): ${error.detail ?? error.message}` - : "") + : ""), ); } } @@ -291,7 +291,7 @@ async function autoCreateTeam( export async function buildOrgNotFoundError( orgSlug: string, usageHint: string, - detectedFrom?: string + detectedFrom?: string, ): Promise<never> { // Try resolving DSN-style org IDs (e.g., o1081365 → actual slug) const effectiveOrg = await resolveEffectiveOrg(orgSlug); @@ -300,12 +300,12 @@ export async function buildOrgNotFoundError( `Organization '${orgSlug}'`, `not found (did you mean '${effectiveOrg}'?)`, usageHint, - [`Try using '${effectiveOrg}' as the org slug instead of '${orgSlug}'`] + [`Try using '${effectiveOrg}' as the org slug instead of '${orgSlug}'`], ); } const orgHint = await fetchOrgListHint( - `Specify org explicitly: ${usageHint}` + `Specify org explicitly: ${usageHint}`, ); const suggestions: string[] = []; @@ -318,6 +318,6 @@ export async function buildOrgNotFoundError( `Organization '${orgSlug}'`, "not found", usageHint, - suggestions + suggestions, ); } diff --git a/packages/cli/src/lib/response-cache.ts b/packages/cli/src/lib/response-cache.ts index 521679180..6019c1e62 100644 --- a/packages/cli/src/lib/response-cache.ts +++ b/packages/cli/src/lib/response-cache.ts @@ -122,7 +122,7 @@ export function classifyUrl(url: string): TtlTier { export function buildCacheKey( method: string, url: string, - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): string { const normalized = normalizeUrl(method, url); return createHmac("sha256", identity).update(normalized).digest("hex"); @@ -146,7 +146,7 @@ export function normalizeUrl(method: string, url: string): string { return 1; } return 0; - }) + }), ); parsed.search = sortedParams.toString() ? `?${sortedParams.toString()}` : ""; return `${method.toUpperCase()}|${parsed.toString()}`; @@ -269,7 +269,7 @@ function isEntryFresh( policy: CachePolicy, entry: CacheEntry, requestHeaders: Record<string, string>, - url: string + url: string, ): boolean { const newRequest = { url, method: "GET", headers: requestHeaders }; if (policy.satisfiesWithoutRevalidation(newRequest)) { @@ -296,7 +296,7 @@ function isEntryFresh( */ function buildResponseHeaders( policy: CachePolicy, - entry: CacheEntry + entry: CacheEntry, ): Record<string, string> { const policyHeaders = policy.responseHeaders(); const result: Record<string, string> = {}; @@ -429,7 +429,7 @@ export async function getCachedResponse( method: string, url: string, requestHeaders: Record<string, string>, - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): Promise<Response | undefined> { if ( method !== "GET" || @@ -440,7 +440,7 @@ export async function getCachedResponse( } let key: string; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { key = buildCacheKey(method, url, identity); } catch { @@ -486,7 +486,7 @@ export async function getCachedResponse( // Best-effort cleanup of the broken entry. span.setAttribute("cache.hit", false); recordCacheHit("http", false); - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. unlink(cacheFilePath(key)).catch(() => { // Ignored — fire-and-forget }); @@ -496,7 +496,7 @@ export async function getCachedResponse( { "cache.key": [key], "network.peer.address": getCacheDir(), - } + }, ); } @@ -507,7 +507,7 @@ export async function getCachedResponse( async function readCacheEntry(key: string): Promise<CacheEntry | undefined> { const filePath = cacheFilePath(key); let raw: string; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { raw = await readFile(filePath, "utf-8"); } catch { @@ -519,7 +519,7 @@ async function readCacheEntry(key: string): Promise<CacheEntry | undefined> { return JSON.parse(raw) as CacheEntry; } catch { // Corrupted cache file — delete it - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await unlink(filePath).catch(() => { // Best-effort cleanup of corrupted file }); @@ -542,13 +542,12 @@ async function readCacheEntry(key: string): Promise<CacheEntry | undefined> { * @param requestHeaders - Request headers * @param response - The fetch Response to cache (must be cloned before passing) */ -// biome-ignore lint/nursery/useMaxParams: preserve the public cache API; the final identity pins an in-flight request. export async function storeCachedResponse( method: string, url: string, requestHeaders: Record<string, string>, response: Response, - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): Promise<void> { if ( method !== "GET" || @@ -560,7 +559,7 @@ export async function storeCachedResponse( } let key: string; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { key = buildCacheKey(method, url, identity); } catch { @@ -568,7 +567,7 @@ export async function storeCachedResponse( return; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await withCacheSpan( url, @@ -588,7 +587,7 @@ export async function storeCachedResponse( { "cache.key": [key], "network.peer.address": getCacheDir(), - } + }, ); } catch { // Cache write failures are non-fatal — silently ignore @@ -612,7 +611,7 @@ export async function storeCachedResponse( */ async function atomicWriteCacheFile( finalPath: string, - serialized: string + serialized: string, ): Promise<void> { const tmpPath = `${finalPath}.${process.pid}.${randomUUID()}.tmp`; try { @@ -648,7 +647,7 @@ async function writeResponseToCache(req: WriteRequest): Promise<number> { const policy = new CachePolicy( { url, method: "GET", headers: requestHeaders }, { status: response.status, headers: responseHeadersObj }, - POLICY_OPTIONS + POLICY_OPTIONS, ); if (!policy.storable()) { @@ -683,7 +682,7 @@ async function writeResponseToCache(req: WriteRequest): Promise<number> { // Probabilistic cleanup to avoid unbounded cache growth if (Math.random() < CLEANUP_PROBABILITY) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. cleanupCache().catch(() => { // Non-fatal: cleanup failure doesn't affect cache correctness }); @@ -705,9 +704,9 @@ async function writeResponseToCache(req: WriteRequest): Promise<number> { */ export async function invalidateCachedResponsesMatching( prefix: string, - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): Promise<void> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const cacheDir = getCacheDir(); const files = await readdir(cacheDir); @@ -718,12 +717,12 @@ export async function invalidateCachedResponsesMatching( await cacheIO.map(jsonFiles, async (file) => { const filePath = join(cacheDir, file); - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const raw = await readFile(filePath, "utf-8"); const entry = JSON.parse(raw) as CacheEntry; if (entry.identity === identity && entry.url?.startsWith(prefix)) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await unlink(filePath).catch(() => { /* another process may have deleted it */ }); @@ -742,7 +741,7 @@ export async function invalidateCachedResponsesMatching( * Called on `auth logout` and `auth login` since cached data is tied to the user. */ export async function clearResponseCache(): Promise<void> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await rm(getCacheDir(), { recursive: true, force: true }); } catch { @@ -816,7 +815,7 @@ const STALE_TEMP_FILE_MS = 60_000; /** Delete `.tmp` files older than {@link STALE_TEMP_FILE_MS}. Best-effort. */ async function deleteStaleTempFiles( cacheDir: string, - tmpFiles: string[] + tmpFiles: string[], ): Promise<void> { const cutoff = Date.now() - STALE_TEMP_FILE_MS; await cacheIO.map(tmpFiles, async (file) => { @@ -824,7 +823,7 @@ async function deleteStaleTempFiles( try { const stats = await stat(filePath); if (stats.mtimeMs < cutoff) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await unlink(filePath).catch(() => { // Already gone — another sweep or the owning process removed it. }); @@ -847,7 +846,7 @@ type EntryMetadata = { file: string; createdAt: number; expired: boolean }; */ async function collectEntryMetadata( cacheDir: string, - jsonFiles: string[] + jsonFiles: string[], ): Promise<EntryMetadata[]> { const entries: EntryMetadata[] = []; const now = Date.now(); @@ -894,21 +893,21 @@ async function collectEntryMetadata( /** Delete cache files that have expired */ async function deleteExpiredEntries( cacheDir: string, - entries: EntryMetadata[] + entries: EntryMetadata[], ): Promise<void> { const expired = entries.filter((e) => e.expired); await cacheIO.map(expired, (entry) => - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. unlink(join(cacheDir, entry.file)).catch(() => { // Best-effort: file may have been deleted by another process - }) + }), ); } /** Evict the oldest entries when over the max count */ async function evictExcessEntries( cacheDir: string, - entries: EntryMetadata[] + entries: EntryMetadata[], ): Promise<void> { const remaining = entries.filter((e) => !e.expired); if (remaining.length <= MAX_CACHE_ENTRIES) { @@ -918,9 +917,9 @@ async function evictExcessEntries( remaining.sort((a, b) => a.createdAt - b.createdAt); const toEvict = remaining.slice(0, remaining.length - MAX_CACHE_ENTRIES); await cacheIO.map(toEvict, (entry) => - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. unlink(join(cacheDir, entry.file)).catch(() => { // Best-effort eviction - }) + }), ); } diff --git a/packages/cli/src/lib/retention.ts b/packages/cli/src/lib/retention.ts index 614524169..69534c95d 100644 --- a/packages/cli/src/lib/retention.ts +++ b/packages/cli/src/lib/retention.ts @@ -70,7 +70,7 @@ export const SCAN_PERIODS: Record<HexEntityType, string> = { const LOG_RETENTION_CAP = RETENTION_DAYS.log; if (LOG_RETENTION_CAP === null || LOG_RETENTION_CAP <= 0) { throw new Error( - `RETENTION_DAYS.log must be a positive number; got ${LOG_RETENTION_CAP}` + `RETENTION_DAYS.log must be a positive number; got ${LOG_RETENTION_CAP}`, ); } export const LOG_RETENTION_PERIOD = `${LOG_RETENTION_CAP}d`; diff --git a/packages/cli/src/lib/safe-read.ts b/packages/cli/src/lib/safe-read.ts index 9a7a90d35..0c0ae9819 100644 --- a/packages/cli/src/lib/safe-read.ts +++ b/packages/cli/src/lib/safe-read.ts @@ -31,7 +31,7 @@ import { handleFileError, isRegularFile } from "./dsn/fs-utils.js"; */ export async function safeReadFile( filePath: string, - operation: string + operation: string, ): Promise<string | null> { if (!(await isRegularFile(filePath, `${operation}.stat`))) { return null; diff --git a/packages/cli/src/lib/scan/binary.ts b/packages/cli/src/lib/scan/binary.ts index b6173cd2c..918d5e49e 100644 --- a/packages/cli/src/lib/scan/binary.ts +++ b/packages/cli/src/lib/scan/binary.ts @@ -156,7 +156,7 @@ export function isLikelyBinary(head: Uint8Array): boolean { */ export function classifyByExtension( absPath: string, - textExtensions: ReadonlySet<string> + textExtensions: ReadonlySet<string>, ): { isBinary: boolean } | null { const ext = extname(absPath).toLowerCase(); if (!ext) { @@ -183,7 +183,7 @@ export function classifyByExtension( * wrap this in try/catch. */ export async function readHeadAndSniff( - absPath: string + absPath: string, ): Promise<{ head: Uint8Array; isBinary: boolean }> { const handle = await open(absPath, "r"); try { diff --git a/packages/cli/src/lib/scan/concurrent.ts b/packages/cli/src/lib/scan/concurrent.ts index 4c65f167f..47dbc00ad 100644 --- a/packages/cli/src/lib/scan/concurrent.ts +++ b/packages/cli/src/lib/scan/concurrent.ts @@ -57,7 +57,7 @@ export type MapFilesOptions<T> = ConcurrentOptions & { export async function mapFilesConcurrent<TIn, TOut>( source: AsyncIterable<TIn>, fn: (item: TIn) => Promise<TOut | null>, - opts: MapFilesOptions<TOut> = {} + opts: MapFilesOptions<TOut> = {}, ): Promise<TOut[]> { const limit = pLimit(opts.concurrency ?? CONCURRENCY_LIMIT); const results: TOut[] = []; @@ -84,7 +84,7 @@ export async function mapFilesConcurrent<TIn, TOut>( if (verdict?.done) { state.earlyExit = true; } - }) + }), ); } } finally { @@ -111,7 +111,7 @@ export async function mapFilesConcurrent<TIn, TOut>( export async function* mapFilesConcurrentStream<TIn, TOut>( source: AsyncIterable<TIn>, fn: (item: TIn) => Promise<TOut[] | null>, - opts: ConcurrentOptions = {} + opts: ConcurrentOptions = {}, ): AsyncGenerator<TOut> { const limit = pLimit(opts.concurrency ?? CONCURRENCY_LIMIT); const state = { earlyExit: false }; @@ -158,7 +158,7 @@ export async function* mapFilesConcurrentStream<TIn, TOut>( queue.push(entry); } wakeUp(); - }) + }), ); } await Promise.all(tasks); @@ -203,7 +203,7 @@ export async function* mapFilesConcurrentStream<TIn, TOut>( // the post-try body), so an error thrown outside this block // would be silently lost on the break path. if (producerError) { - // biome-ignore lint/correctness/noUnsafeFinally: intentional — this is the only path to surface producer errors on the break path + // oxlint-disable-next-line no-unsafe-finally -- intentional — this is the only path to surface producer errors on the break path throw producerError; } } @@ -217,7 +217,7 @@ function throwIfAborted(signal: AbortSignal | undefined): void { if (signal?.aborted) { throw new DOMException( signal.reason instanceof Error ? signal.reason.message : "Aborted", - "AbortError" + "AbortError", ); } } diff --git a/packages/cli/src/lib/scan/constants.ts b/packages/cli/src/lib/scan/constants.ts index 75fbee6a0..536a76c26 100644 --- a/packages/cli/src/lib/scan/constants.ts +++ b/packages/cli/src/lib/scan/constants.ts @@ -234,7 +234,7 @@ export const MAX_FILE_SIZE = 256 * 1024; */ export const CONCURRENCY_LIMIT = Math.min( 16, - Math.max(2, availableParallelism()) + Math.max(2, availableParallelism()), ); /** diff --git a/packages/cli/src/lib/scan/grep-worker-source.ts b/packages/cli/src/lib/scan/grep-worker-source.ts index 6d6b20e03..bd7940e49 100644 --- a/packages/cli/src/lib/scan/grep-worker-source.ts +++ b/packages/cli/src/lib/scan/grep-worker-source.ts @@ -13,7 +13,7 @@ import { fileURLToPath } from "node:url"; const GREP_WORKER_SOURCE = readFileSync( resolve(dirname(fileURLToPath(import.meta.url)), "grep-worker.js"), - "utf-8" + "utf-8", ); export default GREP_WORKER_SOURCE; diff --git a/packages/cli/src/lib/scan/grep-worker.js b/packages/cli/src/lib/scan/grep-worker.js index 2e28a64ad..127b1f148 100644 --- a/packages/cli/src/lib/scan/grep-worker.js +++ b/packages/cli/src/lib/scan/grep-worker.js @@ -37,7 +37,7 @@ const { readFileSync } = require("node:fs"); const textEncoder = new TextEncoder(); -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: hot regex loop with literal gate + line counter + line-bound extraction + per-file cap is inherently branchy +// hot regex loop with literal gate + line counter + line-bound extraction + per-file cap is inherently branchy self.onmessage = (event) => { const { paths, @@ -121,7 +121,7 @@ self.onmessage = (event) => { const linePoolBytes = textEncoder.encode(linePool); self.postMessage( { type: "result", ints: packed, linePoolBytes }, - { transfer: [packed.buffer, linePoolBytes.buffer] } + { transfer: [packed.buffer, linePoolBytes.buffer] }, ); }; diff --git a/packages/cli/src/lib/scan/grep.ts b/packages/cli/src/lib/scan/grep.ts index 045065fb5..5a706bf65 100644 --- a/packages/cli/src/lib/scan/grep.ts +++ b/packages/cli/src/lib/scan/grep.ts @@ -78,7 +78,7 @@ function createStats(): GrepStats { async function* tapWalkerStats<T extends WalkEntry>( source: AsyncIterable<T>, - stats: GrepStats + stats: GrepStats, ): AsyncGenerator<T> { for await (const item of source) { stats.filesConsidered += 1; @@ -93,7 +93,7 @@ async function* applyGrepFilters( excludes: CompiledMatcher[]; includeBinary: boolean; stats: GrepStats; - } + }, ): AsyncGenerator<WalkEntry> { for await (const entry of source) { if (entry.isBinary && !opts.includeBinary) { @@ -145,7 +145,7 @@ type PerFileOptions = { */ async function readAndGrep( entry: WalkEntry, - opts: PerFileOptions + opts: PerFileOptions, ): Promise<GrepMatch[] | null> { let content: string; try { @@ -183,7 +183,7 @@ async function readAndGrep( function grepByWholeBuffer( content: string, entry: WalkEntry, - opts: PerFileOptions + opts: PerFileOptions, ): GrepMatch[] { const ensured = opts.multiline ? ensureGlobalMultilineFlags(opts.regex) @@ -292,9 +292,9 @@ type GrepPipelineOptions = { */ const WORKER_BATCH_SIZE = 200; -// biome-ignore lint/suspicious/useAwait: yield* delegates to sub-generators +// yield* delegates to sub-generators async function* grepFilesInternal( - opts: GrepPipelineOptions + opts: GrepPipelineOptions, ): AsyncGenerator<GrepMatch> { if (shouldUseWorkers()) { yield* grepViaWorkers(opts); @@ -315,7 +315,7 @@ function shouldUseWorkers(): boolean { * ~4× slower on 10k-file many-match workloads. */ async function* grepViaAsyncMain( - opts: GrepPipelineOptions + opts: GrepPipelineOptions, ): AsyncGenerator<GrepMatch> { for await (const match of mapFilesConcurrentStream( opts.walkSource, @@ -328,7 +328,7 @@ async function* grepViaAsyncMain( } return matches; }, - opts.concurrent + opts.concurrent, )) { opts.stats.matchesEmitted += 1; yield match; @@ -349,9 +349,9 @@ async function* grepViaAsyncMain( * workers return packed results via transferable `Uint32Array`. * Main thread decodes and yields in worker-completion order. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: producer/consumer pattern with early exit, batch dispatch, and abort-signal propagation is inherently branchy +// producer/consumer pattern with early exit, batch dispatch, and abort-signal propagation is inherently branchy async function* grepViaWorkers( - opts: GrepPipelineOptions + opts: GrepPipelineOptions, ): AsyncGenerator<GrepMatch> { const pool = getWorkerPool(); @@ -386,7 +386,7 @@ async function* grepViaWorkers( const dispatchBatch = ( paths: string[], rels: string[], - mtimes: readonly number[] | null + mtimes: readonly number[] | null, ): void => { opts.stats.filesRead += paths.length; inflightCount += 1; @@ -408,7 +408,7 @@ async function* grepViaWorkers( }, () => { failedBatches += 1; - } + }, ) .finally(() => { inflightCount -= 1; @@ -419,7 +419,7 @@ async function* grepViaWorkers( let producerError: unknown = null; const recordMtimes = opts.perFile.recordMtimes; - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: walker drain + path-prefix handling + batch flush + error capture is inherently branchy + // walker drain + path-prefix handling + batch flush + error capture is inherently branchy const producer = (async () => { let batch: string[] = []; let batchRel: string[] = []; @@ -435,7 +435,7 @@ async function* grepViaWorkers( batchRel.push( opts.perFile.pathPrefix ? joinPosix(opts.perFile.pathPrefix, entry.relativePath) - : entry.relativePath + : entry.relativePath, ); if (batchMtimes !== null) { batchMtimes.push(entry.mtime); @@ -528,7 +528,7 @@ async function* grepViaWorkers( await Promise.allSettled(dispatchPromises); } if (producerError !== null) { - // biome-ignore lint/correctness/noUnsafeFinally: re-raising walker error (e.g. AbortError) so callers see it + // oxlint-disable-next-line no-unsafe-finally -- re-raising walker error (e.g. AbortError) so callers see it throw producerError; } // If every dispatched batch failed, the "no matches" result is @@ -536,9 +536,9 @@ async function* grepViaWorkers( // callers (notably the DSN cache layer) don't persist a // false-negative empty result. if (dispatchedBatches > 0 && failedBatches === dispatchedBatches) { - // biome-ignore lint/correctness/noUnsafeFinally: surfacing pipeline-wide failure so false-negative empty result doesn't leak upstream + // oxlint-disable-next-line no-unsafe-finally -- surfacing pipeline-wide failure so false-negative empty result doesn't leak upstream throw new Error( - `worker pipeline: all ${dispatchedBatches} dispatched batch(es) failed` + `worker pipeline: all ${dispatchedBatches} dispatched batch(es) failed`, ); } } @@ -597,7 +597,7 @@ function setupGrepPipeline(opts: GrepOptions): GrepPipelineSetup { const stats = createStats(); const walkSource = applyGrepFilters( tapWalkerStats(walkFiles(walkOpts), stats), - { includes, excludes, includeBinary, stats } + { includes, excludes, includeBinary, stats }, ); return { @@ -622,7 +622,7 @@ function setupGrepPipeline(opts: GrepOptions): GrepPipelineSetup { * Consumer `break` halts in-flight work. Throws `ValidationError` * on bad regex input; propagates `AbortError` from `opts.signal`. */ -// biome-ignore lint/suspicious/useAwait: yield* delegates to async generator +// yield* delegates to async generator export async function* grepFiles(opts: GrepOptions): AsyncGenerator<GrepMatch> { const setup = setupGrepPipeline(opts); diff --git a/packages/cli/src/lib/scan/ignore.ts b/packages/cli/src/lib/scan/ignore.ts index 420b75b6b..bf98d8d57 100644 --- a/packages/cli/src/lib/scan/ignore.ts +++ b/packages/cli/src/lib/scan/ignore.ts @@ -122,7 +122,7 @@ export class IgnoreStack implements IgnoreMatcher { if (opts.includeGitInfoExclude) { await appendGitignoreFile( root, - path.join(opts.cwd, ".git", "info", "exclude") + path.join(opts.cwd, ".git", "info", "exclude"), ); } } @@ -185,13 +185,13 @@ export class IgnoreStack implements IgnoreMatcher { * instances, a child `!foo` pattern flips an earlier `ignored=true` * back to `false` because we see later results last. */ - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: two-tier fast+slow path with negation handling is inherently branchy + // two-tier fast+slow path with negation handling is inherently branchy isIgnored(relPath: string, isDirectory: boolean): boolean { if (path.isAbsolute(relPath)) { // Programming error — a misuse that would silently produce wrong // results inside `ignore`. Throwing here flags it immediately. throw new Error( - `IgnoreStack.isIgnored requires a relative path, got: ${relPath}` + `IgnoreStack.isIgnored requires a relative path, got: ${relPath}`, ); } if (relPath === "" || relPath === ".") { diff --git a/packages/cli/src/lib/scan/index.ts b/packages/cli/src/lib/scan/index.ts index 9015dcea6..3cee56fdc 100644 --- a/packages/cli/src/lib/scan/index.ts +++ b/packages/cli/src/lib/scan/index.ts @@ -1,4 +1,4 @@ -// biome-ignore-all lint/performance/noBarrelFile: intentional public API +// Intentional public API barrel. /** * Scan module — pure-TS ripgrep-compatible file scanner. * @@ -34,10 +34,7 @@ export { readHeadAndSniff, } from "./binary.js"; export type { ConcurrentOptions, MapFilesOptions } from "./concurrent.js"; -export { - mapFilesConcurrent, - mapFilesConcurrentStream, -} from "./concurrent.js"; +export { mapFilesConcurrent, mapFilesConcurrentStream } from "./concurrent.js"; export { BINARY_SNIFF_BYTES, CONCURRENCY_LIMIT, diff --git a/packages/cli/src/lib/scan/literal-extract.ts b/packages/cli/src/lib/scan/literal-extract.ts index e33a9394d..143280b14 100644 --- a/packages/cli/src/lib/scan/literal-extract.ts +++ b/packages/cli/src/lib/scan/literal-extract.ts @@ -137,10 +137,10 @@ const ESCAPED_LITERAL_CHARS = new Set([ * Honors the `i` flag by lower-casing the literal — the caller * should also lower-case the haystack before searching. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: single-pass parser over regex source; all branches are guarded cases +// single-pass parser over regex source; all branches are guarded cases export function extractInnerLiteral( source: string, - flags: string + flags: string, ): string | null { // Quick-reject: top-level alternation. Walk once checking for `|` // outside brackets/parens (rough; better than parsing the whole diff --git a/packages/cli/src/lib/scan/path-utils.ts b/packages/cli/src/lib/scan/path-utils.ts index 74377002c..8df8af03b 100644 --- a/packages/cli/src/lib/scan/path-utils.ts +++ b/packages/cli/src/lib/scan/path-utils.ts @@ -43,7 +43,7 @@ export function compileMatcher(pattern: string): CompiledMatcher { * can short-circuit on `.length === 0` without null-checks. */ export function compileMatchers( - patterns: string | readonly string[] | undefined + patterns: string | readonly string[] | undefined, ): CompiledMatcher[] { if (patterns === undefined) { return []; @@ -63,7 +63,7 @@ export function compileMatchers( export function matchesAny( matchers: readonly CompiledMatcher[], relPath: string, - basename: string + basename: string, ): boolean { for (const m of matchers) { if (m.test(m.pathMode ? relPath : basename)) { diff --git a/packages/cli/src/lib/scan/regex.ts b/packages/cli/src/lib/scan/regex.ts index 7b3dfdb7c..77e21df4c 100644 --- a/packages/cli/src/lib/scan/regex.ts +++ b/packages/cli/src/lib/scan/regex.ts @@ -93,7 +93,7 @@ export function extractInlineFlags(source: string): { * tokenizer — we track three states (char class, paren depth, escape) * and each needs its own branch. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: regex tokenizer is inherently branchy +// regex tokenizer is inherently branchy function unwrapScopedGroup(source: string, openIndex: number): string | null { let depth = 1; let i = openIndex; @@ -192,7 +192,7 @@ export type CompilePatternOptions = { */ export function compilePattern( pattern: string | RegExp, - opts: CompilePatternOptions = {} + opts: CompilePatternOptions = {}, ): RegExp { if (pattern instanceof RegExp) { return pattern; @@ -214,7 +214,7 @@ export function compilePattern( } catch (error) { throw new ValidationError( `Invalid grep pattern: ${(error as Error).message}`, - "pattern" + "pattern", ); } } diff --git a/packages/cli/src/lib/scan/walker.ts b/packages/cli/src/lib/scan/walker.ts index 679b9fa85..412ea0c45 100644 --- a/packages/cli/src/lib/scan/walker.ts +++ b/packages/cli/src/lib/scan/walker.ts @@ -83,8 +83,7 @@ type WalkStats = { /** * Aggregate of everything the per-entry helpers need. Collecting these - * into one record keeps each helper's arity small — Biome's - * `useMaxParams` rule caps us at 4 (plus `this`), and individually + * into one record keeps each helper's arity small; individually * passing cfg/matcher/stats/stack/budget state would blow past that. */ type WalkContext = { @@ -155,7 +154,7 @@ async function processDir( frame: DirFrame, ctx: WalkContext, push: (entry: WalkEntry) => void, - isCancelled: () => boolean + isCancelled: () => boolean, ): Promise<void> { const { cfg, matcher, stats } = ctx; stats.dirsVisited += 1; @@ -278,7 +277,7 @@ async function* walkFilesImpl(opts: WalkOptions): AsyncGenerator<WalkEntry> { stats.hitTimeBudget, stats.maxDepthReached, Math.round(cfg.clock() - startedAt), - cfg.concurrency + cfg.concurrency, ); } } @@ -303,7 +302,7 @@ async function* walkSerial(ctx: WalkContext): AsyncGenerator<WalkEntry> { ctx.stats.dirsVisited += 1; ctx.stats.maxDepthReached = Math.max( ctx.stats.maxDepthReached, - frame.depth + frame.depth, ); const entries = await listDirEntries(frame.absDir, cfg.concurrency); @@ -426,7 +425,7 @@ async function* walkParallel(ctx: WalkContext): AsyncGenerator<WalkEntry> { const workerCount = cfg.concurrency; const workers: Promise<void>[] = []; - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: worker lifecycle (cancelled, abort, idle-wait, error path, wake coordination) is inherently branchy + // worker lifecycle (cancelled, abort, idle-wait, error path, wake coordination) is inherently branchy const runWorker = async (): Promise<void> => { while (true) { // Outer try catches: @@ -541,14 +540,14 @@ async function* walkParallel(ctx: WalkContext): AsyncGenerator<WalkEntry> { * Process a single directory entry: skip / descend / yield. * * Extracted from the generator body purely to keep cognitive complexity - * under Biome's ceiling. Mutates `ctx.stats`, pushes directories onto + * manageable. Mutates `ctx.stats`, pushes directories onto * `ctx.stack`, and returns a `WalkEntry` when a file should be yielded. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: filter cascade (hidden, symlink, dir, file, ignore, ext) is inherently branchy +// filter cascade (hidden, symlink, dir, file, ignore, ext) is inherently branchy async function processEntry( entry: Dirent, frame: DirFrame, - ctx: WalkContext + ctx: WalkContext, ): Promise<WalkEntry | null> { const { cfg, matcher } = ctx; if (!cfg.hidden && entry.name.startsWith(".")) { @@ -624,7 +623,7 @@ async function processEntry( return tryYieldFile( { absPath: abs, relPath: rel, fileDepth }, cfg, - ctx.stats + ctx.stats, ); } @@ -633,7 +632,7 @@ async function maybeDescend( abs: string, rel: string, parentDepth: number, - ctx: WalkContext + ctx: WalkContext, ): Promise<void> { const { cfg, matcher } = ctx; // Default descent is depth + 1; callers (e.g. DSN scanner) can @@ -707,7 +706,7 @@ type FileCoords = { async function tryYieldFile( coords: FileCoords, cfg: NormalizedOptions, - stats: WalkStats + stats: WalkStats, ): Promise<WalkEntry | null> { let statResult: { size: number; mtimeMs: number }; try { @@ -769,7 +768,7 @@ async function tryYieldFile( async function classifyFile( absPath: string, size: number, - cfg: NormalizedOptions + cfg: NormalizedOptions, ): Promise<boolean> { if (!cfg.classifyBinary) { // (a) caller ignores `isBinary` — skip all classification work. @@ -827,7 +826,7 @@ function compareByName(a: Dirent, b: Dirent): number { */ async function listDirEntries( dir: string, - concurrency: number + concurrency: number, ): Promise<Dirent[]> { try { const entries = @@ -854,7 +853,7 @@ async function listDirEntries( */ async function notifyDirectoryVisit( absDir: string, - hook: (dir: string, mtimeMs: number) => void + hook: (dir: string, mtimeMs: number) => void, ): Promise<void> { try { const s = await stat(absDir); @@ -888,7 +887,7 @@ async function inodeKey(absPath: string): Promise<string | null> { * `maybeDescend` via `inodeKey`. */ async function statSymlinkTarget( - absPath: string + absPath: string, ): Promise<{ isFile: boolean; isDirectory: boolean } | null> { try { const s = await stat(absPath); diff --git a/packages/cli/src/lib/scan/worker-pool.ts b/packages/cli/src/lib/scan/worker-pool.ts index 47dfa2818..a1c777195 100644 --- a/packages/cli/src/lib/scan/worker-pool.ts +++ b/packages/cli/src/lib/scan/worker-pool.ts @@ -130,7 +130,7 @@ export function getWorkerPool(): WorkerPool { } if (!isWorkerSupported()) { throw new Error( - "Worker pool requested but Workers are unavailable in this runtime" + "Worker pool requested but Workers are unavailable in this runtime", ); } @@ -230,7 +230,7 @@ export function getWorkerPool(): WorkerPool { } ourSlot.reject(err); } - } + }, ); return result; }, @@ -244,7 +244,7 @@ export function getWorkerPool(): WorkerPool { } pw.inflight = 0; unrefWorker(pw.worker); - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { pw.worker.terminate(); } catch { @@ -294,7 +294,7 @@ export function decodeWorkerMatches( result: WorkerGrepResult, paths: readonly string[], relPaths: readonly string[], - mtimes: readonly number[] | null = null + mtimes: readonly number[] | null = null, ): GrepMatch[] { const { ints, linePoolBytes } = result; const linePool = LINE_POOL_DECODER.decode(linePoolBytes); diff --git a/packages/cli/src/lib/scope-recovery.ts b/packages/cli/src/lib/scope-recovery.ts index 395f3f1b7..a7059cb49 100644 --- a/packages/cli/src/lib/scope-recovery.ts +++ b/packages/cli/src/lib/scope-recovery.ts @@ -35,7 +35,7 @@ const defaultRuntime: ScopeRecoveryRuntime = { async function inspectOAuthScopes( runtime: ScopeRecoveryRuntime, - startedWithOAuth = false + startedWithOAuth = false, ): Promise<OAuthScopeState | undefined> { try { const source = runtime.getAuthSource(); @@ -69,14 +69,14 @@ async function inspectOAuthScopes( /** Whether the active stored OAuth token is invalid or lacks a current CLI scope. */ async function currentOAuthGrantNeedsRefresh( runtime: ScopeRecoveryRuntime, - startedWithOAuth: boolean + startedWithOAuth: boolean, ): Promise<boolean> { const state = await inspectOAuthScopes(runtime, startedWithOAuth); return Boolean(state && state.kind !== "current"); } function hasActiveOAuthGrant( - runtime: ScopeRecoveryRuntime = defaultRuntime + runtime: ScopeRecoveryRuntime = defaultRuntime, ): boolean { try { return runtime.getAuthSource() === "oauth"; @@ -89,13 +89,13 @@ function hasActiveOAuthGrant( export type OAuthScopeRecoveryGate = { readonly shouldDelegate: ( error: unknown, - options: { unattended: boolean } + options: { unattended: boolean }, ) => Promise<boolean>; }; /** Preserve command-specific fallbacks unless central OAuth recovery can own the error. */ export function captureOAuthScopeRecoveryGate( - runtime: ScopeRecoveryRuntime = defaultRuntime + runtime: ScopeRecoveryRuntime = defaultRuntime, ): OAuthScopeRecoveryGate { const startedWithOAuth = hasActiveOAuthGrant(runtime); return { @@ -117,7 +117,7 @@ export function captureOAuthScopeRecoveryGate( async function refreshOAuthScopes( state: Exclude<OAuthScopeState, { kind: "current" }>, runInteractiveLogin: InteractiveLogin, - runtime: ScopeRecoveryRuntime + runtime: ScopeRecoveryRuntime, ): Promise<boolean> { if (!(runtime.inputIsTty() && runtime.promptsAllowed())) { return false; @@ -126,11 +126,11 @@ async function refreshOAuthScopes( runtime.assertTrustedHost(); if (state.kind === "missing") { runtime.write( - `Your CLI authorization is missing ${state.scopes.join(", ")}. Starting authorization...\n\n` + `Your CLI authorization is missing ${state.scopes.join(", ")}. Starting authorization...\n\n`, ); } else { runtime.write( - "Your CLI authorization is no longer valid. Starting authorization...\n\n" + "Your CLI authorization is no longer valid. Starting authorization...\n\n", ); } return Boolean(await runInteractiveLogin()); @@ -139,7 +139,7 @@ async function refreshOAuthScopes( /** Refresh a stored OAuth grant when it lacks any scope requested by this CLI. */ export async function ensureCurrentOAuthScopes( runInteractiveLogin: InteractiveLogin, - runtime: ScopeRecoveryRuntime = defaultRuntime + runtime: ScopeRecoveryRuntime = defaultRuntime, ): Promise<boolean> { const state = await inspectOAuthScopes(runtime); if (!state || state.kind === "current") { @@ -153,7 +153,7 @@ export async function runWithScopeRecovery( proceed: (commandArgs: string[]) => Promise<void>, argv: string[], runInteractiveLogin: InteractiveLogin, - runtime: ScopeRecoveryRuntime = defaultRuntime + runtime: ScopeRecoveryRuntime = defaultRuntime, ): Promise<void> { const startedWithOAuth = hasActiveOAuthGrant(runtime); try { diff --git a/packages/cli/src/lib/sdk-invoke.ts b/packages/cli/src/lib/sdk-invoke.ts index e8a99d993..1f5f131a1 100644 --- a/packages/cli/src/lib/sdk-invoke.ts +++ b/packages/cli/src/lib/sdk-invoke.ts @@ -35,7 +35,7 @@ function hasStreamingFlag(args: string[]): boolean { */ function buildIsolatedEnv( options?: SentryOptions, - jsonByDefault = true + jsonByDefault = true, ): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = { ...process.env }; if (options?.token) { @@ -99,7 +99,7 @@ async function resolveCommand(path: string[]): Promise<ResolvedCommand> { if (!cached) { const { routes } = await import("../app.js"); // Walk route tree: routes → sub-route → command - // biome-ignore lint/suspicious/noExplicitAny: Stricli's RoutingTarget union requires runtime duck-typing + // oxlint-disable-next-line typescript/no-explicit-any -- Stricli's RoutingTarget union requires runtime duck-typing let target: any = routes; for (const segment of path) { target = target.getRoutingTargetForInput(segment); @@ -113,8 +113,8 @@ async function resolveCommand(path: string[]): Promise<ResolvedCommand> { cached = { loader: () => command.loader().then( - // biome-ignore lint/suspicious/noExplicitAny: Stricli CommandModule shape has a default export - (m: any) => (typeof m === "function" ? m : m.default) + // oxlint-disable-next-line typescript/no-explicit-any -- Stricli CommandModule shape has a default export + (m: any) => (typeof m === "function" ? m : m.default), ), flagDefs, }; @@ -167,7 +167,7 @@ function resolveFlagDefault(def: FlagDef): unknown { */ export function applyFlagDefaults( flags: Record<string, unknown>, - flagDefs: Record<string, FlagDef> + flagDefs: Record<string, FlagDef>, ): Record<string, unknown> { const result: Record<string, unknown> = {}; // Copy caller-provided flags, skipping undefined values so they @@ -190,12 +190,12 @@ export function applyFlagDefaults( return result; } -// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences use ESC (0x1b) +// oxlint-disable-next-line no-control-regex -- ANSI escape sequences use ESC (0x1b) const ANSI_RE = /\x1b\[[0-9;]*m/g; /** Flush Sentry telemetry (no beforeExit handler in library mode). */ async function flushTelemetry(): Promise<void> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const Sentry = await import("@sentry/node-core/light"); const client = Sentry.getClient(); @@ -211,7 +211,7 @@ async function flushTelemetry(): Promise<void> { function buildSdkError( stderrChunks: string[], exitCode: number, - thrown?: unknown + thrown?: unknown, ): SentryError { const stderrStr = stderrChunks.join(""); const message = @@ -240,7 +240,7 @@ function extractExitCode(thrown: unknown): number { */ function concatChunksToBytes(chunks: Array<string | Uint8Array>): Uint8Array { const parts = chunks.map((c) => - typeof c === "string" ? new TextEncoder().encode(c) : c + typeof c === "string" ? new TextEncoder().encode(c) : c, ); const total = parts.reduce((sum, p) => sum + p.byteLength, 0); const out = new Uint8Array(total); @@ -258,7 +258,7 @@ function concatChunksToBytes(chunks: Array<string | Uint8Array>): Uint8Array { */ export function parseOutput<T>( capturedResult: unknown, - stdoutChunks: Array<string | Uint8Array> + stdoutChunks: Array<string | Uint8Array>, ): T { if (capturedResult !== undefined) { return capturedResult as T; @@ -273,7 +273,7 @@ export function parseOutput<T>( if (!stdoutStr.trim()) { return undefined as T; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(stdoutStr) as T; } catch { @@ -318,7 +318,7 @@ type CaptureOptions = { async function buildCaptureContext( env: NodeJS.ProcessEnv, cwd: string, - opts?: CaptureOptions + opts?: CaptureOptions, ): Promise<CaptureContext> { const stdoutChunks: Array<string | Uint8Array> = []; const stderrChunks: string[] = []; @@ -346,7 +346,7 @@ async function buildCaptureContext( const { getConfigDir } = await import("./db/index.js"); - // biome-ignore lint/suspicious/noExplicitAny: abortSignal is an internal extension to the fake process + // oxlint-disable-next-line typescript/no-explicit-any -- abortSignal is an internal extension to the fake process const fakeProcess: any = { stdout, stderr, @@ -397,8 +397,8 @@ async function executeWithCapture<T>( options: SentryOptions | undefined, executor: ( captureCtx: CaptureContext, - span: Span | undefined - ) => Promise<void> + span: Span | undefined, + ) => Promise<void>, ): Promise<T> { const env = buildIsolatedEnv(options); const cwd = options?.cwd ?? process.cwd(); @@ -434,13 +434,13 @@ async function executeWithCapture<T>( if (captureCtx.context.process.exitCode !== 0) { throw buildSdkError( captureCtx.stderrChunks, - captureCtx.context.process.exitCode + captureCtx.context.process.exitCode, ); } return parseOutput<T>( captureCtx.getCapturedResult(), - captureCtx.stdoutChunks + captureCtx.stdoutChunks, ); }); }); @@ -458,8 +458,8 @@ function executeWithStream<T>( options: SentryOptions | undefined, executor: ( captureCtx: CaptureContext, - span: Span | undefined - ) => Promise<void> + span: Span | undefined, + ) => Promise<void>, ): AsyncChannel<T> { const controller = new AbortController(); @@ -494,7 +494,7 @@ function executeWithStream<T>( const { withTelemetry } = await import("./telemetry.js"); - // biome-ignore lint/style/noNonNullAssertion: captureCtx is assigned on the line above + // oxlint-disable-next-line typescript/no-non-null-assertion -- captureCtx is assigned on the line above await withTelemetry(async (span) => executor(captureCtx!, span), { libraryMode: true, }); @@ -504,8 +504,8 @@ function executeWithStream<T>( channel.error( buildSdkError( captureCtx.stderrChunks, - captureCtx.context.process.exitCode - ) + captureCtx.context.process.exitCode, + ), ); } else { // Drain any raw stdout the command wrote directly (via stdout.write) @@ -555,7 +555,7 @@ export function buildInvoker(options?: SentryOptions) { commandPath: string[], flags: Record<string, unknown>, positionalArgs: string[], - meta?: { streaming?: boolean } + meta?: { streaming?: boolean }, ): Promise<T> | AsyncIterable<T> { if (meta?.streaming) { return executeWithStream<T>(options, async (ctx, span) => { @@ -568,7 +568,7 @@ export function buildInvoker(options?: SentryOptions) { await handler.call( ctx.context, { ...resolvedFlags, json: true }, - ...positionalArgs + ...positionalArgs, ); }); } @@ -583,7 +583,7 @@ export function buildInvoker(options?: SentryOptions) { await handler.call( ctx.context, { ...resolvedFlags, json: true }, - ...positionalArgs + ...positionalArgs, ); }); }; @@ -609,7 +609,7 @@ export function buildRunner(options?: SentryOptions) { const { run: stricliRun } = await import("@stricli/core"); const { app } = await import("../app.js"); const { buildContext } = await import("../context.js"); - // biome-ignore lint/suspicious/noExplicitAny: fakeProcess duck-types the process interface + // oxlint-disable-next-line typescript/no-explicit-any -- fakeProcess duck-types the process interface const process_ = ctx.context.process as any; await stricliRun(app, args, buildContext(process_, span)); }); @@ -619,7 +619,7 @@ export function buildRunner(options?: SentryOptions) { const { run: stricliRun } = await import("@stricli/core"); const { app } = await import("../app.js"); const { buildContext } = await import("../context.js"); - // biome-ignore lint/suspicious/noExplicitAny: fakeProcess duck-types the process interface + // oxlint-disable-next-line typescript/no-explicit-any -- fakeProcess duck-types the process interface const process_ = ctx.context.process as any; await stricliRun(app, args, buildContext(process_, span)); }); diff --git a/packages/cli/src/lib/search-query.ts b/packages/cli/src/lib/search-query.ts index 6441df35f..a151fc18d 100644 --- a/packages/cli/src/lib/search-query.ts +++ b/packages/cli/src/lib/search-query.ts @@ -90,7 +90,7 @@ function serializeNodes(nodes: SearchNode[]): string { * Comparison filters, free text, paren groups, and boolean ops cannot. */ function isMergeableFilter( - node: SearchNode + node: SearchNode, ): node is | (SearchNode & { type: "text_filter" }) | (SearchNode & { type: "text_in_filter" }) { @@ -118,7 +118,7 @@ function isMergeableFilter( /** Extract all values from a mergeable filter node. */ function valuesFromNode( - node: SearchNode & { type: "text_filter" | "text_in_filter" } + node: SearchNode & { type: "text_filter" | "text_in_filter" }, ): string[] { if (node.type === "text_in_filter") { return node.values; @@ -232,7 +232,7 @@ function isOrNode(nodes: SearchNode[], j: number): boolean { */ function collectOrChain( nodes: SearchNode[], - start: number + start: number, ): [SearchNode[], number] { const group = [nodes[start] as SearchNode]; let j = start + 1; @@ -375,7 +375,7 @@ export function sanitizeQuery(query: string | undefined): string | undefined { "Remove the parentheses and use in-list syntax instead:\n" + " (level:error OR level:warning) → level:[error,warning]\n\n" + "Search syntax: https://docs.sentry.io/concepts/search/", - "query" + "query", ); } @@ -392,7 +392,7 @@ export function sanitizeQuery(query: string | undefined): string | undefined { if (hasAnd) { const sanitized = serializeNodes(stripAndNodes(nodes)); notes.push( - "Sentry search implicitly ANDs terms — removed explicit AND operator." + "Sentry search implicitly ANDs terms — removed explicit AND operator.", ); warnRunningQuery(notes, sanitized); return sanitized; @@ -406,7 +406,7 @@ export function sanitizeQuery(query: string | undefined): string | undefined { function preParseRewriteNotes( query: string, normalized: string, - withNumericProject: string + withNumericProject: string, ): string[] { const notes: string[] = []; if (normalized !== query) { @@ -414,7 +414,7 @@ function preParseRewriteNotes( } if (withNumericProject !== normalized) { notes.push( - "`project` is the slug; numeric ids use project_id. Rewrote numeric project: filters." + "`project` is the slug; numeric ids use project_id. Rewrote numeric project: filters.", ); } return notes; @@ -439,7 +439,7 @@ function warnRunningQuery(notes: string[], result: string): void { function handleOr( nodes: SearchNode[], hasAnd: boolean, - notes: string[] + notes: string[], ): string { const rewritten = tryRewriteOr(nodes); if (rewritten) { @@ -465,7 +465,7 @@ function handleOr( ' - Write in-list syntax directly: --query "key:[val1,val2]"\n' + " - Run separate queries for each term\n\n" + "Search syntax: https://docs.sentry.io/concepts/search/", - "query" + "query", ); } @@ -618,7 +618,7 @@ function normalizeQuery(query: string): string { */ function transformUnquoted( query: string, - fn: (unquoted: string) => string + fn: (unquoted: string) => string, ): string { // Fast path: no quotes → transform the whole string if (!query.includes('"')) { @@ -660,7 +660,7 @@ function transformUnquoted( function fixMismatchedBrackets(query: string): string { return query.replace( MALFORMED_IN_LIST_RE, - (_match, inner: string) => `[${inner.replace(TRAILING_COMMA_RE, "")}]` + (_match, inner: string) => `[${inner.replace(TRAILING_COMMA_RE, "")}]`, ); } @@ -672,7 +672,7 @@ function fixMismatchedBrackets(query: string): string { */ function stripTrailingListCommas(query: string): string { return query.replace(BALANCED_BRACKET_RE, (match) => - match.replace(TRAILING_LIST_COMMA_RE, "]") + match.replace(TRAILING_LIST_COMMA_RE, "]"), ); } diff --git a/packages/cli/src/lib/seer-trial.ts b/packages/cli/src/lib/seer-trial.ts index 63c00b908..45f20ed85 100644 --- a/packages/cli/src/lib/seer-trial.ts +++ b/packages/cli/src/lib/seer-trial.ts @@ -68,7 +68,7 @@ export function isTrialEligible(error: unknown): error is SeerError { */ export async function promptAndStartTrial( orgSlug: string, - reason: SeerErrorReason + reason: SeerErrorReason, ): Promise<boolean> { const log = logger.withTag("seer"); @@ -86,7 +86,7 @@ export async function promptAndStartTrial( // No trial available (expired or already used) log.info( "No Seer trial available. If you've already used your trial, " + - "consider upgrading your plan to continue using Seer." + "consider upgrading your plan to continue using Seer.", ); log.info(` ${buildBillingUrl(orgSlug, "seer")}`); return false; @@ -101,7 +101,7 @@ export async function promptAndStartTrial( const daysText = trial.lengthDays ? `${trial.lengthDays}-day ` : ""; const confirmed = await log.prompt( `A free ${daysText}Seer trial is available. Start trial?`, - { type: "confirm", initial: true } + { type: "confirm", initial: true }, ); // Symbol(clack:cancel) is truthy — strict equality check @@ -118,7 +118,7 @@ export async function promptAndStartTrial( } catch (error) { log.debug("Trial start API call failed", error); log.warn( - "Failed to start trial. Please try again or visit your Sentry settings:" + "Failed to start trial. Please try again or visit your Sentry settings:", ); log.warn(` ${buildBillingUrl(orgSlug, "seer")}`); log.warn("If the problem persists, contact support@sentry.io for help."); diff --git a/packages/cli/src/lib/sentry-client.ts b/packages/cli/src/lib/sentry-client.ts index 93ee236e8..996d172c9 100644 --- a/packages/cli/src/lib/sentry-client.ts +++ b/packages/cli/src/lib/sentry-client.ts @@ -97,13 +97,13 @@ const responseCredentialIdentities = new WeakMap<Response, string>(); /** Provenance attached only after manually validated discovery requests. */ export function getResponseRequestOrigin( - response: Response + response: Response, ): string | undefined { return responseRequestOrigins.get(response); } export function getResponseCredentialIdentity( - response: Response + response: Response, ): string | undefined { return responseCredentialIdentities.get(response); } @@ -144,7 +144,7 @@ function isUserAbort(error: unknown, signal?: AbortSignal | null): boolean { function prepareHeaders( input: Request | string | URL, init: RequestInit | undefined, - credential: CredentialContext + credential: CredentialContext, ): Headers { // Host-scoping guard (defense in depth). Primary rejection happens at the // URL-arg / rc-shim entry points; this catches any code path that mutated @@ -153,7 +153,7 @@ function prepareHeaders( throw new HostScopeError( "Credentials", normalizeOrigin(input) ?? "<unknown host>", - credential.host + credential.host, ); } @@ -170,7 +170,7 @@ function prepareHeaders( throw new HostScopeError( "Credentials", normalizeOrigin(input) ?? "<unknown host>", - claimUrl + claimUrl, ); } @@ -202,7 +202,7 @@ function prepareHeaders( applyCustomHeaders( headers, input, - isRequestOriginTrustedForContext(input, credential) + isRequestOriginTrustedForContext(input, credential), ); return headers; @@ -214,7 +214,7 @@ function prepareHeaders( */ async function handleUnauthorized( headers: Headers, - credential: CredentialContext + credential: CredentialContext, ): Promise<boolean> { if (headers.get(RETRY_MARKER_HEADER)) { return false; @@ -251,7 +251,7 @@ async function handleUnauthorized( /** Link an external abort signal to an AbortController */ function linkAbortSignal( signal: AbortSignal | undefined | null, - controller: AbortController + controller: AbortController, ): void { if (!signal) { return; @@ -355,7 +355,7 @@ async function handleResponse( response: Response, headers: Headers, credential: CredentialContext, - isLastAttempt: boolean + isLastAttempt: boolean, ): Promise<AttemptResult> { if (response.status === 401 && !isLastAttempt) { const refreshed = await handleUnauthorized(headers, credential); @@ -383,7 +383,7 @@ async function handleResponse( function handleFetchError( error: unknown, signal: AbortSignal | undefined | null, - isLastAttempt: boolean + isLastAttempt: boolean, ): AttemptResult { if (error instanceof HostScopeError) { return { action: "throw", error }; @@ -399,7 +399,7 @@ function handleFetchError( error: new ApiError( "TLS certificate error", 0, - buildTlsErrorDetail(error as Error) + buildTlsErrorDetail(error as Error), ), }; } @@ -412,7 +412,7 @@ function handleFetchError( action: "throw", error: new TimeoutError( `Request timed out after ${seconds}s.`, - "The Sentry API did not respond in time. Try again; if the problem persists, check https://status.sentry.io." + "The Sentry API did not respond in time. Try again; if the problem persists, check https://status.sentry.io.", ), }; } @@ -436,7 +436,7 @@ function extractFullUrl(input: Request | string | URL): string { /** Extract the URL pathname for span naming */ function extractUrlPath(input: Request | string | URL): string { const raw = extractFullUrl(input); - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return new URL(raw).pathname; } catch { @@ -455,7 +455,7 @@ async function tryCacheHit( method: string, fullUrl: string, requestHeaders: Record<string, string>, - identity: string + identity: string, ): Promise<Response | undefined> { if (method !== "GET") { return; @@ -477,7 +477,7 @@ function cacheResponse( requestHeaders: Record<string, string>; identity: string; }, - response: Response + response: Response, ): void { const { method, fullUrl, requestHeaders, identity } = request; if (method !== "GET" || !response.ok) { @@ -490,7 +490,7 @@ function cacheResponse( fullUrl, requestHeaders, response.clone() as Response, - identity + identity, ).catch((error) => { log.debug("Response cache write failed", error); }); @@ -510,7 +510,7 @@ async function invalidateAfterMutation( method: string, fullUrl: string, response: Response, - identity: string + identity: string, ): Promise<void> { if (method === "GET" || !response.ok) { return; @@ -519,8 +519,8 @@ async function invalidateAfterMutation( const prefixes = computeInvalidationPrefixes(fullUrl, getApiBaseUrl()); await Promise.all( prefixes.map((prefix) => - invalidateCachedResponsesMatching(prefix, identity) - ) + invalidateCachedResponsesMatching(prefix, identity), + ), ); } catch (error) { log.debug("Post-mutation cache invalidation failed", error); @@ -554,7 +554,7 @@ type AttemptInputFactory = () => { */ async function buildAttemptFactory( input: Request | string | URL, - init: RequestInit | undefined + init: RequestInit | undefined, ): Promise<AttemptInputFactory> { if (input instanceof Request) { // Cast: Bun's `Request` has extras (toJSON/count/getAll) that `.clone()` @@ -601,12 +601,12 @@ async function fetchWithRetry({ : undefined); const shouldRefresh = Boolean( credential.refreshable && - credential.expiresAt && - (Date.now() >= credential.expiresAt || - (issuedAt && - (credential.expiresAt - Date.now()) / - (credential.expiresAt - issuedAt) <= - REFRESH_THRESHOLD)) + credential.expiresAt && + (Date.now() >= credential.expiresAt || + (issuedAt && + (credential.expiresAt - Date.now()) / + (credential.expiresAt - issuedAt) <= + REFRESH_THRESHOLD)), ); const refreshed = shouldRefresh ? await refreshToken({ expectedCredential: credential }) @@ -640,13 +640,13 @@ async function fetchWithRetry({ requestHeaders: { authorization: headers.get("Authorization") ?? "" }, identity: credential.identity, }, - result.response + result.response, ); await invalidateAfterMutation( method, fullUrl, result.response, - credential.identity + credential.identity, ); return result.response; } @@ -656,7 +656,7 @@ async function fetchWithRetry({ const delay = backoffDelay(attempt); log.debug( - `${method} ${new URL(fullUrl).pathname} → retry ${attempt + 1}/${MAX_RETRIES} after ${delay}ms` + `${method} ${new URL(fullUrl).pathname} → retry ${attempt + 1}/${MAX_RETRIES} after ${delay}ms`, ); await sleepMs(delay); } @@ -690,11 +690,11 @@ type AuthenticatedFetchOptions = { }; function createAuthenticatedFetch( - options: AuthenticatedFetchOptions = {} + options: AuthenticatedFetchOptions = {}, ): (input: Request | string | URL, init?: RequestInit) => Promise<Response> { return function authenticatedFetch( input: Request | string | URL, - init?: RequestInit + init?: RequestInit, ): Promise<Response> { // Reset cache-hit age so it reflects only this request's outcome. // Commands read it after their primary API call to show cache-age hints. @@ -724,7 +724,7 @@ function createAuthenticatedFetch( if (!credential) { await refreshToken(); throw new Error( - "Authentication state was not available after refresh" + "Authentication state was not available after refresh", ); } // A synthetic HTTP cache response has no validated final-origin or @@ -735,12 +735,12 @@ function createAuthenticatedFetch( method, fullUrl, authHeaders(credential.token), - credential.identity + credential.identity, ); if (cached) { span.setAttribute("http.response.status_code", cached.status); log.debug( - `${method} ${urlPath} → ${cached.status} (cache hit, ${(performance.now() - startTime).toFixed(0)}ms)` + `${method} ${urlPath} → ${cached.status} (cache hit, ${(performance.now() - startTime).toFixed(0)}ms)`, ); return cached; } @@ -758,11 +758,11 @@ function createAuthenticatedFetch( span.setStatus({ code: 2, message: `${response.status}` }); } log.debug( - `${method} ${urlPath} → ${response.status} (${(performance.now() - startTime).toFixed(0)}ms)` + `${method} ${urlPath} → ${response.status} (${(performance.now() - startTime).toFixed(0)}ms)`, ); return response; }, - { "http.request.method": method, "url.path": urlPath } + { "http.request.method": method, "url.path": urlPath }, ); }; } @@ -819,13 +819,13 @@ function redirectLoopKey(url: URL): string { async function rejectRedirect( response: Response, destination: string, - credential: CredentialContext + credential: CredentialContext, ): Promise<never> { await cancelResponseBody(response); throw new HostScopeError( "Redirect destination", destination, - credential.host + credential.host, ); } @@ -855,7 +855,7 @@ async function validatedRedirectTarget({ } catch (error) { log.debug( "Rejected malformed redirect location", - error instanceof Error ? error.name : "unknown error" + error instanceof Error ? error.name : "unknown error", ); return await rejectRedirect(response, "<invalid host>", credential); } @@ -863,7 +863,7 @@ async function validatedRedirectTarget({ return await rejectRedirect( response, normalizeOrigin(next) ?? "<unknown host>", - credential + credential, ); } const key = redirectLoopKey(next); @@ -878,7 +878,7 @@ async function buildRedirectRequest( request: Request, requestHeaders: Headers, next: URL, - status: number + status: number, ): Promise<{ request: Request; headers: Headers }> { const method = redirectedMethod(request.method.toUpperCase(), status); const headers = new Headers(requestHeaders); @@ -911,13 +911,13 @@ async function fetchFollowingValidatedRedirects({ async function follow( request: Request, requestHeaders: Headers, - hops: number + hops: number, ): Promise<Response> { if (!isRequestOriginTrustedForContext(request, credential)) { throw new HostScopeError( "Credentials", normalizeOrigin(request) ?? "<unknown host>", - credential.host + credential.host, ); } const response = await fetchWithTimeout({ @@ -948,7 +948,7 @@ async function fetchFollowingValidatedRedirects({ request, requestHeaders, next, - response.status + response.status, ); await cancelResponseBody(response); return await follow(redirected.request, redirected.headers, hops + 1); @@ -999,7 +999,7 @@ let cachedFetch: typeof fetch | null = null; * Cast to `typeof fetch` for compatibility with @sentry/api SDK options. */ function getAuthenticatedFetch( - options: AuthenticatedFetchOptions = {} + options: AuthenticatedFetchOptions = {}, ): typeof fetch { if (options.credential || options.validatedRedirects) { return createAuthenticatedFetch(options) as unknown as typeof fetch; @@ -1048,7 +1048,7 @@ export function getControlSiloUrl(credential = getCredentialContext()): string { */ export function getSdkConfig( regionUrl: string, - options: AuthenticatedFetchOptions = {} + options: AuthenticatedFetchOptions = {}, ) { const normalizedBase = regionUrl.endsWith("/") ? regionUrl.slice(0, -1) @@ -1098,7 +1098,7 @@ export function __resolveRequestTimeoutMsForTests(fullUrl: string): number { * disposer restores the original list. */ export function __injectTimeoutOverrideForTests( - override: TimeoutOverride + override: TimeoutOverride, ): () => void { ENDPOINT_TIMEOUT_OVERRIDES.unshift(override); return () => { diff --git a/packages/cli/src/lib/sentry-url-parser.ts b/packages/cli/src/lib/sentry-url-parser.ts index 8f0175f2e..1d3e32e2a 100644 --- a/packages/cli/src/lib/sentry-url-parser.ts +++ b/packages/cli/src/lib/sentry-url-parser.ts @@ -57,7 +57,7 @@ export type ParsedSentryUrl = { function matchOrganizationsPath( baseUrl: string, segments: string[], - searchParams: URLSearchParams + searchParams: URLSearchParams, ): ParsedSentryUrl | null { if (segments[0] !== "organizations" || !segments[1]) { return null; @@ -110,7 +110,7 @@ function matchOrganizationsPath( */ function matchSettingsPath( baseUrl: string, - segments: string[] + segments: string[], ): ParsedSentryUrl | null { if ( segments[0] !== "settings" || @@ -133,7 +133,7 @@ function matchSettingsPath( */ function matchSubdomainPath( segments: string[], - searchParams: URLSearchParams + searchParams: URLSearchParams, ): Omit<ParsedSentryUrl, "baseUrl" | "org"> | null { // /issues/{id}/ (optionally with /events/{eventId}/) if (segments[0] === "issues" && segments[1]) { @@ -166,7 +166,7 @@ function matchSubdomainPath( function matchSubdomainTailPath( segments: string[], - searchParams: URLSearchParams + searchParams: URLSearchParams, ): Omit<ParsedSentryUrl, "baseUrl" | "org"> | null { // /settings/projects/{project}/ (org-scoped subdomain settings URL) if (segments[0] === "settings" && segments[1] === "projects" && segments[2]) { @@ -207,7 +207,7 @@ function matchSubdomainTailPath( */ function matchTracePath( segments: string[], - startIndex: number + startIndex: number, ): { status: "absent" | "list" } | { status: "detail"; traceId: string } { let index = startIndex; const hasExplorePrefix = segments[index] === "explore"; @@ -240,7 +240,7 @@ function matchTracePath( function matchFeedbackPath( segments: string[], startIndex: number, - searchParams: URLSearchParams + searchParams: URLSearchParams, ): Omit<ParsedSentryUrl, "baseUrl" | "org"> | null { if ( segments[startIndex] !== "feedback" || @@ -259,7 +259,7 @@ function matchFeedbackPath( function matchReplayPath( segments: string[], - startIndex: number + startIndex: number, ): | { status: "absent" | "list" | "invalid" } | { status: "detail"; replayId: string } { @@ -301,7 +301,7 @@ function matchSubdomainOrg( baseUrl: string, hostname: string, segments: string[], - searchParams: URLSearchParams + searchParams: URLSearchParams, ): ParsedSentryUrl | null { // Must be a subdomain of sentry.io (e.g., "my-org.sentry.io") if (!hostname.endsWith(`.${DEFAULT_SENTRY_HOST}`)) { @@ -334,7 +334,7 @@ function matchSubdomainOrg( */ function matchSharePath( baseUrl: string, - segments: string[] + segments: string[], ): ParsedSentryUrl | null { if (segments[0] !== "share" || segments[1] !== "issue" || !segments[2]) { return null; @@ -417,9 +417,9 @@ export function applySentryUrlContext(baseUrl: string): void { // the trust check. if (isSaaSTrustOrigin(baseUrl)) { // Clear any self-hosted URL so API calls fall back to default SaaS routing. - // biome-ignore lint/performance/noDelete: env registry requires delete to truly unset; assignment coerces to string in Node.js + // env registry requires delete to truly unset; assignment coerces to string in Node.js delete env.SENTRY_HOST; - // biome-ignore lint/performance/noDelete: env registry requires delete to truly unset; assignment coerces to string in Node.js + // env registry requires delete to truly unset; assignment coerces to string in Node.js delete env.SENTRY_URL; return; } diff --git a/packages/cli/src/lib/sentry-urls.ts b/packages/cli/src/lib/sentry-urls.ts index 7fb27f6de..96cbd35ee 100644 --- a/packages/cli/src/lib/sentry-urls.ts +++ b/packages/cli/src/lib/sentry-urls.ts @@ -81,7 +81,7 @@ export function isSaaS(): boolean { * @returns true if the hostname is sentry.io or a subdomain of sentry.io */ export function isSentrySaasUrl(url: string): boolean { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = new URL(url); return ( @@ -112,7 +112,7 @@ export function isSentrySaasUrl(url: string): boolean { * @returns true only if the URL is a strictly-SaaS origin */ export function isSaaSTrustOrigin(url: string): boolean { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = new URL(url); return ( @@ -130,7 +130,7 @@ export function isSaaSTrustOrigin(url: string): boolean { * for user-supplied strings that may be bare hostnames. */ export function normalizeOrigin( - input: string | URL | Request | undefined | null + input: string | URL | Request | undefined | null, ): string | undefined { if (input === null || input === undefined) { return; @@ -143,7 +143,7 @@ export function normalizeOrigin( } else { raw = input.url; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return new URL(raw).origin; } catch { @@ -154,12 +154,12 @@ export function normalizeOrigin( /** Normalize only credential-free HTTP(S) origins; allow root-relative URLs with a base. */ export function normalizeHttpOrigin( input: string | undefined | null, - base?: string + base?: string, ): string | undefined { if (!input) { return; } - // biome-ignore lint/plugin: malformed external URLs are rejected with undefined by design. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- malformed external URLs are rejected with undefined by design. try { const parsed = base ? new URL(input, base) : new URL(input); if ( @@ -179,12 +179,12 @@ export function normalizeHttpOrigin( /** Validate an API-provided region URL while retaining an installation path. */ export function normalizeRegionBaseUrl( raw: string, - responseOrigin: string + responseOrigin: string, ): string | undefined { if (!(raw.startsWith("/") || HTTP_URL_RE.test(raw))) { return; } - // biome-ignore lint/plugin: reject malformed region metadata without making discovery fail. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- reject malformed region metadata without making discovery fail. try { const parsed = new URL(raw, responseOrigin); const origin = normalizeHttpOrigin(parsed.href); @@ -206,7 +206,7 @@ export function normalizeRegionBaseUrl( * to parse after the prefix. */ export function normalizeUserInputToOrigin( - input: string | undefined + input: string | undefined, ): string | undefined { const prefixed = normalizeUrl(input); return prefixed ? normalizeOrigin(prefixed) : undefined; @@ -280,7 +280,7 @@ export function buildEventSearchUrl(orgSlug: string, eventId: string): string { */ export function buildProjectIssuesUrl( orgSlug: string, - projectId?: string + projectId?: string, ): string { const filter = projectId ? `?project=${projectId}` : ""; if (isSaaS()) { @@ -302,7 +302,7 @@ export function parseOrgProjectFromSettingsUrl(url: string): { orgSlug?: string; projectSlug?: string; } { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = new URL(url); const segments = parsed.pathname.split("/").filter(Boolean); @@ -423,7 +423,7 @@ export function buildDashboardsListUrl(orgSlug: string): string { */ export function buildDashboardUrl( orgSlug: string, - dashboardId: string + dashboardId: string, ): string { if (isSaaS()) { return `${getOrgBaseUrl(orgSlug)}/dashboard/${dashboardId}/`; @@ -456,7 +456,7 @@ export function buildTraceUrl(orgSlug: string, traceId: string): string { */ export function buildIssueAlertsUrl( orgSlug: string, - projectSlug?: string + projectSlug?: string, ): string { const projectFilter = projectSlug ? `?project=${projectSlug}` : ""; if (isSaaS()) { diff --git a/packages/cli/src/lib/sentry-web-urls.ts b/packages/cli/src/lib/sentry-web-urls.ts index d61b58854..9030d3c11 100644 --- a/packages/cli/src/lib/sentry-web-urls.ts +++ b/packages/cli/src/lib/sentry-web-urls.ts @@ -57,7 +57,7 @@ export function buildEventSearchUrl(orgSlug: string, eventId: string): string { export function buildProjectIssuesUrl( orgSlug: string, - projectId?: string + projectId?: string, ): string { return render(() => buildProjectIssuesUrlPure(orgSlug, projectId)); } @@ -88,7 +88,7 @@ export function buildDashboardsListUrl(orgSlug: string): string { export function buildDashboardUrl( orgSlug: string, - dashboardId: string + dashboardId: string, ): string { return render(() => buildDashboardUrlPure(orgSlug, dashboardId)); } @@ -99,7 +99,7 @@ export function buildTraceUrl(orgSlug: string, traceId: string): string { export function buildIssueAlertsUrl( orgSlug: string, - projectSlug?: string + projectSlug?: string, ): string { return render(() => buildIssueAlertsUrlPure(orgSlug, projectSlug)); } diff --git a/packages/cli/src/lib/sentryclirc-import.ts b/packages/cli/src/lib/sentryclirc-import.ts index fe31ffcf5..b1791e793 100644 --- a/packages/cli/src/lib/sentryclirc-import.ts +++ b/packages/cli/src/lib/sentryclirc-import.ts @@ -215,7 +215,7 @@ function extractFields(iniData: ReturnType<typeof parseIni>): { /** Read and parse a single .sentryclirc file into a DiscoveredRcFile */ async function readRcFile( rcPath: string, - location: RcFileLocation + location: RcFileLocation, ): Promise<DiscoveredRcFile | null> { const content = await tryReadSentryCliRc(rcPath); if (content === null) { @@ -241,7 +241,7 @@ async function readRcFile( * content hashes, and location classification. */ export async function discoverRcFiles( - cwd: string + cwd: string, ): Promise<DiscoveredRcFile[]> { const files: DiscoveredRcFile[] = []; const globalPathSet = getGlobalPaths(); @@ -268,7 +268,7 @@ export async function discoverRcFiles( seen.add(globalPath); const file = await readRcFile( globalPath, - classifyRcFileLocation(globalPath) + classifyRcFileLocation(globalPath), ); if (file) { files.push(file); @@ -351,7 +351,7 @@ function isDefaultSet(getter: () => string | null): boolean { /** Determine which effective fields would be new (not already in SQLite) */ function computeNewFields( effective: ImportPlan["effective"], - isSaas: boolean + isSaas: boolean, ): ImportableField[] { const fields: ImportableField[] = []; @@ -388,7 +388,7 @@ function computeNewFields( */ export function checkSntrysClaim( token: string, - url: string + url: string, ): string | undefined { const claim = parseSntrysClaim(token); if (!claim?.url) { @@ -408,7 +408,7 @@ export function checkSntrysClaim( function buildSecurityWarnings( effectiveSources: ImportPlan["effectiveSources"], effective: ImportPlan["effective"], - isSaas: boolean + isSaas: boolean, ): string[] { const warnings: string[] = []; @@ -422,7 +422,7 @@ function buildSecurityWarnings( warnings.push( "Token and URL come from different files — URL may have been injected.\n" + ` Token: ${effectiveSources.token}\n` + - ` URL: ${effectiveSources.url}` + ` URL: ${effectiveSources.url}`, ); } @@ -517,14 +517,14 @@ async function validateAndFetchUser(result: ImportResult): Promise<boolean> { result.stored.token = false; result.tokenValid = false; result.warnings.push( - "Token validation failed (invalid credentials) — the token was not stored." + "Token validation failed (invalid credentials) — the token was not stored.", ); return false; } // Transient network error — keep the token, warn the user log.debug("Token validation failed with transient error", error); result.warnings.push( - "Could not validate token (network error). Token was stored — run 'sentry auth status' to verify." + "Could not validate token (network error). Token was stored — run 'sentry auth status' to verify.", ); return true; } @@ -563,7 +563,7 @@ function trySetDefault( getter: () => string | null, setter: (v: string) => void, value: string, - label: string + label: string, ): boolean { try { if (!getter()) { @@ -579,14 +579,14 @@ function trySetDefault( /** Store default values that are not already set */ function storeDefaults( effective: ImportPlan["effective"], - result: ImportResult + result: ImportResult, ): void { if (effective.url && !isSaaSTrustOrigin(effective.url)) { result.stored.url = trySetDefault( getDefaultUrl, setDefaultUrl, effective.url, - "URL" + "URL", ); } if (effective.org) { @@ -594,7 +594,7 @@ function storeDefaults( getDefaultOrganization, setDefaultOrganization, effective.org, - "org" + "org", ); } if (effective.project) { @@ -602,7 +602,7 @@ function storeDefaults( getDefaultProject, setDefaultProject, effective.project, - "project" + "project", ); } } @@ -619,7 +619,7 @@ function storeDefaults( */ export async function executeImport( plan: ImportPlan, - options: ExecuteImportOptions = {} + options: ExecuteImportOptions = {}, ): Promise<ImportResult> { const { validateToken = true } = options; const { effective } = plan; @@ -718,7 +718,7 @@ function getImportRecord(): ImportRecord | null { try { const db = getDatabase(); const raw = getMetadata(db, [IMPORT_COMPLETED_KEY]).get( - IMPORT_COMPLETED_KEY + IMPORT_COMPLETED_KEY, ); if (!raw) { return null; @@ -836,7 +836,7 @@ function hasStoredAuth(): boolean { */ async function verifyFileHash( filePath: string, - expectedHash: string + expectedHash: string, ): Promise<boolean> { try { const content = await tryReadSentryCliRc(filePath); diff --git a/packages/cli/src/lib/sentryclirc.ts b/packages/cli/src/lib/sentryclirc.ts index 23f5d9831..6aeaf0eb5 100644 --- a/packages/cli/src/lib/sentryclirc.ts +++ b/packages/cli/src/lib/sentryclirc.ts @@ -85,7 +85,7 @@ const FIELD_MAP: ReadonlyArray<{ function applyConfig( result: SentryCliRcConfig, iniData: ReturnType<typeof parseIni>, - filePath: string + filePath: string, ): void { for (const { section, key, field } of FIELD_MAP) { if (result[field] !== undefined) { @@ -148,7 +148,7 @@ function isNarrowAbsenceError(error: unknown): boolean { * for this committed config load. */ export async function tryReadSentryCliRc( - filePath: string + filePath: string, ): Promise<string | null> { let statResult: Awaited<ReturnType<typeof stat>>; try { @@ -181,12 +181,12 @@ export async function tryReadSentryCliRc( async function tryApplyFile( result: SentryCliRcConfig, filePath: string, - isGlobal: boolean + isGlobal: boolean, ): Promise<void> { const content = await tryReadSentryCliRc(filePath); if (content !== null) { log.debug( - `Found ${isGlobal ? "global" : "local"} ${CONFIG_FILENAME} at ${filePath}` + `Found ${isGlobal ? "global" : "local"} ${CONFIG_FILENAME} at ${filePath}`, ); applyConfig(result, parseIni(content), filePath); } @@ -219,7 +219,7 @@ export function getGlobalPaths(): Set<string> { * so that global values are included in the cached config. */ export async function applyGlobalFallbacks( - result: SentryCliRcConfig + result: SentryCliRcConfig, ): Promise<void> { for (const globalPath of getGlobalPaths()) { if (isComplete(result)) { @@ -240,7 +240,7 @@ export async function applyGlobalFallbacks( */ export async function applySentryCliRcDir( result: SentryCliRcConfig, - dir: string + dir: string, ): Promise<boolean> { if (isComplete(result)) { return true; @@ -262,7 +262,7 @@ export async function applySentryCliRcDir( */ export function setSentryCliRcCache( cwd: string, - config: SentryCliRcConfig + config: SentryCliRcConfig, ): void { cache.set(cwd, Promise.resolve(config)); } @@ -365,7 +365,7 @@ export async function applySentryCliRcEnvShim(cwd: string): Promise<void> { !env.SENTRY_TOKEN?.trim() ) { log.debug( - `Setting SENTRY_AUTH_TOKEN from ${CONFIG_FILENAME} (${config.sources.token})` + `Setting SENTRY_AUTH_TOKEN from ${CONFIG_FILENAME} (${config.sources.token})`, ); env.SENTRY_AUTH_TOKEN = config.token; rcInjectedTokenSource = config.sources.token; @@ -374,7 +374,7 @@ export async function applySentryCliRcEnvShim(cwd: string): Promise<void> { const normalizedRcUrl = config.url ? normalizeUrl(config.url) : undefined; if (normalizedRcUrl && !env.SENTRY_HOST?.trim() && !env.SENTRY_URL?.trim()) { log.debug( - `Setting SENTRY_URL from ${CONFIG_FILENAME} (${config.sources.url})` + `Setting SENTRY_URL from ${CONFIG_FILENAME} (${config.sources.url})`, ); env.SENTRY_URL = normalizedRcUrl; } diff --git a/packages/cli/src/lib/shell.ts b/packages/cli/src/lib/shell.ts index 2e9b971c3..39b1ece21 100644 --- a/packages/cli/src/lib/shell.ts +++ b/packages/cli/src/lib/shell.ts @@ -77,7 +77,7 @@ export function detectShellType(shellPath: string | undefined): ShellType { export function getConfigCandidates( shellType: ShellType, homeDir: string, - xdgConfigHome?: string + xdgConfigHome?: string, ): string[] { const xdg = xdgConfigHome || join(homeDir, ".config"); @@ -134,7 +134,7 @@ export function findExistingConfigFile(candidates: string[]): string | null { export function detectShell( shellPath: string | undefined, homeDir: string, - xdgConfigHome?: string + xdgConfigHome?: string, ): ShellInfo { const type = detectShellType(shellPath); const name = shellPath ? basename(shellPath) : type; @@ -154,7 +154,7 @@ export function detectShell( */ export function getPathCommand( shellType: ShellType, - directory: string + directory: string, ): string { if (shellType === "fish") { return `fish_add_path "${directory}"`; @@ -167,7 +167,7 @@ export function getPathCommand( */ export function isInPath( directory: string, - pathEnv: string | undefined + pathEnv: string | undefined, ): boolean { if (!pathEnv) { return false; @@ -192,15 +192,15 @@ async function addToShellConfig( configFile: string, directory: string, command: string, - label: string + label: string, ): Promise<PathModificationResult> { const exists = await access(configFile).then( () => true, - () => false + () => false, ); if (!exists) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await writeFile(configFile, `# sentry\n${command}\n`, "utf-8"); return { @@ -230,7 +230,7 @@ async function addToShellConfig( }; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const newContent = content.endsWith("\n") ? `${content}\n# sentry\n${command}\n` @@ -256,13 +256,13 @@ async function addToShellConfig( export function addToPath( configFile: string, directory: string, - shellType: ShellType + shellType: ShellType, ): Promise<PathModificationResult> { return addToShellConfig( configFile, directory, getPathCommand(shellType, directory), - "PATH" + "PATH", ); } @@ -281,13 +281,13 @@ export function getFpathCommand(directory: string): string { */ export function addToFpath( configFile: string, - directory: string + directory: string, ): Promise<PathModificationResult> { return addToShellConfig( configFile, directory, getFpathCommand(directory), - "fpath" + "fpath", ); } @@ -296,7 +296,7 @@ export function addToFpath( */ export async function addToGitHubPath( directory: string, - env: NodeJS.ProcessEnv + env: NodeJS.ProcessEnv, ): Promise<boolean> { if (env.GITHUB_ACTIONS !== "true" || !env.GITHUB_PATH) { return false; diff --git a/packages/cli/src/lib/sixel-image.ts b/packages/cli/src/lib/sixel-image.ts index 1d8bbd42f..c4ad23f05 100644 --- a/packages/cli/src/lib/sixel-image.ts +++ b/packages/cli/src/lib/sixel-image.ts @@ -100,7 +100,7 @@ function isSofJpegMarker(marker: number): boolean { * found within the buffer or the stream is malformed. */ function readJpegDimensions( - body: Uint8Array + body: Uint8Array, ): { width: number; height: number } | undefined { // Must read up to offset+8 (the 2-byte width) for a SOF, so require // offset+8 to be in bounds (offset+9 <= body.length). @@ -153,7 +153,7 @@ function readJpegDimensions( */ export function readImageDimensions( body: Uint8Array, - format: SupportedImageFormat + format: SupportedImageFormat, ): { width: number; height: number } | undefined { if (format === "png") { if (body.length < 24) { @@ -174,7 +174,7 @@ export function readImageDimensions( */ export function detectImageFormat( body: Uint8Array, - contentType?: string | null + contentType?: string | null, ): SupportedImageFormat | undefined { // PNG signature: 89 50 4E 47 0D 0A 1A 0A if ( @@ -212,7 +212,7 @@ export function detectImageFormat( */ export function decodeImage( body: Uint8Array, - format: SupportedImageFormat + format: SupportedImageFormat, ): DecodedImage | undefined { const dims = readImageDimensions(body, format); if ( @@ -220,7 +220,7 @@ export function decodeImage( (dims.width > MAX_DECODE_DIMENSION || dims.height > MAX_DECODE_DIMENSION) ) { log.debug( - `Refusing to decode ${format} image: declared dimensions ${dims.width}×${dims.height} exceed ${MAX_DECODE_DIMENSION}px cap` + `Refusing to decode ${format} image: declared dimensions ${dims.width}×${dims.height} exceed ${MAX_DECODE_DIMENSION}px cap`, ); return; } @@ -247,7 +247,7 @@ export function decodeImage( function pixelAt( img: DecodedImage, x: number, - y: number + y: number, ): [number, number, number, number] { const i = (y * img.width + x) * 4; return [ @@ -271,7 +271,7 @@ function pixelAt( export function downscale( img: DecodedImage, maxWidth: number, - maxHeight: number + maxHeight: number, ): DecodedImage { if (img.width <= maxWidth && img.height <= maxHeight) { return img; @@ -362,7 +362,7 @@ function collectOpaqueColors(img: DecodedImage): [number, number, number][] { * when none can be usefully divided further. */ function selectSplitTarget( - boxes: ColorBox[] + boxes: ColorBox[], ): { index: number; channel: number } | undefined { let index = -1; let channel = 0; @@ -382,7 +382,7 @@ function selectSplitTarget( /** Split a box in two at the median of the given channel. */ function splitBox(box: ColorBox, channel: number): [ColorBox, ColorBox] { const sorted = [...box.colors].sort( - (a, b) => (a[channel] as number) - (b[channel] as number) + (a, b) => (a[channel] as number) - (b[channel] as number), ); const mid = Math.floor(sorted.length / 2); return [{ colors: sorted.slice(0, mid) }, { colors: sorted.slice(mid) }]; @@ -394,7 +394,7 @@ function splitBox(box: ColorBox, channel: number): [ColorBox, ColorBox] { */ export function buildPalette( img: DecodedImage, - size: number + size: number, ): [number, number, number][] { const colors = collectOpaqueColors(img); if (colors.length === 0) { @@ -409,7 +409,7 @@ export function buildPalette( } const [left, right] = splitBox( boxes[target.index] as ColorBox, - target.channel + target.channel, ); boxes = [ ...boxes.slice(0, target.index), @@ -427,7 +427,7 @@ function nearestPaletteIndex( palette: [number, number, number][], r: number, g: number, - b: number + b: number, ): number { let best = 0; let bestDist = Number.POSITIVE_INFINITY; @@ -466,7 +466,7 @@ type IndexedPlane = { function encodeColorBand( plane: IndexedPlane, y0: number, - colorIndex: number + colorIndex: number, ): string { const { indices, width, height } = plane; const parts: string[] = []; @@ -534,7 +534,7 @@ function colorsInBand(plane: IndexedPlane, y0: number): number[] { export function encodeImageToSixel( img: DecodedImage, maxWidth?: number, - preserveDimensions = false + preserveDimensions = false, ): string | undefined { const effectiveMaxWidth = preserveDimensions ? (maxWidth ?? DEFAULT_MAX_WIDTH) @@ -542,7 +542,7 @@ export function encodeImageToSixel( const scaled = downscale( img, effectiveMaxWidth, - preserveDimensions ? img.height : DEFAULT_MAX_HEIGHT + preserveDimensions ? img.height : DEFAULT_MAX_HEIGHT, ); const palette = buildPalette(scaled, PALETTE_SIZE); if (palette.length === 0) { @@ -597,7 +597,7 @@ export function encodeImageToSixel( export function imageBytesToSixel( body: Uint8Array, contentType?: string | null, - maxWidth?: number + maxWidth?: number, ): string | undefined { const format = detectImageFormat(body, contentType); if (!format) { diff --git a/packages/cli/src/lib/sixel.ts b/packages/cli/src/lib/sixel.ts index 4c634512e..659cdcc7f 100644 --- a/packages/cli/src/lib/sixel.ts +++ b/packages/cli/src/lib/sixel.ts @@ -70,7 +70,7 @@ export type GraphicsRendererPreference = "auto" | GraphicsFormat; /** Select a graphics renderer from the protocols currently available. */ export function selectGraphicsFormatFromAvailability( preference: GraphicsRendererPreference, - available: { kitty: boolean; sixel: boolean } + available: { kitty: boolean; sixel: boolean }, ): GraphicsFormat | undefined { if (preference === "kitty" && available.kitty) { return "kitty"; @@ -94,7 +94,7 @@ export function selectGraphicsFormatFromAvailability( * transmits full RGBA without palette quantization. */ export function selectGraphicsFormat( - preference: GraphicsRendererPreference = "auto" + preference: GraphicsRendererPreference = "auto", ): GraphicsFormat | undefined { return selectGraphicsFormatFromAvailability(preference, { kitty: canRenderKitty(), @@ -110,7 +110,7 @@ export function selectGraphicsFormat( * never reports its geometry still renders graphics instead of ASCII. */ export function graphicsCellSize( - preference: GraphicsRendererPreference = "auto" + preference: GraphicsRendererPreference = "auto", ): { cellWidth: number; cellHeight: number } | undefined { if (!selectGraphicsFormat(preference)) { return; @@ -129,15 +129,15 @@ export function graphicsCellSize( } /** Primary DA reply: `ESC [ ? <p;p;...> c` — attribute list; `4` == sixel. */ -// biome-ignore lint/suspicious/noControlCharactersInRegex: parsing terminal escapes +// oxlint-disable-next-line no-control-regex -- parsing terminal escapes const DA1_RE = /\x1b\[\?([0-9;]*)c/; /** Cell-size report: `ESC [ 6 ; <height> ; <width> t`. */ -// biome-ignore lint/suspicious/noControlCharactersInRegex: parsing terminal escapes +// oxlint-disable-next-line no-control-regex -- parsing terminal escapes const CELL_SIZE_RE = /\x1b\[6;(\d+);(\d+)t/; /** Kitty graphics query reply: `ESC _ G i=<id>;OK ESC \`. */ -// biome-ignore lint/suspicious/noControlCharactersInRegex: parsing terminal escapes +// oxlint-disable-next-line no-control-regex -- parsing terminal escapes const KITTY_RE = /\x1b_G[^\x1b]*;OK/; /** @@ -188,7 +188,7 @@ export function parseSixelCaps(reply: string): SixelCaps { export function sixelFits( caps: SixelCaps, columns: number, - bannerWidth: number + bannerWidth: number, ): boolean { if (!(caps.supported && caps.cellWidth && caps.cellWidth > 0)) { return false; @@ -266,7 +266,7 @@ function probe(): SixelCaps { } let savedStty: string | undefined; let fd: number | undefined; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { fd = openSync("/dev/tty", "r+"); savedStty = execSync("stty -g < /dev/tty", { encoding: "utf8" }).trim(); @@ -281,7 +281,7 @@ function probe(): SixelCaps { return UNSUPPORTED; } finally { if (savedStty) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { execSync(`stty ${savedStty} < /dev/tty`); } catch { @@ -289,7 +289,7 @@ function probe(): SixelCaps { } } if (fd !== undefined) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { closeSync(fd); } catch { @@ -355,7 +355,7 @@ export function canRenderKitty(): boolean { * session, mirroring the fit check {@link sixelFits} does for the banner. */ export function terminalPixelWidth( - columns: number = process.stdout.columns ?? 80 + columns: number = process.stdout.columns ?? 80, ): number | undefined { const caps = detectSixelCaps(); if ( @@ -374,7 +374,7 @@ export function terminalPixelWidth( * guessing, because a guessed row height corrupts the dashboard grid. */ export function terminalPixelHeight( - rows: number = process.stdout.rows ?? 24 + rows: number = process.stdout.rows ?? 24, ): number | undefined { const caps = detectSixelCaps(); if ( @@ -391,7 +391,7 @@ export function terminalPixelHeight( * block-art banner. */ export function sixelBanner( - columns: number = process.stdout.columns ?? 80 + columns: number = process.stdout.columns ?? 80, ): string | undefined { // Re-evaluate the cheap, I/O-free gates on every call. detectSixelCaps caches // the probe result, so this ensures a later opt-out (NO_COLOR, diff --git a/packages/cli/src/lib/snapshots/archive.ts b/packages/cli/src/lib/snapshots/archive.ts index ff13006ab..01fd1671e 100644 --- a/packages/cli/src/lib/snapshots/archive.ts +++ b/packages/cli/src/lib/snapshots/archive.ts @@ -50,7 +50,7 @@ function safeEntryDest(root: string, name: string): string | null { */ export async function extractZipStream( chunks: AsyncIterable<Uint8Array>, - outDir: string + outDir: string, ): Promise<number> { const root = resolve(outDir); mkdirSync(root, { recursive: true }); @@ -87,7 +87,7 @@ export async function extractZipStream( failure ??= err; resolveWrite(); }); - }) + }), ); file.ondata = (err, data, final) => { diff --git a/packages/cli/src/lib/snapshots/diff.ts b/packages/cli/src/lib/snapshots/diff.ts index ab022a357..a5b5ef480 100644 --- a/packages/cli/src/lib/snapshots/diff.ts +++ b/packages/cli/src/lib/snapshots/diff.ts @@ -19,7 +19,7 @@ export const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg"] as const; /** Extension allowlist for {@link walkFiles} (extname form, with the dot). */ const IMAGE_EXT_SET: ReadonlySet<string> = new Set( - IMAGE_EXTENSIONS.map((ext) => `.${ext}`) + IMAGE_EXTENSIONS.map((ext) => `.${ext}`), ); /** @@ -65,7 +65,7 @@ export type CategorizedImages = { export function categorizeImages( base: string[], head: string[], - selective: boolean + selective: boolean, ): CategorizedImages { const baseSet = new Set(base); const headSet = new Set(head); @@ -134,7 +134,7 @@ export function compareImages( baseBuf: Buffer, headBuf: Buffer, path: string, - opts: DiffOptions + opts: DiffOptions, ): ImageDiff { const base = decodeImage(baseBuf, path); const head = decodeImage(headBuf, path); diff --git a/packages/cli/src/lib/snapshots/images.ts b/packages/cli/src/lib/snapshots/images.ts index 95b373a41..3e580a50e 100644 --- a/packages/cli/src/lib/snapshots/images.ts +++ b/packages/cli/src/lib/snapshots/images.ts @@ -27,7 +27,7 @@ export const IMAGE_EXTENSIONS: ReadonlySet<string> = new Set([ /** Same extensions in the walker's `.ext` (lowercased, dotted) form. */ const WALK_EXTENSIONS: ReadonlySet<string> = new Set( - [...IMAGE_EXTENSIONS].map((ext) => `.${ext}`) + [...IMAGE_EXTENSIONS].map((ext) => `.${ext}`), ); const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); @@ -35,7 +35,7 @@ const JPEG_SIGNATURE = Buffer.from([0xff, 0xd8]); /** Read dimensions from a PNG header, or return undefined for another format. */ function readPngDimensions( - content: Buffer + content: Buffer, ): { width: number; height: number } | undefined { if ( content.length >= 24 && @@ -64,7 +64,7 @@ function isJpegFrameMarker(marker: number): boolean { function readJpegSegment( content: Buffer, offset: number, - marker: number + marker: number, ): { width: number; height: number } | undefined { const segmentLength = content.readUInt16BE(offset); if (segmentLength < 2 || offset + segmentLength > content.length) { @@ -82,7 +82,7 @@ function readJpegSegment( /** Read dimensions from a JPEG header, or return undefined for another format. */ function readJpegDimensions( - content: Buffer + content: Buffer, ): { width: number; height: number } | undefined { if (content.length < 4 || !content.subarray(0, 2).equals(JPEG_SIGNATURE)) { return; @@ -151,7 +151,7 @@ export function pathAsUrl(relativePath: string): string { /** Read and parse an image's `<image>.json` sidecar metadata, if present. */ async function readSidecarMetadata( - imagePath: string + imagePath: string, ): Promise<Record<string, unknown>> { const sidecarPath = imagePath.replace(FILE_EXTENSION, ".json"); if (sidecarPath === imagePath) { @@ -236,14 +236,14 @@ export function validateImageSizes(images: CollectedImage[]): void { (img) => ` ${img.relativePath} (${img.width}x${img.height} = ${ img.width * img.height - } pixels)` + } pixels)`, ); if (violations.length > 0) { throw new ValidationError( `The following images exceed the maximum pixel limit of ${MAX_PIXELS_PER_IMAGE}:\n${violations.join( - "\n" + "\n", )}`, - "path" + "path", ); } } @@ -259,6 +259,6 @@ export function splitAndTrim(input: string, separator: string): string[] { /** Normalize image name entries: strip a leading `./` and `\`→`/`. */ export function normalizeImageNames(names: string[]): string[] { return names.map((s) => - (s.startsWith("./") ? s.slice(2) : s).replaceAll("\\", "/") + (s.startsWith("./") ? s.slice(2) : s).replaceAll("\\", "/"), ); } diff --git a/packages/cli/src/lib/sourcemap/debug-id.ts b/packages/cli/src/lib/sourcemap/debug-id.ts index 2db18e5c7..5754949d6 100644 --- a/packages/cli/src/lib/sourcemap/debug-id.ts +++ b/packages/cli/src/lib/sourcemap/debug-id.ts @@ -133,7 +133,7 @@ export function getDebugIdSnippet(debugId: string): string { */ export function prependDebugIdSnippet( jsContent: string, - snippet: string + snippet: string, ): string { if (jsContent.startsWith("#!")) { const newlineIdx = jsContent.indexOf("\n"); @@ -178,7 +178,7 @@ export function prependDebugIdSnippet( export async function injectDebugId( jsPath: string, mapPath: string, - options?: { skipSnippet?: boolean } + options?: { skipSnippet?: boolean }, ): Promise<{ debugId: string; wasInjected: boolean }> { const [jsBytes, mapContent] = await Promise.all([ readFile(jsPath), @@ -267,7 +267,7 @@ type SourcemapJson = { function mutateSourcemap( map: SourcemapJson, debugId: string, - options: { offsetMappings: boolean } + options: { offsetMappings: boolean }, ): void { if (map.sources) { map.sources = map.sources.map((s) => (s ? s.replaceAll("\\", "/") : s)); @@ -318,7 +318,7 @@ const INLINE_DIRECTIVE_RE = */ export async function injectInlineDebugId( jsPath: string, - decoded: DecodedInlineMap + decoded: DecodedInlineMap, ): Promise<{ debugId: string; wasInjected: boolean; @@ -359,7 +359,7 @@ export async function injectInlineDebugId( const last = matches.at(-1); if (last?.index === undefined) { log.debug( - `inline sourcemap directive not found for rewrite in ${jsPath}; leaving file unmodified` + `inline sourcemap directive not found for rewrite in ${jsPath}; leaving file unmodified`, ); return { debugId: "", wasInjected: false }; } diff --git a/packages/cli/src/lib/sourcemap/inject.ts b/packages/cli/src/lib/sourcemap/inject.ts index 4b0152b17..b84d772bb 100644 --- a/packages/cli/src/lib/sourcemap/inject.ts +++ b/packages/cli/src/lib/sourcemap/inject.ts @@ -90,7 +90,7 @@ export type InjectDirectoryOptions = { */ export async function injectDirectory( dir: string, - options: InjectDirectoryOptions = {} + options: InjectDirectoryOptions = {}, ): Promise<InjectResult[]> { const extensions = options.extensions ? new Set(options.extensions.map((e) => (e.startsWith(".") ? e : `.${e}`))) @@ -160,11 +160,11 @@ async function readMapDebugId(map: MapSource): Promise<string | undefined> { } try { return readSourcemapDebugId( - JSON.parse(await readFile(map.mapPath, "utf-8")) + JSON.parse(await readFile(map.mapPath, "utf-8")), ); } catch (err) { log.warn( - `could not read a debug ID from ${map.mapPath}: ${err instanceof Error ? err.message : String(err)}` + `could not read a debug ID from ${map.mapPath}: ${err instanceof Error ? err.message : String(err)}`, ); return; } @@ -300,7 +300,7 @@ async function readDirectiveTail(filePath: string): Promise<Buffer> { * enough back to include its start. */ function findSourceMappingDirective( - tail: Buffer + tail: Buffer, ): SourceMappingDirective | undefined { for (const line of linesFromEnd(tail)) { const directive = parseSourceMappingDirective(line); @@ -349,7 +349,7 @@ function skipSpaces(buf: Buffer, from: number): number { * `sourceMappingURL` directive. */ export function parseSourceMappingDirective( - line: Buffer + line: Buffer, ): SourceMappingDirective | undefined { // Trim trailing whitespace/CR at the byte level. let endIdx = line.length; @@ -409,7 +409,7 @@ export function parseSourceMappingDirective( * `undefined` when no directive is present or the file cannot be read. */ async function extractSourceMappingDirective( - jsPath: string + jsPath: string, ): Promise<SourceMappingDirective | undefined> { try { const tail = await readDirectiveTail(jsPath); @@ -446,7 +446,7 @@ async function fileExists(path: string): Promise<boolean> { * @returns A {@link MapSource} if a sourcemap is found, undefined otherwise. */ async function findCompanionMap( - jsPath: string + jsPath: string, ): Promise<MapSource | undefined> { // Fast path: convention-based naming (most bundlers use this) const conventionPath = `${jsPath}.map`; @@ -467,7 +467,7 @@ async function findCompanionMap( const decoded = tryDecodeInlineSourcemap(directive.value); if (!decoded) { log.warn( - `skipping ${jsPath}: inline sourcemap is not valid base64 JSON; leaving file unmodified` + `skipping ${jsPath}: inline sourcemap is not valid base64 JSON; leaving file unmodified`, ); return; } @@ -531,7 +531,7 @@ const SOURCEMAP_SKIP_DIRS: readonly string[] = [NODE_MODULES_DIRNAME]; */ export async function buildIgnoreMatcher( patterns?: string[], - ignoreFilePath?: string + ignoreFilePath?: string, ): Promise<ReturnType<typeof ignore> | undefined> { const hasPatterns = patterns && patterns.length > 0; if (!(hasPatterns || ignoreFilePath)) { @@ -550,7 +550,7 @@ export async function buildIgnoreMatcher( if (code === "ENOENT") { throw new ValidationError( `Ignore file '${ignoreFilePath}' does not exist.`, - "ignore-file" + "ignore-file", ); } throw err; @@ -568,7 +568,7 @@ export async function buildIgnoreMatcher( export async function discoverFilePairs( dir: string, extensions: Set<string> = DEFAULT_EXTENSIONS, - ignoreMatcher?: ReturnType<typeof ignore> + ignoreMatcher?: ReturnType<typeof ignore>, ): Promise<FilePair[]> { // `walkFiles` requires an absolute cwd. CLI callers pass // user-supplied positional args like `./dist` directly through to @@ -613,7 +613,7 @@ export async function discoverFilePairs( function isMapInsideDir( map: MapSource, absDir: string, - jsPath: string + jsPath: string, ): boolean { if (map.kind === "inline") { return true; @@ -623,7 +623,7 @@ function isMapInsideDir( return true; } log.debug( - `skipping sourcemap outside directory: ${map.mapPath} (resolved from ${jsPath})` + `skipping sourcemap outside directory: ${map.mapPath} (resolved from ${jsPath})`, ); return false; } @@ -639,7 +639,7 @@ export async function assertDirectoryReadable(dir: string): Promise<void> { if (!s.isDirectory()) { throw new ValidationError( `Path '${dir}' is not a directory.`, - "directory" + "directory", ); } } catch (err) { @@ -650,13 +650,13 @@ export async function assertDirectoryReadable(dir: string): Promise<void> { if (code === "ENOENT") { throw new ValidationError( `Directory '${dir}' does not exist.`, - "directory" + "directory", ); } const msg = err instanceof Error ? err.message : String(err); throw new ValidationError( `Cannot read directory '${dir}': ${msg}`, - "directory" + "directory", ); } } @@ -676,7 +676,7 @@ export type DiscoveryDiagnostic = { */ export async function diagnoseEmptyDiscovery( dir: string, - options: InjectDirectoryOptions = {} + options: InjectDirectoryOptions = {}, ): Promise<DiscoveryDiagnostic> { // Build one set covering JS extensions + `.map` so the walker visits // both in a single pass. @@ -778,7 +778,7 @@ function compareByteWise(a: string, b: string): number { export async function resolveDirectorySourcemaps( dir: string, extensions: Set<string> = DEFAULT_EXTENSIONS, - ignoreMatcher?: ReturnType<typeof ignore> + ignoreMatcher?: ReturnType<typeof ignore>, ): Promise<SourcemapResolution[]> { const absDir = resolvePath(dir); const results: SourcemapResolution[] = []; @@ -842,7 +842,7 @@ export async function resolveDirectorySourcemaps( */ export function buildEmptyDiscoveryError( dir: string, - diag: DiscoveryDiagnostic + diag: DiscoveryDiagnostic, ): ValidationError { const { jsFiles, mapFiles } = diag; if (jsFiles === 0 && mapFiles === 0) { @@ -850,7 +850,7 @@ export function buildEmptyDiscoveryError( `Directory '${dir}' contains no JS or sourcemap files. ` + "Check the path points at your build output, or pass " + "--allow-empty to suppress this error.", - "directory" + "directory", ); } if (jsFiles > 0 && mapFiles === 0) { @@ -859,7 +859,7 @@ export function buildEmptyDiscoveryError( "files. Your bundler is not emitting sourcemaps. For Vite/Astro: " + "`vite.environments.client.build.sourcemap: 'hidden'`. For webpack: " + "`devtool: 'hidden-source-map'`. Pass --allow-empty to suppress.", - "directory" + "directory", ); } if (mapFiles > 0 && jsFiles === 0) { @@ -867,7 +867,7 @@ export function buildEmptyDiscoveryError( `Found ${mapFiles} .map file(s) in '${dir}' but no companion JS ` + "files. Ensure your build emits both JS and maps to the same " + "directory. Pass --allow-empty to suppress.", - "directory" + "directory", ); } return new ValidationError( @@ -875,6 +875,6 @@ export function buildEmptyDiscoveryError( "no JS file has a matching `<name>.map` companion. Check that your " + "bundler emits JS and sourcemaps with matching basenames. Pass " + "--allow-empty to suppress.", - "directory" + "directory", ); } diff --git a/packages/cli/src/lib/sourcemap/inline-sourcemap.ts b/packages/cli/src/lib/sourcemap/inline-sourcemap.ts index a5002ea97..164d1ae3c 100644 --- a/packages/cli/src/lib/sourcemap/inline-sourcemap.ts +++ b/packages/cli/src/lib/sourcemap/inline-sourcemap.ts @@ -74,7 +74,7 @@ export function isInlineSourcemapUrl(url: string): boolean { * @returns The decoded map plus re-encode metadata, or `undefined` on failure */ export function tryDecodeInlineSourcemap( - url: string + url: string, ): DecodedInlineMap | undefined { const match = url.match(INLINE_SOURCEMAP_DATA_URL_RE); if (!match) { @@ -105,7 +105,7 @@ export function tryDecodeInlineSourcemap( */ export function encodeInlineSourcemap( map: unknown, - dataUrlPrefix: string + dataUrlPrefix: string, ): string { const base64 = Buffer.from(JSON.stringify(map)).toString("base64"); return `${dataUrlPrefix}${base64}`; diff --git a/packages/cli/src/lib/sourcemap/zip.ts b/packages/cli/src/lib/sourcemap/zip.ts index 2678cb975..6a15fe167 100644 --- a/packages/cli/src/lib/sourcemap/zip.ts +++ b/packages/cli/src/lib/sourcemap/zip.ts @@ -123,7 +123,7 @@ export class ZipWriter { */ static async create( outputPath: string, - options: { compression?: ZipCompression } = {} + options: { compression?: ZipCompression } = {}, ): Promise<ZipWriter> { const fh = await open(outputPath, "w"); const writer = new ZipWriter(fh, options.compression ?? "deflate"); @@ -150,7 +150,7 @@ export class ZipWriter { * Safe to call multiple times — subsequent calls are no-ops. */ async close(): Promise<void> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await this.fh.close().catch(() => { // Already closed — ignore }); @@ -183,7 +183,7 @@ export class ZipWriter { // Last mod time and date — 0 (unused) header.writeUInt16LE(0, 10); header.writeUInt16LE(0, 12); - // biome-ignore lint/suspicious/noBitwiseOperators: coerce signed CRC-32 to unsigned for writeUInt32LE + // oxlint-disable-next-line no-bitwise -- coerce signed CRC-32 to unsigned for writeUInt32LE header.writeUInt32LE(checksum >>> 0, 14); header.writeUInt32LE(payload.length, 18); header.writeUInt32LE(data.length, 22); @@ -232,7 +232,7 @@ export class ZipWriter { // Last mod time and date — 0 rec.writeUInt16LE(0, 12); rec.writeUInt16LE(0, 14); - // biome-ignore lint/suspicious/noBitwiseOperators: coerce signed CRC-32 to unsigned for writeUInt32LE + // oxlint-disable-next-line no-bitwise -- coerce signed CRC-32 to unsigned for writeUInt32LE rec.writeUInt32LE(entry.crc >>> 0, 16); rec.writeUInt32LE(entry.compressedSize, 20); rec.writeUInt32LE(entry.uncompressedSize, 24); diff --git a/packages/cli/src/lib/span-tree.ts b/packages/cli/src/lib/span-tree.ts index 390c17c24..d2ab384e7 100644 --- a/packages/cli/src/lib/span-tree.ts +++ b/packages/cli/src/lib/span-tree.ts @@ -23,7 +23,7 @@ const log = logger.withTag("span-tree"); function truncateSpanTree( spans: TraceSpan[], maxDepth: number, - currentDepth = 1 + currentDepth = 1, ): TraceSpan[] { if (currentDepth > maxDepth) { return []; @@ -64,7 +64,7 @@ export type SpanTreeResult = { export async function getSpanTreeLines( orgSlug: string, event: SentryEvent, - maxDepth: number + maxDepth: number, ): Promise<SpanTreeResult> { const traceId = event.contexts?.trace?.trace_id ?? null; const parsed = event.dateCreated @@ -86,7 +86,7 @@ export async function getSpanTreeLines( const spans = await getDetailedTrace( orgSlug, traceId, - timestamp === undefined ? {} : { timestamp } + timestamp === undefined ? {} : { timestamp }, ); const lines = formatSimpleSpanTree(traceId, spans, maxDepth); // Truncate spans to match depth limit for JSON output diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index 4175e7cbe..1364b35a2 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -11,7 +11,7 @@ import { chmodSync, statSync } from "node:fs"; import { createRequire } from "node:module"; -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; const _require = createRequire(import.meta.url); @@ -137,7 +137,7 @@ export function computeTelemetryEffective(): TelemetryEffective { return { enabled: false, source: `env:${DO_NOT_TRACK_ENV_VAR}` }; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const pref = getTelemetryPreference(); if (pref !== undefined) { @@ -178,7 +178,7 @@ export function isTelemetryEnabled(): boolean { */ export async function withTelemetry<T>( callback: (span: Span | undefined) => T | Promise<T>, - options?: { libraryMode?: boolean } + options?: { libraryMode?: boolean }, ): Promise<T> { const enabled = isTelemetryEnabled(); const client = initSentry(enabled, options); @@ -191,11 +191,10 @@ export async function withTelemetry<T>( // Flush deferred completion telemetry (queued during __complete fast-path). // Best-effort: never block CLI execution for telemetry emission. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { - const { drainCompletionTelemetry } = await import( - "./db/completion-telemetry.js" - ); + const { drainCompletionTelemetry } = + await import("./db/completion-telemetry.js"); for (const entry of drainCompletionTelemetry()) { Sentry.metrics.distribution("completion.duration_ms", entry.durationMs, { attributes: { command_path: entry.commandPath }, @@ -203,7 +202,7 @@ export async function withTelemetry<T>( Sentry.metrics.distribution( "completion.result_count", entry.resultCount, - { attributes: { command_path: entry.commandPath } } + { attributes: { command_path: entry.commandPath } }, ); } } catch { @@ -226,7 +225,7 @@ export async function withTelemetry<T>( } throw e; } - } + }, ); } catch (e) { // Route through reportCliError so silencing (OutputError, expected-auth @@ -273,7 +272,7 @@ export async function withTelemetry<T>( * @internal Exported for testing */ export function createBeforeExitHandler( - client: Sentry.LightNodeClient + client: Sentry.LightNodeClient, ): () => void { let isFlushing = false; return () => { @@ -290,7 +289,7 @@ export function createBeforeExitHandler( // Flush pending events before exit. Convert PromiseLike to Promise // for proper error handling. The async work causes beforeExit to // re-fire when complete, which the isFlushing guard handles. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. Promise.resolve(client.flush(3000)).catch(() => { // Ignore flush errors — telemetry should never block CLI exit }); @@ -438,7 +437,7 @@ const LIBRARY_EXCLUDED_INTEGRATIONS = new Set([ * Checked once at module load so the integration filter is a simple boolean. */ const hasGetSystemErrorMap = (() => { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Dynamic require to avoid bundler issues — the check only matters at runtime const util = _require("node:util") as Record<string, unknown>; @@ -524,7 +523,7 @@ function setAgentTags(info: AgentInfo): void { export function initSentry( enabled: boolean, - options?: { libraryMode?: boolean } + options?: { libraryMode?: boolean }, ): Sentry.LightNodeClient | undefined { const libraryMode = options?.libraryMode ?? false; const environment = getCliEnvironment(); @@ -620,14 +619,14 @@ export function initSentry( const filtered = defaults.filter( (integration) => !excluded.has(integration.name) && - (integration.name !== "NodeSystemError" || hasGetSystemErrorMap) + (integration.name !== "NodeSystemError" || hasGetSystemErrorMap), ); // Re-add Context integration with cpu: false to avoid os.cpus() crash // on systems where /proc/cpuinfo is not accessible (CLI-1ED). if (!libraryMode) { filtered.push( - Sentry.nodeContextIntegration({ device: { cpu: false } }) + Sentry.nodeContextIntegration({ device: { cpu: false } }), ); } @@ -637,7 +636,7 @@ export function initSentry( // 5s interval for CLI — most commands complete in <10s. if (!libraryMode) { filtered.push( - Sentry.nodeRuntimeMetricsIntegration({ collectionIntervalMs: 5000 }) + Sentry.nodeRuntimeMetricsIntegration({ collectionIntervalMs: 5000 }), ); } @@ -779,7 +778,7 @@ export function initSentry( */ export function setCommandSpanName( span: Span | undefined, - command: string + command: string, ): void { if (span) { Sentry.updateSpanName(span, command); @@ -912,7 +911,7 @@ export function setArgsContext(args: readonly unknown[]): void { Sentry.setContext("args", { values: args.map((arg) => - typeof arg === "string" ? arg : JSON.stringify(arg) + typeof arg === "string" ? arg : JSON.stringify(arg), ), count: args.length, }); @@ -982,7 +981,7 @@ export function createWizardPromptTelemetry(): WizardPromptTelemetry { "wizard.user_wait_ms", totalWaitMs, "millisecond", - Sentry.getRootSpan(span) + Sentry.getRootSpan(span), ); Sentry.metrics.distribution("wizard.user_wait_ms", waitMs, { attributes: { @@ -996,7 +995,7 @@ export function createWizardPromptTelemetry(): WizardPromptTelemetry { "wizard.prompt.kind": kind, "wizard.prompt.phase": stepId ? "workflow" : "preflight", ...(stepId ? { "wizard.step.id": stepId } : {}), - } + }, ); }, }; @@ -1023,7 +1022,7 @@ export function withTracing<T>( name: string, op: string, fn: () => T | Promise<T>, - attributes?: Record<string, string | number | boolean> + attributes?: Record<string, string | number | boolean>, ): Promise<T> { return Sentry.startSpan( { name, op, attributes, onlyIfParent: true }, @@ -1036,7 +1035,7 @@ export function withTracing<T>( span.setStatus({ code: 2 }); // Error throw error; } - } + }, ); } @@ -1072,7 +1071,7 @@ export function withTracingSpan<T>( name: string, op: string, fn: (span: Span) => T | Promise<T>, - attributes?: Record<string, string | number | boolean> + attributes?: Record<string, string | number | boolean>, ): Promise<T> { return Sentry.startSpan( { name, op, attributes, onlyIfParent: true }, @@ -1097,7 +1096,7 @@ export function withTracingSpan<T>( } throw error; } - } + }, ); } @@ -1115,7 +1114,7 @@ export function withTracingSpan<T>( export function withHttpSpan<T>( method: string, url: string, - fn: () => Promise<T> + fn: () => Promise<T>, ): Promise<T> { return withTracing(`${method} ${url}`, "http.client", fn, { "http.request.method": method, @@ -1146,17 +1145,17 @@ export function withDbSpan<T>(operation: string, fn: () => T): T { attributes: { "db.system": "sqlite" }, onlyIfParent: true, }, - fn + fn, ); } /** Intentional no-op used as a self-replacement target for one-shot functions. */ -// biome-ignore lint/suspicious/noEmptyBlockStatements: intentional noop +// intentional noop const noop = (): void => {}; /** Resolves the database path, falling back to a default if the import fails. */ function resolveDbPath(): string { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { getDbPath } = _require("./db/index.js") as { getDbPath: () => string; @@ -1182,7 +1181,7 @@ let warnReadonlyDatabaseOnce = (): void => { process.stderr.write( `\nWarning: Sentry CLI local database is read-only. Caching and preferences won't persist.\n` + ` Path: ${dbPath}\n` + - " Fix: sentry cli fix\n\n" + " Fix: sentry cli fix\n\n", ); }; @@ -1229,7 +1228,7 @@ function isOwnedByRoot(filePath: string): boolean { if (process.platform === "win32") { return false; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return statSync(filePath).uid === 0; } catch { @@ -1259,12 +1258,12 @@ function tryRepairReadonly(): boolean { "\nWarning: Sentry CLI config directory is owned by root.\n" + ` Path: ${configDir}\n` + ` Fix: sudo chown -R ${username} "${configDir}"\n` + - " Or: sudo sentry cli fix\n\n" + " Or: sudo sentry cli fix\n\n", ); return false; } - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Repair config directory (needs rwx for WAL/SHM creation) chmodSync(configDir, 0o700); @@ -1278,7 +1277,7 @@ function tryRepairReadonly(): boolean { warnReadonlyDatabaseOnce = noop; process.stderr.write( - "\nNote: Database permissions were auto-repaired. Caching will resume on next command.\n\n" + "\nNote: Database permissions were auto-repaired. Caching will resume on next command.\n\n", ); return true; } catch { @@ -1300,7 +1299,7 @@ export function resetReadonlyWarning(): void { process.stderr.write( `\nWarning: Sentry CLI local database is read-only. Caching and preferences won't persist.\n` + ` Path: ${dbPath}\n` + - " Fix: sentry cli fix\n\n" + " Fix: sentry cli fix\n\n", ); }; } @@ -1352,7 +1351,7 @@ function createTracedStatement<T>(stmt: T, sql: string): T { // Non-traced methods get bound to preserve 'this' context for native methods if ( !TRACED_STATEMENT_METHODS.includes( - prop as (typeof TRACED_STATEMENT_METHODS)[number] + prop as (typeof TRACED_STATEMENT_METHODS)[number], ) ) { return value.bind(target); @@ -1392,7 +1391,7 @@ function createTracedStatement<T>(stmt: T, sql: string): T { // Re-throw if repair didn't help or wasn't applicable throw error; } - } + }, ); }, }) as T; @@ -1473,7 +1472,7 @@ export function withSerializeSpan<T>(operation: string, fn: () => T): T { op: "serialize", onlyIfParent: true, }, - fn + fn, ); } @@ -1489,7 +1488,7 @@ export function withSerializeSpan<T>(operation: string, fn: () => T): T { */ export function withFsSpan<T>( operation: string, - fn: () => T | Promise<T> + fn: () => T | Promise<T>, ): Promise<T> { return withTracing(operation, "file", fn); } @@ -1511,7 +1510,7 @@ export function withCacheSpan<T>( name: string, op: "cache.get" | "cache.put", fn: (span: Span) => T | Promise<T>, - attributes?: Record<string, string | number | boolean | string[]> + attributes?: Record<string, string | number | boolean | string[]>, ): Promise<T> { return Sentry.startSpan( { name, op, attributes, onlyIfParent: true }, @@ -1524,6 +1523,6 @@ export function withCacheSpan<T>( span.setStatus({ code: 2 }); // Error throw error; } - } + }, ); } diff --git a/packages/cli/src/lib/telemetry/zstd-transport.ts b/packages/cli/src/lib/telemetry/zstd-transport.ts index 16b1cbdb5..a00923814 100644 --- a/packages/cli/src/lib/telemetry/zstd-transport.ts +++ b/packages/cli/src/lib/telemetry/zstd-transport.ts @@ -26,9 +26,9 @@ * through `createGzip()` by the time `httpModule.request()` runs). */ -// biome-ignore lint/performance/noNamespaceImport: http module must be passed as a namespace object (matches SDK's HTTPModule interface) +// oxlint-disable-next-line sentry-cli/no-namespace-import -- http module must be passed as a namespace object (matches SDK's HTTPModule interface) import * as http from "node:http"; -// biome-ignore lint/performance/noNamespaceImport: same as above for https +// oxlint-disable-next-line sentry-cli/no-namespace-import -- same as above for https import * as https from "node:https"; import { Readable } from "node:stream"; import { promisify } from "node:util"; @@ -98,10 +98,10 @@ const zstdCompressAsync = * wires up an executor. */ export function makeCompressedTransport( - options: NodeTransportOptions + options: NodeTransportOptions, ): Transport { let urlSegments: URL; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { urlSegments = new URL(options.url); } catch { @@ -168,7 +168,7 @@ export function makeCompressedTransport( */ export function shouldFallbackToDefault( url: URL, - options: NodeTransportOptions + options: NodeTransportOptions, ): boolean { const isHttps = url.protocol === "https:"; const httpProxy = process.env.http_proxy ?? process.env.HTTP_PROXY; @@ -195,13 +195,14 @@ type PerformRequestArgs = { }; async function performRequest( - args: PerformRequestArgs + args: PerformRequestArgs, ): Promise<TransportMakeRequestResponse> { const { request, options, httpModule, agent, encoding } = args; const rawBuffer = normalizeBody(request.body); const { payload, encodingApplied } = await maybeCompress(rawBuffer, encoding); + // oxlint-disable-next-line unicorn/no-useless-fallback-in-spread -- Retain the existing fallback while migrating lint tools. const headers: Record<string, string> = { ...(options.headers ?? {}) }; if (encodingApplied !== "none") { headers["content-encoding"] = encodingApplied; @@ -237,7 +238,7 @@ async function performRequest( : rateLimitsHeader, }, }); - } + }, ); req.on("error", reject); Readable.from(payload).pipe(req); @@ -276,7 +277,7 @@ type CompressResult = { */ export async function maybeCompress( buf: Buffer, - encoding: SelectedEncoding + encoding: SelectedEncoding, ): Promise<CompressResult> { const threshold = encoding === "zstd" ? ZSTD_THRESHOLD : GZIP_THRESHOLD; if (buf.length <= threshold) { @@ -330,6 +331,6 @@ export function isNoProxyExempt(urlSegments: URL): boolean { return true; } return entries.some( - (ex) => urlSegments.host.endsWith(ex) || urlSegments.hostname.endsWith(ex) + (ex) => urlSegments.host.endsWith(ex) || urlSegments.hostname.endsWith(ex), ); } diff --git a/packages/cli/src/lib/time-range.ts b/packages/cli/src/lib/time-range.ts index 5c978b091..f800162b7 100644 --- a/packages/cli/src/lib/time-range.ts +++ b/packages/cli/src/lib/time-range.ts @@ -70,7 +70,7 @@ export const PERIOD_BRIEF = `Time range: "7d", "${EXAMPLE_START}..${EXAMPLE_END} * Returns null if the string isn't a valid relative period. */ export function parseRelativeParts( - value: string + value: string, ): { value: number; unit: string } | null { if (value.length < 2) { return null; @@ -110,7 +110,7 @@ function validateDate(raw: string): Date { if (Number.isNaN(d.getTime())) { throw new ValidationError( `Invalid date: '${raw}'. Expected ISO-8601 format (e.g., ${EXAMPLE_START} or ${EXAMPLE_START}T12:00:00).`, - "period" + "period", ); } return d; @@ -132,7 +132,7 @@ export function parseDate(raw: string, position: DatePosition): string { if (trimmed.length === 0) { throw new ValidationError( `Empty date value. Expected ISO-8601 format (e.g., ${EXAMPLE_START})`, - "period" + "period", ); } @@ -212,7 +212,7 @@ function tryParseOperator(value: string): AbsoluteTimeRange | null { if (dateStr.length === 0) { throw new ValidationError( `Missing date after '${op.prefix}'. Expected e.g., '${op.prefix}${EXAMPLE_START}'.`, - "period" + "period", ); } const parsed = parseDate(dateStr, op.position); @@ -242,7 +242,7 @@ function tryParseRange(value: string): AbsoluteTimeRange | null { throw new ValidationError( "Empty range '..'. Provide at least one date " + `(e.g., '${EXAMPLE_START}..', '..${EXAMPLE_END}', '${EXAMPLE_START}..${EXAMPLE_END}').`, - "period" + "period", ); } @@ -257,7 +257,7 @@ function tryParseRange(value: string): AbsoluteTimeRange | null { throw new ValidationError( `Start date '${left}' is after end date '${right}'. ` + "The start must be before the end.", - "period" + "period", ); } } @@ -277,7 +277,7 @@ function tryParseRelative(value: string): RelativeTimeRange | null { if (parts.value === 0) { throw new ValidationError( `Invalid period '${value}': duration cannot be zero.`, - "period" + "period", ); } return { type: "relative", period: value }; @@ -299,7 +299,7 @@ export function parsePeriod(value: string): TimeRange { throw new ValidationError( "Empty period value. Use a relative duration (e.g., '7d', '24h') " + `or a date range (e.g., '${EXAMPLE_START}..${EXAMPLE_END}', '>=${EXAMPLE_START}').`, - "period" + "period", ); } @@ -317,7 +317,7 @@ function throwInvalidPeriod(value: string): never { `Invalid period '${value}'. Use a relative duration (e.g., '7d', '24h'), ` + `a date range (e.g., '${EXAMPLE_START}..${EXAMPLE_END}'), ` + `or a comparison operator (e.g., '>=${EXAMPLE_START}', '<${EXAMPLE_END}').`, - "period" + "period", ); } @@ -411,7 +411,7 @@ function relativeToSeconds(period: string): number | undefined { /** Compute seconds between two ISO-8601 datetime strings. */ function absoluteRangeToSeconds( start: string, - end: string + end: string, ): number | undefined { const startMs = new Date(start).getTime(); const endMs = new Date(end).getTime(); @@ -492,7 +492,7 @@ export function appendPeriodHint( parts: string[], period: TimeRange, defaultPeriod: string, - flag = "--period" + flag = "--period", ): void { const formatted = formatTimeRangeFlag(period); if (formatted !== defaultPeriod) { diff --git a/packages/cli/src/lib/timezone.ts b/packages/cli/src/lib/timezone.ts index f459efcce..aa06d8b99 100644 --- a/packages/cli/src/lib/timezone.ts +++ b/packages/cli/src/lib/timezone.ts @@ -54,7 +54,7 @@ const ZONEINFO_MARKER = "/zoneinfo/"; * disambiguates. */ export function runtimeTimezone(): string { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return Intl.DateTimeFormat().resolvedOptions().timeZone || UTC_FALLBACK; } catch { @@ -107,7 +107,7 @@ export function detectOsTimezone(): string | null { function detectUnixTimezone(): string | null { // /etc/timezone is the canonical, greppable source on Debian/Ubuntu and // many container images. It holds a single IANA name. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const contents = readFileSync("/etc/timezone", "utf-8").trim(); if (contents.length > 0 && contents !== UTC_FALLBACK) { @@ -119,7 +119,7 @@ function detectUnixTimezone(): string | null { // /etc/localtime is a symlink into the zoneinfo database on macOS and most // Linux distros. Its target encodes the IANA name. - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const target = readlinkSync("/etc/localtime"); return ianaFromZoneinfoPath(target); @@ -133,7 +133,7 @@ function detectUnixTimezone(): string | null { * Windows zone name to IANA where possible. */ function detectWindowsTimezone(): string | null { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const windowsName = execSync("tzutil /g", { encoding: "utf-8", diff --git a/packages/cli/src/lib/token-claims.ts b/packages/cli/src/lib/token-claims.ts index a5b75aef3..9915a9071 100644 --- a/packages/cli/src/lib/token-claims.ts +++ b/packages/cli/src/lib/token-claims.ts @@ -41,7 +41,7 @@ export type SntrysClaim = { * Read the file-level JSDoc before adding new callers. */ export function parseSntrysClaim( - token: string | undefined + token: string | undefined, ): SntrysClaim | undefined { if (!token || token.length > MAX_TOKEN_LENGTH) { return; @@ -56,14 +56,14 @@ export function parseSntrysClaim( } const payloadEncoded = token.slice( SNTRYS_PREFIX.length, - token.lastIndexOf("_") + token.lastIndexOf("_"), ); if (!payloadEncoded) { return; } let parsed: unknown; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { parsed = JSON.parse(Buffer.from(payloadEncoded, "base64").toString("utf8")); } catch { diff --git a/packages/cli/src/lib/token-host.ts b/packages/cli/src/lib/token-host.ts index a76535a4f..b1158bc7b 100644 --- a/packages/cli/src/lib/token-host.ts +++ b/packages/cli/src/lib/token-host.ts @@ -32,7 +32,7 @@ import { isSaaSTrustOrigin, normalizeOrigin } from "./sentry-urls.js"; */ export function isHostTrusted( candidate: string | URL | Request | undefined | null, - trusted: string | undefined | null + trusted: string | undefined | null, ): boolean { if (!trusted) { return false; @@ -104,7 +104,7 @@ export function resetLoginTrustAnchorForTesting(): void { function isOriginTrustedFor( requestInput: string | URL | Request | undefined | null, anchorHost: string, - identity: string + identity: string, ): boolean { if (isHostTrusted(requestInput, anchorHost)) { return true; @@ -121,7 +121,7 @@ function isOriginTrustedFor( * Returns `true` when no token is active (nothing to protect). */ export function isRequestOriginTrusted( - requestInput: string | URL | Request | undefined | null + requestInput: string | URL | Request | undefined | null, ): boolean { const credential = getCredentialContext(); if (!credential) { @@ -133,7 +133,7 @@ export function isRequestOriginTrusted( /** Evaluate the host trust against the credential captured for this request. */ export function isRequestOriginTrustedForContext( requestInput: string | URL | Request | undefined | null, - context: { host: string; identity: string } + context: { host: string; identity: string }, ): boolean { return isOriginTrustedFor(requestInput, context.host, context.identity); } @@ -145,7 +145,7 @@ export function isRequestOriginTrustedForContext( export function isHostTrustedForClaim( requestInput: string | URL | Request | undefined | null, claimUrl: string, - identity = getIdentityFingerprint() + identity = getIdentityFingerprint(), ): boolean { return isOriginTrustedFor(requestInput, claimUrl, identity); } @@ -159,7 +159,7 @@ export function isHostTrustedForClaim( * No anchor at all → fail closed. */ export function isRequestOriginTrustedForCustomHeaders( - requestInput: string | URL | Request | undefined | null + requestInput: string | URL | Request | undefined | null, ): boolean { if (getActiveTokenHost()) { return isRequestOriginTrusted(requestInput); diff --git a/packages/cli/src/lib/trace-target.ts b/packages/cli/src/lib/trace-target.ts index 1dee7ac7b..097fa4417 100644 --- a/packages/cli/src/lib/trace-target.ts +++ b/packages/cli/src/lib/trace-target.ts @@ -125,7 +125,7 @@ export type ResolvedTraceOrg = { */ export function parseTraceTarget( args: string[], - usageHint: string + usageHint: string, ): ParsedTraceTarget { if (args.length === 0) { throw new ContextError("Trace ID", usageHint, []); @@ -139,7 +139,7 @@ export function parseTraceTarget( // Warn about extra positional args that will be ignored if (args.length > 2) { log.warn( - `Extra arguments ignored: ${args.slice(2).join(" ")}. Expected: ${usageHint}` + `Extra arguments ignored: ${args.slice(2).join(" ")}. Expected: ${usageHint}`, ); } @@ -168,7 +168,7 @@ export function parseTraceTarget( */ export function parseSlashSeparatedTraceTarget( input: string, - usageHint: string + usageHint: string, ): ParsedTraceTarget { const lastSlash = input.lastIndexOf("/"); @@ -237,7 +237,7 @@ export function parseSlashSeparatedTraceTarget( */ export function targetArgToTraceTarget( targetArg: string, - traceId: string + traceId: string, ): ParsedTraceTarget { const parsed = parseOrgProjectArg(targetArg); @@ -292,7 +292,7 @@ export function targetArgToTraceTarget( * there isn't a usable trace ID slot (e.g., zero args). */ export function extractRawTraceId( - args: string[] + args: string[], ): { rawTraceId: string; targetArg?: string } | null { if (args.length === 0) { return null; @@ -334,7 +334,7 @@ export function extractRawTraceId( */ export async function parseTraceTargetWithRecovery( args: string[], - usageHint: string + usageHint: string, ): Promise<ParsedTraceTarget> { try { return parseTraceTarget(args, usageHint); @@ -368,7 +368,7 @@ export async function parseTraceTargetWithRecovery( * confusing slug error. */ async function recoveryContextFromTargetArg( - targetArg: string | undefined + targetArg: string | undefined, ): Promise<{ org: string; project?: string }> { if (!targetArg) { return { org: "", project: undefined }; @@ -416,7 +416,7 @@ export function warnIfNormalized(parsed: ParsedTraceTarget, tag: string): void { if ("normalized" in parsed && parsed.normalized) { const taggedLog = logger.withTag(tag); taggedLog.warn( - "Normalized slug (Sentry slugs use lowercase with dashes, not spaces)" + "Normalized slug (Sentry slugs use lowercase with dashes, not spaces)", ); } } @@ -445,7 +445,7 @@ function commandNameFromUsageHint(usageHint: string): string { export async function resolveTraceOrgOptionalProject( parsed: ParsedTraceTarget, cwd: string, - usageHint: string + usageHint: string, ): Promise<ResolvedTraceOrgProject | ResolvedTraceOrg> { if (parsed.type === "org-scoped") { setOrgProjectContext([parsed.org], []); @@ -456,7 +456,7 @@ export async function resolveTraceOrgOptionalProject( parsed, cwd, commandNameFromUsageHint(usageHint), - usageHint + usageHint, ); return { traceId: parsed.traceId, ...resolved }; } @@ -475,7 +475,7 @@ export async function resolveTraceOrgOptionalProject( export async function resolveTraceOrgProject( parsed: ParsedTraceTarget, cwd: string, - usageHint: string + usageHint: string, ): Promise<ResolvedTraceOrgProject> { switch (parsed.type) { case "explicit": @@ -521,13 +521,13 @@ export async function resolveTraceOrgProject( async function resolveProjectSearchTarget( parsed: Extract<ParsedTraceTarget, { type: "project-search" }>, cwd: string, - usageHint: string + usageHint: string, ): Promise<ResolvedTraceOrgProject> { const target = await resolveProjectBoundTarget( parsed, cwd, commandNameFromUsageHint(usageHint), - { usageHint } + { usageHint }, ); return { traceId: parsed.traceId, @@ -551,7 +551,7 @@ async function resolveProjectSearchTarget( export async function resolveTraceOrg( parsed: ParsedTraceTarget, cwd: string, - usageHint: string + usageHint: string, ): Promise<ResolvedTraceOrg> { switch (parsed.type) { case "explicit": @@ -567,7 +567,7 @@ export async function resolveTraceOrg( parsed, cwd, commandNameFromUsageHint(usageHint), - usageHint + usageHint, ); return { traceId: parsed.traceId, org }; } @@ -628,7 +628,7 @@ export type ParsedDualModeArgs = */ export function parseDualModeArgs( args: string[], - traceUsageHint: string + traceUsageHint: string, ): ParsedDualModeArgs { if (args.length === 0) { return { mode: "project" }; diff --git a/packages/cli/src/lib/trials.ts b/packages/cli/src/lib/trials.ts index 53ca19a9e..359c4f234 100644 --- a/packages/cli/src/lib/trials.ts +++ b/packages/cli/src/lib/trials.ts @@ -78,7 +78,7 @@ for (const entry of Object.values(TRIAL_NAMES)) { */ export function findAvailableTrial( trials: ProductTrial[], - name: string + name: string, ): ProductTrial | null { const entry = TRIAL_NAMES[name]; if (!entry) { @@ -112,7 +112,7 @@ export function humanizeCategory(category: string): string { // Preserve all-caps words like "UI", "API"; title-case the rest w === w.toUpperCase() && w.length > 1 ? w - : w.charAt(0).toUpperCase() + w.slice(1) + : w.charAt(0).toUpperCase() + w.slice(1), ) .join(" "); } diff --git a/packages/cli/src/lib/upgrade.ts b/packages/cli/src/lib/upgrade.ts index b29d67ff2..9a2d56e7c 100644 --- a/packages/cli/src/lib/upgrade.ts +++ b/packages/cli/src/lib/upgrade.ts @@ -73,7 +73,7 @@ const log = logger.withTag("upgrade"); // Re-export for backward compatibility — consumers that import // InstallationMethod from upgrade.ts continue to work. export type { InstallationMethod } from "./binary.js"; -// biome-ignore lint/performance/noBarrelFile: backward-compat re-export, not a barrel +// backward-compat re-export, not a barrel export { parseInstallationMethod } from "./binary.js"; /** Package managers that can be used for global installs */ @@ -118,29 +118,29 @@ export type ResolvedUpgradeVersion = { function extractReleaseVersions( data: unknown, - source: UpgradeSource + source: UpgradeSource, ): string[] { if (source.tagPrefix ? !Array.isArray(data) : Array.isArray(data)) { throw new UpgradeError( "network_error", - "GitHub returned invalid release metadata" + "GitHub returned invalid release metadata", ); } const releases = Array.isArray(data) ? data : [data]; return releases .filter( (release): release is Record<string, unknown> => - typeof release === "object" && release !== null + typeof release === "object" && release !== null, ) .filter((release) => !(release.draft || release.prerelease)) .map((release) => release.tag_name) .filter( (tag): tag is string => - typeof tag === "string" && tag.startsWith(source.tagPrefix) + typeof tag === "string" && tag.startsWith(source.tagPrefix), ) .map((tag) => tag.slice(source.tagPrefix.length)) .map((tag) => - source.tagPrefix ? tag : tag.replace(VERSION_PREFIX_REGEX, "") + source.tagPrefix ? tag : tag.replace(VERSION_PREFIX_REGEX, ""), ) .filter((tag) => semverValid(tag) === tag && semverPrerelease(tag) === null) .sort((a, b) => compareVersions(b, a)); @@ -148,7 +148,7 @@ function extractReleaseVersions( function getNextGitHubReleasePage( response: Response, - source: UpgradeSource + source: UpgradeSource, ): string | undefined { const link = response.headers.get("link"); const match = link?.match(NEXT_PAGE_LINK_REGEX); @@ -158,14 +158,14 @@ function getNextGitHubReleasePage( if (!URL.canParse(match[1])) { throw new UpgradeError( "network_error", - "GitHub returned an invalid release pagination URL" + "GitHub returned an invalid release pagination URL", ); } const url = new URL(match[1]); const isSelectedSourcePath = url.pathname === `/repos/${source.githubRepo}/releases`; const isCanonicalRepositoryPath = CANONICAL_RELEASES_PATH_REGEX.test( - url.pathname + url.pathname, ); const page = url.searchParams.get("page"); if ( @@ -177,7 +177,7 @@ function getNextGitHubReleasePage( ) { throw new UpgradeError( "network_error", - "GitHub returned an invalid release pagination URL" + "GitHub returned an invalid release pagination URL", ); } const nextPage = new URL(getGitHubLatestReleaseUrl(source)); @@ -195,7 +195,7 @@ function getNextGitHubReleasePage( */ export function buildKnownCurlPaths( homeDir: string, - env: NodeJS.ProcessEnv + env: NodeJS.ProcessEnv, ): string[] { const paths = KNOWN_CURL_DIRS.map((dir) => join(homeDir, dir) + sep); // Honor an absolute XDG_BIN_HOME, matching determineInstallDir's precedence. @@ -266,7 +266,7 @@ export function getCurlInstallPaths(): { // Fallback to default path (for fresh installs or non-curl runs like tests) const defaultPath = join( determineInstallDir(homedir(), process.env), - getBinaryFilename() + getBinaryFilename(), ); return getBinaryPaths(defaultPath); } @@ -295,7 +295,7 @@ export function startCleanupOldBinary(): void { */ function runCommand( command: string, - args: string[] + args: string[], ): Promise<{ stdout: string; exitCode: number }> { return new Promise((resolve, reject) => { const proc = spawn(command, args, { @@ -327,7 +327,7 @@ function runCommand( * @returns true if sentry is installed globally via this package manager */ async function isInstalledWith(pm: PackageManager): Promise<boolean> { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const args = pm === "yarn" @@ -354,7 +354,7 @@ async function isInstalledWith(pm: PackageManager): Promise<boolean> { */ function isHomebrewInstall(): boolean { let execPath = process.execPath; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { execPath = realpathSync(execPath); } catch { @@ -496,7 +496,7 @@ export async function detectInstallationMethod(): Promise<InstallationMethod> { */ export async function fetchLatestFromGitHubWithSource( signal?: AbortSignal, - sources: readonly UpgradeSource[] = UPGRADE_SOURCES + sources: readonly UpgradeSource[] = UPGRADE_SOURCES, ): Promise<ResolvedUpgradeVersion> { const resolved = await resolveUpgradeSource({ getProbeUrl: getGitHubLatestReleaseUrl, @@ -510,7 +510,7 @@ export async function fetchLatestFromGitHubWithSource( const data = await parseUpgradeJson( response, signal, - "GitHub returned invalid release metadata" + "GitHub returned invalid release metadata", ); versions.push(...extractReleaseVersions(data, resolved.source)); const nextPage = getNextGitHubReleasePage(response, resolved.source); @@ -519,7 +519,7 @@ export async function fetchLatestFromGitHubWithSource( if (!version) { throw new UpgradeError( "network_error", - "No version found in GitHub release" + "No version found in GitHub release", ); } return { version, source: resolved.source }; @@ -527,19 +527,19 @@ export async function fetchLatestFromGitHubWithSource( if (visitedPages.has(nextPage)) { throw new UpgradeError( "network_error", - "GitHub returned cyclic release pagination" + "GitHub returned cyclic release pagination", ); } visitedPages.add(nextPage); response = await fetchWithUpgradeError( nextPage, { headers: getGitHubHeaders(), signal }, - "GitHub" + "GitHub", ); if (!response.ok) { throw new UpgradeError( "network_error", - `Failed to fetch from GitHub: HTTP ${response.status}` + `Failed to fetch from GitHub: HTTP ${response.status}`, ); } } @@ -548,12 +548,12 @@ export async function fetchLatestFromGitHubWithSource( /** Fetch the latest standalone CLI version from the ordered GitHub sources. */ export async function fetchLatestFromGitHub( signal?: AbortSignal, - source?: UpgradeSource + source?: UpgradeSource, ): Promise<string> { return ( await fetchLatestFromGitHubWithSource( signal, - source ? [source] : UPGRADE_SOURCES + source ? [source] : UPGRADE_SOURCES, ) ).version; } @@ -568,20 +568,20 @@ export async function fetchLatestFromNpm(): Promise<string> { const response = await fetchWithUpgradeError( `${NPM_REGISTRY_URL}/latest`, { headers: { Accept: "application/json" } }, - "npm registry" + "npm registry", ); if (!response.ok) { throw new UpgradeError( "network_error", - `Failed to fetch from npm: ${response.status}` + `Failed to fetch from npm: ${response.status}`, ); } const data = await parseUpgradeJson( response, undefined, - "npm registry returned invalid metadata" + "npm registry returned invalid metadata", ); if ( typeof data !== "object" || @@ -592,7 +592,7 @@ export async function fetchLatestFromNpm(): Promise<string> { ) { throw new UpgradeError( "network_error", - "npm registry returned invalid metadata" + "npm registry returned invalid metadata", ); } @@ -601,7 +601,7 @@ export async function fetchLatestFromNpm(): Promise<string> { function validateStableVersion( version: string | undefined, - source: string + source: string, ): string { if (!version) { throw new UpgradeError("network_error", `No version found in ${source}`); @@ -609,7 +609,7 @@ function validateStableVersion( if (semverValid(version) !== version || semverPrerelease(version) !== null) { throw new UpgradeError( "network_error", - `${source} returned an invalid stable version` + `${source} returned an invalid stable version`, ); } return version; @@ -628,7 +628,7 @@ function validateStableVersion( */ export async function fetchLatestNightlyVersionWithSource( signal?: AbortSignal, - sources: readonly UpgradeSource[] = UPGRADE_SOURCES + sources: readonly UpgradeSource[] = UPGRADE_SOURCES, ): Promise<ResolvedUpgradeVersion> { if (signal?.aborted) { throw signal.reason; @@ -643,7 +643,7 @@ export async function fetchLatestNightlyVersionWithSource( async function resolveNightlyManifest( tag: string, signal: AbortSignal | undefined, - sources: readonly UpgradeSource[] + sources: readonly UpgradeSource[], ): Promise<{ source: UpgradeSource; manifest: OciManifest }> { for (const source of sources) { try { @@ -675,12 +675,12 @@ async function resolveNightlyManifest( /** Fetch the latest nightly version from the ordered release sources. */ export async function fetchLatestNightlyVersion( signal?: AbortSignal, - source?: UpgradeSource + source?: UpgradeSource, ): Promise<string> { return ( await fetchLatestNightlyVersionWithSource( signal, - source ? [source] : UPGRADE_SOURCES + source ? [source] : UPGRADE_SOURCES, ) ).version; } @@ -699,7 +699,7 @@ export async function fetchLatestNightlyVersion( */ export function fetchLatestVersion( method: InstallationMethod, - channel: ReleaseChannel = "stable" + channel: ReleaseChannel = "stable", ): Promise<string> { if (channel === "nightly") { return fetchLatestNightlyVersion(); @@ -712,7 +712,7 @@ export function fetchLatestVersion( /** Resolve the latest version and selected source for a standalone upgrade. */ export function resolveLatestUpgradeVersion( channel: ReleaseChannel, - signal?: AbortSignal + signal?: AbortSignal, ): Promise<ResolvedUpgradeVersion> { return channel === "nightly" ? fetchLatestNightlyVersionWithSource(signal) @@ -721,13 +721,13 @@ export function resolveLatestUpgradeVersion( function validateNightlyManifestVersion( manifest: OciManifest, - expectedVersion: string + expectedVersion: string, ): void { const manifestVersion = getNightlyVersion(manifest); if (manifestVersion !== expectedVersion) { throw new UpgradeError( "network_error", - `Nightly manifest version ${manifestVersion} does not match requested version ${expectedVersion}` + `Nightly manifest version ${manifestVersion} does not match requested version ${expectedVersion}`, ); } } @@ -735,12 +735,12 @@ function validateNightlyManifestVersion( async function validatePinnedGitHubRelease( response: Response, version: string, - source: UpgradeSource + source: UpgradeSource, ): Promise<void> { const release = await parseUpgradeJson( response, undefined, - `GitHub returned invalid metadata for version ${version}` + `GitHub returned invalid metadata for version ${version}`, ); const expectedTag = `${source.tagPrefix}${version}`; if ( @@ -753,21 +753,21 @@ async function validatePinnedGitHubRelease( ) { throw new UpgradeError( "network_error", - `GitHub returned invalid metadata for version ${version}` + `GitHub returned invalid metadata for version ${version}`, ); } } /** Resolve and validate a pinned standalone version against ordered sources. */ export async function resolveExistingUpgradeVersion( - version: string + version: string, ): Promise<ResolvedUpgradeVersion | null> { try { if (isNightlyVersion(version)) { const resolved = await resolveNightlyManifest( `nightly-${version}`, undefined, - UPGRADE_SOURCES + UPGRADE_SOURCES, ); validateNightlyManifestVersion(resolved.manifest, version); return { version, source: resolved.source }; @@ -779,7 +779,7 @@ export async function resolveExistingUpgradeVersion( await validatePinnedGitHubRelease( selected.response, version, - selected.source + selected.source, ); return { version, source: selected.source }; } catch (error) { @@ -804,7 +804,7 @@ export async function resolveExistingUpgradeVersion( */ async function nightlyVersionExists( version: string, - source: UpgradeSource + source: UpgradeSource, ): Promise<boolean> { const token = await getAnonymousToken(source); try { @@ -812,7 +812,7 @@ async function nightlyVersionExists( token, `nightly-${version}`, undefined, - source + source, ); validateNightlyManifestVersion(manifest, version); return true; @@ -826,7 +826,7 @@ async function nightlyVersionExists( async function standaloneVersionExists( version: string, - source?: UpgradeSource + source?: UpgradeSource, ): Promise<boolean> { if (!isNightlyVersion(version)) { validateStableVersion(version, "Requested standalone version"); @@ -838,7 +838,7 @@ async function standaloneVersionExists( const response = await fetchWithUpgradeError( getGitHubReleaseByTagUrl(version, source), { headers: getGitHubHeaders() }, - "GitHub" + "GitHub", ); if (response.ok) { await validatePinnedGitHubRelease(response, version, source); @@ -849,7 +849,7 @@ async function standaloneVersionExists( } throw new UpgradeError( "network_error", - `Failed to fetch from GitHub: HTTP ${response.status}` + `Failed to fetch from GitHub: HTTP ${response.status}`, ); } const resolved = await resolveExistingUpgradeVersion(version); @@ -871,7 +871,7 @@ async function standaloneVersionExists( export async function versionExists( method: InstallationMethod, version: string, - source?: UpgradeSource + source?: UpgradeSource, ): Promise<boolean> { if (isNightlyVersion(version) || method === "curl" || method === "brew") { return standaloneVersionExists(version, source); @@ -882,7 +882,7 @@ export async function versionExists( const response = await fetchWithUpgradeError( `${NPM_REGISTRY_URL}/${version}`, { method: "HEAD" }, - "npm registry" + "npm registry", ); if (response.ok) { return true; @@ -892,7 +892,7 @@ export async function versionExists( } throw new UpgradeError( "network_error", - `Failed to fetch from npm: ${response.status}` + `Failed to fetch from npm: ${response.status}`, ); } @@ -922,7 +922,7 @@ function writeChunkSync(fd: number, chunk: Uint8Array): void { const n = writeSync(fd, chunk, written, chunk.byteLength - written); if (n <= 0) { throw new Error( - `writeSync returned ${n} for chunk of ${chunk.byteLength - written} bytes` + `writeSync returned ${n} for chunk of ${chunk.byteLength - written} bytes`, ); } written += n; @@ -938,13 +938,13 @@ function writeChunkSync(fd: number, chunk: Uint8Array): void { async function drainBodyToFd( body: ReadableStream<Uint8Array>, fd: number, - onBytes: (n: number) => void + onBytes: (n: number) => void, ): Promise<{ streamError: unknown; writeError: Error | undefined }> { let writeError: Error | undefined; let streamError: unknown; try { for await (const chunk of body.pipeThrough( - new DecompressionStream("gzip") + new DecompressionStream("gzip"), )) { if (writeError) { break; @@ -984,7 +984,7 @@ async function drainBodyToFd( async function streamDecompressToFile( body: ReadableStream<Uint8Array>, destPath: string, - setMessage?: SetMessage + setMessage?: SetMessage, ): Promise<void> { // Indeterminate byte counter: the decompressed size isn't known ahead of // time (Content-Length covers only the compressed stream), so we show a live @@ -996,7 +996,7 @@ async function streamDecompressToFile( // Drain the body to the fd. Awaited so we know before closeSync whether // the drain terminated with an error. const { streamError, writeError } = await drainBodyToFd(body, fd, (n) => - progress.onProgress(n) + progress.onProgress(n), ); progress.done(); @@ -1051,7 +1051,7 @@ async function downloadNightlyToPath( destPath: string, version?: string, setMessage?: SetMessage, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<void> { const token = await getAnonymousToken(source); const manifest = version @@ -1066,13 +1066,13 @@ async function downloadNightlyToPath( token, layer.digest, undefined, - source + source, ); if (!response.body) { throw new UpgradeError( "execution_failed", - "GHCR blob response had no body" + "GHCR blob response had no body", ); } await streamDecompressToFile(response.body, destPath, setMessage); @@ -1093,18 +1093,18 @@ async function downloadStableToPath( version: string, destPath: string, setMessage?: SetMessage, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<void> { const url = getBinaryDownloadUrl(version, source); const headers = getGitHubHeaders(); // Try gzip-compressed download first (~60% smaller) - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const gzResponse = await fetchWithUpgradeError( `${url}.gz`, { headers }, - "GitHub" + "GitHub", ); if (gzResponse.ok && gzResponse.body) { await streamDecompressToFile(gzResponse.body, destPath, setMessage); @@ -1120,7 +1120,7 @@ async function downloadStableToPath( if (!response.ok) { throw new UpgradeError( "execution_failed", - `Failed to download binary: HTTP ${response.status}` + `Failed to download binary: HTTP ${response.status}`, ); } @@ -1194,14 +1194,14 @@ async function waitForBinaryVisible(path: string): Promise<number> { } const delay = VERIFY_BASE_DELAY_MS * 2 ** (attempt - 1); log.debug( - `Downloaded binary not yet visible at ${path}, retrying in ${delay}ms (attempt ${attempt}/${VERIFY_MAX_ATTEMPTS})` + `Downloaded binary not yet visible at ${path}, retrying in ${delay}ms (attempt ${attempt}/${VERIFY_MAX_ATTEMPTS})`, ); await setTimeout(delay); } throw new UpgradeError( "execution_failed", `Downloaded binary is missing or empty at ${path}. ` + - "This is usually transient — rerun `sentry cli upgrade` to retry." + "This is usually transient — rerun `sentry cli upgrade` to retry.", ); } @@ -1231,13 +1231,12 @@ async function waitForBinaryVisible(path: string): Promise<number> { * @returns The downloaded binary path and lock path to release * @throws {UpgradeError} When download fails */ -// biome-ignore lint/nursery/useMaxParams: compatibility API; source preserves one selected repository across the download. export async function downloadBinaryToTemp( version: string, downloadTag?: string, offline?: OfflineMode, setMessage?: SetMessage, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<DownloadResult> { const { tempPath, lockPath } = getCurlInstallPaths(); @@ -1245,7 +1244,7 @@ export async function downloadBinaryToTemp( try { // Clean up any leftover temp file from interrupted download - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { unlinkSync(tempPath); } catch { @@ -1259,7 +1258,7 @@ export async function downloadBinaryToTemp( tempPath, !!offline, setMessage, - source + source, ); let patchBytes: number | undefined; if (deltaResult) { @@ -1271,7 +1270,7 @@ export async function downloadBinaryToTemp( ? `Cannot upgrade to ${version} in offline mode — no pre-downloaded update is available. ` + "Run `sentry cli upgrade` without `--offline` to download the update directly." : `Cannot upgrade to ${version} — the network is unavailable and no pre-downloaded update was found. ` + - "Check your internet connection and try again." + "Check your internet connection and try again.", ); } else { log.debug("Downloading full binary"); @@ -1280,7 +1279,7 @@ export async function downloadBinaryToTemp( downloadTag, tempPath, setMessage, - source + source, ); } @@ -1296,7 +1295,7 @@ export async function downloadBinaryToTemp( // Clear consumed patch cache — patches for the old version are useless // after the binary has been updated (whether via delta or full download). - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. clearPatchCache().catch(() => { /* best-effort — don't fail the upgrade if cache cleanup fails */ }); @@ -1323,13 +1322,12 @@ export async function downloadBinaryToTemp( * @param destPath - Path to write the patched binary * @returns Delta result with SHA-256 and size info, or null if delta is unavailable */ -// biome-ignore lint/nursery/useMaxParams: mirrors the established download helper while forwarding source affinity. async function tryDeltaUpgrade( version: string, destPath: string, offline?: boolean, setMessage?: SetMessage, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<DeltaResult | null> { return await attemptDeltaUpgrade( version, @@ -1337,7 +1335,7 @@ async function tryDeltaUpgrade( destPath, offline, setMessage, - source + source, ); } @@ -1348,13 +1346,12 @@ async function tryDeltaUpgrade( * @param downloadTag - Git tag override for the download URL * @param destPath - Path to write the binary */ -// biome-ignore lint/nursery/useMaxParams: internal dispatch retains the established download arguments plus source affinity. async function downloadFullBinary( version: string, downloadTag: string | undefined, destPath: string, setMessage?: SetMessage, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<void> { if (isNightlyVersion(version)) { await downloadNightlyToPath(destPath, version, setMessage, source); @@ -1363,7 +1360,7 @@ async function downloadFullBinary( downloadTag ?? version, destPath, setMessage, - source + source, ); } } @@ -1392,15 +1389,15 @@ function executeUpgradeHomebrew(): Promise<void> { reject( new UpgradeError( "execution_failed", - `brew upgrade failed with exit code ${code}` - ) + `brew upgrade failed with exit code ${code}`, + ), ); } }); proc.on("error", (err) => { reject( - new UpgradeError("execution_failed", `brew failed: ${err.message}`) + new UpgradeError("execution_failed", `brew failed: ${err.message}`), ); }); }); @@ -1415,7 +1412,7 @@ function executeUpgradeHomebrew(): Promise<void> { */ function executeUpgradePackageManager( pm: PackageManager, - version: string + version: string, ): Promise<void> { return new Promise((resolve, reject) => { const args = @@ -1437,15 +1434,15 @@ function executeUpgradePackageManager( reject( new UpgradeError( "execution_failed", - `${pm} install failed with exit code ${code}` - ) + `${pm} install failed with exit code ${code}`, + ), ); } }); proc.on("error", (err) => { reject( - new UpgradeError("execution_failed", `${pm} failed: ${err.message}`) + new UpgradeError("execution_failed", `${pm} failed: ${err.message}`), ); }); }); @@ -1469,14 +1466,14 @@ function executeUpgradePackageManager( * @returns Download result with paths (curl), or null (package manager) * @throws {UpgradeError} When method is unknown or installation fails */ -// biome-ignore lint/nursery/useMaxParams: established 4-param shape; setMessage is a defaulted spinner-progress extension +// oxlint-disable-next-line max-params -- established 4-param shape; setMessage is a defaulted spinner-progress extension export async function executeUpgrade( method: InstallationMethod, version: string, downloadTag?: string, offline?: OfflineMode, setMessage?: SetMessage, - source: UpgradeSource = PRIMARY_UPGRADE_SOURCE + source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise<DownloadResult | null> { switch (method) { case "curl": @@ -1485,7 +1482,7 @@ export async function executeUpgrade( downloadTag, offline, setMessage, - source + source, ); case "brew": await executeUpgradeHomebrew(); diff --git a/packages/cli/src/lib/utils.ts b/packages/cli/src/lib/utils.ts index 554548b5c..ae980c09d 100644 --- a/packages/cli/src/lib/utils.ts +++ b/packages/cli/src/lib/utils.ts @@ -91,7 +91,7 @@ export function slugify(name: string): string { export function getRealUsername(): string { // userInfo() can throw on systems with missing or corrupted passwd entries. let osUsername = ""; - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { osUsername = userInfo().username; } catch { diff --git a/packages/cli/src/lib/version-check.ts b/packages/cli/src/lib/version-check.ts index 34393b1c7..a535a8306 100644 --- a/packages/cli/src/lib/version-check.ts +++ b/packages/cli/src/lib/version-check.ts @@ -7,7 +7,7 @@ * subsequent runs. */ -// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import +// oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import { compare as semverCompare } from "semver"; import type { UpgradeSource } from "./binary.js"; @@ -71,7 +71,7 @@ const SUPPRESSED_CLI_SUBCOMMANDS = new Set(["setup", "fix"]); /** Global value-flag names that consume the following token as their value. */ const GLOBAL_VALUE_FLAG_NAMES = new Set( - GLOBAL_FLAGS.filter((f) => f.kind === "value").map((f) => f.name) + GLOBAL_FLAGS.filter((f) => f.kind === "value").map((f) => f.name), ); /** @@ -145,7 +145,7 @@ function cliGroupIndex(args: readonly string[]): number | undefined { */ function cliSubcommandAfterGroup( args: readonly string[], - start: number + start: number, ): string | undefined { for (let i = start + 1; i < args.length; i += 1) { const token = args[i] ?? ""; @@ -232,7 +232,7 @@ async function maybePrefetchPatches( channel: "stable" | "nightly", latestVersion: string, signal: AbortSignal, - source: UpgradeSource + source: UpgradeSource, ): Promise<void> { if (semverCompare(latestVersion, CLI_VERSION) !== 1) { return; @@ -311,7 +311,7 @@ function checkForUpdateInBackgroundImpl(): void { pendingAbortController = null; span.end(); } - } + }, ); } @@ -360,7 +360,7 @@ function canNotifyAgain(lastNotified: number | null): boolean { * subsequent invocations within the rate-limit window return null. */ function getUpdateNotificationWithCopy( - formatNotification: (latestVersion: string) => string + formatNotification: (latestVersion: string) => string, ): string | null { // Gate 1: non-TTY stderr (scripts, CI, pipes). if (!isStderrTTY()) { @@ -484,7 +484,7 @@ export function getUpdateNotification(): string | null { */ export function getErrorUpdateNotification( error: unknown, - args: string[] + args: string[], ): string | null { if (shouldSuppressNotification(args)) { return null; diff --git a/packages/cli/src/lib/wasm/binary.ts b/packages/cli/src/lib/wasm/binary.ts index 4af1f3ecd..b1b878b65 100644 --- a/packages/cli/src/lib/wasm/binary.ts +++ b/packages/cli/src/lib/wasm/binary.ts @@ -82,7 +82,7 @@ type SectionKind = { /** {@link SECTION_ORDER} keyed by section id. */ const SECTION_KINDS = new Map<number, SectionKind>( - SECTION_ORDER.map(([id, name], rank) => [id, { name, rank }]) + SECTION_ORDER.map(([id, name], rank) => [id, { name, rank }]), ); /** Magic bytes and version that open every WebAssembly module. */ @@ -148,7 +148,7 @@ export type VarUint32 = { size: number; }; -// biome-ignore-start lint/suspicious/noBitwiseOperators: LEB128 is defined in terms of bit groups +// oxlint-disable no-bitwise -- LEB128 is defined in terms of bit groups /** * Read an unsigned LEB128 32-bit integer. * @@ -165,7 +165,7 @@ export function readVarUint32(bytes: Uint8Array, offset: number): VarUint32 { const byte = bytes[offset + size]; if (byte === undefined) { throw new WasmParseError( - `truncated LEB128 integer at offset ${offset + size}` + `truncated LEB128 integer at offset ${offset + size}`, ); } // Arithmetic rather than `<<`: a 5th group shifted by 28 would overflow @@ -174,7 +174,7 @@ export function readVarUint32(bytes: Uint8Array, offset: number): VarUint32 { if ((byte & CONTINUATION_BIT) === 0) { if (value > MAX_UINT32) { throw new WasmParseError( - `LEB128 integer at offset ${offset} exceeds 32 bits` + `LEB128 integer at offset ${offset} exceeds 32 bits`, ); } return { value, size: size + 1 }; @@ -182,7 +182,7 @@ export function readVarUint32(bytes: Uint8Array, offset: number): VarUint32 { scale *= GROUP_SIZE; } throw new WasmParseError( - `LEB128 integer at offset ${offset} is longer than ${MAX_VARUINT32_BYTES} bytes` + `LEB128 integer at offset ${offset} is longer than ${MAX_VARUINT32_BYTES} bytes`, ); } @@ -202,7 +202,7 @@ export function writeVarUint32(value: number): Uint8Array { } while (remaining !== 0); return Uint8Array.from(bytes); } -// biome-ignore-end lint/suspicious/noBitwiseOperators: LEB128 is defined in terms of bit groups +// oxlint-enable no-bitwise /** * Split a module into its sections, in file order. @@ -233,7 +233,7 @@ export function parseSections(bytes: Uint8Array): WasmSection[] { const payloadEnd = offset + payloadLength; if (payloadEnd > bytes.length) { throw new WasmParseError( - `section at offset ${start} claims ${payloadLength} bytes but only ${bytes.length - offset} remain` + `section at offset ${start} claims ${payloadLength} bytes but only ${bytes.length - offset} remain`, ); } lastRank = checkSectionOrder(kind, lastRank, start); @@ -307,7 +307,7 @@ export function makeBuildIdSection(buildId: Uint8Array): WasmSection { export function makeExternalDebugInfoSection(url: string): WasmSection { return makeCustomSection( EXTERNAL_DEBUG_INFO_SECTION, - encodeByteVector(new TextEncoder().encode(url)) + encodeByteVector(new TextEncoder().encode(url)), ); } @@ -339,13 +339,13 @@ export function isNameSection(section: WasmSection): boolean { function assertWasmHeader(bytes: Uint8Array): void { if (bytes.length < WASM_HEADER_LENGTH) { throw new WasmParseError( - `too short to be a WebAssembly module (${bytes.length} bytes)` + `too short to be a WebAssembly module (${bytes.length} bytes)`, ); } for (let index = 0; index < WASM_HEADER_LENGTH; index++) { if (bytes[index] !== WASM_HEADER[index]) { throw new WasmParseError( - "not a WebAssembly module: bad magic or unsupported version" + "not a WebAssembly module: bad magic or unsupported version", ); } } @@ -367,7 +367,7 @@ function sectionKind(id: number, offset: number): SectionKind | null { const kind = SECTION_KINDS.get(id); if (kind === undefined) { throw new WasmParseError( - `unknown section id ${id} at offset ${offset}: not a section any released WebAssembly version defines` + `unknown section id ${id} at offset ${offset}: not a section any released WebAssembly version defines`, ); } return kind; @@ -386,14 +386,14 @@ function sectionKind(id: number, offset: number): SectionKind | null { function checkSectionOrder( kind: SectionKind | null, lastRank: number, - offset: number + offset: number, ): number { if (kind === null) { return lastRank; } if (kind.rank <= lastRank) { throw new WasmParseError( - `${kind.name} section at offset ${offset} is out of order or repeated` + `${kind.name} section at offset ${offset} is out of order or repeated`, ); } return kind.rank; @@ -413,7 +413,7 @@ function checkSectionOrder( */ function readCustomHeader( id: number, - payload: Uint8Array + payload: Uint8Array, ): { name?: string; contents?: Uint8Array } { if (id !== CUSTOM_SECTION_ID) { return {}; @@ -427,7 +427,7 @@ function readCustomHeader( } return { name: new TextDecoder("utf-8", { fatal: true }).decode( - payload.subarray(size, nameEnd) + payload.subarray(size, nameEnd), ), contents: payload.subarray(nameEnd), }; @@ -460,13 +460,13 @@ function encodeByteVector(bytes: Uint8Array): Uint8Array { */ function decodeByteVector( contents: Uint8Array, - sectionName: string + sectionName: string, ): Uint8Array | null { try { const { value: length, size } = readVarUint32(contents, 0); if (size + length !== contents.length) { log.debug( - `${sectionName} declares ${length} bytes but holds ${contents.length - size}` + `${sectionName} declares ${length} bytes but holds ${contents.length - size}`, ); return null; } diff --git a/packages/cli/src/lib/wasm/build-id.ts b/packages/cli/src/lib/wasm/build-id.ts index 3effc5f54..4c8e68480 100644 --- a/packages/cli/src/lib/wasm/build-id.ts +++ b/packages/cli/src/lib/wasm/build-id.ts @@ -33,7 +33,7 @@ export function formatBuildId(buildId: Uint8Array): string { * @returns The bytes, or `null` when the string is not a UUID */ export function uuidToBytes(uuid: string): Uint8Array | null { - // biome-ignore lint/plugin: a parse failure is the answer — the caller turns null into its own error + // oxlint-disable-next-line sentry-cli/no-silent-catch -- a parse failure is the answer — the caller turns null into its own error try { return new Uint8Array(UUID.parse(uuid).bytes); } catch { @@ -64,7 +64,7 @@ export function randomBuildId(): Uint8Array { * already has a good one would orphan every debug file uploaded against it. */ export function buildIdFromSections( - sections: WasmSection[] + sections: WasmSection[], ): Uint8Array | null { for (const section of sections) { if (section.name !== BUILD_ID_SECTION || !section.contents) { diff --git a/packages/cli/src/lib/wasm/split.ts b/packages/cli/src/lib/wasm/split.ts index 8afc6b151..6ca4b76d1 100644 --- a/packages/cli/src/lib/wasm/split.ts +++ b/packages/cli/src/lib/wasm/split.ts @@ -96,7 +96,7 @@ export type SplitWasmResult = { */ export function splitWasm( bytes: Uint8Array, - options: SplitWasmOptions = {} + options: SplitWasmOptions = {}, ): SplitWasmResult { let sections = parseSections(bytes); let moduleChanged = false; @@ -116,7 +116,7 @@ export function splitWasm( if (options.strip) { const stripNames = options.stripNames ?? false; const kept = sections.filter( - (section) => !isStrippable(section, stripNames) + (section) => !isStrippable(section, stripNames), ); if (kept.length !== sections.length) { sections = kept; diff --git a/packages/cli/src/lib/which.ts b/packages/cli/src/lib/which.ts index 0db662bd1..df1b28934 100644 --- a/packages/cli/src/lib/which.ts +++ b/packages/cli/src/lib/which.ts @@ -26,9 +26,9 @@ const NEWLINE_RE = /\r?\n/; */ export function whichSync( command: string, - opts?: { PATH?: string } + opts?: { PATH?: string }, ): string | null { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const isWindows = process.platform === "win32"; // If a custom PATH is provided, override it in the subprocess env. @@ -58,7 +58,7 @@ export function whichSync( encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"], env, - } + }, ); } diff --git a/packages/cli/src/lib/wrangler.ts b/packages/cli/src/lib/wrangler.ts index b713bdd81..959802df9 100644 --- a/packages/cli/src/lib/wrangler.ts +++ b/packages/cli/src/lib/wrangler.ts @@ -89,7 +89,7 @@ function hasSpotlightVar(args: readonly string[]): boolean { * inside quotes or command substitutions do not. The returned offset is where * Wrangler's `--var` must be inserted. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: this is a single-pass shell lexer; splitting its quote/escape/substitution state would obscure the transitions +// this is a single-pass shell lexer; splitting its quote/escape/substitution state would obscure the transitions function findShellCommandEnd(script: string, start: number): number { let quote: "'" | '"' | "`" | undefined; let escaped = false; @@ -150,10 +150,10 @@ function findShellCommandEnd(script: string, start: number): number { * Returns undefined when the position is inside a quote or command * substitution, which means a textual `wrangler dev` is not executable code. */ -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: same single-pass shell lexer as findShellCommandEnd, walking in the opposite direction conceptually +// same single-pass shell lexer as findShellCommandEnd, walking in the opposite direction conceptually function findShellSegmentStart( script: string, - position: number + position: number, ): number | undefined { let quote: "'" | '"' | "`" | undefined; let escaped = false; @@ -218,7 +218,7 @@ type ShellWranglerCommand = { /** Find an executable Wrangler command, excluding quoted text and arguments. */ function findShellWranglerCommand( - script: string + script: string, ): ShellWranglerCommand | undefined { for (const match of script.matchAll(WRANGLER_DEV_SHELL_GLOBAL_RE)) { const matchIndex = match.index; @@ -241,7 +241,7 @@ function findShellWranglerCommand( function injectIntoShellCommand( script: string, binding: string, - windows = false + windows = false, ): string | undefined { const command = findShellWranglerCommand(script); if (!command) { @@ -264,17 +264,17 @@ function directWranglerIndex(args: readonly string[]): number { const executable = executableName(args[0] ?? ""); if (executable === "npx" || executable === "bunx") { return args.findIndex( - (arg, index) => index > 0 && executableName(arg) === "wrangler" + (arg, index) => index > 0 && executableName(arg) === "wrangler", ); } if (PACKAGE_MANAGERS.has(executable)) { const wrapperIndex = args.findIndex((arg) => - ["dlx", "exec", "x"].includes(arg) + ["dlx", "exec", "x"].includes(arg), ); if (wrapperIndex !== -1) { return args.findIndex( (arg, index) => - index > wrapperIndex && executableName(arg) === "wrangler" + index > wrapperIndex && executableName(arg) === "wrangler", ); } } @@ -304,14 +304,14 @@ type PackageScriptInvocation = { /** Return the first non-option token at or after `start`. */ function firstNonOption( args: readonly string[], - start: number + start: number, ): string | undefined { return args.slice(start).find((arg) => !arg.startsWith("-")); } /** Extract package-script metadata from standard and shorthand invocations. */ function packageScriptInvocation( - args: readonly string[] + args: readonly string[], ): PackageScriptInvocation | undefined { const manager = executableName(args[0] ?? ""); if (!PACKAGE_MANAGERS.has(manager)) { @@ -340,7 +340,7 @@ function packageScriptInvocation( /** Read a package script without failing command startup on malformed files. */ async function readPackageScript( cwd: string, - name: string + name: string, ): Promise<string | undefined> { try { const raw = await readFile(join(cwd, "package.json"), "utf8"); @@ -356,21 +356,21 @@ async function readPackageScript( /** Whether the project has a Wrangler config or the command names one. */ async function hasWranglerConfig( cwd: string, - args: readonly string[] + args: readonly string[], ): Promise<boolean> { if ( args.some( - (arg, index) => arg === "--config" && typeof args[index + 1] === "string" + (arg, index) => arg === "--config" && typeof args[index + 1] === "string", ) || args.some( - (arg) => arg.startsWith("--config=") || WRANGLER_CONFIG_RE.test(arg) + (arg) => arg.startsWith("--config=") || WRANGLER_CONFIG_RE.test(arg), ) ) { return true; } for (const filename of WRANGLER_CONFIG_FILES) { - // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. + // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, filename)); return true; @@ -384,7 +384,7 @@ async function hasWranglerConfig( /** Try to inject into a shell string carried by `sh -c` or `cmd /c`. */ function injectIntoShellArgs( args: readonly string[], - binding: string + binding: string, ): WranglerCommandAugmentation | undefined { for (let index = 1; index < args.length; index += 1) { const shellSwitch = args[index - 1]?.toLowerCase(); @@ -395,7 +395,7 @@ function injectIntoShellArgs( const injected = injectIntoShellCommand( args[index] ?? "", binding, - shellExecutable === "cmd" || shellExecutable === "cmd.exe" + shellExecutable === "cmd" || shellExecutable === "cmd.exe", ); if (injected) { const augmented = [...args]; @@ -411,7 +411,7 @@ function injectIntoPackageScript( args: readonly string[], binding: string, invocation: PackageScriptInvocation, - script: string + script: string, ): WranglerCommandAugmentation { const unchanged = { args: [...args], injected: false }; const packageWrangler = findShellWranglerCommand(script); @@ -420,7 +420,7 @@ function injectIntoPackageScript( } if (script.slice(packageWrangler.end).trim()) { logger.warn( - `Could not inject SENTRY_SPOTLIGHT into compound package script "${invocation.name}"; run sentry local run without an explicit command to use auto-detection` + `Could not inject SENTRY_SPOTLIGHT into compound package script "${invocation.name}"; run sentry local run without an explicit command to use auto-detection`, ); return unchanged; } @@ -463,7 +463,7 @@ function injectIntoPackageScript( export async function injectWranglerSpotlightBinding( args: readonly string[], spotlightUrl: string, - cwd: string + cwd: string, ): Promise<WranglerCommandAugmentation> { const unchanged = { args: [...args], injected: false }; const packageInvocation = packageScriptInvocation(args); @@ -493,6 +493,6 @@ export async function injectWranglerSpotlightBinding( args, binding, packageInvocation, - packageScript + packageScript, ); } diff --git a/packages/cli/src/types/conversation.ts b/packages/cli/src/types/conversation.ts index 21835a8a7..23989ff11 100644 --- a/packages/cli/src/types/conversation.ts +++ b/packages/cli/src/types/conversation.ts @@ -43,8 +43,8 @@ export const ConversationListItemSchema = object({ email: nullable(string()), username: nullable(string()), ip_address: nullable(string()), - }) - ) + }), + ), ), toolNames: array(string()), toolErrors: number(), diff --git a/packages/cli/src/types/dashboard.ts b/packages/cli/src/types/dashboard.ts index 08b9dbe01..d834450e6 100644 --- a/packages/cli/src/types/dashboard.ts +++ b/packages/cli/src/types/dashboard.ts @@ -132,7 +132,7 @@ export const DashboardListItemSchema = looseObject({ object({ name: optional(string()), email: optional(string()), - }) + }), ), widgetDisplay: optional(array(string())), }); @@ -147,7 +147,7 @@ export const DashboardDetailSchema = looseObject({ object({ name: optional(string()), email: optional(string()), - }) + }), ), projects: optional(array(number())), environment: optional(array(string())), @@ -215,7 +215,7 @@ export function parseWidgetInput(raw: unknown): DashboardWidget { }); throw new ValidationError( `Invalid widget definition:\n${issues.join("\n")}`, - "widget-json" + "widget-json", ); } @@ -411,7 +411,7 @@ function isTracemetricsAggregate(aggregate: string): boolean { */ export function validateAggregateNames( aggregates: string[], - dataset?: string + dataset?: string, ): void { // tracemetrics uses a different aggregate format — validate structure, not function names if (dataset === "tracemetrics") { @@ -427,7 +427,7 @@ export function validateAggregateNames( " - metric_name: the name passed to Sentry.metrics.distribution/gauge/count\n" + " - metric_type: distribution, gauge, counter, set\n" + " - unit: none, byte, second, millisecond, etc. (must match SDK emission)", - "query" + "query", ); } } @@ -449,7 +449,7 @@ export function validateAggregateNames( `Unknown aggregate function "${fn}".\n\n` + `Valid functions: ${validFunctions.join(", ")}\n` + `Aliases (auto-resolved): ${aliasList}`, - "query" + "query", ); } } @@ -486,7 +486,7 @@ const MAX_LIMITS: Partial<Record<string, number>> = { * Clamps per-display-type limits to their maximums with a warning. */ export function prepareWidgetQueries( - inputWidget: DashboardWidget + inputWidget: DashboardWidget, ): DashboardWidget { let widget = inputWidget; // Clamp to per-display-type limit maximums @@ -498,7 +498,7 @@ export function prepareWidgetQueries( widget.limit > maxLimit ) { logger.warn( - `${widget.displayType} widgets support a maximum of ${maxLimit} rows. Clamping --limit from ${widget.limit} to ${maxLimit}.` + `${widget.displayType} widgets support a maximum of ${maxLimit} rows. Clamping --limit from ${widget.limit} to ${maxLimit}.`, ); widget = { ...widget, limit: maxLimit }; } @@ -585,7 +585,7 @@ function buildOccupiedGrid(widgets: DashboardWidget[]): { /** Check whether a rectangle fits at a position without overlapping occupied cells. */ function regionFits( occupied: Set<string>, - rect: { px: number; py: number; w: number; h: number } + rect: { px: number; py: number; w: number; h: number }, ): boolean { for (let dy = 0; dy < rect.h; dy++) { for (let dx = 0; dx < rect.w; dx++) { @@ -610,7 +610,7 @@ type WidgetSize = { w: number; h: number; minH: number }; function assignLayoutDense( widget: DashboardWidget, size: WidgetSize, - grid: OccupiedGrid + grid: OccupiedGrid, ): DashboardWidget { const { w, h, minH } = size; for (let y = 0; y <= grid.maxY; y++) { @@ -628,7 +628,7 @@ function assignLayoutDense( * Reverse-scans because the API preserves insertion order. */ function findLastLayout( - widgets: DashboardWidget[] + widgets: DashboardWidget[], ): DashboardWidgetLayout | undefined { for (let i = widgets.length - 1; i >= 0; i--) { const layout = widgets[i]?.layout; @@ -648,7 +648,7 @@ function assignLayoutSequential( widget: DashboardWidget, existingWidgets: DashboardWidget[], size: WidgetSize, - grid: OccupiedGrid + grid: OccupiedGrid, ): DashboardWidget { const { w, h, minH } = size; const lastLayout = findLastLayout(existingWidgets); @@ -688,7 +688,7 @@ function assignLayoutSequential( export function assignDefaultLayout( widget: DashboardWidget, existingWidgets: DashboardWidget[], - mode: WidgetLayoutMode = "sequential" + mode: WidgetLayoutMode = "sequential", ): DashboardWidget { if (widget.layout) { return widget; @@ -722,18 +722,18 @@ function assertLayoutInt( value: number, flag: string, min: number, - max?: number + max?: number, ): void { if (!Number.isInteger(value) || value < min) { throw new ValidationError( `--${flag} must be ${min === 0 ? "a non-negative" : "a positive"} integer (got ${value}).`, - flag + flag, ); } if (max !== undefined && value > max) { throw new ValidationError( `--${flag} must be ${min}–${max} (dashboard grid is ${GRID_COLUMNS} columns wide).`, - flag + flag, ); } } @@ -750,7 +750,7 @@ function assertLayoutInt( */ export function validateWidgetLayout( flags: WidgetLayoutFlags, - existing?: DashboardWidgetLayout + existing?: DashboardWidgetLayout, ): void { if (flags.col !== undefined) { assertLayoutInt(flags.col, "col", 0, GRID_COLUMNS - 1); @@ -775,7 +775,7 @@ export function validateWidgetLayout( ) { throw new ValidationError( `Widget overflows the grid: col(${effectiveX}) + width(${effectiveW}) = ${effectiveX + effectiveW}, but the grid is ${GRID_COLUMNS} columns wide.`, - "col" + "col", ); } } @@ -813,7 +813,7 @@ function cleanQuery(q: DashboardWidgetQuery): DashboardWidgetQuery { * @returns Widget safe for PUT (only API-accepted fields) */ export function stripWidgetServerFields( - widget: DashboardWidget + widget: DashboardWidget, ): DashboardWidget { const cleaned: DashboardWidget = { title: widget.title, @@ -897,7 +897,7 @@ export const EventsStatsSeriesSchema = looseObject({ looseObject({ fields: optional(record(string(), string())), units: optional(record(string(), nullable(string()))), - }) + }), ), }); @@ -914,7 +914,7 @@ export const EventsTableResponseSchema = object({ object({ fields: optional(record(string(), string())), units: optional(record(string(), nullable(string()))), - }) + }), ), }); @@ -1010,7 +1010,7 @@ const WIDGET_TYPE_TO_DATASET: Record<string, string> = { * @returns The API dataset string, or null if the type isn't queryable */ export function mapWidgetTypeToDataset( - widgetType: string | undefined + widgetType: string | undefined, ): string | null { if (!widgetType) { return null; diff --git a/packages/cli/src/types/feedback.ts b/packages/cli/src/types/feedback.ts index c3a69397f..916d037ff 100644 --- a/packages/cli/src/types/feedback.ts +++ b/packages/cli/src/types/feedback.ts @@ -104,41 +104,41 @@ export const FeedbackMetadataSchema = pipe( looseObject({ message: pipe(string(), description("Feedback message")), contact_email: optional( - nullable(pipe(string(), description("End-user email address"))) + nullable(pipe(string(), description("End-user email address"))), ), name: optional( - nullable(pipe(string(), description("End-user display name"))) + nullable(pipe(string(), description("End-user display name"))), ), title: optional(pipe(string(), description("Feedback title"))), value: optional(pipe(string(), description("Feedback display value"))), initial_priority: optional( pipe( number(), - description("Initial priority assigned when the feedback was created") - ) + description("Initial priority assigned when the feedback was created"), + ), ), source: optional( - nullable(pipe(string(), description("Feedback capture source"))) + nullable(pipe(string(), description("Feedback capture source"))), ), summary: optional( - nullable(pipe(string(), description("AI-generated summary"))) + nullable(pipe(string(), description("AI-generated summary"))), ), sdk: optional( pipe( object({ name: pipe(string(), description("SDK name")), name_normalized: optional( - pipe(string(), description("Normalized SDK name")) + pipe(string(), description("Normalized SDK name")), ), }), - description("SDK that captured the feedback") - ) + description("SDK that captured the feedback"), + ), ), associated_event_id: optional( - pipe(string(), description("Associated error event ID")) + pipe(string(), description("Associated error event ID")), ), }), - description("Feedback metadata") + description("Feedback metadata"), ); /** Documentation schema for a modern Feedback issue. */ @@ -147,21 +147,21 @@ export const SentryFeedbackSchema = pipe( ...SentryIssueSchema.entries, issueCategory: pipe( literal("feedback"), - description("Issue category discriminator") + description("Issue category discriminator"), ), issueType: pipe( literal("feedback"), - description("Issue type discriminator") + description("Issue type discriminator"), ), metadata: FeedbackMetadataSchema, hasSeen: optional( - pipe(boolean(), description("Whether the feedback has been read")) + pipe(boolean(), description("Whether the feedback has been read")), ), latestEventHasAttachments: optional( - pipe(boolean(), description("Whether the latest event has attachments")) + pipe(boolean(), description("Whether the latest event has attachments")), ), }), - description("Sentry User Feedback") + description("Sentry User Feedback"), ); /** Documentation schema for flattened `feedback view` JSON output. */ @@ -173,8 +173,8 @@ export const FeedbackViewOutputSchema = pipe( replayIds: pipe(array(string()), description("Related Session Replay IDs")), attachments: pipe( array(vEventAttachmentDetailsResponse), - description("Attachments on the latest feedback event") + description("Attachments on the latest feedback event"), ), }), - description("Feedback view output") + description("Feedback view output"), ); diff --git a/packages/cli/src/types/index.ts b/packages/cli/src/types/index.ts index 25ef9b045..e803731e3 100644 --- a/packages/cli/src/types/index.ts +++ b/packages/cli/src/types/index.ts @@ -1,4 +1,4 @@ -// biome-ignore-all lint/performance/noBarrelFile: intentional public API +// Intentional public API barrel. /** * Type definitions for the Sentry CLI * diff --git a/packages/cli/src/types/oauth.ts b/packages/cli/src/types/oauth.ts index acfdf6bea..b2a728305 100644 --- a/packages/cli/src/types/oauth.ts +++ b/packages/cli/src/types/oauth.ts @@ -40,7 +40,7 @@ export const TokenResponseSchema = looseObject({ id: string(), name: nullable(string()), email: nullable(string()), - }) + }), ), }); diff --git a/packages/cli/src/types/replay.ts b/packages/cli/src/types/replay.ts index aa283d113..e8415be80 100644 --- a/packages/cli/src/types/replay.ts +++ b/packages/cli/src/types/replay.ts @@ -30,7 +30,7 @@ export const ReplayGeoSchema = pipe( region: nullish(string()), subdivision: nullish(string()), }), - description("User geo metadata") + description("User geo metadata"), ); /** @@ -45,7 +45,7 @@ export const ReplayUserSchema = pipe( display_name: nullish(string()), geo: nullish(ReplayGeoSchema), }), - description("User metadata") + description("User metadata"), ); /** @@ -56,7 +56,7 @@ export const ReplayBrowserSchema = pipe( name: nullish(string()), version: nullish(string()), }), - description("Browser metadata") + description("Browser metadata"), ); /** @@ -67,7 +67,7 @@ export const ReplayOsSchema = pipe( name: nullish(string()), version: nullish(string()), }), - description("Operating system metadata") + description("Operating system metadata"), ); /** @@ -78,7 +78,7 @@ export const ReplaySdkSchema = pipe( name: nullish(string()), version: nullish(string()), }), - description("SDK metadata") + description("SDK metadata"), ); /** @@ -92,7 +92,7 @@ export const ReplayDeviceSchema = pipe( model_id: nullish(string()), name: nullish(string()), }), - description("Device metadata") + description("Device metadata"), ); /** @@ -104,7 +104,7 @@ export const ReplayOtaUpdatesSchema = pipe( runtime_version: nullish(string()), update_id: nullish(string()), }), - description("OTA update metadata") + description("OTA update metadata"), ); /** @@ -115,7 +115,7 @@ export const ReplayOtaUpdatesSchema = pipe( */ export const ReplayTagsSchema = pipe( fallback(record(string(), array(string())), {}), - description("Replay tags") + description("Replay tags"), ) as GenericSchema<unknown, ReplayTags>; /** @@ -172,7 +172,7 @@ function replayNullableBoolean(descriptionText: string) { function replayNullishObject<T extends GenericSchema>( schema: T, - descriptionText: string + descriptionText: string, ) { return pipe(nullish(schema), description(descriptionText)); } @@ -223,7 +223,7 @@ function buildReplayListItemShape< error_ids: pipe(fields.errorIds, description("Linked error IDs")), finished_at: replayNullableString("Replay finish timestamp"), has_viewed: replayNullableBoolean( - "Whether the current user has viewed the replay" + "Whether the current user has viewed the replay", ), id: pipe(string(), description("Replay ID")), info_ids: pipe(fields.infoIds, description("Linked info event IDs")), @@ -241,7 +241,7 @@ function buildReplayListItemShape< user: replayNullishObject(ReplayUserSchema, "User metadata"), warning_ids: pipe( fields.warningIds, - description("Linked warning event IDs") + description("Linked warning event IDs"), ), }; } @@ -257,7 +257,7 @@ const ReplayListItemSchemaBase = looseObject( infoIds: replayStringArrayWithFallback(), otaUpdates: replayNullishObject( ReplayOtaUpdatesSchema, - "OTA update metadata" + "OTA update metadata", ), projectId: optional(nullable(union([string(), number()]))), releases: replayStringArrayWithFallback(), @@ -265,11 +265,11 @@ const ReplayListItemSchemaBase = looseObject( traceIds: replayStringArrayWithFallback(), urls: replayStringArrayWithFallback(), warningIds: replayStringArrayWithFallback(), - }) + }), ); export const ReplayListItemSchema = pipe( ReplayListItemSchemaBase, - description("Replay list row") + description("Replay list row"), ); /** @@ -277,7 +277,7 @@ export const ReplayListItemSchema = pipe( */ export const ReplayClickSchema = pipe( record(string(), unknown()), - description("Replay click selector summary") + description("Replay click selector summary"), ); /** @@ -288,11 +288,11 @@ export const ReplayDetailsSchema = pipe( ...ReplayListItemSchemaBase.entries, clicks: pipe( optional(array(ReplayClickSchema)), - description("Replay click summaries") + description("Replay click summaries"), ), replay_type: pipe(optional(nullable(string())), description("Replay type")), }), - description("Replay details") + description("Replay details"), ); /** Envelope returned by the replay index endpoint. */ @@ -322,11 +322,11 @@ const ReplayListItemOutputSchemaBase = object( traceIds: replayStringArray(), urls: replayStringArray(), warningIds: replayStringArray(), - }) + }), ); export const ReplayListItemOutputSchema = pipe( ReplayListItemOutputSchemaBase, - description("Replay list row") + description("Replay list row"), ); /** Documentation-oriented replay detail schema used for command metadata. */ @@ -335,11 +335,11 @@ export const ReplayDetailsOutputSchema = pipe( ...ReplayListItemOutputSchemaBase.entries, clicks: pipe( optional(array(ReplayClickSchema)), - description("Replay click summaries") + description("Replay click summaries"), ), replay_type: pipe(optional(nullable(string())), description("Replay type")), }), - description("Replay details") + description("Replay details"), ); /** A summarized replay activity event extracted from recording segments. */ @@ -347,15 +347,15 @@ export const ReplayActivityEventSchema = pipe( object({ timestampMs: pipe( nullable(number()), - description("Milliseconds since UNIX epoch for the activity event") + description("Milliseconds since UNIX epoch for the activity event"), ), label: pipe(string(), description("Activity label")), details: pipe( array(string()), - description("Supplemental activity details") + description("Supplemental activity details"), ), }), - description("Summarized replay activity event") + description("Summarized replay activity event"), ); /** Related issue metadata extracted from replay-linked event IDs. */ @@ -364,18 +364,18 @@ export const ReplayRelatedIssueSchema = pipe( eventId: pipe(string(), description("Replay-linked event ID")), issueId: pipe( optional(nullable(string())), - description("Resolved issue ID") + description("Resolved issue ID"), ), shortId: pipe( optional(nullable(string())), - description("Resolved issue short ID") + description("Resolved issue short ID"), ), title: pipe( optional(nullable(string())), - description("Resolved issue title") + description("Resolved issue title"), ), }), - description("Replay-related issue") + description("Replay-related issue"), ); /** Related trace metadata extracted from replay trace IDs. */ @@ -384,22 +384,22 @@ export const ReplayRelatedTraceSchema = pipe( traceId: pipe(string(), description("Replay-linked trace ID")), errorCount: pipe( optional(nullable(number())), - description("Trace error count") + description("Trace error count"), ), logCount: pipe( optional(nullable(number())), - description("Trace log count") + description("Trace log count"), ), performanceIssueCount: pipe( optional(nullable(number())), - description("Trace performance issue count") + description("Trace performance issue count"), ), spanCount: pipe( optional(nullable(number())), - description("Trace span count") + description("Trace span count"), ), }), - description("Replay-related trace") + description("Replay-related trace"), ); /** Replay view output with related context and summarized activity. */ @@ -409,24 +409,24 @@ export const ReplayViewOutputSchema = pipe( org: pipe(string(), description("Organization slug")), activity: pipe( array(ReplayActivityEventSchema), - description("Summarized replay activity") + description("Summarized replay activity"), ), relatedIssues: pipe( array(ReplayRelatedIssueSchema), - description("Replay-related issues") + description("Replay-related issues"), ), relatedTraces: pipe( array(ReplayRelatedTraceSchema), - description("Replay-related traces") + description("Replay-related traces"), ), }), - description("Replay view output") + description("Replay view output"), ); /** Replay IDs keyed by resource identifier (issue ID, trace ID, replay ID). */ export const ReplayIdsByResourceSchema = pipe( record(string(), array(string())), - description("Replay IDs grouped by resource identifier") + description("Replay IDs grouped by resource identifier"), ); export type ReplayGeo = InferOutput<typeof ReplayGeoSchema>; diff --git a/packages/cli/src/types/seer.ts b/packages/cli/src/types/seer.ts index 3bd538a2c..a706dd945 100644 --- a/packages/cli/src/types/seer.ts +++ b/packages/cli/src/types/seer.ts @@ -195,7 +195,7 @@ export const AutofixStateSchema = looseObject({ organization_id: optional(number()), project_id: optional(number()), repos: optional(array(unknown())), - }) + }), ), codebases: optional(record(string(), CodebaseInfoSchema)), steps: optional(array(AutofixStepSchema)), @@ -264,7 +264,7 @@ export function requireAutofixRunId(state: AutofixState): string | number { const runId = state.sentry_run_id ?? state.run_id; if (runId === undefined) { throw new Error( - "Unexpected autofix response: missing both sentry_run_id and run_id." + "Unexpected autofix response: missing both sentry_run_id and run_id.", ); } return runId; @@ -278,7 +278,7 @@ type WithCauses = { key: string; causes?: RootCause[] }; * in the legacy format where causes are stored directly on the container. */ function searchContainersForRootCauses( - containers: WithCauses[] + containers: WithCauses[], ): RootCause[] | null { for (const container of containers) { // Require a non-empty causes array. An empty `causes: []` is truthy and @@ -311,7 +311,7 @@ type AgentRootCauseData = { * Maps to the existing {@link RootCause} shape for downstream compatibility. */ function searchBlocksForAgentRootCause( - blocks: WithArtifacts[] + blocks: WithArtifacts[], ): RootCause[] | null { for (const block of blocks) { if (!block.artifacts) { @@ -384,7 +384,7 @@ type WithArtifacts = { artifacts?: ArtifactEntry[] }; * Search artifacts array for a solution artifact. */ function findSolutionInArtifacts( - artifacts: ArtifactEntry[] + artifacts: ArtifactEntry[], ): SolutionArtifact | null { for (const artifact of artifacts) { if (artifact.key === "solution") { @@ -418,7 +418,7 @@ function findNoSolutionReason(artifacts: ArtifactEntry[]): string | undefined { * Search containers (blocks or steps) for a no-solution reason in artifacts. */ function searchContainersForNoSolutionReason( - containers: WithArtifacts[] + containers: WithArtifacts[], ): string | undefined { for (const container of containers) { if (container.artifacts) { @@ -440,7 +440,7 @@ function searchContainersForNoSolutionReason( * {@link searchContainersForStepLevelSolution} for the no-solution path. */ function searchContainersForStepLevelNoSolutionReason( - containers: StepWithSolution[] + containers: StepWithSolution[], ): string | undefined { for (const container of containers) { if ( @@ -458,7 +458,7 @@ function searchContainersForStepLevelNoSolutionReason( * Search an array of containers (blocks or steps) for a solution artifact. */ function searchContainersForSolution( - containers: WithArtifacts[] + containers: WithArtifacts[], ): SolutionArtifact | null { for (const container of containers) { if (container.artifacts) { @@ -494,7 +494,7 @@ type StepWithSolution = { * formatters and commands don't need changes. */ function searchContainersForStepLevelSolution( - containers: StepWithSolution[] + containers: StepWithSolution[], ): SolutionArtifact | null { for (const container of containers) { if ( @@ -540,7 +540,7 @@ export function extractSolution(state: AutofixState): SolutionArtifact | null { // Search blocks first (explorer mode / newer API) if (stateWithExtras.blocks) { const stepLevel = searchContainersForStepLevelSolution( - stateWithExtras.blocks + stateWithExtras.blocks, ); if (stepLevel) { return stepLevel; @@ -554,7 +554,7 @@ export function extractSolution(state: AutofixState): SolutionArtifact | null { // Search steps (regular autofix API) if (stateWithExtras.steps) { const stepLevel = searchContainersForStepLevelSolution( - stateWithExtras.steps + stateWithExtras.steps, ); if (stepLevel) { return stepLevel; @@ -583,7 +583,7 @@ export function extractSolution(state: AutofixState): SolutionArtifact | null { * @returns Reason string if found, undefined otherwise */ export function extractNoSolutionReason( - state: AutofixState + state: AutofixState, ): string | undefined { const stateWithExtras = state as AutofixState & { blocks?: (WithArtifacts & StepWithSolution)[]; @@ -614,7 +614,7 @@ export function extractNoSolutionReason( * the cognitive-complexity budget. */ function findNoSolutionReasonInContainers( - containers: (WithArtifacts & StepWithSolution)[] + containers: (WithArtifacts & StepWithSolution)[], ): string | undefined { const stepLevel = searchContainersForStepLevelNoSolutionReason(containers); if (stepLevel) { diff --git a/packages/cli/src/types/sentry.ts b/packages/cli/src/types/sentry.ts index e635af55d..f6cbde71e 100644 --- a/packages/cli/src/types/sentry.ts +++ b/packages/cli/src/types/sentry.ts @@ -233,93 +233,93 @@ export const SentryIssueSchema = pipe( "project", "metadata", "assignedTo", - ]) + ]), ).entries, id: pipe(string(), description("Numeric issue ID")), shortId: pipe( string(), - description("Human-readable short ID (e.g. PROJ-ABC)") + description("Human-readable short ID (e.g. PROJ-ABC)"), ), title: pipe(string(), description("Issue title")), culprit: optional( pipe( vGetOrganizationIssueResponse.entries.culprit, - description("Culprit string") - ) + description("Culprit string"), + ), ), count: optional( pipe( vGetOrganizationIssueResponse.entries.count, - description("Total event count") - ) + description("Total event count"), + ), ), userCount: optional( pipe( vGetOrganizationIssueResponse.entries.userCount, - description("Number of affected users") - ) + description("Number of affected users"), + ), ), firstSeen: optional( pipe( vGetOrganizationIssueResponse.entries.firstSeen, - description("First occurrence (ISO 8601)") - ) + description("First occurrence (ISO 8601)"), + ), ), lastSeen: optional( pipe( vGetOrganizationIssueResponse.entries.lastSeen, - description("Most recent occurrence (ISO 8601)") - ) + description("Most recent occurrence (ISO 8601)"), + ), ), level: optional( pipe( vGetOrganizationIssueResponse.entries.level, - description("Severity level") - ) + description("Severity level"), + ), ), status: optional( pipe( vGetOrganizationIssueResponse.entries.status, - description("Issue status") - ) + description("Issue status"), + ), ), permalink: optional( pipe( vGetOrganizationIssueResponse.entries.permalink, - description("URL to the issue in Sentry") - ) + description("URL to the issue in Sentry"), + ), ), project: optional( pipe( vGetOrganizationIssueResponse.entries.project, - description("Project info") - ) + description("Project info"), + ), ), metadata: optional( pipe( vGetOrganizationIssueResponse.entries.metadata, - description("Issue metadata") - ) + description("Issue metadata"), + ), ), assignedTo: optional( pipe( vGetOrganizationIssueResponse.entries.assignedTo, - description("Assigned user or team") - ) + description("Assigned user or team"), + ), ), priority: optional(pipe(string(), description("Triage priority"))), platform: optional(pipe(string(), description("Platform"))), substatus: optional( - pipe(nullable(string()), description("Issue substatus")) + pipe(nullable(string()), description("Issue substatus")), ), isUnhandled: optional( - pipe(boolean(), description("Whether the issue is unhandled")) + pipe(boolean(), description("Whether the issue is unhandled")), ), seerFixabilityScore: optional( - pipe(nullable(number()), description("Seer AI fixability score (0-1)")) + pipe(nullable(number()), description("Seer AI fixability score (0-1)")), ), }), - description("Sentry issue") + description("Sentry issue"), ); /** @@ -339,13 +339,13 @@ export const IssueViewOutputSchema = pipe( description( "Latest event for the issue (full detail). Select named fields with " + "`--fields event.id,event.title` to avoid pulling the whole payload; " + - "the `request` entry may include live session data." - ) - ) + "the `request` entry may include live session data.", + ), + ), ), org: optional(pipe(nullable(string()), description("Organization slug"))), replayIds: optional( - pipe(array(string()), description("Related Session Replay IDs")) + pipe(array(string()), description("Related Session Replay IDs")), ), trace: optional( pipe( @@ -353,16 +353,16 @@ export const IssueViewOutputSchema = pipe( object({ traceId: pipe( string(), - description("Trace ID from the latest event") + description("Trace ID from the latest event"), ), spans: pipe(array(unknown()), description("Span tree data")), - }) + }), ), - description("Trace context from the latest event's span tree") - ) + description("Trace context from the latest event's span tree"), + ), ), }), - description("Issue view output") + description("Issue view output"), ); // Event @@ -420,7 +420,7 @@ const EventViewAttachmentSchema = looseObject({ download: pipe( string(), url(), - description("Absolute authenticated API URL for the attachment bytes") + description("Absolute authenticated API URL for the attachment bytes"), ), }); @@ -440,18 +440,18 @@ export const EventViewOutputSchema = pipe( object({ traceId: pipe(string(), description("Trace ID")), spans: pipe(array(unknown()), description("Span tree data")), - }) + }), ), - description("Trace context, or null when unavailable") + description("Trace context, or null when unavailable"), ), attachments: pipe( array(EventViewAttachmentSchema), description( - "Event attachments; each includes metadata and an absolute authenticated download URL" - ) + "Event attachments; each includes metadata and an absolute authenticated download URL", + ), ), }), - description("Event view output") + description("Event view output"), ); // Issue Event (list endpoint) @@ -516,59 +516,59 @@ export const IssueEventSchema = pipe( id: pipe(string(), description("Internal event ID")), "event.type": pipe( string(), - description("Event type (error, default, transaction)") + description("Event type (error, default, transaction)"), ), groupID: optional( - pipe(_IssueEventElement.entries.groupID, description("Group (issue) ID")) + pipe(_IssueEventElement.entries.groupID, description("Group (issue) ID")), ), eventID: pipe(string(), description("UUID-format event ID")), projectID: optional( - pipe(_IssueEventElement.entries.projectID, description("Project ID")) + pipe(_IssueEventElement.entries.projectID, description("Project ID")), ), message: optional( - pipe(_IssueEventElement.entries.message, description("Event message")) + pipe(_IssueEventElement.entries.message, description("Event message")), ), title: optional( - pipe(_IssueEventElement.entries.title, description("Event title")) + pipe(_IssueEventElement.entries.title, description("Event title")), ), location: optional( pipe( _IssueEventElement.entries.location, - description("Source location (file:line)") - ) + description("Source location (file:line)"), + ), ), culprit: optional( pipe( _IssueEventElement.entries.culprit, - description("Culprit function/module") - ) + description("Culprit function/module"), + ), ), user: optional( - pipe(_IssueEventElement.entries.user, description("User context")) + pipe(_IssueEventElement.entries.user, description("User context")), ), tags: optional( - pipe(_IssueEventElement.entries.tags, description("Event tags")) + pipe(_IssueEventElement.entries.tags, description("Event tags")), ), platform: optional( pipe( _IssueEventElement.entries.platform, - description("Platform (python, javascript, etc.)") - ) + description("Platform (python, javascript, etc.)"), + ), ), dateCreated: optional( pipe( _IssueEventElement.entries.dateCreated, - description("ISO 8601 creation timestamp") - ) + description("ISO 8601 creation timestamp"), + ), ), crashFile: optional( - pipe(_IssueEventElement.entries.crashFile, description("Crash file URL")) + pipe(_IssueEventElement.entries.crashFile, description("Crash file URL")), ), metadata: optional( - pipe(_IssueEventElement.entries.metadata, description("Event metadata")) + pipe(_IssueEventElement.entries.metadata, description("Event metadata")), ), }), - description("Issue event (list endpoint)") + description("Issue event (list endpoint)"), ); // Project Keys (DSN) @@ -686,19 +686,19 @@ export const TraceMetaSchema = pipe( object({ "transaction.event_id": pipe( nullable(string()), - description("Transaction event ID") + description("Transaction event ID"), ), "count()": pipe(number(), description("Transaction child count")), - }) + }), ), - description("Per-transaction child counts") + description("Per-transaction child counts"), ), span_count_map: pipe( record(string(), number()), - description("Span counts grouped by operation") + description("Span counts grouped by operation"), ), }), - description("Trace metadata") + description("Trace metadata"), ); export type TraceMeta = InferOutput<typeof TraceMetaSchema>; @@ -947,7 +947,7 @@ export const SentryLogSchema = pipe( unknown(), transform(Number), number(), - description("Nanosecond-precision timestamp") + description("Nanosecond-precision timestamp"), ), /** Log message content */ message: optional(nullable(pipe(string(), description("Log message")))), @@ -956,16 +956,16 @@ export const SentryLogSchema = pipe( nullable( pipe( string(), - description("Severity level (error, warning, info, debug)") - ) - ) + description("Severity level (error, warning, info, debug)"), + ), + ), ), /** Trace ID for correlation with traces */ trace: optional( - nullable(pipe(string(), description("Trace ID for correlation"))) + nullable(pipe(string(), description("Trace ID for correlation"))), ), }), - description("Sentry log") + description("Sentry log"), ); export type SentryLog = InferOutput<typeof SentryLogSchema>; @@ -976,7 +976,7 @@ export const LogsResponseSchema = object({ meta: optional( looseObject({ fields: optional(record(string(), string())), - }) + }), ), }); @@ -1027,7 +1027,7 @@ export const DetailedSentryLogSchema = pipe( /** OpenTelemetry instrumentation scope name */ "sentry.otel.instrumentation_scope.name": optional(nullable(string())), }), - description("Detailed Sentry log") + description("Detailed Sentry log"), ); export type DetailedSentryLog = InferOutput<typeof DetailedSentryLogSchema>; @@ -1038,7 +1038,7 @@ export const DetailedLogsResponseSchema = object({ meta: optional( looseObject({ fields: optional(record(string(), string())), - }) + }), ), }); @@ -1117,7 +1117,7 @@ export const TraceLogSchema = pipe( /** Log message content */ message: optional(nullable(string())), }), - description("Trace log") + description("Trace log"), ); export type TraceLog = InferOutput<typeof TraceLogSchema>; @@ -1129,7 +1129,7 @@ export const TraceLogsResponseSchema = looseObject({ looseObject({ fields: optional(record(string(), string())), units: optional(record(string(), string())), - }) + }), ), }); @@ -1157,7 +1157,7 @@ export const TransactionListItemSchema = pipe( /** Project slug */ project: pipe(string(), description("Project slug")), }), - description("Transaction list item") + description("Transaction list item"), ); export type TransactionListItem = InferOutput<typeof TransactionListItemSchema>; @@ -1168,7 +1168,7 @@ export const TransactionsResponseSchema = object({ meta: optional( looseObject({ fields: optional(record(string(), string())), - }) + }), ), }); @@ -1181,27 +1181,27 @@ export const SpanListItemSchema = pipe( looseObject({ id: pipe(string(), description("Span ID")), parent_span: optional( - nullable(pipe(string(), description("Parent span ID"))) + nullable(pipe(string(), description("Parent span ID"))), ), "span.op": optional( nullable( - pipe(string(), description("Span operation (e.g. http.client, db)")) - ) + pipe(string(), description("Span operation (e.g. http.client, db)")), + ), ), description: optional( - nullable(pipe(string(), description("Span description"))) + nullable(pipe(string(), description("Span description"))), ), "span.duration": optional( - nullable(pipe(number(), description("Duration (ms)"))) + nullable(pipe(number(), description("Duration (ms)"))), ), timestamp: pipe(string(), description("Timestamp (ISO 8601)")), project: pipe(string(), description("Project slug")), transaction: optional( - nullable(pipe(string(), description("Transaction name"))) + nullable(pipe(string(), description("Transaction name"))), ), trace: pipe(string(), description("Trace ID")), }), - description("Span list item") + description("Span list item"), ); export type SpanListItem = InferOutput<typeof SpanListItemSchema>; @@ -1212,7 +1212,7 @@ export const SpansResponseSchema = object({ meta: optional( looseObject({ fields: optional(record(string(), string())), - }) + }), ), }); @@ -1237,20 +1237,20 @@ export const SentryRepositorySchema = pipe( url: pipe(nullable(string()), description("Repository URL")), provider: pipe( RepositoryProviderSchema, - description("Version control provider") + description("Version control provider"), ), status: pipe(string(), description("Integration status")), // Optional metadata dateCreated: optional( - pipe(string(), description("Creation date (ISO 8601)")) + pipe(string(), description("Creation date (ISO 8601)")), ), integrationId: optional(pipe(string(), description("Integration ID"))), externalSlug: optional( - nullable(pipe(string(), description("External slug (e.g. org/repo)"))) + nullable(pipe(string(), description("External slug (e.g. org/repo)"))), ), externalId: optional(nullable(pipe(string(), description("External ID")))), }), - description("Sentry repository") + description("Sentry repository"), ); export type SentryRepository = InferOutput<typeof SentryRepositorySchema>; @@ -1268,53 +1268,53 @@ export type SentryRepository = InferOutput<typeof SentryRepositorySchema>; export const MonitorConfigSchema = pipe( looseObject({ schedule_type: optional( - pipe(string(), description("Schedule type: 'crontab' or 'interval'")) + pipe(string(), description("Schedule type: 'crontab' or 'interval'")), ), schedule: optional( pipe( union([string(), array(union([string(), number()]))]), - description("Crontab string or [value, unit] interval tuple") - ) + description("Crontab string or [value, unit] interval tuple"), + ), ), timezone: optional( nullable( - pipe(string(), description("Schedule timezone (tz database string)")) - ) + pipe(string(), description("Schedule timezone (tz database string)")), + ), ), checkin_margin: optional( nullable( pipe( number(), - description("Allowed minutes after the expected check-in time") - ) - ) + description("Allowed minutes after the expected check-in time"), + ), + ), ), max_runtime: optional( nullable( pipe( number(), - description("Allowed minutes a check-in may run before timing out") - ) - ) + description("Allowed minutes a check-in may run before timing out"), + ), + ), ), failure_issue_threshold: optional( nullable( pipe( number(), - description("Consecutive failures before an issue is created") - ) - ) + description("Consecutive failures before an issue is created"), + ), + ), ), recovery_threshold: optional( nullable( pipe( number(), - description("Consecutive successes before an issue is resolved") - ) - ) + description("Consecutive successes before an issue is resolved"), + ), + ), ), }), - description("Monitor configuration") + description("Monitor configuration"), ); export type MonitorConfig = InferOutput<typeof MonitorConfigSchema>; @@ -1334,16 +1334,16 @@ export const SentryMonitorSchema = pipe( name: pipe(string(), description("Monitor name")), status: pipe( string(), - description("Monitor status (e.g. active, disabled)") + description("Monitor status (e.g. active, disabled)"), ), isMuted: optional( - pipe(boolean(), description("Whether the monitor is muted")) + pipe(boolean(), description("Whether the monitor is muted")), ), config: optional( - pipe(MonitorConfigSchema, description("Schedule configuration")) + pipe(MonitorConfigSchema, description("Schedule configuration")), ), dateCreated: optional( - pipe(string(), description("Creation date (ISO 8601)")) + pipe(string(), description("Creation date (ISO 8601)")), ), project: optional( pipe( @@ -1352,11 +1352,11 @@ export const SentryMonitorSchema = pipe( slug: optional(pipe(string(), description("Project slug"))), name: optional(pipe(string(), description("Project name"))), }), - description("Owning project") - ) + description("Owning project"), + ), ), }), - description("Sentry monitor") + description("Sentry monitor"), ); export type SentryMonitor = InferOutput<typeof SentryMonitorSchema>; @@ -1380,22 +1380,25 @@ export const SentryTeamSchema = looseObject({ "isMember", "teamRole", "memberCount", - ]) + ]), ).entries, id: pipe(string(), description("Team ID")), slug: pipe(string(), description("Team slug")), name: pipe(string(), description("Team name")), dateCreated: optional( - pipe(vBaseTeam.entries.dateCreated, description("Creation date (ISO 8601)")) + pipe( + vBaseTeam.entries.dateCreated, + description("Creation date (ISO 8601)"), + ), ), isMember: optional( - pipe(vBaseTeam.entries.isMember, description("Whether you are a member")) + pipe(vBaseTeam.entries.isMember, description("Whether you are a member")), ), teamRole: optional( - pipe(vBaseTeam.entries.teamRole, description("Your role in the team")) + pipe(vBaseTeam.entries.teamRole, description("Your role in the team")), ), memberCount: optional( - pipe(vBaseTeam.entries.memberCount, description("Number of members")) + pipe(vBaseTeam.entries.memberCount, description("Number of members")), ), }); @@ -1427,7 +1430,7 @@ export const ProductTrialSchema = pipe( /** Trial category (e.g., "seerUsers", "seerAutofix") */ category: pipe( string(), - description("Trial category (e.g. seerUsers, seerAutofix)") + description("Trial category (e.g. seerUsers, seerAutofix)"), ), /** ISO date when the trial started, null if not started */ startDate: pipe(nullable(string()), description("Start date (ISO 8601)")), @@ -1440,7 +1443,7 @@ export const ProductTrialSchema = pipe( /** Duration of the trial in days, null if unknown */ lengthDays: pipe(nullable(number()), description("Trial duration in days")), }), - description("Product trial") + description("Product trial"), ); export type ProductTrial = InferOutput<typeof ProductTrialSchema>; diff --git a/packages/cli/test/commands/agent-conversation/list.test.ts b/packages/cli/test/commands/agent-conversation/list.test.ts index 21a26bd6c..f8d6dae71 100644 --- a/packages/cli/test/commands/agent-conversation/list.test.ts +++ b/packages/cli/test/commands/agent-conversation/list.test.ts @@ -24,11 +24,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { @@ -38,11 +38,11 @@ vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbAuth from "../../../src/lib/db/auth.js"; import { ContextError } from "../../../src/lib/errors.js"; @@ -53,11 +53,11 @@ vi.mock("../../../src/lib/polling.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as polling from "../../../src/lib/polling.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -67,11 +67,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { @@ -81,11 +81,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; import { parsePeriod } from "../../../src/lib/time-range.js"; import type { ConversationListItem } from "../../../src/types/conversation.js"; @@ -118,13 +118,13 @@ function noop() { /** Passthrough mock for `withProgress` — bypasses spinner, calls fn directly */ function mockWithProgress( _opts: unknown, - fn: (setMessage: () => void) => unknown + fn: (setMessage: () => void) => unknown, ) { return fn(noop); } function makeConversation( - overrides: Partial<ConversationListItem> = {} + overrides: Partial<ConversationListItem> = {}, ): ConversationListItem { return { conversationId: "conv-abc-123", @@ -243,7 +243,7 @@ describe("listCommand.func", () => { // resolveOrg receives the positional org directly expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: ORG }) + expect.objectContaining({ org: ORG }), ); // listConversations called with the resolved org expect(listConversationsSpy).toHaveBeenCalledWith(ORG, expect.any(Object)); @@ -261,11 +261,11 @@ describe("listCommand.func", () => { await func.call(context, JSON_FLAGS, undefined); expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: undefined }) + expect.objectContaining({ org: undefined }), ); expect(listConversationsSpy).toHaveBeenCalledWith( "auto-org", - expect.any(Object) + expect.any(Object), ); }); @@ -276,7 +276,7 @@ describe("listCommand.func", () => { const func = await listCommand.loader(); await expect(func.call(context, HUMAN_FLAGS, undefined)).rejects.toThrow( - ContextError + ContextError, ); }); @@ -327,7 +327,7 @@ describe("listCommand.func", () => { expect(listConversationsSpy).toHaveBeenCalledWith( ORG, - expect.objectContaining({ query: "has:errors" }) + expect.objectContaining({ query: "has:errors" }), ); }); @@ -342,12 +342,12 @@ describe("listCommand.func", () => { await func.call( context, { ...JSON_FLAGS, period: parsePeriod("24h") }, - ORG + ORG, ); expect(listConversationsSpy).toHaveBeenCalledWith( ORG, - expect.objectContaining({ statsPeriod: "24h" }) + expect.objectContaining({ statsPeriod: "24h" }), ); }); @@ -363,7 +363,7 @@ describe("listCommand.func", () => { expect(listConversationsSpy).toHaveBeenCalledWith( ORG, - expect.objectContaining({ limit: 50 }) + expect.objectContaining({ limit: 50 }), ); }); @@ -456,7 +456,7 @@ describe("listCommand.func", () => { "agent-conversation-list", expect.any(String), "next", - "next-cursor" + "next-cursor", ); }); }); diff --git a/packages/cli/test/commands/agent-conversation/view.test.ts b/packages/cli/test/commands/agent-conversation/view.test.ts index 7a8a96056..57ec32e75 100644 --- a/packages/cli/test/commands/agent-conversation/view.test.ts +++ b/packages/cli/test/commands/agent-conversation/view.test.ts @@ -22,11 +22,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { @@ -36,11 +36,11 @@ vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbAuth from "../../../src/lib/db/auth.js"; vi.mock("../../../src/lib/polling.js", async (importOriginal) => { @@ -50,11 +50,11 @@ vi.mock("../../../src/lib/polling.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as polling from "../../../src/lib/polling.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -64,12 +64,12 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); import { ContextError } from "../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { AgentConversationSpan } from "../../../src/types/conversation.js"; @@ -102,13 +102,13 @@ function noop() { /** Passthrough mock for `withProgress` — bypasses spinner, calls fn directly */ function mockWithProgress( _opts: unknown, - fn: (setMessage: () => void) => unknown + fn: (setMessage: () => void) => unknown, ) { return fn(noop); } function makeSpan( - overrides: Partial<AgentConversationSpan> = {} + overrides: Partial<AgentConversationSpan> = {}, ): AgentConversationSpan { return { "gen_ai.conversation.id": CONVERSATION_ID, @@ -204,7 +204,7 @@ describe("viewCommand.func", () => { // resolveOrg is called with the explicit org from the arg expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: ORG }) + expect.objectContaining({ org: ORG }), ); // Should call getConversationSpans with the resolved org and conversation ID expect(getConversationSpansSpy).toHaveBeenCalledWith(ORG, CONVERSATION_ID); @@ -228,11 +228,11 @@ describe("viewCommand.func", () => { await func.call(context, JSON_FLAGS, CONVERSATION_ID); expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: undefined }) + expect.objectContaining({ org: undefined }), ); expect(getConversationSpansSpy).toHaveBeenCalledWith( "auto-org", - CONVERSATION_ID + CONVERSATION_ID, ); }); @@ -241,7 +241,7 @@ describe("viewCommand.func", () => { const func = await viewCommand.loader(); await expect( - func.call(context, HUMAN_FLAGS, undefined as unknown as string) + func.call(context, HUMAN_FLAGS, undefined as unknown as string), ).rejects.toThrow(ContextError); }); @@ -265,7 +265,7 @@ describe("viewCommand.func", () => { const func = await viewCommand.loader(); await expect( - func.call(context, HUMAN_FLAGS, CONVERSATION_ID) + func.call(context, HUMAN_FLAGS, CONVERSATION_ID), ).rejects.toThrow(ContextError); }); diff --git a/packages/cli/test/commands/alert/issues/create.test.ts b/packages/cli/test/commands/alert/issues/create.test.ts index b3f3e666e..d3b8df0c7 100644 --- a/packages/cli/test/commands/alert/issues/create.test.ts +++ b/packages/cli/test/commands/alert/issues/create.test.ts @@ -1,10 +1,10 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { createCommand } from "../../../../src/commands/alert/issues/create.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; import { ValidationError } from "../../../../src/lib/errors.js"; import type { ResolvedTarget } from "../../../../src/lib/resolve-target.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import { useTestConfigDir } from "../../../helpers.js"; @@ -65,7 +65,7 @@ describe("alert issues create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await expect( @@ -79,8 +79,8 @@ describe("alert issues create", () => { "dry-run": true, json: true, }, - "test-org/test-project" - ) + "test-org/test-project", + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -89,7 +89,7 @@ describe("alert issues create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await expect( @@ -103,8 +103,8 @@ describe("alert issues create", () => { "dry-run": true, json: true, }, - "test-org/test-project" - ) + "test-org/test-project", + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -114,7 +114,7 @@ describe("alert issues create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -127,7 +127,7 @@ describe("alert issues create", () => { "dry-run": true, json: true, }, - "test-org/test-project" + "test-org/test-project", ); expect(createSpy).not.toHaveBeenCalled(); @@ -139,7 +139,7 @@ describe("alert issues create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -155,11 +155,11 @@ describe("alert issues create", () => { "dry-run": true, json: true, }, - "test-org/test-project" + "test-org/test-project", ); const parsed = JSON.parse( - context.stdoutWrite.mock.calls.map((c) => c[0]).join("") + context.stdoutWrite.mock.calls.map((c) => c[0]).join(""), ); expect(parsed).toEqual({ org: "test-org", @@ -203,7 +203,7 @@ describe("alert issues create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await expect( @@ -217,8 +217,8 @@ describe("alert issues create", () => { "dry-run": true, json: true, }, - "test-org/" - ) + "test-org/", + ), ).rejects.toBeInstanceOf(ValidationError); expect(createSpy).not.toHaveBeenCalled(); }); @@ -234,7 +234,7 @@ describe("alert issues create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -247,7 +247,7 @@ describe("alert issues create", () => { "dry-run": false, json: true, }, - "test-org/test-project" + "test-org/test-project", ); expect(createSpy).toHaveBeenCalledWith("test-org", { diff --git a/packages/cli/test/commands/alert/issues/delete.test.ts b/packages/cli/test/commands/alert/issues/delete.test.ts index d3f4d173a..065652b10 100644 --- a/packages/cli/test/commands/alert/issues/delete.test.ts +++ b/packages/cli/test/commands/alert/issues/delete.test.ts @@ -1,10 +1,10 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { deleteCommand } from "../../../../src/commands/alert/issues/delete.js"; import type { IssueAlertRule } from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; import type { ResolvedTarget } from "../../../../src/lib/resolve-target.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import { useTestConfigDir } from "../../../helpers.js"; @@ -80,7 +80,7 @@ describe("alert issues delete", () => { await func.call( context, { ...defaultFlags, "dry-run": true }, - "test-org/test-project/42" + "test-org/test-project/42", ); expect(resolveSpy).toHaveBeenCalledWith( @@ -93,12 +93,12 @@ describe("alert issues delete", () => { cwd: getConfigDir(), usageHint: "sentry alert issues delete <org>/<project>/<rule-id-or-name>", - }) + }), ); expect(getRuleSpy).toHaveBeenCalledWith("test-org", "test-project", "42"); expect(deleteRuleSpy).not.toHaveBeenCalled(); expect(stdoutWrite.mock.calls.map((c) => c[0]).join("")).toContain( - "Would delete issue alert rule" + "Would delete issue alert rule", ); }); @@ -109,11 +109,11 @@ describe("alert issues delete", () => { await func.call( context, { ...defaultFlags, "dry-run": true, json: true }, - "test-org/test-project/42" + "test-org/test-project/42", ); expect( - JSON.parse(stdoutWrite.mock.calls.map((c) => c[0]).join("")) + JSON.parse(stdoutWrite.mock.calls.map((c) => c[0]).join("")), ).toEqual({ dryRun: true, org: "test-org", @@ -131,45 +131,51 @@ describe("alert issues delete", () => { await func.call( context, { ...defaultFlags, yes: true }, - "test-org/test-project/42" + "test-org/test-project/42", ); expect(getRuleSpy).toHaveBeenCalledWith("test-org", "test-project", "42"); expect(deleteRuleSpy).toHaveBeenCalledWith("test-org", "42"); expect(stdoutWrite.mock.calls.map((c) => c[0]).join("")).toContain( - "Deleted issue alert rule" + "Deleted issue alert rule", ); }); test.each([ ["--yes", { ...defaultFlags, yes: true }], ["--dry-run", { ...defaultFlags, "dry-run": true }], - ])("rejects bare rule id with %s before resolving target", async (_, flags) => { - const { context } = createContext(); - const func = await deleteCommand.loader(); - - await expect(func.call(context, flags, "42")).rejects.toThrow( - "Auto-detection is disabled for destructive operations" - ); - - expect(resolveSpy).not.toHaveBeenCalled(); - expect(getRuleSpy).not.toHaveBeenCalled(); - expect(deleteRuleSpy).not.toHaveBeenCalled(); - }); + ])( + "rejects bare rule id with %s before resolving target", + async (_, flags) => { + const { context } = createContext(); + const func = await deleteCommand.loader(); + + await expect(func.call(context, flags, "42")).rejects.toThrow( + "Auto-detection is disabled for destructive operations", + ); + + expect(resolveSpy).not.toHaveBeenCalled(); + expect(getRuleSpy).not.toHaveBeenCalled(); + expect(deleteRuleSpy).not.toHaveBeenCalled(); + }, + ); test.each([ ["--yes", { ...defaultFlags, yes: true }], ["--dry-run", { ...defaultFlags, "dry-run": true }], - ])("rejects org-all issue alert rule target with %s before resolving target", async (_, flags) => { - const { context } = createContext(); - const func = await deleteCommand.loader(); - - await expect(func.call(context, flags, "test-org//42")).rejects.toThrow( - "requires an explicit <org>/<project>/<rule-id-or-name>" - ); - - expect(resolveSpy).not.toHaveBeenCalled(); - expect(getRuleSpy).not.toHaveBeenCalled(); - expect(deleteRuleSpy).not.toHaveBeenCalled(); - }); + ])( + "rejects org-all issue alert rule target with %s before resolving target", + async (_, flags) => { + const { context } = createContext(); + const func = await deleteCommand.loader(); + + await expect(func.call(context, flags, "test-org//42")).rejects.toThrow( + "requires an explicit <org>/<project>/<rule-id-or-name>", + ); + + expect(resolveSpy).not.toHaveBeenCalled(); + expect(getRuleSpy).not.toHaveBeenCalled(); + expect(deleteRuleSpy).not.toHaveBeenCalled(); + }, + ); }); diff --git a/packages/cli/test/commands/alert/issues/edit.test.ts b/packages/cli/test/commands/alert/issues/edit.test.ts index 9bc5c3859..672340efa 100644 --- a/packages/cli/test/commands/alert/issues/edit.test.ts +++ b/packages/cli/test/commands/alert/issues/edit.test.ts @@ -1,11 +1,11 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { editCommand } from "../../../../src/commands/alert/issues/edit.js"; import type { IssueAlertRule } from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; import { ValidationError } from "../../../../src/lib/errors.js"; import type { ResolvedTarget } from "../../../../src/lib/resolve-target.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import { useTestConfigDir } from "../../../helpers.js"; @@ -75,11 +75,11 @@ describe("alert issues edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await expect( - func.call(context, { json: true }, "test-org/test-project/42") + func.call(context, { json: true }, "test-org/test-project/42"), ).rejects.toBeInstanceOf(ValidationError); }); @@ -108,7 +108,7 @@ describe("alert issues edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -120,7 +120,7 @@ describe("alert issues edit", () => { action: ['{"id":"new-action"}'], json: true, }, - "test-org/test-project/42" + "test-org/test-project/42", ); expect(putSpy).toHaveBeenCalledWith("test-org", "42", { @@ -156,13 +156,13 @@ describe("alert issues edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( context, { action: ['{"id":"new-action"}'], json: true }, - "test-org/test-project/42" + "test-org/test-project/42", ); expect(putSpy).toHaveBeenCalledWith("test-org", "42", { @@ -193,13 +193,13 @@ describe("alert issues edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( context, { status: "disabled", json: true }, - "test-org/test-project/42" + "test-org/test-project/42", ); const output = context.stdout.write.mock.calls.map((c) => c[0]).join(""); diff --git a/packages/cli/test/commands/alert/issues/list.test.ts b/packages/cli/test/commands/alert/issues/list.test.ts index c46ba99be..692998161 100644 --- a/packages/cli/test/commands/alert/issues/list.test.ts +++ b/packages/cli/test/commands/alert/issues/list.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { listCommand } from "../../../../src/commands/alert/issues/list.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../../src/lib/browser.js"; import { DEFAULT_SENTRY_URL } from "../../../../src/lib/constants.js"; import { setAuthToken } from "../../../../src/lib/db/auth.js"; @@ -30,7 +30,7 @@ type ListFlags = { type ListFunc = ( this: unknown, flags: ListFlags, - target?: string + target?: string, ) => Promise<void>; function createContext() { @@ -111,7 +111,7 @@ describe("alert issues list pagination", () => { "Content-Type": "application/json", Link: '<https://sentry.io/api/0/>; rel="next"; results="false"; cursor="0:0:0"', }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -159,7 +159,7 @@ describe("alert issues list pagination", () => { "Content-Type": "application/json", Link: '<https://sentry.io/api/0/>; rel="next"; results="true"; cursor="next:0:0"', }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -195,16 +195,16 @@ describe("alert issues list pagination", () => { limit: 30, json: false, }, - "test-org/test-project" + "test-org/test-project", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("test-org"), - "issue alert rules" + "issue alert rules", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("test-project"), - "issue alert rules" + "issue alert rules", ); }); @@ -239,12 +239,12 @@ describe("alert issues list pagination", () => { limit: 30, json: false, }, - "acme-corp" + "acme-corp", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("acme-corp"), - "issue alert rules" + "issue alert rules", ); const openedUrl = String(openInBrowserSpy.mock.calls[0]?.[0]); expect(openedUrl).not.toContain("?project="); @@ -283,16 +283,16 @@ describe("alert issues list pagination", () => { limit: 30, json: false, }, - "myproj" + "myproj", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("org-one"), - "issue alert rules" + "issue alert rules", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("myproj"), - "issue alert rules" + "issue alert rules", ); }); @@ -310,7 +310,7 @@ describe("alert issues list pagination", () => { ]); } const projectMatch = url.pathname.match( - /\/api\/0\/projects\/([^/]+)\/myproj\// + /\/api\/0\/projects\/([^/]+)\/myproj\//, ); if (projectMatch) { const org = projectMatch[1] as string; @@ -338,8 +338,8 @@ describe("alert issues list pagination", () => { limit: 30, json: false, }, - "myproj" - ) + "myproj", + ), ).rejects.toThrow("multiple organizations"); expect(openInBrowserSpy).not.toHaveBeenCalled(); @@ -393,7 +393,7 @@ describe("alert issues list pagination", () => { limit: 10, json: false, }, - "issues" + "issues", ); expect(stdout.output).toContain("issues-rule"); @@ -406,15 +406,15 @@ describe("alert issues list pagination", () => { func.call( context, { web: false, fresh: false, limit: 0, json: true }, - "test-org/test-project" - ) + "test-org/test-project", + ), ).rejects.toThrow("--limit must be at least 1"); await expect( func.call( context, { web: false, fresh: false, limit: 1001, json: true }, - "test-org/test-project" - ) + "test-org/test-project", + ), ).rejects.toThrow("--limit cannot exceed 1000"); }); @@ -441,7 +441,7 @@ describe("alert issues list pagination", () => { const req = new Request(input, init); const url = new URL(req.url); const ruleMatch = url.pathname.match( - /\/api\/0\/organizations\/([^/]+)\/workflows\// + /\/api\/0\/organizations\/([^/]+)\/workflows\//, ); if (ruleMatch) { return Response.json([rule(ruleMatch[1] as string)]); @@ -453,7 +453,7 @@ describe("alert issues list pagination", () => { ]); } const projectMatch = url.pathname.match( - /\/api\/0\/projects\/([^/]+)\/myproj\// + /\/api\/0\/projects\/([^/]+)\/myproj\//, ); if (projectMatch) { const org = projectMatch[1] as string; @@ -477,7 +477,7 @@ describe("alert issues list pagination", () => { limit: 10, json: false, }, - "myproj" + "myproj", ); expect(stdout.output).toContain("Issue alert rules from 2 projects:"); @@ -497,7 +497,7 @@ describe("alert issues list pagination", () => { const req = new Request(input, init); const url = new URL(req.url); const ruleMatch = url.pathname.match( - /\/api\/0\/organizations\/([^/]+)\/workflows\// + /\/api\/0\/organizations\/([^/]+)\/workflows\//, ); if (ruleMatch) { const org = ruleMatch[1] as string; @@ -530,7 +530,7 @@ describe("alert issues list pagination", () => { ]); } const projectMatch = url.pathname.match( - /\/api\/0\/projects\/([^/]+)\/myproj\// + /\/api\/0\/projects\/([^/]+)\/myproj\//, ); if (projectMatch) { const org = projectMatch[1] as string; @@ -554,7 +554,7 @@ describe("alert issues list pagination", () => { limit: 10, json: true, }, - "myproj" + "myproj", ); const parsed = JSON.parse(stdout.output); @@ -568,7 +568,9 @@ describe("alert issues list pagination", () => { }), ]); expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining("Failed to fetch alert rules from org-two/myproj") + expect.stringContaining( + "Failed to fetch alert rules from org-two/myproj", + ), ); }); @@ -603,8 +605,8 @@ describe("alert issues list pagination", () => { limit: 10, json: true, }, - "test-org/test-project" - ) + "test-org/test-project", + ), ).rejects.toMatchObject({ name: "ApiError", status: 403, @@ -637,7 +639,7 @@ describe("alert issues list pagination", () => { const req = new Request(input, init); const url = new URL(req.url); const ruleMatch = url.pathname.match( - /\/api\/0\/organizations\/([^/]+)\/workflows\// + /\/api\/0\/organizations\/([^/]+)\/workflows\//, ); if (ruleMatch) { const org = ruleMatch[1] as string; @@ -645,7 +647,7 @@ describe("alert issues list pagination", () => { perPageByOrg.set(org, perPage); return new Response( JSON.stringify( - Array.from({ length: perPage }, (_, i) => rule(org, i + 1)) + Array.from({ length: perPage }, (_, i) => rule(org, i + 1)), ), { status: 200, @@ -653,7 +655,7 @@ describe("alert issues list pagination", () => { "Content-Type": "application/json", Link: `<https://sentry.io/api/0/>; rel="next"; results="true"; cursor="${org}-next:0:0"`, }, - } + }, ); } @@ -666,7 +668,7 @@ describe("alert issues list pagination", () => { } const projectMatch = url.pathname.match( - /\/api\/0\/projects\/([^/]+)\/myproj\// + /\/api\/0\/projects\/([^/]+)\/myproj\//, ); if (projectMatch) { const org = projectMatch[1] as string; @@ -691,7 +693,7 @@ describe("alert issues list pagination", () => { limit: 10, json: true, }, - "myproj" + "myproj", ); const parsed = JSON.parse(stdout.output); diff --git a/packages/cli/test/commands/alert/issues/view.test.ts b/packages/cli/test/commands/alert/issues/view.test.ts index 7a48f2639..0d53ff622 100644 --- a/packages/cli/test/commands/alert/issues/view.test.ts +++ b/packages/cli/test/commands/alert/issues/view.test.ts @@ -5,13 +5,13 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { viewCommand } from "../../../../src/commands/alert/issues/view.js"; import type { IssueAlertRule } from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../../src/lib/browser.js"; import { ApiError, ValidationError } from "../../../../src/lib/errors.js"; import type { ResolvedTarget } from "../../../../src/lib/resolve-target.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import { useTestConfigDir } from "../../../helpers.js"; @@ -82,11 +82,11 @@ describe("alert issues view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; await expect( - func.call(context, { web: false, json: true }, "acme/frontend") + func.call(context, { web: false, json: true }, "acme/frontend"), ).rejects.toBeInstanceOf(ValidationError); }); @@ -97,11 +97,15 @@ describe("alert issues view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; await expect( - func.call(context, { web: false, json: true }, "test-org/test-project/42") + func.call( + context, + { web: false, json: true }, + "test-org/test-project/42", + ), ).rejects.toBeInstanceOf(ApiError); }); @@ -120,13 +124,13 @@ describe("alert issues view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( context, { web: false, json: false }, - "test-org/test-project/42" + "test-org/test-project/42", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -145,22 +149,22 @@ describe("alert issues view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( context, { web: true, json: false }, - "test-org/test-project/42" + "test-org/test-project/42", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("test-org"), - "issue alert rules" + "issue alert rules", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("test-project"), - "issue alert rules" + "issue alert rules", ); expect(resolveSpy).not.toHaveBeenCalled(); expect(getRuleSpy).not.toHaveBeenCalled(); @@ -177,14 +181,14 @@ describe("alert issues view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; const err = await func .call( context, { web: false, json: true }, - "test-org/test-project/Rule Alph" + "test-org/test-project/Rule Alph", ) .catch((e: unknown) => e); expect(err).toBeInstanceOf(ValidationError); diff --git a/packages/cli/test/commands/alert/metrics/create.test.ts b/packages/cli/test/commands/alert/metrics/create.test.ts index 11f9cce0f..a0692b72e 100644 --- a/packages/cli/test/commands/alert/metrics/create.test.ts +++ b/packages/cli/test/commands/alert/metrics/create.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { createCommand } from "../../../../src/commands/alert/metrics/create.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; import { DEFAULT_SENTRY_URL } from "../../../../src/lib/constants.js"; import { setOrgRegion } from "../../../../src/lib/db/regions.js"; @@ -45,7 +45,7 @@ describe("alert metrics create", () => { Promise.resolve({ projects: [], orgs: [{ slug, name: slug }], - }) + }), ); createSpy = vi.spyOn(apiClient, "createMetricAlertRule"); setOrgRegion("test-org", DEFAULT_SENTRY_URL); @@ -62,7 +62,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await expect( @@ -78,8 +78,8 @@ describe("alert metrics create", () => { "dry-run": true, json: true, }, - "test-org" - ) + "test-org", + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -101,7 +101,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await expect( @@ -117,8 +117,8 @@ describe("alert metrics create", () => { "dry-run": true, json: true, }, - "test-org" - ) + "test-org", + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -127,7 +127,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await expect( @@ -143,8 +143,8 @@ describe("alert metrics create", () => { "dry-run": true, json: true, }, - "test-org" - ) + "test-org", + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -153,7 +153,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -169,7 +169,7 @@ describe("alert metrics create", () => { "dry-run": true, json: true, }, - "test-org" + "test-org", ); expect(createSpy).not.toHaveBeenCalled(); @@ -195,7 +195,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -211,11 +211,11 @@ describe("alert metrics create", () => { "dry-run": true, json: true, }, - "shared" + "shared", ); const output = JSON.parse( - context.stdoutWrite.mock.calls.map((call) => call[0]).join("") + context.stdoutWrite.mock.calls.map((call) => call[0]).join(""), ); expect(output.org).toBe("project-owner"); }); @@ -225,7 +225,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -241,7 +241,7 @@ describe("alert metrics create", () => { "dry-run": true, json: true, }, - "my-org/frontend" + "my-org/frontend", ); expect(findProjectsBySlugSpy).not.toHaveBeenCalled(); @@ -253,7 +253,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -271,11 +271,11 @@ describe("alert metrics create", () => { "dry-run": true, json: true, }, - "test-org" + "test-org", ); const parsed = JSON.parse( - context.stdoutWrite.mock.calls.map((c) => c[0]).join("") + context.stdoutWrite.mock.calls.map((c) => c[0]).join(""), ); expect(parsed).toEqual({ org: "test-org", @@ -306,7 +306,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -322,7 +322,7 @@ describe("alert metrics create", () => { "dry-run": false, json: true, }, - "test-org" + "test-org", ); expect(createSpy).toHaveBeenCalledWith("test-org", { @@ -346,7 +346,7 @@ describe("alert metrics create", () => { const func = (await createCommand.loader()) as unknown as ( this: unknown, flags: CreateFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -362,7 +362,7 @@ describe("alert metrics create", () => { "dry-run": false, json: true, }, - "test-org" + "test-org", ); expect(createSpy).toHaveBeenCalledWith("test-org", { @@ -375,7 +375,7 @@ describe("alert metrics create", () => { projects: ["backend"], }); expect(context.stderr.write).toHaveBeenCalledWith( - expect.stringContaining("is_transaction:true") + expect.stringContaining("is_transaction:true"), ); }); }); diff --git a/packages/cli/test/commands/alert/metrics/delete.test.ts b/packages/cli/test/commands/alert/metrics/delete.test.ts index 8cb9d6bdc..6899e1682 100644 --- a/packages/cli/test/commands/alert/metrics/delete.test.ts +++ b/packages/cli/test/commands/alert/metrics/delete.test.ts @@ -1,9 +1,9 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { deleteCommand } from "../../../../src/commands/alert/metrics/delete.js"; import type { MetricAlertRule } from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import { useTestConfigDir } from "../../../helpers.js"; @@ -72,18 +72,18 @@ describe("alert metrics delete", () => { await func.call( context, { ...defaultFlags, "dry-run": true }, - "test-org/9" + "test-org/9", ); expect(resolveSpy).toHaveBeenCalledWith( "test-org/", getConfigDir(), - "alert metrics delete" + "alert metrics delete", ); expect(getRuleSpy).toHaveBeenCalledWith("test-org", "9"); expect(deleteRuleSpy).not.toHaveBeenCalled(); expect(stdoutWrite.mock.calls.map((c) => c[0]).join("")).toContain( - "Would delete metric alert rule" + "Would delete metric alert rule", ); }); @@ -96,7 +96,7 @@ describe("alert metrics delete", () => { expect(getRuleSpy).toHaveBeenCalledWith("test-org", "9"); expect(deleteRuleSpy).toHaveBeenCalledWith("test-org", "9"); expect(stdoutWrite.mock.calls.map((c) => c[0]).join("")).toContain( - "Deleted metric alert rule" + "Deleted metric alert rule", ); }); @@ -107,11 +107,11 @@ describe("alert metrics delete", () => { await func.call( context, { ...defaultFlags, yes: true, json: true }, - "test-org/9" + "test-org/9", ); expect( - JSON.parse(stdoutWrite.mock.calls.map((c) => c[0]).join("")) + JSON.parse(stdoutWrite.mock.calls.map((c) => c[0]).join("")), ).toEqual({ deleted: true, org: "test-org", @@ -124,16 +124,19 @@ describe("alert metrics delete", () => { test.each([ ["--yes", { ...defaultFlags, yes: true }], ["--dry-run", { ...defaultFlags, "dry-run": true }], - ])("rejects bare rule id with %s before resolving target", async (_, flags) => { - const { context } = createContext(); - const func = await deleteCommand.loader(); - - await expect(func.call(context, flags, "9")).rejects.toThrow( - "Auto-detection is disabled for destructive operations" - ); - - expect(resolveSpy).not.toHaveBeenCalled(); - expect(getRuleSpy).not.toHaveBeenCalled(); - expect(deleteRuleSpy).not.toHaveBeenCalled(); - }); + ])( + "rejects bare rule id with %s before resolving target", + async (_, flags) => { + const { context } = createContext(); + const func = await deleteCommand.loader(); + + await expect(func.call(context, flags, "9")).rejects.toThrow( + "Auto-detection is disabled for destructive operations", + ); + + expect(resolveSpy).not.toHaveBeenCalled(); + expect(getRuleSpy).not.toHaveBeenCalled(); + expect(deleteRuleSpy).not.toHaveBeenCalled(); + }, + ); }); diff --git a/packages/cli/test/commands/alert/metrics/edit.test.ts b/packages/cli/test/commands/alert/metrics/edit.test.ts index fbe45fe02..c3134f012 100644 --- a/packages/cli/test/commands/alert/metrics/edit.test.ts +++ b/packages/cli/test/commands/alert/metrics/edit.test.ts @@ -1,10 +1,10 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { editCommand } from "../../../../src/commands/alert/metrics/edit.js"; import type { MetricAlertRule } from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; import { ValidationError } from "../../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import { useTestConfigDir } from "../../../helpers.js"; @@ -70,11 +70,11 @@ describe("alert metrics edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await expect( - func.call(context, { json: true }, "test-org/9") + func.call(context, { json: true }, "test-org/9"), ).rejects.toBeInstanceOf(ValidationError); }); @@ -105,7 +105,7 @@ describe("alert metrics edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -119,7 +119,7 @@ describe("alert metrics edit", () => { trigger: ['{"alertThreshold":200,"actions":[{"id":"notify"}]}'], json: true, }, - "test-org/9" + "test-org/9", ); expect(putSpy).toHaveBeenCalledWith("test-org", "9", { @@ -164,7 +164,7 @@ describe("alert metrics edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -173,7 +173,7 @@ describe("alert metrics edit", () => { name: "Metric Rule Renamed", json: true, }, - "test-org/9" + "test-org/9", ); expect(putSpy).toHaveBeenCalledWith( @@ -183,7 +183,7 @@ describe("alert metrics edit", () => { name: "Metric Rule Renamed", query: "", triggers: [{ alertThreshold: 100 }], - }) + }), ); }); @@ -214,7 +214,7 @@ describe("alert metrics edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -223,13 +223,13 @@ describe("alert metrics edit", () => { query: "", json: true, }, - "test-org/9" + "test-org/9", ); expect(putSpy).toHaveBeenCalledWith( "test-org", "9", - expect.objectContaining({ query: "" }) + expect.objectContaining({ query: "" }), ); }); @@ -260,7 +260,7 @@ describe("alert metrics edit", () => { const func = (await editCommand.loader()) as unknown as ( this: unknown, flags: EditFlags, - arg: string + arg: string, ) => Promise<void>; await func.call( @@ -269,7 +269,7 @@ describe("alert metrics edit", () => { dataset: "transactions", json: true, }, - "test-org/9" + "test-org/9", ); expect(putSpy).toHaveBeenCalledWith( @@ -278,10 +278,10 @@ describe("alert metrics edit", () => { expect.objectContaining({ dataset: "spans", query: "environment:prod is_transaction:true", - }) + }), ); expect(context.stderr.write).toHaveBeenCalledWith( - expect.stringContaining("is_transaction:true") + expect.stringContaining("is_transaction:true"), ); }); }); diff --git a/packages/cli/test/commands/alert/metrics/list.test.ts b/packages/cli/test/commands/alert/metrics/list.test.ts index e4f9e340e..12873a310 100644 --- a/packages/cli/test/commands/alert/metrics/list.test.ts +++ b/packages/cli/test/commands/alert/metrics/list.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { listCommand } from "../../../../src/commands/alert/metrics/list.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../../src/lib/browser.js"; import { DEFAULT_SENTRY_URL } from "../../../../src/lib/constants.js"; import { setAuthToken } from "../../../../src/lib/db/auth.js"; @@ -76,7 +76,7 @@ type ListFlags = { type ListFunc = ( this: unknown, flags: ListFlags, - target?: string + target?: string, ) => Promise<void>; function createContext() { @@ -140,7 +140,7 @@ describe("alert metrics list pagination", () => { "Content-Type": "application/json", Link: '<https://sentry.io/api/0/>; rel="next"; results="false"; cursor="0:0:0"', }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -157,7 +157,7 @@ describe("alert metrics list pagination", () => { limit: 1, json: true, }, - "test-org/" + "test-org/", ); const parsed = JSON.parse(stdout.output); @@ -177,7 +177,7 @@ describe("alert metrics list pagination", () => { "Content-Type": "application/json", Link: '<https://sentry.io/api/0/>; rel="next"; results="true"; cursor="next:0:0"', }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -195,7 +195,7 @@ describe("alert metrics list pagination", () => { json: true, query: "zzz", }, - "test-org/" + "test-org/", ); const parsed = JSON.parse(stdout.output); @@ -217,12 +217,12 @@ describe("alert metrics list pagination", () => { limit: 30, json: false, }, - "test-org/" + "test-org/", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("test-org"), - "metric alert rules" + "metric alert rules", ); }); @@ -257,12 +257,12 @@ describe("alert metrics list pagination", () => { limit: 30, json: false, }, - "acme-corp" + "acme-corp", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("acme-corp"), - "metric alert rules" + "metric alert rules", ); }); @@ -299,12 +299,12 @@ describe("alert metrics list pagination", () => { limit: 30, json: false, }, - "myproj" + "myproj", ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("org-one"), - "metric alert rules" + "metric alert rules", ); }); @@ -322,7 +322,7 @@ describe("alert metrics list pagination", () => { ]); } const projectMatch = url.pathname.match( - /\/api\/0\/projects\/([^/]+)\/myproj\// + /\/api\/0\/projects\/([^/]+)\/myproj\//, ); if (projectMatch) { const org = projectMatch[1] as string; @@ -350,8 +350,8 @@ describe("alert metrics list pagination", () => { limit: 30, json: false, }, - "myproj" - ) + "myproj", + ), ).rejects.toThrow("multiple organizations"); expect(openInBrowserSpy).not.toHaveBeenCalled(); @@ -396,7 +396,7 @@ describe("alert metrics list pagination", () => { limit: 10, json: false, }, - "metrics" + "metrics", ); expect(stdout.output).toContain("metrics-ru"); @@ -421,7 +421,7 @@ describe("alert metrics list pagination", () => { const req = new Request(input, init); const url = new URL(req.url); const alertMatch = url.pathname.match( - /\/api\/0\/organizations\/([^/]+)\/detectors\// + /\/api\/0\/organizations\/([^/]+)\/detectors\//, ); if (alertMatch) { return Response.json([rule(alertMatch[1] as string)]); @@ -433,7 +433,7 @@ describe("alert metrics list pagination", () => { ]); } const projectMatch = url.pathname.match( - /\/api\/0\/projects\/([^/]+)\/myproj\// + /\/api\/0\/projects\/([^/]+)\/myproj\//, ); if (projectMatch) { const org = projectMatch[1] as string; @@ -457,7 +457,7 @@ describe("alert metrics list pagination", () => { limit: 10, json: false, }, - "myproj" + "myproj", ); expect(stdout.output).toContain("Metric alert rules from 2 organizations:"); @@ -478,7 +478,7 @@ describe("alert metrics list pagination", () => { const req = new Request(input, init); const url = new URL(req.url); const alertMatch = url.pathname.match( - /\/api\/0\/organizations\/([^/]+)\/detectors\// + /\/api\/0\/organizations\/([^/]+)\/detectors\//, ); if (alertMatch) { const org = alertMatch[1] as string; @@ -502,7 +502,7 @@ describe("alert metrics list pagination", () => { ]); } const projectMatch = url.pathname.match( - /\/api\/0\/projects\/([^/]+)\/myproj\// + /\/api\/0\/projects\/([^/]+)\/myproj\//, ); if (projectMatch) { const org = projectMatch[1] as string; @@ -526,7 +526,7 @@ describe("alert metrics list pagination", () => { limit: 10, json: true, }, - "myproj" + "myproj", ); const parsed = JSON.parse(stdout.output); @@ -540,7 +540,9 @@ describe("alert metrics list pagination", () => { }), ]); expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining("Failed to fetch metric alert rules from org-two") + expect.stringContaining( + "Failed to fetch metric alert rules from org-two", + ), ); }); @@ -568,8 +570,8 @@ describe("alert metrics list pagination", () => { limit: 10, json: true, }, - "test-org/" - ) + "test-org/", + ), ).rejects.toMatchObject({ name: "ApiError", status: 403, @@ -589,7 +591,7 @@ describe("alert metrics list pagination", () => { const req = new Request(input, init); const url = new URL(req.url); const alertMatch = url.pathname.match( - /\/api\/0\/organizations\/([^/]+)\/detectors\// + /\/api\/0\/organizations\/([^/]+)\/detectors\//, ); if (alertMatch) { const org = alertMatch[1] as string; @@ -597,7 +599,7 @@ describe("alert metrics list pagination", () => { perPageByOrg.set(org, perPage); return new Response( JSON.stringify( - Array.from({ length: perPage }, (_, i) => rule(org, i + 1)) + Array.from({ length: perPage }, (_, i) => rule(org, i + 1)), ), { status: 200, @@ -605,7 +607,7 @@ describe("alert metrics list pagination", () => { "Content-Type": "application/json", Link: `<https://sentry.io/api/0/>; rel="next"; results="true"; cursor="${org}-next:0:0"`, }, - } + }, ); } @@ -618,7 +620,7 @@ describe("alert metrics list pagination", () => { } const projectMatch = url.pathname.match( - /\/api\/0\/projects\/([^/]+)\/myproj\// + /\/api\/0\/projects\/([^/]+)\/myproj\//, ); if (projectMatch) { const org = projectMatch[1] as string; @@ -643,7 +645,7 @@ describe("alert metrics list pagination", () => { limit: 10, json: true, }, - "myproj" + "myproj", ); const parsed = JSON.parse(stdout.output); diff --git a/packages/cli/test/commands/alert/metrics/view.test.ts b/packages/cli/test/commands/alert/metrics/view.test.ts index 461a277d2..4e75390c2 100644 --- a/packages/cli/test/commands/alert/metrics/view.test.ts +++ b/packages/cli/test/commands/alert/metrics/view.test.ts @@ -5,12 +5,12 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { viewCommand } from "../../../../src/commands/alert/metrics/view.js"; import type { MetricAlertRule } from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../../src/lib/browser.js"; import { ApiError, ValidationError } from "../../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import { useTestConfigDir } from "../../../helpers.js"; @@ -74,11 +74,11 @@ describe("alert metrics view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; await expect( - func.call(context, { web: false, json: true }, "acme/") + func.call(context, { web: false, json: true }, "acme/"), ).rejects.toBeInstanceOf(ValidationError); }); @@ -89,11 +89,11 @@ describe("alert metrics view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; await expect( - func.call(context, { web: false, json: true }, "test-org/42") + func.call(context, { web: false, json: true }, "test-org/42"), ).rejects.toBeInstanceOf(ApiError); }); @@ -113,7 +113,7 @@ describe("alert metrics view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; await func.call(context, { web: false, json: false }, "test-org/9"); @@ -134,14 +134,14 @@ describe("alert metrics view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; await func.call(context, { web: true, json: false }, "test-org/9"); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining("test-org"), - "metric alert rules" + "metric alert rules", ); expect(resolveSpy).not.toHaveBeenCalled(); expect(getRuleSpy).not.toHaveBeenCalled(); @@ -158,7 +158,7 @@ describe("alert metrics view", () => { const func = (await viewCommand.loader()) as unknown as ( this: unknown, flags: ViewFlags, - arg: string + arg: string, ) => Promise<void>; const err = await func diff --git a/packages/cli/test/commands/alert/mutation-utils.test.ts b/packages/cli/test/commands/alert/mutation-utils.test.ts index 79fa4dc9b..9c1ab5f1b 100644 --- a/packages/cli/test/commands/alert/mutation-utils.test.ts +++ b/packages/cli/test/commands/alert/mutation-utils.test.ts @@ -177,13 +177,13 @@ describe("validateIssueRuleArrays", () => { test("passes for non-empty conditions", () => { expect(() => - validateIssueRuleArrays([{ id: "a" }], undefined, "conditions") + validateIssueRuleArrays([{ id: "a" }], undefined, "conditions"), ).not.toThrow(); }); test("passes for non-empty actions", () => { expect(() => - validateIssueRuleArrays(undefined, [{ id: "a" }], "actions") + validateIssueRuleArrays(undefined, [{ id: "a" }], "actions"), ).not.toThrow(); }); }); @@ -226,15 +226,12 @@ describe("normalizeMetricDataset", () => { expect(normalizeMetricDataset(" Error-Events ")).toBe("errors"); }); - test.each([ - "errors", - "sessions", - "events", - "spans", - "metrics", - ])('passes canonical value "%s" through unchanged', (value) => { - expect(normalizeMetricDataset(value)).toBe(value); - }); + test.each(["errors", "sessions", "events", "spans", "metrics"])( + 'passes canonical value "%s" through unchanged', + (value) => { + expect(normalizeMetricDataset(value)).toBe(value); + }, + ); test("lowercases unknown values but leaves them otherwise unchanged", () => { expect(normalizeMetricDataset("Unknown")).toBe("unknown"); @@ -267,18 +264,16 @@ describe("resolveMetricDataset", () => { }); }); - test.each([ - "transaction", - "transactions", - "Transactions", - " TRANSACTION ", - ])('routes "%s" to spans and adds is_transaction:true with a notice', (input) => { - const result = resolveMetricDataset(input, "environment:prod"); - expect(result.dataset).toBe("spans"); - expect(result.query).toBe("environment:prod is_transaction:true"); - expect(result.notice).toContain("spans"); - expect(result.notice).toContain("is_transaction:true"); - }); + test.each(["transaction", "transactions", "Transactions", " TRANSACTION "])( + 'routes "%s" to spans and adds is_transaction:true with a notice', + (input) => { + const result = resolveMetricDataset(input, "environment:prod"); + expect(result.dataset).toBe("spans"); + expect(result.query).toBe("environment:prod is_transaction:true"); + expect(result.notice).toContain("spans"); + expect(result.notice).toContain("is_transaction:true"); + }, + ); test("adds the filter to an empty query", () => { const result = resolveMetricDataset("transactions", ""); @@ -289,7 +284,7 @@ describe("resolveMetricDataset", () => { test("does not duplicate an existing is_transaction:true filter", () => { const result = resolveMetricDataset( "transactions", - "environment:prod is_transaction:true" + "environment:prod is_transaction:true", ); expect(result.query).toBe("environment:prod is_transaction:true"); }); @@ -304,14 +299,12 @@ describe("validateMetricDataset", () => { }); } - test.each([ - "error", - "error-events", - "METRIC", - "span", - ])('passes for alias "%s"', (dataset) => { - expect(() => validateMetricDataset(dataset)).not.toThrow(); - }); + test.each(["error", "error-events", "METRIC", "span"])( + 'passes for alias "%s"', + (dataset) => { + expect(() => validateMetricDataset(dataset)).not.toThrow(); + }, + ); test.each([ "tracemetrics", @@ -402,7 +395,7 @@ describe("validateMetricTriggers", () => { expect(() => validateMetricTriggers([ { alertThreshold: 100, actions: [{ type: "email" }] }, - ]) + ]), ).not.toThrow(); }); }); diff --git a/packages/cli/test/commands/alert/rule-resolve.test.ts b/packages/cli/test/commands/alert/rule-resolve.test.ts index 3dfa11a97..d9f6f6d61 100644 --- a/packages/cli/test/commands/alert/rule-resolve.test.ts +++ b/packages/cli/test/commands/alert/rule-resolve.test.ts @@ -125,7 +125,7 @@ describe("parseIssueRuleArg", () => { test("single slash throws ValidationError (missing rule)", () => { expect(() => parseIssueRuleArg("org/project", HINT)).toThrow( - ValidationError + ValidationError, ); }); @@ -145,7 +145,7 @@ describe("parseIssueRuleArg", () => { test("trailing slash after two slashes throws ValidationError", () => { expect(() => parseIssueRuleArg("org/project/", HINT)).toThrow( - ValidationError + ValidationError, ); }); @@ -193,7 +193,7 @@ describe("parseMetricRuleArg", () => { test("two slashes (org/project/rule) throws — metric alerts are org-scoped", () => { expect(() => parseMetricRuleArg("org/project/42", HINT)).toThrow( - ValidationError + ValidationError, ); }); }); @@ -218,13 +218,13 @@ describe("listAllIssueRulesForTarget", () => { 1, "test-org", "test-project", - { perPage: 100, cursor: undefined } + { perPage: 100, cursor: undefined }, ); expect(mockListIssueAlertsPaginated).toHaveBeenNthCalledWith( 2, "test-org", "test-project", - { perPage: 100, cursor: "cursor-1" } + { perPage: 100, cursor: "cursor-1" }, ); expect(fakeLog.warn).not.toHaveBeenCalled(); }); @@ -241,11 +241,11 @@ describe("listAllIssueRulesForTarget", () => { expect(rules).toHaveLength(MAX_PAGINATION_PAGES); expect(mockListIssueAlertsPaginated).toHaveBeenCalledTimes( - MAX_PAGINATION_PAGES + MAX_PAGINATION_PAGES, ); expect(fakeLog.warn).toHaveBeenCalledTimes(1); expect(fakeLog.warn.mock.calls[0]?.[0]).toContain( - "Pagination limit reached for issue alert rules in test-org/test-project." + "Pagination limit reached for issue alert rules in test-org/test-project.", ); }); }); @@ -267,7 +267,7 @@ describe("resolveMetricAlertRule name matching with null names", () => { const resolution = await resolveMetricAlertRule( ["test-org"], "Prod Errors", - HINT_METRIC + HINT_METRIC, ); expect(resolution.rule.id).toBe("1"); @@ -286,7 +286,7 @@ describe("resolveMetricAlertRule name matching with null names", () => { // Null names are skipped, so the ref matches nothing and we get the // domain-level "not found" error rather than a TypeError on toLowerCase(). await expect( - resolveMetricAlertRule(["test-org"], "missing", HINT_METRIC) + resolveMetricAlertRule(["test-org"], "missing", HINT_METRIC), ).rejects.not.toThrow(TypeError); }); }); @@ -313,7 +313,7 @@ describe("listAllMetricRulesForOrg", () => { { perPage: 100, cursor: undefined, - } + }, ); expect(mockListMetricAlertsPaginated).toHaveBeenNthCalledWith( 2, @@ -321,7 +321,7 @@ describe("listAllMetricRulesForOrg", () => { { perPage: 100, cursor: "cursor-1", - } + }, ); expect(fakeLog.warn).not.toHaveBeenCalled(); }); @@ -338,11 +338,11 @@ describe("listAllMetricRulesForOrg", () => { expect(rules).toHaveLength(MAX_PAGINATION_PAGES); expect(mockListMetricAlertsPaginated).toHaveBeenCalledTimes( - MAX_PAGINATION_PAGES + MAX_PAGINATION_PAGES, ); expect(fakeLog.warn).toHaveBeenCalledTimes(1); expect(fakeLog.warn.mock.calls[0]?.[0]).toContain( - "Pagination limit reached for metric alert rules in test-org." + "Pagination limit reached for metric alert rules in test-org.", ); }); }); diff --git a/packages/cli/test/commands/api.property.test.ts b/packages/cli/test/commands/api.property.test.ts index c15d1f484..142195239 100644 --- a/packages/cli/test/commands/api.property.test.ts +++ b/packages/cli/test/commands/api.property.test.ts @@ -53,7 +53,7 @@ const validMethodArb = constantFrom( "delete", "patch", "Get", - "pOsT" + "pOsT", ); /** Invalid HTTP methods */ @@ -65,7 +65,7 @@ const invalidMethodArb = constantFrom( "INVALID", "", "GETS", - "POSTING" + "POSTING", ); /** Path segments (alphanumeric with hyphens, no slashes) */ @@ -73,18 +73,18 @@ const pathSegmentArb = stringMatching(/^[a-z0-9][a-z0-9-]{0,20}[a-z0-9]$/); /** Simple endpoint paths without query strings */ const simplePathArb = array(pathSegmentArb, { minLength: 1, maxLength: 5 }).map( - (segments) => segments.join("/") + (segments) => segments.join("/"), ); /** Query string (starts with ?, contains valid chars) */ const queryStringArb = stringMatching(/^\?[a-zA-Z0-9_=&%-]{1,50}$/).map((q) => - q.length > 1 ? q : "?q=1" + q.length > 1 ? q : "?q=1", ); /** Endpoint with optional query string */ const endpointArb = tuple( simplePathArb, - oneof(constantFrom(""), queryStringArb) + oneof(constantFrom(""), queryStringArb), ).map(([path, query]) => path + query); /** Valid field key base (alphanumeric with underscores) */ @@ -93,13 +93,13 @@ const fieldKeyBaseArb = stringMatching(/^[a-zA-Z_][a-zA-Z0-9_]{0,15}$/); /** Bracket segment (alphanumeric key or empty for array push) */ const bracketSegmentArb = oneof( fieldKeyBaseArb.map((k) => `[${k}]`), - constantFrom("[]") // array push + constantFrom("[]"), // array push ); /** Valid nested field key like "user[name]" or "tags[]" */ const nestedFieldKeyArb = tuple( fieldKeyBaseArb, - array(bracketSegmentArb, { minLength: 0, maxLength: 3 }) + array(bracketSegmentArb, { minLength: 0, maxLength: 3 }), ).map(([base, brackets]) => { // Ensure empty brackets only at end const emptyIdx = brackets.indexOf("[]"); @@ -121,8 +121,8 @@ const jsonValueArb = oneof( "3.14", '"hello"', "[1,2,3]", - '{"a":1}' - ) + '{"a":1}', + ), ); /** @@ -138,7 +138,7 @@ const plainStringArb = stringMatching(/^[a-zA-Z][a-zA-Z0-9 ]{0,20}$/).filter( !/^\d+(\.\d+)?$/.test(s) && !s.startsWith('"') && !s.startsWith("[") && - !s.startsWith("{") + !s.startsWith("{"), ); describe("normalizeEndpoint properties", () => { @@ -151,7 +151,7 @@ describe("normalizeEndpoint properties", () => { expect(result.startsWith("/")).toBe(false); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -169,7 +169,7 @@ describe("normalizeEndpoint properties", () => { expect(result[queryIdx - 1]).toBe("/"); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -182,7 +182,7 @@ describe("normalizeEndpoint properties", () => { // Query string should be preserved exactly expect(result.endsWith(query)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -193,7 +193,7 @@ describe("normalizeEndpoint properties", () => { const twice = normalizeEndpoint(once); expect(twice).toBe(once); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -208,7 +208,7 @@ describe("normalizeEndpoint properties", () => { expect(resultWith).toBe(resultWithout); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -223,7 +223,7 @@ describe("normalizeEndpoint properties", () => { expect(resultWith).toBe(resultWithout); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -233,7 +233,7 @@ describe("normalizeEndpoint properties", () => { const result = normalizeEndpoint(endpoint); expect(result.startsWith("api/0/")).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -245,10 +245,10 @@ describe("parseMethod properties", () => { const result = parseMethod(method); expect(result).toBe(method.toUpperCase() as typeof result); expect(["GET", "POST", "PUT", "DELETE", "PATCH"]).toContain( - result as string + result as string, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -257,7 +257,7 @@ describe("parseMethod properties", () => { property(invalidMethodArb, (method) => { expect(() => parseMethod(method)).toThrow(/Invalid method/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -267,7 +267,7 @@ describe("parseMethod properties", () => { const result = parseMethod(method); expect(result).toBe((result as string).toUpperCase() as typeof result); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -281,7 +281,7 @@ describe("parseFieldKey properties", () => { const expectedBase = key.split("[")[0]; expect(segments[0]).toBe(expectedBase); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -293,10 +293,10 @@ describe("parseFieldKey properties", () => { await fcAssert( property(fieldKeyBaseArb, (key) => { expect(() => parseFieldKey(`[${key}]`)).toThrow( - /Invalid field key format/ + /Invalid field key format/, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -307,7 +307,7 @@ describe("parseFieldKey properties", () => { const bracketCount = (key.match(/\[/g) || []).length; expect(segments.length).toBe(bracketCount + 1); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -320,20 +320,20 @@ describe("setNestedValue security properties", () => { // __proto__ as base key expect(() => setNestedValue(obj, "__proto__", "value")).toThrow( - /"__proto__" is not allowed/ + /"__proto__" is not allowed/, ); // __proto__ in brackets expect(() => setNestedValue(obj, `${key}[__proto__]`, "value")).toThrow( - /"__proto__" is not allowed/ + /"__proto__" is not allowed/, ); // __proto__ deeply nested expect(() => - setNestedValue(obj, `${key}[nested][__proto__]`, "value") + setNestedValue(obj, `${key}[nested][__proto__]`, "value"), ).toThrow(/"__proto__" is not allowed/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -343,14 +343,14 @@ describe("setNestedValue security properties", () => { const obj: Record<string, unknown> = {}; expect(() => setNestedValue(obj, "constructor", "value")).toThrow( - /"constructor" is not allowed/ + /"constructor" is not allowed/, ); expect(() => - setNestedValue(obj, `${key}[constructor]`, "value") + setNestedValue(obj, `${key}[constructor]`, "value"), ).toThrow(/"constructor" is not allowed/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -360,14 +360,14 @@ describe("setNestedValue security properties", () => { const obj: Record<string, unknown> = {}; expect(() => setNestedValue(obj, "prototype", "value")).toThrow( - /"prototype" is not allowed/ + /"prototype" is not allowed/, ); expect(() => setNestedValue(obj, `${key}[prototype]`, "value")).toThrow( - /"prototype" is not allowed/ + /"prototype" is not allowed/, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -401,7 +401,7 @@ describe("setNestedValue behavior properties", () => { setNestedValue(obj, key, value); expect(obj[key]).toBe(value); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -416,9 +416,9 @@ describe("setNestedValue behavior properties", () => { expect(obj[base]).toBeDefined(); expect(typeof obj[base]).toBe("object"); expect((obj[base] as Record<string, unknown>)[nested]).toBe(value); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -431,7 +431,7 @@ describe("setNestedValue behavior properties", () => { expect(Array.isArray(obj[key])).toBe(true); expect((obj[key] as unknown[])[0]).toBe(value); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -449,9 +449,9 @@ describe("setNestedValue behavior properties", () => { expect(arr.length).toBe(2); expect(arr[0]).toBe(value1); expect(arr[1]).toBe(value2); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -460,10 +460,10 @@ describe("setNestedValue behavior properties", () => { property(tuple(fieldKeyBaseArb, fieldKeyBaseArb), ([base, end]) => { const obj: Record<string, unknown> = {}; expect(() => setNestedValue(obj, `${base}[][${end}]`, "value")).toThrow( - /empty brackets \[\] can only appear at the end/ + /empty brackets \[\] can only appear at the end/, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -476,7 +476,7 @@ describe("parseFieldValue properties", () => { const expected = JSON.parse(jsonStr); expect(result).toEqual(expected); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -486,7 +486,7 @@ describe("parseFieldValue properties", () => { const result = parseFieldValue(str); expect(result).toBe(str); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -509,7 +509,7 @@ describe("parseFieldValue properties", () => { /** Arbitrary JSON object with string values (for request bodies) */ const jsonObjectArb = dictionary(fieldKeyBaseArb, plainStringArb).filter( - (d) => Object.keys(d).length > 0 + (d) => Object.keys(d).length > 0, ); /** JSON objects stringified */ @@ -523,12 +523,12 @@ const jsonArrayStringArb = array(jsonValue(), { /** Valid key=value field string */ const keyValueFieldArb = tuple(fieldKeyBaseArb, plainStringArb).map( - ([k, v]) => `${k}=${v}` + ([k, v]) => `${k}=${v}`, ); /** Non-JSON, non-key=value field string (has no '=' and doesn't start with {/[) */ const bareFieldArb = stringMatching(/^[a-zA-Z][a-zA-Z0-9]{1,15}$/).filter( - (s) => !(s.includes("=") || s.startsWith("{") || s.startsWith("[")) + (s) => !(s.includes("=") || s.startsWith("{") || s.startsWith("[")), ); /** @@ -561,7 +561,7 @@ describe("property: normalizeFields JSON guard", () => { }); expect(output).toBe(""); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -574,7 +574,7 @@ describe("property: normalizeFields JSON guard", () => { }); expect(output).toBe(""); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -586,7 +586,7 @@ describe("property: parseDataBody", () => { const result = parseDataBody(JSON.stringify(obj)); expect(result).toEqual(obj); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -596,7 +596,7 @@ describe("property: parseDataBody", () => { const result = parseDataBody(s); expect(result).toBe(s); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -613,9 +613,9 @@ describe("property: extractJsonBody", () => { expect(result.remaining).toEqual(fields); }); expect(output).toBe(""); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -629,7 +629,7 @@ describe("property: extractJsonBody", () => { }); expect(output).toContain("request body"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -638,7 +638,7 @@ describe("property: extractJsonBody", () => { property( tuple( oneof(jsonObjectStringArb, keyValueFieldArb, bareFieldArb), - array(keyValueFieldArb, { minLength: 0, maxLength: 3 }) + array(keyValueFieldArb, { minLength: 0, maxLength: 3 }), ), ([first, rest]) => { const fields = [first, ...rest]; @@ -648,9 +648,9 @@ describe("property: extractJsonBody", () => { const bodyCount = result.body !== undefined ? 1 : 0; expect(remainingCount + bodyCount).toBe(fields.length); }); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -659,7 +659,7 @@ describe("property: extractJsonBody", () => { property(jsonObjectStringArb, jsonObjectStringArb, (a, b) => { expect(() => extractJsonBody([a, b])).toThrow(ValidationError); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -671,13 +671,13 @@ describe("property: buildFromFields", () => { const output = captureStderr(() => { // GET with a JSON field: should NOT extract body (throws instead) expect(() => buildFromFields("GET", { "raw-field": [json] })).toThrow( - ValidationError + ValidationError, ); }); // No hint emitted (extraction skipped for GET) expect(output).toBe(""); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -690,7 +690,7 @@ describe("property: buildFromFields", () => { expect(result.params).toBeUndefined(); }); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -727,9 +727,9 @@ describe("property: buildFromFields", () => { expect(body).toHaveProperty(k); } }); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -748,12 +748,12 @@ describe("property: buildFromFields", () => { buildFromFields("PUT", { "raw-field": [jsonStr], field: [`${key}=${fieldVal}`], - }) + }), ).toThrow(ValidationError); }); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -768,9 +768,9 @@ describe("property: buildFromFields", () => { }); // No JSON hint — only colon-correction warnings might appear expect(output).not.toContain("request body"); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -783,12 +783,12 @@ describe("property: resolveBody", () => { asyncProperty(jsonObjectStringArb, async (json) => { const result = await resolveBody( { method: "PUT", data: json }, - MOCK_STDIN + MOCK_STDIN, ); expect(result.body).toBeDefined(); expect(result.params).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -798,11 +798,11 @@ describe("property: resolveBody", () => { await expect( resolveBody( { method: "PUT", data: json, input: "file.json" }, - MOCK_STDIN - ) + MOCK_STDIN, + ), ).rejects.toThrow(ValidationError); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -815,12 +815,12 @@ describe("property: resolveBody", () => { await expect( resolveBody( { method: "PUT", data: json, field: [field] }, - MOCK_STDIN - ) + MOCK_STDIN, + ), ).rejects.toThrow(ValidationError); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -831,21 +831,21 @@ describe("property: resolveBody", () => { /** Arbitrary for clean API endpoint paths (no query string, for dry-run tests) */ const dryRunEndpointArb = stringMatching( - /^[a-z][a-z0-9-]*\/[a-z0-9-]*\/$/ + /^[a-z][a-z0-9-]*\/[a-z0-9-]*\/$/, ).filter((s) => s.length > 3 && s.length < 80); /** Arbitrary for query param values */ const paramValueArb = stringMatching(/^[a-zA-Z0-9_-]+$/).filter( - (s) => s.length > 0 && s.length < 40 + (s) => s.length > 0 && s.length < 40, ); /** Arbitrary for query param maps */ const paramsArb = dictionary( stringMatching(/^[a-zA-Z][a-zA-Z0-9_]*$/).filter( - (s) => s.length > 0 && s.length < 20 + (s) => s.length > 0 && s.length < 20, ), paramValueArb, - { minKeys: 0, maxKeys: 3 } + { minKeys: 0, maxKeys: 3 }, ); describe("property: resolveRequestUrl", () => { @@ -855,7 +855,7 @@ describe("property: resolveRequestUrl", () => { const url = resolveRequestUrl(endpoint); expect(url).toContain("/api/0/"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -868,7 +868,7 @@ describe("property: resolveRequestUrl", () => { : endpoint; expect(url).toContain(normalized); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -883,7 +883,7 @@ describe("property: resolveRequestUrl", () => { } } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -905,7 +905,7 @@ describe("property: resolveEffectiveHeaders", () => { expect(result[key]).toBe(value); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -919,7 +919,7 @@ describe("property: resolveEffectiveHeaders", () => { const result = resolveEffectiveHeaders(custom, body); expect(result["Content-Type"]).toBe("application/json"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -932,7 +932,7 @@ describe("property: resolveEffectiveHeaders", () => { expect(result["Content-Type"]).toBeUndefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -940,14 +940,14 @@ describe("property: resolveEffectiveHeaders", () => { const contentTypeArb = constantFrom( "text/plain", "text/xml", - "application/x-www-form-urlencoded" + "application/x-www-form-urlencoded", ); fcAssert( property(contentTypeArb, jsonValue(), (ct, body) => { const result = resolveEffectiveHeaders({ "Content-Type": ct }, body); expect(result["Content-Type"]).toBe(ct); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/commands/api.test.ts b/packages/cli/test/commands/api.test.ts index bba18c3b6..80f8c8649 100644 --- a/packages/cli/test/commands/api.test.ts +++ b/packages/cli/test/commands/api.test.ts @@ -1,4 +1,4 @@ -// biome-ignore-all lint/performance/useTopLevelRegex: regex in test assertions is fine +// Regexes in test assertions need no shared top-level state. /** * API Command Unit Tests * @@ -7,7 +7,7 @@ import { writeFile } from "node:fs/promises"; import { Readable } from "node:stream"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { @@ -67,27 +67,27 @@ describe("normalizeEndpoint edge cases", () => { describe("normalizeEndpoint: api/0/ prefix stripping (CLI-K1)", () => { test("strips api/0/ prefix to avoid doubled path", () => { expect(normalizeEndpoint("api/0/projects/my-org/my-project/")).toBe( - "projects/my-org/my-project/" + "projects/my-org/my-project/", ); }); test("strips /api/0/ prefix with leading slash", () => { expect(normalizeEndpoint("/api/0/projects/my-org/my-project/")).toBe( - "projects/my-org/my-project/" + "projects/my-org/my-project/", ); }); test("strips api/0/ prefix and adds trailing slash", () => { expect(normalizeEndpoint("api/0/organizations/my-org/issues")).toBe( - "organizations/my-org/issues/" + "organizations/my-org/issues/", ); }); test("strips api/0/ prefix with query string", () => { expect( normalizeEndpoint( - "api/0/projects/my-org/my-proj/environments/?visibility=visible" - ) + "api/0/projects/my-org/my-proj/environments/?visibility=visible", + ), ).toBe("projects/my-org/my-proj/environments/?visibility=visible"); }); @@ -99,14 +99,14 @@ describe("normalizeEndpoint: api/0/ prefix stripping (CLI-K1)", () => { test("strips regional origins from absolute URLs", () => { expect( normalizeEndpoint( - "https://de.sentry.io/sentry/api/0/projects/my-org/my-project/events/abc/attachments/1/?download=1" - ) + "https://de.sentry.io/sentry/api/0/projects/my-org/my-project/events/abc/attachments/1/?download=1", + ), ).toBe("projects/my-org/my-project/events/abc/attachments/1/?download=1"); }); test("does not strip partial api/ prefix", () => { expect(normalizeEndpoint("api/1/organizations/")).toBe( - "api/1/organizations/" + "api/1/organizations/", ); }); }); @@ -152,7 +152,7 @@ describe("apiCommand tags org from the path", () => { "dry-run": true, json: false, }, - "projects/acme/web/events/" + "projects/acme/web/events/", ); expect(setTagSpy).toHaveBeenCalledWith("sentry.org", "acme"); @@ -167,13 +167,13 @@ describe("normalizeEndpoint: path traversal hardening (#350)", () => { test("rejects leading ../ traversal", () => { expect(() => normalizeEndpoint("../../admin/settings/")).toThrow( - /path traversal/ + /path traversal/, ); }); test("rejects mid-path traversal", () => { expect(() => normalizeEndpoint("organizations/my-org/../admin/")).toThrow( - /path traversal/ + /path traversal/, ); }); @@ -183,46 +183,46 @@ describe("normalizeEndpoint: path traversal hardening (#350)", () => { test("rejects traversal before normalizing an absolute URL", () => { expect(() => - normalizeEndpoint("https://sentry.io/api/0/projects/acme/%2e%2e/admin/") + normalizeEndpoint("https://sentry.io/api/0/projects/acme/%2e%2e/admin/"), ).toThrow(/path traversal/); }); test("allows single dots in paths", () => { expect(normalizeEndpoint("organizations/.well-known/")).toBe( - "organizations/.well-known/" + "organizations/.well-known/", ); }); test("allows double dots inside segment names", () => { expect(normalizeEndpoint("organizations/my..org/")).toBe( - "organizations/my..org/" + "organizations/my..org/", ); }); test("rejects control characters in endpoint", () => { expect(() => normalizeEndpoint("organizations/\x00admin/")).toThrow( - /Invalid/ + /Invalid/, ); }); test("strips newlines from multi-line pasted endpoints (CLI-FR)", () => { expect( normalizeEndpoint( - "organizations/my-org/issues/?\n environment=Production&project=123" - ) + "organizations/my-org/issues/?\n environment=Production&project=123", + ), ).toBe("organizations/my-org/issues/?environment=Production&project=123"); }); test("strips carriage returns and surrounding indentation", () => { expect(normalizeEndpoint("organizations/my-org/\r\n issues/")).toBe( - "organizations/my-org/issues/" + "organizations/my-org/issues/", ); }); test("preserves tabs within segments (not line-break related)", () => { // Tabs without adjacent line breaks are control chars — rejected expect(() => normalizeEndpoint("organizations/\tmy-org/")).toThrow( - /Invalid/ + /Invalid/, ); }); }); @@ -230,16 +230,16 @@ describe("normalizeEndpoint: path traversal hardening (#350)", () => { describe("parseFieldKey error cases", () => { test("throws for invalid format with unmatched brackets", () => { expect(() => parseFieldKey("user[name")).toThrow( - /Invalid field key format/ + /Invalid field key format/, ); expect(() => parseFieldKey("user]name[")).toThrow( - /Invalid field key format/ + /Invalid field key format/, ); }); test("throws for nested brackets", () => { expect(() => parseFieldKey("user[[name]]")).toThrow( - /Invalid field key format/ + /Invalid field key format/, ); }); @@ -257,7 +257,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "user", "John"); expect(() => setNestedValue(obj, "user[name]", "Jane")).toThrow( - /expected map type under "user", got string/ + /expected map type under "user", got string/, ); }); @@ -265,7 +265,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "count", 42); expect(() => setNestedValue(obj, "count[value]", 100)).toThrow( - /expected map type under "count", got number/ + /expected map type under "count", got number/, ); }); @@ -273,7 +273,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "tags", "foo"); expect(() => setNestedValue(obj, "tags[]", "bar")).toThrow( - /expected array type under "tags", got string/ + /expected array type under "tags", got string/, ); }); @@ -281,7 +281,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "tags[name]", "foo"); expect(() => setNestedValue(obj, "tags[]", "bar")).toThrow( - /expected array type under "tags", got map/ + /expected array type under "tags", got map/, ); }); @@ -289,7 +289,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "items[]", "first"); expect(() => setNestedValue(obj, "items[key]", "value")).toThrow( - /expected map type under "items", got array/ + /expected map type under "items", got array/, ); }); @@ -304,7 +304,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "flag", true); expect(() => setNestedValue(obj, "flag[value]", "test")).toThrow( - /expected map type under "flag", got boolean/ + /expected map type under "flag", got boolean/, ); }); @@ -312,7 +312,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "empty", null); expect(() => setNestedValue(obj, "empty[value]", "test")).toThrow( - /expected map type under "empty", got/ + /expected map type under "empty", got/, ); }); @@ -320,7 +320,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "flag", false); expect(() => setNestedValue(obj, "flag[]", "item")).toThrow( - /expected array type under "flag", got boolean/ + /expected array type under "flag", got boolean/, ); }); @@ -328,7 +328,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "empty", null); expect(() => setNestedValue(obj, "empty[]", "item")).toThrow( - /expected array type under "empty", got/ + /expected array type under "empty", got/, ); }); @@ -336,7 +336,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "a[b][c]", "value"); expect(() => setNestedValue(obj, "a[b][c][d]", "nested")).toThrow( - /expected map type under "a\[b\]\[c\]", got string/ + /expected map type under "a\[b\]\[c\]", got string/, ); }); @@ -344,7 +344,7 @@ describe("setNestedValue type conflicts", () => { const obj: Record<string, unknown> = {}; setNestedValue(obj, "a[b][]", "item"); expect(() => setNestedValue(obj, "a[b][key]", "value")).toThrow( - /expected map type under "a\[b\]", got array/ + /expected map type under "a\[b\]", got array/, ); }); }); @@ -397,7 +397,7 @@ describe("parseFields", () => { "user[name]=John", "user[roles][]=admin", "user[roles][]=editor", - ]) + ]), ).toEqual({ user: { name: "John", roles: ["admin", "editor"] }, }); @@ -432,14 +432,14 @@ describe("parseFields", () => { test("handles overwriting array with object", () => { // First create array, then try to treat it as object - should throw expect(() => parseFields(["items[]=first", "items[key]=value"])).toThrow( - /expected map type/ + /expected map type/, ); }); test("handles overwriting object with array", () => { // First create object, then try to treat it as array - should throw expect(() => parseFields(["items[key]=value", "items[]=item"])).toThrow( - /expected array type/ + /expected array type/, ); }); }); @@ -614,7 +614,7 @@ describe("parseHeaders", () => { test("parses multiple headers", () => { expect( - parseHeaders(["Content-Type: application/json", "Accept: text/plain"]) + parseHeaders(["Content-Type: application/json", "Accept: text/plain"]), ).toEqual({ "Content-Type": "application/json", Accept: "text/plain", @@ -665,7 +665,7 @@ describe("buildQueryParams", () => { count: "42", active: "true", value: "null", - } + }, ); }); @@ -677,7 +677,7 @@ describe("buildQueryParams", () => { expect(() => buildQueryParams(["invalid"])).toThrow(ValidationError); expect(() => buildQueryParams(["invalid"])).toThrow(/Invalid field format/); expect(() => buildQueryParams(["no-equals"])).toThrow( - /Invalid field format/ + /Invalid field format/, ); }); @@ -699,7 +699,7 @@ describe("buildQueryParams", () => { test("handles arrays of objects by JSON stringifying each element", () => { expect( - buildQueryParams(['filters=[{"key":"value"},{"key2":"value2"}]']) + buildQueryParams(['filters=[{"key":"value"},{"key2":"value2"}]']), ).toEqual({ filters: ['{"key":"value"}', '{"key2":"value2"}'], }); @@ -713,16 +713,16 @@ describe("buildQueryParams", () => { test("throws for empty key", () => { expect(() => buildQueryParams(["=value"])).toThrow( - /Invalid field key format/ + /Invalid field key format/, ); }); test("throws for invalid key format with unmatched brackets", () => { expect(() => buildQueryParams(["key[=value"])).toThrow( - /Invalid field key format/ + /Invalid field key format/, ); expect(() => buildQueryParams(["key]=value"])).toThrow( - /Invalid field key format/ + /Invalid field key format/, ); }); @@ -744,7 +744,7 @@ describe("buildRawQueryParams", () => { test("keeps JSON-like values as raw strings (no parsing)", () => { expect( - buildRawQueryParams(["data=[1,2,3]", 'obj={"key":"value"}']) + buildRawQueryParams(["data=[1,2,3]", 'obj={"key":"value"}']), ).toEqual({ data: "[1,2,3]", obj: '{"key":"value"}', @@ -770,13 +770,13 @@ describe("buildRawQueryParams", () => { test("throws ValidationError for invalid field format without equals", () => { expect(() => buildRawQueryParams(["invalid"])).toThrow(ValidationError); expect(() => buildRawQueryParams(["invalid"])).toThrow( - /Invalid field format/ + /Invalid field format/, ); }); test("throws for empty key", () => { expect(() => buildRawQueryParams(["=value"])).toThrow( - /key cannot be empty/ + /key cannot be empty/, ); }); @@ -797,7 +797,7 @@ describe("buildQueryParamsFromFields", () => { test("builds params from typed fields only", () => { expect( - buildQueryParamsFromFields(["status=resolved", "count=10"], undefined) + buildQueryParamsFromFields(["status=resolved", "count=10"], undefined), ).toEqual({ status: "resolved", count: "10", @@ -806,7 +806,7 @@ describe("buildQueryParamsFromFields", () => { test("builds params from raw fields only", () => { expect( - buildQueryParamsFromFields(undefined, ["name=test", "value=raw"]) + buildQueryParamsFromFields(undefined, ["name=test", "value=raw"]), ).toEqual({ name: "test", value: "raw", @@ -828,7 +828,7 @@ describe("buildQueryParamsFromFields", () => { test("typed fields parse JSON, raw fields do not", () => { expect( - buildQueryParamsFromFields(["arr=[1,2,3]"], ["raw=[1,2,3]"]) + buildQueryParamsFromFields(["arr=[1,2,3]"], ["raw=[1,2,3]"]), ).toEqual({ arr: ["1", "2", "3"], // typed: parsed as JSON array, stringified raw: "[1,2,3]", // raw: kept as literal string @@ -918,7 +918,7 @@ describe("prepareRequestOptions", () => { const result = prepareRequestOptions( "GET", ["status=resolved"], - ["raw=value"] + ["raw=value"], ); expect(result.params).toEqual({ status: "resolved", raw: "value" }); expect(result.body).toBeUndefined(); @@ -934,7 +934,7 @@ describe("prepareRequestOptions", () => { const result = prepareRequestOptions( "GET", ["value=123"], // typed: parsed as number, stringified back - ["value=raw-string"] // raw: kept as-is, overrides typed + ["value=raw-string"], // raw: kept as-is, overrides typed ); expect(result.params).toEqual({ value: "raw-string" }); }); @@ -998,7 +998,7 @@ describe("buildBodyFromFields", () => { describe("formatApiResponse", () => { test("formats JSON object with pretty-printing", () => { expect(formatApiResponse({ key: "value", num: 42 })).toBe( - '{\n "key": "value",\n "num": 42\n}' + '{\n "key": "value",\n "num": 42\n}', ); }); @@ -1008,7 +1008,7 @@ describe("formatApiResponse", () => { test("formats string directly without JSON quoting", () => { expect(formatApiResponse("plain text response")).toBe( - "plain text response" + "plain text response", ); }); @@ -1052,7 +1052,7 @@ describe("formatBinaryErrorBody", () => { const summary = formatBinaryErrorBody( 500, new Headers(), - new Uint8Array(0) + new Uint8Array(0), ); expect(summary).toContain("unknown"); expect(summary).toContain("0 bytes"); @@ -1065,7 +1065,7 @@ describe("resolveApiResponseOutput", () => { test("silent + success returns undefined (no body)", () => { const out = resolveApiResponseOutput( { status: 200, headers: new Headers(), body: "ok" }, - { silent: true, isTTY: false } + { silent: true, isTTY: false }, ); expect(out).toBeUndefined(); }); @@ -1074,7 +1074,7 @@ describe("resolveApiResponseOutput", () => { try { resolveApiResponseOutput( { status: 500, headers: new Headers(), body: "boom" }, - { silent: true, isTTY: false } + { silent: true, isTTY: false }, ); throw new Error("expected throw"); } catch (error) { @@ -1087,7 +1087,7 @@ describe("resolveApiResponseOutput", () => { try { resolveApiResponseOutput( { status: 404, headers: new Headers(), body: { detail: "not found" } }, - { silent: false, isTTY: false } + { silent: false, isTTY: false }, ); throw new Error("expected throw"); } catch (error) { @@ -1110,13 +1110,13 @@ describe("resolveApiResponseOutput", () => { isTTY: false, method: "GET", endpoint: "issues/7670740039/committers/", - } + }, ); throw new Error("expected throw"); } catch (error) { expect(error).toBeInstanceOf(OutputError); expect((error as OutputError).data).toBe( - "HTTP 404 Not Found — GET /api/0/issues/7670740039/committers/" + "HTTP 404 Not Found — GET /api/0/issues/7670740039/committers/", ); } }); @@ -1135,13 +1135,13 @@ describe("resolveApiResponseOutput", () => { isTTY: false, method: "GET", endpoint: "missing/", - } + }, ); throw new Error("expected throw"); } catch (error) { expect(error).toBeInstanceOf(OutputError); expect((error as OutputError).data).toBe( - "HTTP 404 Not Found — GET /api/0/missing/" + "HTTP 404 Not Found — GET /api/0/missing/", ); } }); @@ -1164,13 +1164,13 @@ describe("resolveApiResponseOutput", () => { isTTY: false, method: "GET", endpoint: "missing/", - } + }, ); throw new Error("expected throw"); } catch (error) { expect(error).toBeInstanceOf(OutputError); expect((error as OutputError).data).toBe( - `HTTP ${status} ${statusText} — GET /api/0/missing/` + `HTTP ${status} ${statusText} — GET /api/0/missing/`, ); } }); @@ -1190,7 +1190,7 @@ describe("resolveApiResponseOutput", () => { json: true, method: "GET", endpoint: "missing/", - } + }, ); throw new Error("expected throw"); } catch (error) { @@ -1211,7 +1211,7 @@ describe("resolveApiResponseOutput", () => { headers: new Headers({ "content-type": "image/png" }), body: png, }, - { silent: false, isTTY: false } + { silent: false, isTTY: false }, ); throw new Error("expected throw"); } catch (error) { @@ -1227,7 +1227,7 @@ describe("resolveApiResponseOutput", () => { test("success + text body returns the body unchanged", () => { const out = resolveApiResponseOutput( { status: 200, headers: new Headers(), body: { ok: true } }, - { silent: false, isTTY: false } + { silent: false, isTTY: false }, ); expect(out).toEqual({ ok: true }); }); @@ -1235,7 +1235,7 @@ describe("resolveApiResponseOutput", () => { test("success + binary body returns raw bytes (non-TTY, no warning)", () => { const out = resolveApiResponseOutput( { status: 200, headers: new Headers(), body: png }, - { silent: false, isTTY: false } + { silent: false, isTTY: false }, ); expect(out).toBe(png); }); @@ -1243,7 +1243,7 @@ describe("resolveApiResponseOutput", () => { test("success + binary body to a TTY still returns raw bytes (warn only, no hard-refuse)", () => { const out = resolveApiResponseOutput( { status: 200, headers: new Headers(), body: png }, - { silent: false, isTTY: true } + { silent: false, isTTY: true }, ); expect(out).toBe(png); }); @@ -1283,7 +1283,7 @@ describe("resolveBinaryTtyOutput", () => { // and no inline image is produced — the caller keeps the raw bytes. const out = resolveBinaryTtyOutput( new Uint8Array([0x89, 0x50, 0x4e, 0x47]), - new Headers({ "content-type": "image/png" }) + new Headers({ "content-type": "image/png" }), ); expect(out).toBeUndefined(); }); @@ -1294,7 +1294,7 @@ describe("resolveBinaryTtyOutput", () => { const out = resolveBinaryTtyOutput( new Uint8Array([0x89, 0x50, 0x4e, 0x47]), new Headers({ "content-type": "image/png" }), - false + false, ); expect(out).toBeUndefined(); expect(stderrOutput).toContain("Binary response written to a TTY"); @@ -1349,9 +1349,8 @@ describe("buildBodyFromInput", () => { test("reads JSON from file", async () => { // Create a temp file using test config dir (which is writable) - const { createTestConfigDir, cleanupTestDir } = await import( - "../helpers.js" - ); + const { createTestConfigDir, cleanupTestDir } = + await import("../helpers.js"); const testDir = await createTestConfigDir("test-api-file-"); const tempFile = `${testDir}/test-input.json`; await writeFile(tempFile, JSON.stringify({ key: "value" })); @@ -1367,9 +1366,8 @@ describe("buildBodyFromInput", () => { test("reads non-JSON from file", async () => { // Create a temp file using test config dir (which is writable) - const { createTestConfigDir, cleanupTestDir } = await import( - "../helpers.js" - ); + const { createTestConfigDir, cleanupTestDir } = + await import("../helpers.js"); const testDir = await createTestConfigDir("test-api-file-"); const tempFile = `${testDir}/test-input.txt`; await writeFile(tempFile, "plain text from file"); @@ -1387,7 +1385,7 @@ describe("buildBodyFromInput", () => { const mockStdin = createMockStdin(""); await expect( - buildBodyFromInput("/nonexistent/path/file.json", mockStdin) + buildBodyFromInput("/nonexistent/path/file.json", mockStdin), ).rejects.toBeInstanceOf(ValidationError); }); }); @@ -1406,7 +1404,7 @@ describe("resolveEffectiveHeaders", () => { test("does not override explicit Content-Type", () => { const headers = resolveEffectiveHeaders( { "Content-Type": "text/plain" }, - { key: "value" } + { key: "value" }, ); expect(headers["Content-Type"]).toBe("text/plain"); }); @@ -1414,7 +1412,7 @@ describe("resolveEffectiveHeaders", () => { test("case-insensitive Content-Type check", () => { const headers = resolveEffectiveHeaders( { "content-type": "text/xml" }, - { key: "value" } + { key: "value" }, ); expect(headers["content-type"]).toBe("text/xml"); expect(headers["Content-Type"]).toBeUndefined(); @@ -1423,7 +1421,7 @@ describe("resolveEffectiveHeaders", () => { test("preserves custom headers", () => { const headers = resolveEffectiveHeaders( { Authorization: "Bearer token", "X-Custom": "value" }, - undefined + undefined, ); expect(headers.Authorization).toBe("Bearer token"); expect(headers["X-Custom"]).toBe("value"); @@ -1460,7 +1458,7 @@ describe("parseDataBody", () => { test("parses nested JSON", () => { expect( - parseDataBody('{"status":"ignored","statusDetails":{"ignoreCount":1}}') + parseDataBody('{"status":"ignored","statusDetails":{"ignoreCount":1}}'), ).toEqual({ status: "ignored", statusDetails: { ignoreCount: 1 } }); }); @@ -1544,10 +1542,10 @@ describe("extractJsonBody", () => { test("throws on multiple JSON bodies", () => { expect(() => extractJsonBody(['{"a":1}', '{"b":2}'])).toThrow( - ValidationError + ValidationError, ); expect(() => extractJsonBody(['{"a":1}', '{"b":2}'])).toThrow( - /Multiple JSON bodies/ + /Multiple JSON bodies/, ); }); @@ -1630,13 +1628,13 @@ describe("buildFromFields", () => { buildFromFields("PUT", { "raw-field": ["[1,2,3]"], field: ["extra=field"], - }) + }), ).toThrow(ValidationError); expect(() => buildFromFields("PUT", { "raw-field": ["[1,2,3]"], field: ["extra=field"], - }) + }), ).toThrow(/Cannot combine a JSON array/); }); @@ -1644,7 +1642,7 @@ describe("buildFromFields", () => { // GET with a bare JSON field: no body extracted, falls to buildRawQueryParams // which throws "Invalid field format" since there is no '=' expect(() => - buildFromFields("GET", { "raw-field": ['{"status":"ignored"}'] }) + buildFromFields("GET", { "raw-field": ['{"status":"ignored"}'] }), ).toThrow(ValidationError); // No hint should have been emitted (JSON extraction was skipped for GET) expect(stderrOutput).toBe(""); @@ -1655,13 +1653,13 @@ describe("buildFromFields", () => { buildFromFields("PUT", { "raw-field": ['{"status":"ignored","statusDetails":{"ignoreCount":1}}'], field: ["statusDetails[minCount]=5"], - }) + }), ).toThrow(ValidationError); expect(() => buildFromFields("PUT", { "raw-field": ['{"status":"ignored","statusDetails":{"ignoreCount":1}}'], field: ["statusDetails[minCount]=5"], - }) + }), ).toThrow(/conflict/i); }); @@ -1684,7 +1682,7 @@ describe("resolveBody", () => { test("--data returns parsed JSON body", async () => { const result = await resolveBody( { method: "PUT", data: '{"status":"resolved"}' }, - MOCK_STDIN + MOCK_STDIN, ); expect(result.body).toEqual({ status: "resolved" }); expect(result.params).toBeUndefined(); @@ -1693,7 +1691,7 @@ describe("resolveBody", () => { test("--data with non-JSON returns raw string body", async () => { const result = await resolveBody( { method: "POST", data: "hello world" }, - MOCK_STDIN + MOCK_STDIN, ); expect(result.body).toBe("hello world"); }); @@ -1702,14 +1700,14 @@ describe("resolveBody", () => { await expect( resolveBody( { method: "PUT", data: '{"a":1}', input: "file.json" }, - MOCK_STDIN - ) + MOCK_STDIN, + ), ).rejects.toThrow(ValidationError); await expect( resolveBody( { method: "PUT", data: '{"a":1}', input: "file.json" }, - MOCK_STDIN - ) + MOCK_STDIN, + ), ).rejects.toThrow(/--data.*--input/i); }); @@ -1717,14 +1715,14 @@ describe("resolveBody", () => { await expect( resolveBody( { method: "PUT", data: '{"a":1}', field: ["key=value"] }, - MOCK_STDIN - ) + MOCK_STDIN, + ), ).rejects.toThrow(ValidationError); await expect( resolveBody( { method: "PUT", data: '{"a":1}', field: ["key=value"] }, - MOCK_STDIN - ) + MOCK_STDIN, + ), ).rejects.toThrow(/--data.*--field|--field.*--data/i); }); @@ -1732,15 +1730,15 @@ describe("resolveBody", () => { await expect( resolveBody( { method: "PUT", data: '{"a":1}', "raw-field": ["key=value"] }, - MOCK_STDIN - ) + MOCK_STDIN, + ), ).rejects.toThrow(ValidationError); }); test("falls through to buildFromFields when neither --data nor --input", async () => { const result = await resolveBody( { method: "PUT", field: ["status=resolved"] }, - MOCK_STDIN + MOCK_STDIN, ); expect(result.body).toEqual({ status: "resolved" }); expect(result.params).toBeUndefined(); @@ -1749,7 +1747,7 @@ describe("resolveBody", () => { test("GET fields produce params, not body", async () => { const result = await resolveBody( { method: "GET", "raw-field": ["query=is:unresolved"] }, - MOCK_STDIN + MOCK_STDIN, ); expect(result.body).toBeUndefined(); expect(result.params).toEqual({ query: "is:unresolved" }); @@ -1758,7 +1756,7 @@ describe("resolveBody", () => { test("GET --data converts URL-encoded string to query params", async () => { const result = await resolveBody( { method: "GET", data: "stat=received&resolution=1d" }, - MOCK_STDIN + MOCK_STDIN, ); expect(result.body).toBeUndefined(); expect(result.params).toEqual({ stat: "received", resolution: "1d" }); @@ -1767,7 +1765,7 @@ describe("resolveBody", () => { test("GET --data converts JSON object to query params", async () => { const result = await resolveBody( { method: "GET", data: '{"stat":"received","resolution":"1d"}' }, - MOCK_STDIN + MOCK_STDIN, ); expect(result.body).toBeUndefined(); expect(result.params).toEqual({ stat: "received", resolution: "1d" }); @@ -1775,26 +1773,26 @@ describe("resolveBody", () => { test("GET --data with JSON array throws ValidationError", async () => { await expect( - resolveBody({ method: "GET", data: "[1,2,3]" }, MOCK_STDIN) + resolveBody({ method: "GET", data: "[1,2,3]" }, MOCK_STDIN), ).rejects.toThrow(ValidationError); await expect( - resolveBody({ method: "GET", data: "[1,2,3]" }, MOCK_STDIN) + resolveBody({ method: "GET", data: "[1,2,3]" }, MOCK_STDIN), ).rejects.toThrow(/cannot.*query parameters/i); }); test("GET --data with JSON primitive throws ValidationError", async () => { await expect( - resolveBody({ method: "GET", data: "null" }, MOCK_STDIN) + resolveBody({ method: "GET", data: "null" }, MOCK_STDIN), ).rejects.toThrow(ValidationError); await expect( - resolveBody({ method: "GET", data: "42" }, MOCK_STDIN) + resolveBody({ method: "GET", data: "42" }, MOCK_STDIN), ).rejects.toThrow(ValidationError); }); test("POST --data still returns body (regression guard)", async () => { const result = await resolveBody( { method: "POST", data: '{"status":"resolved"}' }, - MOCK_STDIN + MOCK_STDIN, ); expect(result.body).toEqual({ status: "resolved" }); expect(result.params).toBeUndefined(); @@ -1838,25 +1836,25 @@ describe("dataToQueryParams", () => { test("throws on JSON array", () => { expect(() => dataToQueryParams([1, 2, 3])).toThrow(ValidationError); expect(() => dataToQueryParams([1, 2, 3])).toThrow( - /cannot.*JSON primitive or array.*query parameters/i + /cannot.*JSON primitive or array.*query parameters/i, ); }); test("throws on null", () => { expect(() => - dataToQueryParams(null as unknown as Record<string, unknown>) + dataToQueryParams(null as unknown as Record<string, unknown>), ).toThrow(ValidationError); }); test("throws on boolean", () => { expect(() => - dataToQueryParams(true as unknown as Record<string, unknown>) + dataToQueryParams(true as unknown as Record<string, unknown>), ).toThrow(ValidationError); }); test("throws on number", () => { expect(() => - dataToQueryParams(42 as unknown as Record<string, unknown>) + dataToQueryParams(42 as unknown as Record<string, unknown>), ).toThrow(ValidationError); }); }); @@ -1868,7 +1866,7 @@ describe("resolveRequestUrl", () => { const url = resolveRequestUrl( "organizations/", undefined, - "https://de.sentry.io/sentry" + "https://de.sentry.io/sentry", ); expect(url).toBe("https://de.sentry.io/sentry/api/0/organizations/"); }); @@ -1926,11 +1924,11 @@ describe("resolveRequestUrl", () => { "dry-run": true, json: true, }, - "https://de.sentry.io/sentry/api/0/organizations/acme/" + "https://de.sentry.io/sentry/api/0/organizations/acme/", ); expect(JSON.parse(output).url).toBe( - "https://de.sentry.io/sentry/api/0/organizations/acme/" + "https://de.sentry.io/sentry/api/0/organizations/acme/", ); }); }); diff --git a/packages/cli/test/commands/auth/default.test.ts b/packages/cli/test/commands/auth/default.test.ts index 2f7448667..82f13341d 100644 --- a/packages/cli/test/commands/auth/default.test.ts +++ b/packages/cli/test/commands/auth/default.test.ts @@ -24,17 +24,17 @@ describe("resolveAuthDefaultTarget", () => { expect(resolveAuthDefaultTarget({ token: "t" }, true)).toBe("login"); expect(resolveAuthDefaultTarget({ force: true }, true)).toBe("login"); expect( - resolveAuthDefaultTarget({ url: "https://sentry.example.com" }, true) + resolveAuthDefaultTarget({ url: "https://sentry.example.com" }, true), ).toBe("login"); expect(resolveAuthDefaultTarget({ "read-only": true }, true)).toBe("login"); expect(resolveAuthDefaultTarget({ scope: ["org:read"] }, true)).toBe( - "login" + "login", ); }); test("status-only flags keep status when authenticated", () => { expect( - resolveAuthDefaultTarget({ "show-token": true, fresh: true }, true) + resolveAuthDefaultTarget({ "show-token": true, fresh: true }, true), ).toBe("status"); }); @@ -47,7 +47,7 @@ describe("authDefaultCommand", () => { test("dispatches to login raw func when logged out", async () => { const isAuthenticated = vi.spyOn( await import("../../../src/lib/db/auth.js"), - "isAuthenticated" + "isAuthenticated", ); isAuthenticated.mockReturnValue(false); @@ -98,7 +98,7 @@ describe("authDefaultCommand", () => { test("dispatches to status raw func when logged in", async () => { const isAuthenticated = vi.spyOn( await import("../../../src/lib/db/auth.js"), - "isAuthenticated" + "isAuthenticated", ); isAuthenticated.mockReturnValue(true); diff --git a/packages/cli/test/commands/auth/login.test.ts b/packages/cli/test/commands/auth/login.test.ts index 4f2722ae3..763892749 100644 --- a/packages/cli/test/commands/auth/login.test.ts +++ b/packages/cli/test/commands/auth/login.test.ts @@ -33,8 +33,8 @@ const { mockIsatty, ttyExports, noop, mockPrompt, fakeLog } = vi.hoisted(() => { // Mock the logger module to intercept the .prompt() call made by the // module-scoped `log = logger.withTag("auth.login")` in login.ts. - const _mockPrompt = vi.fn( - (): Promise<boolean | symbol> => Promise.resolve(true) + const _mockPrompt = vi.fn((): Promise<boolean | symbol> => + Promise.resolve(true), ); const _fakeLog: { prompt: typeof _mockPrompt; @@ -83,9 +83,8 @@ vi.mock("../../../src/lib/logger.js", () => ({ })); // Dynamic import: must run AFTER vi.mock() so login.ts picks up fakeLog. -const { loginCommand, rcTokenHint } = await import( - "../../../src/commands/auth/login.js" -); +const { loginCommand, rcTokenHint } = + await import("../../../src/commands/auth/login.js"); vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { const actual = @@ -94,11 +93,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { @@ -108,11 +107,11 @@ vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbAuth from "../../../src/lib/db/auth.js"; vi.mock("../../../src/lib/db/user.js", async (importOriginal) => { @@ -122,11 +121,11 @@ vi.mock("../../../src/lib/db/user.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbUser from "../../../src/lib/db/user.js"; import { ApiError, @@ -144,11 +143,11 @@ vi.mock("../../../src/lib/interactive-login.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as interactiveLogin from "../../../src/lib/interactive-login.js"; import type { SentryCliRcConfig } from "../../../src/lib/sentryclirc.js"; import { useEnvSandbox } from "../../helpers.js"; @@ -203,13 +202,13 @@ function createContext() { /** Assert setAuthToken was called with the expected token and a host option. */ function expectTokenStored( spy: ReturnType<typeof spyOn>, - expectedToken: string + expectedToken: string, ): void { - // biome-ignore lint/suspicious/noMisplacedAssertion: shared helper + // shared helper expect(spy).toHaveBeenCalled(); - // biome-ignore lint/suspicious/noMisplacedAssertion: shared helper + // shared helper expect(spy.mock.calls[0]?.[0]).toBe(expectedToken); - // biome-ignore lint/suspicious/noMisplacedAssertion: shared helper + // shared helper expect(spy.mock.calls[0]?.[3]).toMatchObject({ host: expect.any(String), }); @@ -344,53 +343,59 @@ describe("loginCommand.func --token path", () => { ["NUL", "synthetic-prefix\0synthetic-suffix"], ["empty value", ""], ["control-only value", "\x01\x7f"], - ])("--force --token rejects %s before changing the session or host", async (_, token) => { - isAuthenticatedSpy.mockReturnValue(true); - process.env.SENTRY_HOST = "https://previous.example.com"; - process.env.SENTRY_URL = "https://previous.example.com"; - const fetchSpy = vi.spyOn(globalThis, "fetch"); - - try { - const { context } = createContext(); - await expect( - func.call(context, { - token, - force: true, - timeout: 900, - url: "https://replacement.example.com", - }) - ).rejects.toBeInstanceOf(MalformedAuthTokenError); - - expect(process.env.SENTRY_HOST).toBe("https://previous.example.com"); - expect(process.env.SENTRY_URL).toBe("https://previous.example.com"); - expect(clearAuthSpy).not.toHaveBeenCalled(); - expect(setAuthTokenSpy).not.toHaveBeenCalled(); - expect(setUserInfoSpy).not.toHaveBeenCalled(); - expect(getUserRegionsSpy).not.toHaveBeenCalled(); - expect(getCurrentUserSpy).not.toHaveBeenCalled(); - expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); - expect(fetchSpy).not.toHaveBeenCalled(); - } finally { - fetchSpy.mockRestore(); - } - }); + ])( + "--force --token rejects %s before changing the session or host", + async (_, token) => { + isAuthenticatedSpy.mockReturnValue(true); + process.env.SENTRY_HOST = "https://previous.example.com"; + process.env.SENTRY_URL = "https://previous.example.com"; + const fetchSpy = vi.spyOn(globalThis, "fetch"); + + try { + const { context } = createContext(); + await expect( + func.call(context, { + token, + force: true, + timeout: 900, + url: "https://replacement.example.com", + }), + ).rejects.toBeInstanceOf(MalformedAuthTokenError); + + expect(process.env.SENTRY_HOST).toBe("https://previous.example.com"); + expect(process.env.SENTRY_URL).toBe("https://previous.example.com"); + expect(clearAuthSpy).not.toHaveBeenCalled(); + expect(setAuthTokenSpy).not.toHaveBeenCalled(); + expect(setUserInfoSpy).not.toHaveBeenCalled(); + expect(getUserRegionsSpy).not.toHaveBeenCalled(); + expect(getCurrentUserSpy).not.toHaveBeenCalled(); + expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); + expect(fetchSpy).not.toHaveBeenCalled(); + } finally { + fetchSpy.mockRestore(); + } + }, + ); test.each([ "\t\n\u00a0synthetic-token\r\n", "\0\u00a0\x01synthetic-token\x7f\ufeff\0", - ])("--token normalizes surrounding whitespace and controls before storage %#", async (token) => { - isAuthenticatedSpy.mockReturnValue(false); - setAuthTokenSpy.mockReturnValue(undefined); - getUserRegionsSpy.mockResolvedValue([]); - getCurrentUserSpy.mockResolvedValue(SAMPLE_USER); - setUserInfoSpy.mockReturnValue(undefined); + ])( + "--token normalizes surrounding whitespace and controls before storage %#", + async (token) => { + isAuthenticatedSpy.mockReturnValue(false); + setAuthTokenSpy.mockReturnValue(undefined); + getUserRegionsSpy.mockResolvedValue([]); + getCurrentUserSpy.mockResolvedValue(SAMPLE_USER); + setUserInfoSpy.mockReturnValue(undefined); - const { context } = createContext(); - await func.call(context, { token, force: false, timeout: 900 }); + const { context } = createContext(); + await func.call(context, { token, force: false, timeout: 900 }); - expectTokenStored(setAuthTokenSpy, "synthetic-token"); - expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); - }); + expectTokenStored(setAuthTokenSpy, "synthetic-token"); + expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); + }, + ); test("--token: null user.name is converted to undefined in setUserInfo", async () => { isAuthenticatedSpy.mockReturnValue(false); @@ -431,7 +436,7 @@ describe("loginCommand.func --token path", () => { const { context } = createContext(); await expect( - func.call(context, { token: "bad-token", force: false, timeout: 900 }) + func.call(context, { token: "bad-token", force: false, timeout: 900 }), ).rejects.toBeInstanceOf(AuthError); expect(clearAuthSpy).toHaveBeenCalled(); @@ -449,7 +454,7 @@ describe("loginCommand.func --token path", () => { const { context } = createContext(); await expect( - func.call(context, { token: "maybe-good", force: false, timeout: 900 }) + func.call(context, { token: "maybe-good", force: false, timeout: 900 }), ).rejects.toBe(cause); expect(clearAuthSpy).not.toHaveBeenCalled(); @@ -783,9 +788,8 @@ describe("applyLoginUrl (host resolution)", () => { }); test("explicit --url takes precedence and writes env", async () => { - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); const host = applyLoginUrl("https://sentry.example.com"); expect(host).toBe("https://sentry.example.com"); expect(process.env.SENTRY_HOST).toBe("https://sentry.example.com"); @@ -793,17 +797,15 @@ describe("applyLoginUrl (host resolution)", () => { }); test("no --url + no env falls back to SaaS default", async () => { - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); expect(applyLoginUrl(undefined)).toBe("https://sentry.io"); }); test("no --url + SENTRY_HOST with scheme uses env host", async () => { process.env.SENTRY_HOST = "https://sentry.acme.com"; - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); expect(applyLoginUrl(undefined)).toBe("https://sentry.acme.com"); }); @@ -812,26 +814,23 @@ describe("applyLoginUrl (host resolution)", () => { // on bare hostnames. new URL("sentry.acme.com") throws → silent fallback // to SaaS default → token mis-scoped. process.env.SENTRY_HOST = "sentry.acme.com"; - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); expect(applyLoginUrl(undefined)).toBe("https://sentry.acme.com"); }); test("no --url + bare hostname SENTRY_URL prefixes https://", async () => { process.env.SENTRY_URL = "sentry.acme.com"; - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); expect(applyLoginUrl(undefined)).toBe("https://sentry.acme.com"); }); test("SENTRY_HOST takes precedence over SENTRY_URL", async () => { process.env.SENTRY_HOST = "https://host.example.com"; process.env.SENTRY_URL = "https://url.example.com"; - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); expect(applyLoginUrl(undefined)).toBe("https://host.example.com"); }); }); @@ -845,12 +844,10 @@ describe("applyLoginUrl (trust anchor registration)", () => { savedUrl = process.env.SENTRY_URL; delete process.env.SENTRY_HOST; delete process.env.SENTRY_URL; - const { resetEnvTokenHostForTesting } = await import( - "../../../src/lib/env-token-host.js" - ); - const { resetLoginTrustAnchorForTesting } = await import( - "../../../src/lib/token-host.js" - ); + const { resetEnvTokenHostForTesting } = + await import("../../../src/lib/env-token-host.js"); + const { resetLoginTrustAnchorForTesting } = + await import("../../../src/lib/token-host.js"); resetEnvTokenHostForTesting(); resetLoginTrustAnchorForTesting(); }); @@ -866,68 +863,58 @@ describe("applyLoginUrl (trust anchor registration)", () => { } else { delete process.env.SENTRY_URL; } - const { resetEnvTokenHostForTesting } = await import( - "../../../src/lib/env-token-host.js" - ); - const { resetLoginTrustAnchorForTesting } = await import( - "../../../src/lib/token-host.js" - ); + const { resetEnvTokenHostForTesting } = + await import("../../../src/lib/env-token-host.js"); + const { resetLoginTrustAnchorForTesting } = + await import("../../../src/lib/token-host.js"); resetEnvTokenHostForTesting(); resetLoginTrustAnchorForTesting(); }); test("explicit --url registers trust anchor (user-supplied argv is trusted)", async () => { - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); - const { isRequestOriginTrustedForCustomHeaders } = await import( - "../../../src/lib/token-host.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); + const { isRequestOriginTrustedForCustomHeaders } = + await import("../../../src/lib/token-host.js"); applyLoginUrl("https://sentry.acme.com"); expect( isRequestOriginTrustedForCustomHeaders( - "https://sentry.acme.com/oauth/device/code/" - ) + "https://sentry.acme.com/oauth/device/code/", + ), ).toBe(true); }); test("SENTRY_HOST from boot env registers trust anchor (shell export is trusted)", async () => { process.env.SENTRY_HOST = "https://sentry.acme.com"; - const { captureEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); - const { isRequestOriginTrustedForCustomHeaders } = await import( - "../../../src/lib/token-host.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); + const { isRequestOriginTrustedForCustomHeaders } = + await import("../../../src/lib/token-host.js"); applyLoginUrl(undefined); expect( isRequestOriginTrustedForCustomHeaders( - "https://sentry.acme.com/oauth/device/code/" - ) + "https://sentry.acme.com/oauth/device/code/", + ), ).toBe(true); }); test("rc-poisoned SENTRY_URL does NOT register trust anchor (attacker path)", async () => { // Boot: no env set → env-token-host captures SaaS default - const { captureEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); // Simulate .sentryclirc shim writing env.SENTRY_URL AFTER boot (the // auth login has skipRcUrlCheck: true). This is the attacker path. process.env.SENTRY_URL = "https://evil.com"; - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); - const { isRequestOriginTrustedForCustomHeaders } = await import( - "../../../src/lib/token-host.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); + const { isRequestOriginTrustedForCustomHeaders } = + await import("../../../src/lib/token-host.js"); applyLoginUrl(undefined); // The rc-sourced host doesn't match boot env (which was empty → @@ -935,15 +922,15 @@ describe("applyLoginUrl (trust anchor registration)", () => { // applyCustomHeaders against evil.com must fail closed. expect( isRequestOriginTrustedForCustomHeaders( - "https://evil.com/oauth/device/code/" - ) + "https://evil.com/oauth/device/code/", + ), ).toBe(false); }); }); function makeRcConfig( token: string | undefined, - url?: string + url?: string, ): SentryCliRcConfig { return { token, @@ -955,7 +942,7 @@ function makeRcConfig( describe("rcTokenHint", () => { test("no token → no hint", () => { expect( - rcTokenHint(makeRcConfig(undefined), "https://sentry.io") + rcTokenHint(makeRcConfig(undefined), "https://sentry.io"), ).toBeUndefined(); }); @@ -968,7 +955,7 @@ describe("rcTokenHint", () => { test("self-hosted, rc URL matches → hint includes --url", () => { const hint = rcTokenHint( makeRcConfig("sntrys_abc", "https://self.example.com"), - "https://self.example.com" + "https://self.example.com", ); expect(hint).toContain("--url https://self.example.com"); }); @@ -976,7 +963,7 @@ describe("rcTokenHint", () => { test("self-hosted, rc URL mismatches → no hint (token is for a different instance)", () => { const hint = rcTokenHint( makeRcConfig("sntrys_abc", "https://other.example.com"), - "https://self.example.com" + "https://self.example.com", ); expect(hint).toBeUndefined(); }); @@ -984,7 +971,7 @@ describe("rcTokenHint", () => { test("self-hosted, no rc URL → no hint (bare SaaS token shouldn't be suggested for self-hosted)", () => { const hint = rcTokenHint( makeRcConfig("sntrys_abc"), - "https://self.example.com" + "https://self.example.com", ); expect(hint).toBeUndefined(); }); @@ -1031,7 +1018,7 @@ describe("loginCommand.func scope selection (--read-only / --scope)", () => { force: false, timeout: 900, "read-only": true, - }) + }), ).rejects.toBeInstanceOf(ValidationError); expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); }); @@ -1044,7 +1031,7 @@ describe("loginCommand.func scope selection (--read-only / --scope)", () => { force: false, timeout: 900, scope: ["project:read"], - }) + }), ).rejects.toBeInstanceOf(ValidationError); expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); }); @@ -1057,7 +1044,7 @@ describe("loginCommand.func scope selection (--read-only / --scope)", () => { timeout: 900, "read-only": true, scope: ["project:read"], - }) + }), ).rejects.toBeInstanceOf(ValidationError); expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); }); @@ -1069,7 +1056,7 @@ describe("loginCommand.func scope selection (--read-only / --scope)", () => { force: false, timeout: 900, scope: ["not:a:scope"], - }) + }), ).rejects.toBeInstanceOf(ValidationError); expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); }); diff --git a/packages/cli/test/commands/auth/logout.test.ts b/packages/cli/test/commands/auth/logout.test.ts index 1d02b1c1a..66559a800 100644 --- a/packages/cli/test/commands/auth/logout.test.ts +++ b/packages/cli/test/commands/auth/logout.test.ts @@ -10,15 +10,15 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { logoutCommand } from "../../../src/commands/auth/logout.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbAuth from "../../../src/lib/db/auth.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbIndex from "../../../src/lib/db/index.js"; import { AuthError } from "../../../src/lib/errors.js"; type LogoutFunc = ( this: unknown, - flags: Record<string, never> + flags: Record<string, never>, ) => Promise<void>; function createContext() { diff --git a/packages/cli/test/commands/auth/refresh.test.ts b/packages/cli/test/commands/auth/refresh.test.ts index 64fe6a6c0..c61a533a6 100644 --- a/packages/cli/test/commands/auth/refresh.test.ts +++ b/packages/cli/test/commands/auth/refresh.test.ts @@ -7,10 +7,10 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { refreshCommand } from "../../../src/commands/auth/refresh.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbAuth from "../../../src/lib/db/auth.js"; import { AuthError, ValidationError } from "../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as interactiveLogin from "../../../src/lib/interactive-login.js"; type RefreshFlags = { @@ -73,7 +73,7 @@ describe("refreshCommand.func", () => { } catch (err) { expect(err).toBeInstanceOf(AuthError); expect((err as AuthError).message).toContain( - "Cannot refresh an environment variable token" + "Cannot refresh an environment variable token", ); expect((err as AuthError).message).toContain("Update SENTRY_AUTH_TOKEN"); } @@ -301,7 +301,7 @@ describe("refreshCommand.func scope re-authentication (--scope / --read-only)", force: false, "read-only": true, scope: ["project:read"], - }) + }), ).rejects.toBeInstanceOf(ValidationError); expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); expect(refreshTokenSpy).not.toHaveBeenCalled(); @@ -314,7 +314,7 @@ describe("refreshCommand.func scope re-authentication (--scope / --read-only)", json: false, force: false, scope: ["not:valid"], - }) + }), ).rejects.toBeInstanceOf(ValidationError); expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); }); @@ -332,7 +332,7 @@ describe("refreshCommand.func scope re-authentication (--scope / --read-only)", json: false, force: false, scope: ["project:read"], - }) + }), ).rejects.toBeInstanceOf(AuthError); expect(runInteractiveLoginSpy).not.toHaveBeenCalled(); }); diff --git a/packages/cli/test/commands/auth/status.test.ts b/packages/cli/test/commands/auth/status.test.ts index f2f5b8192..48f55043b 100644 --- a/packages/cli/test/commands/auth/status.test.ts +++ b/packages/cli/test/commands/auth/status.test.ts @@ -20,11 +20,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { @@ -34,11 +34,11 @@ vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbAuth from "../../../src/lib/db/auth.js"; vi.mock("../../../src/lib/db/defaults.js", async (importOriginal) => { @@ -48,11 +48,11 @@ vi.mock("../../../src/lib/db/defaults.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbDefaults from "../../../src/lib/db/defaults.js"; vi.mock("../../../src/lib/db/index.js", async (importOriginal) => { @@ -62,11 +62,11 @@ vi.mock("../../../src/lib/db/index.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbIndex from "../../../src/lib/db/index.js"; vi.mock("../../../src/lib/db/user.js", async (importOriginal) => { @@ -76,11 +76,11 @@ vi.mock("../../../src/lib/db/user.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbUser from "../../../src/lib/db/user.js"; import { AuthError } from "../../../src/lib/errors.js"; diff --git a/packages/cli/test/commands/auth/whoami.test.ts b/packages/cli/test/commands/auth/whoami.test.ts index c02b4c7d0..210e10b92 100644 --- a/packages/cli/test/commands/auth/whoami.test.ts +++ b/packages/cli/test/commands/auth/whoami.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { @@ -30,11 +30,11 @@ vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbAuth from "../../../src/lib/db/auth.js"; vi.mock("../../../src/lib/db/user.js", async (importOriginal) => { @@ -44,11 +44,11 @@ vi.mock("../../../src/lib/db/user.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbUser from "../../../src/lib/db/user.js"; import { AuthError, @@ -178,7 +178,7 @@ describe("whoamiCommand.func", () => { const { context } = createContext(); await expect(func.call(context, { json: false })).rejects.toBeInstanceOf( - AuthError + AuthError, ); }); diff --git a/packages/cli/test/commands/bash-hook.test.ts b/packages/cli/test/commands/bash-hook.test.ts index c4a8daffa..e6aa45ffd 100644 --- a/packages/cli/test/commands/bash-hook.test.ts +++ b/packages/cli/test/commands/bash-hook.test.ts @@ -18,7 +18,7 @@ useTestConfigDir("bash-hook-"); * Run bash-hook command and capture stdout. */ async function runBashHook( - args: string[] + args: string[], ): Promise<{ output: string; exitCode: number }> { let output = ""; const mockContext: SentryContext = { @@ -149,7 +149,7 @@ describe("bashHookCommand (script output mode)", () => { "https://key@o1.ingest.sentry.io/1", ]); expect(output).toContain( - "export SENTRY_DSN='https://key@o1.ingest.sentry.io/1'" + "export SENTRY_DSN='https://key@o1.ingest.sentry.io/1'", ); }); }); diff --git a/packages/cli/test/commands/build/download.test.ts b/packages/cli/test/commands/build/download.test.ts index ab6055e7c..70b3bdaf8 100644 --- a/packages/cli/test/commands/build/download.test.ts +++ b/packages/cli/test/commands/build/download.test.ts @@ -12,11 +12,11 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { downloadCommand } from "../../../src/commands/build/download.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as preprod from "../../../src/lib/api/preprod-artifacts.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as region from "../../../src/lib/region.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; let tmpDir: string; @@ -29,7 +29,7 @@ function createContext() { stdout: { write: (data: string | Uint8Array) => { writes.push( - typeof data === "string" ? data : new TextDecoder().decode(data) + typeof data === "string" ? data : new TextDecoder().decode(data), ); return true; }, @@ -56,9 +56,11 @@ describe("build download", () => { beforeEach(async () => { tmpDir = await mkdtemp(join(tmpdir(), "build-download-")); - vi.spyOn(resolveTarget, "resolveOrg").mockResolvedValue({ org: "test-org" }); + vi.spyOn(resolveTarget, "resolveOrg").mockResolvedValue({ + org: "test-org", + }); vi.spyOn(region, "resolveOrgRegion").mockResolvedValue( - "https://us.sentry.io" + "https://us.sentry.io", ); installSpy = vi.spyOn(preprod, "getBuildInstallDetails"); downloadSpy = vi @@ -114,7 +116,7 @@ describe("build download", () => { const func = await downloadCommand.loader(); await expect( - func.call(context, { output: undefined }, "b3") + func.call(context, { output: undefined }, "b3"), ).rejects.toThrow(/not installable/i); expect(downloadSpy).not.toHaveBeenCalled(); }); diff --git a/packages/cli/test/commands/build/upload.test.ts b/packages/cli/test/commands/build/upload.test.ts index e1cda398f..67cb16d7e 100644 --- a/packages/cli/test/commands/build/upload.test.ts +++ b/packages/cli/test/commands/build/upload.test.ts @@ -16,10 +16,10 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { app } from "../../../src/app.js"; import { uploadCommand } from "../../../src/commands/build/upload.js"; import type { SentryContext } from "../../../src/context.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as preprod from "../../../src/lib/api/preprod-artifacts.js"; import { setAuthToken } from "../../../src/lib/db/auth.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -34,7 +34,7 @@ function createContext() { stdout: { write: (data: string | Uint8Array) => { writes.push( - typeof data === "string" ? data : new TextDecoder().decode(data) + typeof data === "string" ? data : new TextDecoder().decode(data), ); return true; }, @@ -64,7 +64,7 @@ async function writeApk(name = "app-release.apk"): Promise<string> { * exit code. */ async function runViaApp( - args: string[] + args: string[], ): Promise<{ stderr: string; exitCode: number | undefined }> { let stderr = ""; const context: SentryContext = { @@ -76,7 +76,8 @@ async function runViaApp( stdout: { write: () => true }, stderr: { write: (data: string | Uint8Array) => { - stderr += typeof data === "string" ? data : new TextDecoder().decode(data); + stderr += + typeof data === "string" ? data : new TextDecoder().decode(data); return true; }, }, @@ -114,7 +115,10 @@ describe("build upload", () => { await func.call(harness.context, {}, apk); expect(uploadSpy).toHaveBeenCalledTimes(1); - const opts = uploadSpy.mock.calls[0]?.[0] as { org: string; project: string }; + const opts = uploadSpy.mock.calls[0]?.[0] as { + org: string; + project: string; + }; expect(opts.org).toBe("test-org"); expect(opts.project).toBe("test-project"); expect(harness.output()).toContain("https://sentry.io/artifact/1"); @@ -132,7 +136,7 @@ describe("build upload", () => { "build-configuration": "Release", "install-group": ["qa", "beta"], }, - apk + apk, ); const meta = uploadSpy.mock.calls[0]?.[0] as { @@ -151,7 +155,7 @@ describe("build upload", () => { await func.call( harness.context, { "head-sha": sha, "head-ref": "main", "pr-number": 9 }, - apk + apk, ); const meta = uploadSpy.mock.calls[0]?.[0] as { @@ -222,7 +226,7 @@ describe("build upload", () => { zipSync({ "Payload/MyApp.app/Info.plist": strToU8("<app/>"), "Payload/MyApp.app/MyApp": strToU8("binary"), - }) + }), ); const harness = createContext(); const func = await uploadCommand.loader(); diff --git a/packages/cli/test/commands/cli.test.ts b/packages/cli/test/commands/cli.test.ts index 4eec11e9d..25cef3fbd 100644 --- a/packages/cli/test/commands/cli.test.ts +++ b/packages/cli/test/commands/cli.test.ts @@ -72,7 +72,7 @@ describe("feedbackCommand.func", () => { }; await expect(func.call(mockContext, {}, "")).rejects.toThrow( - "Please provide a feedback message." + "Please provide a feedback message.", ); }); @@ -84,7 +84,7 @@ describe("feedbackCommand.func", () => { }; await expect(func.call(mockContext, {}, " ")).rejects.toThrow( - "Please provide a feedback message." + "Please provide a feedback message.", ); }); @@ -97,7 +97,7 @@ describe("feedbackCommand.func", () => { // Sentry is disabled in test environment (no DSN) await expect( - func.call(mockContext, {}, "test", "feedback") + func.call(mockContext, {}, "test", "feedback"), ).rejects.toThrow("Feedback not sent: telemetry is disabled."); }); }); @@ -181,13 +181,13 @@ describe("upgradeCommand.func", () => { await func.call( context, { check: true, method: "curl", json: true }, - "2.0.0" + "2.0.0", ); const data = JSON.parse(getStdout()) as UpgradeResult; expect(data.action).toBe("checked"); expect(data.warnings).toContain( - "Run 'sentry cli upgrade 2.0.0' to update." + "Run 'sentry cli upgrade 2.0.0' to update.", ); }); @@ -223,7 +223,7 @@ describe("upgradeCommand.func", () => { // Specify a version that doesn't exist await expect( - func.call(context, { check: false, method: "curl" }, "999.0.0") + func.call(context, { check: false, method: "curl" }, "999.0.0"), ).rejects.toThrow("Version 999.0.0 not found"); expect(callCount).toBe(2); }); diff --git a/packages/cli/test/commands/cli/completion.test.ts b/packages/cli/test/commands/cli/completion.test.ts index a6f613f77..489c65a6f 100644 --- a/packages/cli/test/commands/cli/completion.test.ts +++ b/packages/cli/test/commands/cli/completion.test.ts @@ -18,7 +18,7 @@ import type { SentryContext } from "../../../src/context.js"; */ async function runCompletion( args: string[], - shellEnv?: string + shellEnv?: string, ): Promise<{ output: string; exitCode: number | undefined }> { let output = ""; const env = { ...process.env, SHELL: shellEnv }; diff --git a/packages/cli/test/commands/cli/fix.test.ts b/packages/cli/test/commands/cli/fix.test.ts index 597a57ff2..5f98b6b67 100644 --- a/packages/cli/test/commands/cli/fix.test.ts +++ b/packages/cli/test/commands/cli/fix.test.ts @@ -45,7 +45,7 @@ function createPreMigrationDatabase(db: Database): void { db.exec(statements.join(";\n")); db.query("INSERT INTO schema_version (version) VALUES (4)").run(); db.query( - "INSERT INTO metadata (key, value) VALUES ('json_migration_completed', 'true')" + "INSERT INTO metadata (key, value) VALUES ('json_migration_completed', 'true')", ).run(); } @@ -55,7 +55,7 @@ function createPreMigrationDatabase(db: Database): void { */ function createDatabaseWithMissingTables( db: Database, - missingTables: string[] + missingTables: string[], ): void { const statements: string[] = []; @@ -67,7 +67,7 @@ function createDatabaseWithMissingTables( db.exec(statements.join(";\n")); db.query("INSERT INTO schema_version (version) VALUES (4)").run(); db.query( - "INSERT INTO metadata (key, value) VALUES ('json_migration_completed', 'true')" + "INSERT INTO metadata (key, value) VALUES ('json_migration_completed', 'true')", ).run(); } @@ -206,7 +206,7 @@ describe("sentry cli fix", () => { const verifyDb = new Database(join(getTestDir(), "cli.db")); const tables = verifyDb .query( - "SELECT name FROM sqlite_master WHERE type='table' AND name='dsn_cache'" + "SELECT name FROM sqlite_master WHERE type='table' AND name='dsn_cache'", ) .all(); expect(tables.length).toBe(1); @@ -254,9 +254,9 @@ describe("sentry cli fix", () => { expect(output).toContain("repaired successfully"); expect(exitCode).toBe(0); - // biome-ignore lint/suspicious/noBitwiseOperators: verifying permission bits + // oxlint-disable-next-line no-bitwise -- verifying permission bits const repairedMode = statSync(dbPath).mode & 0o777; - // biome-ignore lint/suspicious/noBitwiseOperators: verifying permission bits + // oxlint-disable-next-line no-bitwise -- verifying permission bits expect(repairedMode & 0o600).toBe(0o600); }); @@ -289,7 +289,7 @@ describe("sentry cli fix", () => { expect(output).not.toContain("✓"); // File should still be readonly — dry-run didn't touch it - // biome-ignore lint/suspicious/noBitwiseOperators: verifying permission bits + // oxlint-disable-next-line no-bitwise -- verifying permission bits const mode = statSync(dbPath).mode & 0o777; expect(mode).toBe(0o444); @@ -392,7 +392,7 @@ describe("sentry cli fix", () => { initSchema(db); db.exec("DROP TABLE pagination_cursors"); db.exec( - "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)" + "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)", ); db.close(); chmodSync(dbPath, 0o600); @@ -418,7 +418,7 @@ describe("sentry cli fix", () => { initSchema(db); db.exec("DROP TABLE pagination_cursors"); db.exec( - "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)" + "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)", ); db.close(); chmodSync(dbPath, 0o600); @@ -436,7 +436,7 @@ describe("sentry cli fix", () => { const verifyDb = new Database(dbPath); const row = verifyDb .query( - "SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'" + "SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'", ) .get() as { sql: string }; expect(row.sql).not.toContain("PRIMARY KEY (command_key, context)"); diff --git a/packages/cli/test/commands/cli/setup.test.ts b/packages/cli/test/commands/cli/setup.test.ts index aeaf4d784..f8861b4fd 100644 --- a/packages/cli/test/commands/cli/setup.test.ts +++ b/packages/cli/test/commands/cli/setup.test.ts @@ -58,9 +58,9 @@ import { setProcessInfoProvider, } from "../../../src/lib/detect-agent.js"; import { setEnv } from "../../../src/lib/env.js"; -// biome-ignore lint/performance/noNamespaceImport: dynamic setup imports are mocked at the module boundary +// oxlint-disable-next-line sentry-cli/no-namespace-import -- dynamic setup imports are mocked at the module boundary import * as interactiveLogin from "../../../src/lib/interactive-login.js"; -// biome-ignore lint/performance/noNamespaceImport: dynamic setup imports are mocked at the module boundary +// oxlint-disable-next-line sentry-cli/no-namespace-import -- dynamic setup imports are mocked at the module boundary import * as scopeRecovery from "../../../src/lib/scope-recovery.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -69,7 +69,7 @@ let originalFetch: typeof globalThis.fetch; /** Helper to mock fetch without TypeScript errors about missing Bun-specific properties */ function mockFetch( - fn: (url: string | URL | Request, init?: RequestInit) => Promise<Response> + fn: (url: string | URL | Request, init?: RequestInit) => Promise<Response>, ): void { globalThis.fetch = fn as typeof globalThis.fetch; } @@ -86,7 +86,7 @@ function createMockContext( homeDir: string; env: Record<string, string | undefined>; execPath: string; - }> = {} + }> = {}, ): { context: SentryContext; getOutput: () => string; @@ -166,7 +166,7 @@ describe("sentry cli setup", () => { beforeEach(() => { testDir = join( "/tmp", - `setup-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `setup-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); vi.mocked(scopeRecovery.ensureCurrentOAuthScopes).mockResolvedValue(false); @@ -195,7 +195,7 @@ describe("sentry cli setup", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); // With --quiet, no output should be produced @@ -217,12 +217,12 @@ describe("sentry cli setup", () => { "--no-agent-skills", "--ensure-auth-scopes", ], - context + context, ); expect(scopeRecovery.ensureCurrentOAuthScopes).toHaveBeenCalledOnce(); expect(scopeRecovery.ensureCurrentOAuthScopes).toHaveBeenCalledWith( - interactiveLogin.runInteractiveLogin + interactiveLogin.runInteractiveLogin, ); }); @@ -243,7 +243,7 @@ describe("sentry cli setup", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); expect(getOutput()).toBe(""); @@ -266,7 +266,7 @@ describe("sentry cli setup", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); expect(getOutput()).toContain("Recorded installation method: curl"); @@ -290,7 +290,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-completions", "--no-agent-skills"], - context + context, ); expect(getOutput()).toContain("PATH:"); @@ -313,7 +313,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-completions", "--no-agent-skills"], - context + context, ); expect(getOutput()).toContain("already in PATH"); @@ -332,7 +332,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-completions", "--no-agent-skills"], - context + context, ); expect(getOutput()).toContain("No shell config file found"); @@ -356,7 +356,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); expect(getOutput()).toContain("Completions:"); @@ -379,7 +379,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); expect(getOutput()).toContain("fpath"); @@ -398,7 +398,7 @@ describe("sentry cli setup", () => { ".local", "share", "zsh", - "site-functions" + "site-functions", ); writeFileSync(zshrc, `# existing\nfpath=("${completionDir}" $fpath)\n`); @@ -415,7 +415,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); // Should still show "Installed to" but not "Added ... to fpath" @@ -443,7 +443,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-completions", "--no-agent-skills"], - context + context, ); expect(getOutput()).toContain("GITHUB_PATH"); @@ -472,11 +472,11 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); expect(getOutput()).toContain( - "Your shell (xonsh) is not directly supported" + "Your shell (xonsh) is not directly supported", ); expect(getOutput()).toContain("bash completions as a fallback"); expect(getOutput()).toContain("bash-completion"); @@ -497,7 +497,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); // Nothing actionable — no message produced @@ -520,7 +520,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); expect(getOutput()).toContain("Completions: Installed to"); @@ -530,7 +530,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); expect(getOutput()).not.toContain("Completions:"); @@ -550,7 +550,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); // sh/ash shells silently skip completions — no message at all @@ -574,7 +574,7 @@ describe("sentry cli setup", () => { "--no-agent-skills", "--quiet", ], - context + context, ); // Should not error @@ -614,7 +614,7 @@ describe("sentry cli setup", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); const combined = getOutput(); @@ -663,7 +663,7 @@ describe("sentry cli setup", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); // With --install, the "Recorded installation method" log is suppressed @@ -706,7 +706,7 @@ describe("sentry cli setup", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); const combined = getOutput(); @@ -750,7 +750,7 @@ describe("sentry cli setup", () => { "--no-agent-skills", "--quiet", ], - context + context, ); expect(getOutput()).toBe(""); @@ -781,7 +781,7 @@ describe("sentry cli setup", () => { existingBinary?: boolean; install?: boolean; method?: string; - } = {} + } = {}, ) { const sourcePath = join(testDir, "sentry-download"); const installDir = join(testDir, "install-dir"); @@ -822,7 +822,7 @@ describe("sentry cli setup", () => { "--no-agent-skills", ...(options.flags ?? []), ], - mock.context + mock.context, ), }; } @@ -832,11 +832,11 @@ describe("sentry cli setup", () => { vi.mocked(interactiveLogin.runInteractiveLogin).mockImplementation( async () => { expect(readFileSync(fixture.binaryPath, "utf8")).toBe( - "fixture-binary" + "fixture-binary", ); expect(fixture.getOutput()).toContain("Installed sentry v"); return { method: "oauth", configPath: getConfigDir() }; - } + }, ); await fixture.run(); @@ -912,26 +912,26 @@ describe("sentry cli setup", () => { expect(readFileSync(fixture.binaryPath, "utf8")).toBe("fixture-binary"); }); - test.each([ - false, - true, - ])("keeps installation successful when OAuth fails (throws: %s)", async (throws) => { - if (throws) { - vi.mocked(interactiveLogin.runInteractiveLogin).mockRejectedValue( - new Error("OAuth unavailable") + test.each([false, true])( + "keeps installation successful when OAuth fails (throws: %s)", + async (throws) => { + if (throws) { + vi.mocked(interactiveLogin.runInteractiveLogin).mockRejectedValue( + new Error("OAuth unavailable"), + ); + } + const fixture = createInstall(); + await fixture.run(); + expect(interactiveLogin.runInteractiveLogin).toHaveBeenCalledOnce(); + expect(fixture.context.process.exitCode).toBe(0); + expect(readFileSync(fixture.binaryPath, "utf8")).toBe("fixture-binary"); + expect(fixture.getOutput()).toContain( + throws + ? "Authentication failed: OAuth unavailable" + : "Run 'sentry auth login' to authenticate later.", ); - } - const fixture = createInstall(); - await fixture.run(); - expect(interactiveLogin.runInteractiveLogin).toHaveBeenCalledOnce(); - expect(fixture.context.process.exitCode).toBe(0); - expect(readFileSync(fixture.binaryPath, "utf8")).toBe("fixture-binary"); - expect(fixture.getOutput()).toContain( - throws - ? "Authentication failed: OAuth unavailable" - : "Run 'sentry auth login' to authenticate later." - ); - }); + }, + ); }); describe("agent skills", () => { @@ -942,7 +942,7 @@ describe("sentry cli setup", () => { originalFetch = globalThis.fetch; mockFetch( async () => - new Response("# Sentry CLI Skill\nTest content", { status: 200 }) + new Response("# Sentry CLI Skill\nTest content", { status: 200 }), ); }); @@ -968,7 +968,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-completions"], - context + context, ); expect(getOutput()).toContain("Agent skills:"); @@ -980,7 +980,7 @@ describe("sentry cli setup", () => { ".claude", "skills", "sentry-cli", - "SKILL.md" + "SKILL.md", ); expect(existsSync(skillPath)).toBe(true); }); @@ -999,7 +999,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-completions"], - context + context, ); expect(getOutput()).not.toContain("Agent skills:"); @@ -1021,15 +1021,19 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-completions"], - context + context, ); expect(getOutput()).toContain("Agent skills: Installed to"); expect( - existsSync(join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md")) + existsSync( + join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md"), + ), ).toBe(true); expect( - existsSync(join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md")) + existsSync( + join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md"), + ), ).toBe(false); }); @@ -1050,7 +1054,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-completions"], - context + context, ); expect(getOutput()).toContain("Agent skills: Installed to"); @@ -1060,7 +1064,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-completions"], - context + context, ); expect(getOutput()).not.toContain("Agent skills:"); @@ -1088,7 +1092,7 @@ describe("sentry cli setup", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); expect(getOutput()).not.toContain("Agent skills:"); @@ -1116,7 +1120,7 @@ describe("sentry cli setup", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); expect(getAgentSkillsPreference()).toBe(false); @@ -1141,12 +1145,14 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-completions"], - context + context, ); expect(getOutput()).not.toContain("Agent skills:"); expect( - existsSync(join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md")) + existsSync( + join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md"), + ), ).toBe(false); }); @@ -1166,7 +1172,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-completions"], - context + context, ); // Skill files are embedded at build time, so installation should succeed @@ -1199,7 +1205,7 @@ describe("sentry cli setup", () => { await run( app, ["cli", "setup", "--no-modify-path", "--no-agent-skills"], - context + context, ); const combined = getOutput(); @@ -1223,7 +1229,7 @@ describe("sentry cli setup — legacy migration", () => { beforeEach(() => { testHome = join( "/tmp", - `setup-mig-home-${Date.now()}-${Math.random().toString(36).slice(2)}` + `setup-mig-home-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testHome, { recursive: true }); }); @@ -1305,7 +1311,7 @@ describe("sentry cli setup — legacy migration", () => { await run(app, setupArgs, context); expect(await readFile(join(installDir, "sentry"), "utf8")).toBe( - "current-binary" + "current-binary", ); }); @@ -1328,7 +1334,7 @@ describe("sentry cli setup — legacy migration", () => { // The ~/.local/bin binary stays put; nothing is copied to the target. expect(existsSync(join(localBin, "sentry"))).toBe(true); expect(await readFile(join(localBin, "sentry"), "utf8")).toBe( - "local-binary" + "local-binary", ); expect(existsSync(join(installDir, "sentry"))).toBe(false); }); @@ -1362,7 +1368,7 @@ describe("sentry cli setup — legacy migration records new path", () => { beforeEach(() => { testHome = join( "/tmp", - `setup-mig-info-${Date.now()}-${Math.random().toString(36).slice(2)}` + `setup-mig-info-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testHome, { recursive: true }); }); @@ -1401,7 +1407,7 @@ describe("sentry cli setup — legacy migration records new path", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); const recorded = getInstallInfo(); @@ -1418,7 +1424,7 @@ describe("sentry cli setup — --channel flag", () => { beforeEach(() => { testDir = join( "/tmp", - `setup-channel-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `setup-channel-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -1446,7 +1452,7 @@ describe("sentry cli setup — --channel flag", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); expect(getReleaseChannel()).toBe("nightly"); @@ -1467,7 +1473,7 @@ describe("sentry cli setup — --channel flag", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); expect(getReleaseChannel()).toBe("stable"); @@ -1490,7 +1496,7 @@ describe("sentry cli setup — --channel flag", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); expect(getOutput()).toContain("Recorded release channel: nightly"); @@ -1517,7 +1523,7 @@ describe("sentry cli setup — --channel flag", () => { "--no-completions", "--no-agent-skills", ], - context + context, ); expect(getOutput()).not.toContain("Recorded release channel: nightly"); diff --git a/packages/cli/test/commands/cli/uninstall.test.ts b/packages/cli/test/commands/cli/uninstall.test.ts index fec8484aa..8a78c5e80 100644 --- a/packages/cli/test/commands/cli/uninstall.test.ts +++ b/packages/cli/test/commands/cli/uninstall.test.ts @@ -42,7 +42,7 @@ afterEach(() => { * rejects in non-TTY test environments without an explicit bypass flag. */ async function runUninstall( - args: string[] + args: string[], ): Promise<{ output: string; exitCode: number | undefined }> { let output = ""; const mockContext: SentryContext = { @@ -174,15 +174,14 @@ describe("removeSentryLinesFromConfig", () => { }); test("removes single # sentry block", async () => { - const { removeSentryLinesFromConfig } = await import( - "../../../src/commands/cli/uninstall.js" - ); + const { removeSentryLinesFromConfig } = + await import("../../../src/commands/cli/uninstall.js"); const configFile = join(tempDir, ".bashrc"); await writeFile( configFile, '# existing\nexport FOO=bar\n\n# sentry\nexport PATH="/home/.sentry/bin:$PATH"\n\nalias ll="ls -la"\n', - "utf-8" + "utf-8", ); const result = await removeSentryLinesFromConfig(configFile); @@ -196,9 +195,8 @@ describe("removeSentryLinesFromConfig", () => { }); test("removes multiple # sentry blocks from same file", async () => { - const { removeSentryLinesFromConfig } = await import( - "../../../src/commands/cli/uninstall.js" - ); + const { removeSentryLinesFromConfig } = + await import("../../../src/commands/cli/uninstall.js"); const configFile = join(tempDir, ".zshrc"); await writeFile( @@ -215,7 +213,7 @@ describe("removeSentryLinesFromConfig", () => { "alias ll='ls -la'", "", ].join("\n"), - "utf-8" + "utf-8", ); const result = await removeSentryLinesFromConfig(configFile); @@ -230,15 +228,14 @@ describe("removeSentryLinesFromConfig", () => { }); test("does not remove # sentry-wizard or similar", async () => { - const { removeSentryLinesFromConfig } = await import( - "../../../src/commands/cli/uninstall.js" - ); + const { removeSentryLinesFromConfig } = + await import("../../../src/commands/cli/uninstall.js"); const configFile = join(tempDir, ".bashrc"); await writeFile( configFile, "# sentry-wizard config\nexport WIZARD=true\n", - "utf-8" + "utf-8", ); const result = await removeSentryLinesFromConfig(configFile); @@ -250,15 +247,14 @@ describe("removeSentryLinesFromConfig", () => { }); test("returns false when no sentry entries exist", async () => { - const { removeSentryLinesFromConfig } = await import( - "../../../src/commands/cli/uninstall.js" - ); + const { removeSentryLinesFromConfig } = + await import("../../../src/commands/cli/uninstall.js"); const configFile = join(tempDir, ".bashrc"); await writeFile( configFile, "# just a normal config\nexport FOO=bar\n", - "utf-8" + "utf-8", ); const result = await removeSentryLinesFromConfig(configFile); diff --git a/packages/cli/test/commands/cli/upgrade.test.ts b/packages/cli/test/commands/cli/upgrade.test.ts index b7fead58f..0ef043ea2 100644 --- a/packages/cli/test/commands/cli/upgrade.test.ts +++ b/packages/cli/test/commands/cli/upgrade.test.ts @@ -9,7 +9,7 @@ * via a spy on process.stderr.write and assert on the collected output. */ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as child_process from "node:child_process"; import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { unlink } from "node:fs/promises"; @@ -48,7 +48,7 @@ let originalFetch: typeof globalThis.fetch; /** Helper to mock fetch */ function mockFetch( - fn: (url: string | URL | Request, init?: RequestInit) => Promise<Response> + fn: (url: string | URL | Request, init?: RequestInit) => Promise<Response>, ): void { globalThis.fetch = fn as typeof globalThis.fetch; } @@ -67,7 +67,7 @@ function createMockContext( env: Record<string, string | undefined>; execPath: string; argv: string[]; - }> = {} + }> = {}, ): { context: SentryContext; getOutput: () => string; @@ -198,7 +198,7 @@ function mockGhcrNightlyVersion(version: string): void { headers: { "content-type": "application/vnd.oci.image.manifest.v1+json", }, - } + }, ); } @@ -275,7 +275,7 @@ function mockNightlyVersion(version: string): void { headers: { "content-type": "application/vnd.oci.image.manifest.v1+json", }, - } + }, ); } return new Response("Not Found", { status: 404 }); @@ -289,7 +289,7 @@ describe("sentry cli upgrade", () => { beforeEach(() => { testDir = join( "/tmp", - `upgrade-cmd-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `upgrade-cmd-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); originalFetch = globalThis.fetch; @@ -314,7 +314,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl"], - context + context, ); const combined = getOutput(); @@ -334,7 +334,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl"], - context + context, ); const combined = getOutput(); @@ -353,13 +353,13 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "88.88.88"], - context + context, ); const combined = getOutput(); expect(combined).toContain("88.88.88"); expect(combined).toContain( - "Run 'sentry cli upgrade 88.88.88' to update." + "Run 'sentry cli upgrade 88.88.88' to update.", ); }); @@ -390,23 +390,23 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "88.88.88"], - context + context, ); expect(requests).toContain( - "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%4088.88.88" + "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%4088.88.88", ); expect(requests).toContain( - "https://api.github.com/repos/getsentry/cli/releases/tags/88.88.88" + "https://api.github.com/repos/getsentry/cli/releases/tags/88.88.88", ); expect(requests).toContain( - "https://api.github.com/repos/getsentry/cli/releases?per_page=30" + "https://api.github.com/repos/getsentry/cli/releases?per_page=30", ); expect(requests).not.toContain( - "https://api.github.com/repos/getsentry/toolkit/releases?per_page=30" + "https://api.github.com/repos/getsentry/toolkit/releases?per_page=30", ); expect( - requests.every((request) => !request.includes("per_page=100")) + requests.every((request) => !request.includes("per_page=100")), ).toBe(true); }); @@ -423,7 +423,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl"], - context + context, ); expect(getOutput()).toContain("Using cached target: 88.88.88"); @@ -446,7 +446,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl"], - context + context, ); expect(getOutput()).toContain("Using cached target: 88.88.88"); @@ -473,7 +473,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl"], - context + context, ); expect(getOutput()).not.toContain("Using cached target"); @@ -495,7 +495,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl"], - context + context, ); const combined = getOutput(); @@ -516,7 +516,7 @@ describe("sentry cli upgrade", () => { const allOutput = getOutput() + errors.join(""); expect(allOutput).toContain( - "Homebrew does not support installing a specific version" + "Homebrew does not support installing a specific version", ); }); @@ -531,7 +531,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "brew"], - context + context, ); const combined = getOutput(); @@ -565,14 +565,14 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "brew"], - context + context, ); expect(requests).toContain( - "https://api.github.com/repos/getsentry/cli/releases?per_page=30" + "https://api.github.com/repos/getsentry/cli/releases?per_page=30", ); expect(requests).not.toContain( - "https://api.github.com/repos/getsentry/toolkit/releases?per_page=30" + "https://api.github.com/repos/getsentry/toolkit/releases?per_page=30", ); }); }); @@ -616,7 +616,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "v1.0.0"], - context + context, ); const combined = getOutput(); @@ -639,7 +639,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "nightly"], - context + context, ); const combined = getOutput(); @@ -660,7 +660,7 @@ describe("sentry cli upgrade", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "nightly"], - context + context, ); const combined = getOutput(); @@ -679,7 +679,7 @@ describe("sentry cli upgrade — nightly channel", () => { beforeEach(() => { testDir = join( "/tmp", - `upgrade-nightly-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `upgrade-nightly-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); originalFetch = globalThis.fetch; @@ -704,7 +704,7 @@ describe("sentry cli upgrade — nightly channel", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "nightly"], - context + context, ); const combined = getOutput(); @@ -723,7 +723,7 @@ describe("sentry cli upgrade — nightly channel", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "stable"], - context + context, ); const combined = getOutput(); @@ -742,7 +742,7 @@ describe("sentry cli upgrade — nightly channel", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl"], - context + context, ); const combined = getOutput(); @@ -762,7 +762,7 @@ describe("sentry cli upgrade — nightly channel", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "nightly"], - context + context, ); expect(getReleaseChannel()).toBe("nightly"); @@ -778,7 +778,7 @@ describe("sentry cli upgrade — nightly channel", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "stable"], - context + context, ); expect(getReleaseChannel()).toBe("stable"); @@ -797,7 +797,7 @@ describe("sentry cli upgrade — nightly channel", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "nightly"], - context + context, ); const combined = getOutput(); @@ -817,7 +817,7 @@ describe("sentry cli upgrade — nightly channel", () => { await run( app, ["cli", "upgrade", "--check", "--method", "curl", "nightly"], - context + context, ); const combined = getOutput(); @@ -867,7 +867,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy beforeEach(() => { testDir = join( TEST_TMP_DIR, - `upgrade-spawn-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `upgrade-spawn-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); mkdirSync(spawnBinDir, { recursive: true }); @@ -994,7 +994,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy await run( app, ["cli", "upgrade", "--method", "curl", "--no-agent-skills"], - context + context, ); const setupCall = spawnedArgs.find((entry) => entry.args.includes("setup")); @@ -1019,81 +1019,79 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy expect(setupCall?.args).toContain("--ensure-auth-scopes"); }); - test.each([ - "npm", - "pnpm", - "bun", - "yarn", - ] as const)("classifies a missing pinned %s version without running the package manager", async (method) => { - const requests: string[] = []; - mockFetch(async (url) => { - requests.push(String(url)); - return new Response(null, { status: 404 }); - }); - const { context, errors, restore } = createMockContext({ - homeDir: testDir, - }); - restoreStderr = restore; - - await run(app, ["cli", "upgrade", "--method", method, "1.2.3"], context); - - expect(errors.join("\n")).toContain("Version 1.2.3 not found"); - expect(requests).toEqual(["https://registry.npmjs.org/sentry/1.2.3"]); - expect(spawnedArgs).toEqual([]); - }); - - test.each([ - "npm", - "pnpm", - "bun", - "yarn", - ] as const)("preserves non-404 HTTP failures for a pinned %s version", async (method) => { - for (const status of [401, 403, 429, 500]) { + test.each(["npm", "pnpm", "bun", "yarn"] as const)( + "classifies a missing pinned %s version without running the package manager", + async (method) => { const requests: string[] = []; mockFetch(async (url) => { requests.push(String(url)); - return new Response(null, { status }); + return new Response(null, { status: 404 }); }); const { context, errors, restore } = createMockContext({ homeDir: testDir, }); + restoreStderr = restore; await run(app, ["cli", "upgrade", "--method", method, "1.2.3"], context); - restore(); - expect(errors.join("\n")).toContain( - `Failed to fetch from npm: ${status}` - ); - expect(errors.join("\n")).not.toContain("Version 1.2.3 not found"); + expect(errors.join("\n")).toContain("Version 1.2.3 not found"); expect(requests).toEqual(["https://registry.npmjs.org/sentry/1.2.3"]); expect(spawnedArgs).toEqual([]); - } - }); + }, + ); + + test.each(["npm", "pnpm", "bun", "yarn"] as const)( + "preserves non-404 HTTP failures for a pinned %s version", + async (method) => { + for (const status of [401, 403, 429, 500]) { + const requests: string[] = []; + mockFetch(async (url) => { + requests.push(String(url)); + return new Response(null, { status }); + }); + const { context, errors, restore } = createMockContext({ + homeDir: testDir, + }); - test.each([ - "npm", - "pnpm", - "bun", - "yarn", - ] as const)("rejects malformed latest metadata for %s without running the package manager", async (method) => { - const requests: string[] = []; - mockFetch(async (url) => { - requests.push(String(url)); - return Response.json(null); - }); - const { context, errors, restore } = createMockContext({ - homeDir: testDir, - }); - restoreStderr = restore; + await run( + app, + ["cli", "upgrade", "--method", method, "1.2.3"], + context, + ); - await run(app, ["cli", "upgrade", "--method", method], context); + restore(); + expect(errors.join("\n")).toContain( + `Failed to fetch from npm: ${status}`, + ); + expect(errors.join("\n")).not.toContain("Version 1.2.3 not found"); + expect(requests).toEqual(["https://registry.npmjs.org/sentry/1.2.3"]); + expect(spawnedArgs).toEqual([]); + } + }, + ); - expect(errors.join("\n")).toContain( - "npm registry returned invalid metadata" - ); - expect(requests).toEqual(["https://registry.npmjs.org/sentry/latest"]); - expect(spawnedArgs).toEqual([]); - }); + test.each(["npm", "pnpm", "bun", "yarn"] as const)( + "rejects malformed latest metadata for %s without running the package manager", + async (method) => { + const requests: string[] = []; + mockFetch(async (url) => { + requests.push(String(url)); + return Response.json(null); + }); + const { context, errors, restore } = createMockContext({ + homeDir: testDir, + }); + restoreStderr = restore; + + await run(app, ["cli", "upgrade", "--method", method], context); + + expect(errors.join("\n")).toContain( + "npm registry returned invalid metadata", + ); + expect(requests).toEqual(["https://registry.npmjs.org/sentry/latest"]); + expect(spawnedArgs).toEqual([]); + }, + ); test("runs the new Homebrew binary and keeps JSON upgrades non-interactive", async () => { mockGitHubVersion("99.99.99"); @@ -1188,7 +1186,7 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy headers: { "content-type": "application/vnd.oci.image.manifest.v1+json", }, - } + }, ); } if (urlStr.includes(`/v2/getsentry/toolkit/blobs/${digest}`)) { @@ -1212,18 +1210,18 @@ describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy // Should have fetched from GHCR (token + manifest + blob) expect(capturedUrls.some((u) => u.includes("ghcr.io/token"))).toBe(true); expect(capturedUrls.some((u) => u.includes("/manifests/nightly"))).toBe( - true + true, ); expect( capturedUrls.some((url) => - url.includes(`/v2/getsentry/toolkit/blobs/${digest}`) - ) + url.includes(`/v2/getsentry/toolkit/blobs/${digest}`), + ), ).toBe(true); expect(capturedUrls.some((url) => url.includes("/v2/getsentry/cli/"))).toBe( - false + false, ); expect(spawnedArgs.some((entry) => entry.args.includes("setup"))).toBe( - true + true, ); expect(getOutput()).toContain("Upgraded to"); expect(getOutput()).toContain("0.99.0-dev.1234567890"); @@ -1264,7 +1262,7 @@ describe("sentry cli upgrade — migrateToStandaloneForNightly (child_process.sp beforeEach(() => { testDir = join( TEST_TMP_DIR, - `upgrade-migrate-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `upgrade-migrate-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); mkdirSync(migrateBinDir, { recursive: true }); @@ -1343,7 +1341,7 @@ describe("sentry cli upgrade — migrateToStandaloneForNightly (child_process.sp headers: { "content-type": "application/vnd.oci.image.manifest.v1+json", }, - } + }, ); } if (urlStr.includes(`/blobs/sha256:${"a".repeat(64)}`)) { @@ -1363,24 +1361,24 @@ describe("sentry cli upgrade — migrateToStandaloneForNightly (child_process.sp await run( app, ["cli", "upgrade", "--method", "npm", "0.99.0-dev.1234567890"], - context + context, ); const combined = getOutput(); expect(combined).toContain( - "Nightly builds are only available as standalone binaries." + "Nightly builds are only available as standalone binaries.", ); expect(combined).toContain("Migrating to standalone installation..."); expect(combined).toContain("Upgraded to"); // Warns about old npm install (rendered via formatUpgradeResult warnings) expect(combined).toContain( - "npm-installed sentry may still appear earlier in PATH" + "npm-installed sentry may still appear earlier in PATH", ); expect(combined).toContain("npm uninstall -g sentry"); expect(getReleaseChannel()).toBe("stable"); expect(migrateSpawnSpy).toHaveBeenCalledTimes(1); expect(migrateSpawnSpy.mock.calls[0]?.[1]).toEqual( - expect.arrayContaining(["--channel", "stable"]) + expect.arrayContaining(["--channel", "stable"]), ); }); @@ -1402,7 +1400,7 @@ describe("sentry cli upgrade — migrateToStandaloneForNightly (child_process.sp layers: [], annotations: { version: "0.99.0-dev.1234567890" }, }), - { status: 200 } + { status: 200 }, ); } return new Response("Unexpected", { status: 500 }); @@ -1423,7 +1421,7 @@ describe("sentry cli upgrade — migrateToStandaloneForNightly (child_process.sp "brew", "0.99.0-dev.1234567890", ], - context + context, ); expect(getOutput()).toContain("0.99.0-dev.1234567890"); @@ -1446,7 +1444,7 @@ describe("sentry cli upgrade — migrateToStandaloneForNightly (child_process.sp await run(app, ["cli", "upgrade", "--method", "brew", "1.2.3"], context); expect(errors.join("\n")).toContain( - "Homebrew does not support installing a specific version" + "Homebrew does not support installing a specific version", ); expect(requests).toEqual([]); expect(migrateSpawnSpy).not.toHaveBeenCalled(); @@ -1469,18 +1467,18 @@ describe("sentry cli upgrade — migrateToStandaloneForNightly (child_process.sp await run( app, ["cli", "upgrade", "--check", "--method", "npm", "1.2.3"], - context + context, ); expect(requests).toContain("https://registry.npmjs.org/sentry/1.2.3"); expect(requests).toContain( - "https://api.github.com/repos/getsentry/toolkit/releases?per_page=30" + "https://api.github.com/repos/getsentry/toolkit/releases?per_page=30", ); expect(requests.some((request) => request.includes("/commits?"))).toBe( - false + false, ); expect( - requests.some((request) => request.includes("/releases/tags/")) + requests.some((request) => request.includes("/releases/tags/")), ).toBe(false); }); }); @@ -1546,7 +1544,7 @@ describe("resolveUpgradeInstallDir", () => { test("keeps a non-legacy install dir unchanged", () => { const current = join(home, "bin"); expect( - resolveUpgradeInstallDir(current, `${current}${delimiter}/usr/bin`) + resolveUpgradeInstallDir(current, `${current}${delimiter}/usr/bin`), ).toBe(current); }); @@ -1557,13 +1555,13 @@ describe("resolveUpgradeInstallDir", () => { test("keeps the legacy dir when the XDG dir is not on PATH", () => { expect(resolveUpgradeInstallDir(legacyBinDir, "/usr/bin:/bin")).toBe( - legacyBinDir + legacyBinDir, ); }); test("keeps the legacy dir when PATH is undefined", () => { expect(resolveUpgradeInstallDir(legacyBinDir, undefined)).toBe( - legacyBinDir + legacyBinDir, ); }); diff --git a/packages/cli/test/commands/code-mappings/upload.test.ts b/packages/cli/test/commands/code-mappings/upload.test.ts index 455324a44..572171014 100644 --- a/packages/cli/test/commands/code-mappings/upload.test.ts +++ b/packages/cli/test/commands/code-mappings/upload.test.ts @@ -43,7 +43,7 @@ describe("code-mappings upload validation", () => { test("rejects nonexistent file", async () => { const ctx = makeContext(); await expect( - func.call(ctx, {}, join(tempDir, "nonexistent.json")) + func.call(ctx, {}, join(tempDir, "nonexistent.json")), ).rejects.toThrow(ValidationError); }); @@ -61,7 +61,7 @@ describe("code-mappings upload validation", () => { const ctx = makeContext(); await expect(func.call(ctx, {}, path)).rejects.toThrow( - "expected a JSON array" + "expected a JSON array", ); }); @@ -79,7 +79,7 @@ describe("code-mappings upload validation", () => { const ctx = makeContext(); await expect(func.call(ctx, {}, path)).rejects.toThrow( - "Invalid code mapping" + "Invalid code mapping", ); }); @@ -89,7 +89,7 @@ describe("code-mappings upload validation", () => { const ctx = makeContext(); await expect(func.call(ctx, {}, path)).rejects.toThrow( - "Invalid code mapping" + "Invalid code mapping", ); }); @@ -97,12 +97,12 @@ describe("code-mappings upload validation", () => { const path = join(tempDir, "bad.json"); await writeFile( path, - JSON.stringify([{ stackRoot: "", sourceRoot: "src" }]) + JSON.stringify([{ stackRoot: "", sourceRoot: "src" }]), ); const ctx = makeContext(); await expect(func.call(ctx, {}, path)).rejects.toThrow( - "Invalid code mapping" + "Invalid code mapping", ); }); @@ -124,7 +124,7 @@ describe("extractRepoName", () => { JSON.stringify([ { stackRoot: "com/example/module", sourceRoot: "modules/module/src" }, { stackRoot: "com/example/other", sourceRoot: "modules/other/src" }, - ]) + ]), ); // The command will pass validation but fail at org/project resolution diff --git a/packages/cli/test/commands/dart-symbol-map/upload.test.ts b/packages/cli/test/commands/dart-symbol-map/upload.test.ts index c352419c3..c6317149a 100644 --- a/packages/cli/test/commands/dart-symbol-map/upload.test.ts +++ b/packages/cli/test/commands/dart-symbol-map/upload.test.ts @@ -30,7 +30,7 @@ afterEach(async () => { * Run the dart-symbol-map upload command and capture stdout/stderr. */ async function runUpload( - args: string[] + args: string[], ): Promise<{ output: string; exitCode: number | undefined; error?: string }> { let output = ""; let error = ""; @@ -71,7 +71,7 @@ describe("sentry dart-symbol-map upload", () => { const mapPath = join(tempDir, "map.json"); await writeFile( mapPath, - JSON.stringify(["obfuscated1", "original1", "obfuscated2", "original2"]) + JSON.stringify(["obfuscated1", "original1", "obfuscated2", "original2"]), ); const { output } = await runUpload([ @@ -157,7 +157,7 @@ describe("sentry dart-symbol-map upload", () => { const mapPath = join(tempDir, "map.json"); await writeFile( mapPath, - JSON.stringify(["obfuscated1", "original1", "orphan"]) + JSON.stringify(["obfuscated1", "original1", "orphan"]), ); const { exitCode } = await runUpload([ diff --git a/packages/cli/test/commands/dashboard/create.test.ts b/packages/cli/test/commands/dashboard/create.test.ts index db4ef297a..463e201ac 100644 --- a/packages/cli/test/commands/dashboard/create.test.ts +++ b/packages/cli/test/commands/dashboard/create.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ContextError, ValidationError } from "../../../src/lib/errors.js"; @@ -31,11 +31,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { DashboardDetail } from "../../../src/types/dashboard.js"; @@ -160,7 +160,7 @@ describe("dashboard create", () => { const func = await createCommand.loader(); await expect( - func.call(context, { json: false }, "My Dashboard") + func.call(context, { json: false }, "My Dashboard"), ).rejects.toThrow(ContextError); }); @@ -171,7 +171,7 @@ describe("dashboard create", () => { context, { json: false }, "my-org/my-project", - "My Dashboard" + "My Dashboard", ); expect(fetchProjectIdSpy).toHaveBeenCalledWith("my-org", "my-project"); diff --git a/packages/cli/test/commands/dashboard/list.test.ts b/packages/cli/test/commands/dashboard/list.test.ts index 5ad8b0b59..1f03d72d6 100644 --- a/packages/cli/test/commands/dashboard/list.test.ts +++ b/packages/cli/test/commands/dashboard/list.test.ts @@ -25,11 +25,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -39,11 +39,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { ContextError } from "../../../src/lib/errors.js"; @@ -54,11 +54,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; vi.mock("../../../src/lib/polling.js", async (importOriginal) => { @@ -68,11 +68,11 @@ vi.mock("../../../src/lib/polling.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as polling from "../../../src/lib/polling.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -82,11 +82,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { DashboardListItem } from "../../../src/types/dashboard.js"; @@ -159,14 +159,14 @@ const DASHBOARD_C: DashboardListItem = { describe("dashboard list command", () => { const listDashboardsPaginatedSpy = vi.mocked( - apiClient.listDashboardsPaginated + apiClient.listDashboardsPaginated, ); const resolveOrgSpy = vi.mocked(resolveTarget.resolveOrgOnlyTarget); const openInBrowserSpy = vi.mocked(browser.openInBrowser); const withProgressSpy = vi.mocked(polling.withProgress); const resolveCursorSpy = vi.mocked(paginationDb.resolveCursor); const advancePaginationStateSpy = vi.mocked( - paginationDb.advancePaginationState + paginationDb.advancePaginationState, ); const hasPreviousPageSpy = vi.mocked(paginationDb.hasPreviousPage); @@ -176,7 +176,7 @@ describe("dashboard list command", () => { withProgressSpy.mockImplementation((_opts, fn) => fn(() => { /* no-op setMessage */ - }) + }), ); resolveCursorSpy.mockReturnValue({ cursor: undefined, @@ -310,7 +310,7 @@ describe("dashboard list command", () => { // perPage derivation tested via integration in the command's own tests. expect(listDashboardsPaginatedSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ cursor: undefined }) + expect.objectContaining({ cursor: undefined }), ); }); @@ -503,20 +503,20 @@ describe("dashboard list command", () => { expect(listDashboardsPaginatedSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ cursor: undefined }) + expect.objectContaining({ cursor: undefined }), ); }); test("throws ContextError when org cannot be resolved", async () => { resolveOrgSpy.mockRejectedValue( - new ContextError("Organization", "sentry dashboard list") + new ContextError("Organization", "sentry dashboard list"), ); const { context } = createMockContext(); const func = await listCommand.loader(); await expect(func.call(context, defaultFlags())).rejects.toThrow( - "organization" + "organization", ); }); diff --git a/packages/cli/test/commands/dashboard/resolve.test.ts b/packages/cli/test/commands/dashboard/resolve.test.ts index 19b5f3f71..4a3ef5aa4 100644 --- a/packages/cli/test/commands/dashboard/resolve.test.ts +++ b/packages/cli/test/commands/dashboard/resolve.test.ts @@ -27,11 +27,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { parseOrgProjectArg } from "../../../src/lib/arg-parsing.js"; import { @@ -48,11 +48,11 @@ vi.mock("../../../src/lib/region.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as region from "../../../src/lib/region.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -62,11 +62,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; // --------------------------------------------------------------------------- @@ -85,7 +85,7 @@ describe("parseDashboardPositionalArgs", () => { } catch (error) { expect(error).toBeInstanceOf(ValidationError); expect((error as ValidationError).message).toContain( - "Dashboard ID or title" + "Dashboard ID or title", ); } }); @@ -143,7 +143,7 @@ describe("parseDashboardPositionalArgs", () => { test("URL with org but no dashboard ID and no second arg throws", () => { expect(() => - parseDashboardPositionalArgs(["https://my-org.sentry.io/"]) + parseDashboardPositionalArgs(["https://my-org.sentry.io/"]), ).toThrow(ValidationError); }); }); @@ -261,7 +261,7 @@ describe("parseDashboardListArgs", () => { expect(() => parseDashboardListArgs([ "https://sentry-sdks.sentry.io/dashboard/4326879/", - ]) + ]), ).toThrow(ValidationError); try { parseDashboardListArgs([ @@ -494,7 +494,7 @@ describe("resolveOrgFromTarget", () => { const org = await resolveOrgFromTarget( parsed, "/tmp", - "sentry dashboard view" + "sentry dashboard view", ); expect(org).toBe("my-org"); expect(resolveEffectiveOrgSpy).toHaveBeenCalledWith("my-org"); @@ -506,7 +506,7 @@ describe("resolveOrgFromTarget", () => { const org = await resolveOrgFromTarget( parsed, "/tmp", - "sentry dashboard list" + "sentry dashboard list", ); expect(org).toBe("my-org"); expect(resolveEffectiveOrgSpy).toHaveBeenCalledWith("o1169445"); @@ -514,12 +514,12 @@ describe("resolveOrgFromTarget", () => { test("auto-detect propagates missing-organization ContextError", async () => { resolveOrgOnlyTargetSpy.mockRejectedValue( - new ContextError("Organization", "sentry dashboard view") + new ContextError("Organization", "sentry dashboard view"), ); const parsed = parseOrgProjectArg(undefined); await expect( - resolveOrgFromTarget(parsed, "/tmp", "sentry dashboard view") + resolveOrgFromTarget(parsed, "/tmp", "sentry dashboard view"), ).rejects.toThrow(ContextError); }); @@ -529,7 +529,7 @@ describe("resolveOrgFromTarget", () => { const org = await resolveOrgFromTarget( parsed, "/tmp", - "sentry dashboard list" + "sentry dashboard list", ); expect(org).toBe("detected-org"); expect(resolveOrgOnlyTargetSpy).toHaveBeenCalled(); @@ -544,14 +544,14 @@ describe("enrichDashboardError", () => { test("re-throws non-ApiError unchanged", async () => { const original = new Error("network failure"); await expect( - enrichDashboardError(original, { orgSlug: "my-org", operation: "list" }) + enrichDashboardError(original, { orgSlug: "my-org", operation: "list" }), ).rejects.toThrow(original); }); test("re-throws ApiError with unhandled status unchanged", async () => { const original = new ApiError("rate limited", 429, "Too many requests"); await expect( - enrichDashboardError(original, { orgSlug: "my-org", operation: "list" }) + enrichDashboardError(original, { orgSlug: "my-org", operation: "list" }), ).rejects.toThrow(ApiError); try { await enrichDashboardError(original, { @@ -747,7 +747,7 @@ describe("enrichDashboardError", () => { const apiErr = new ApiError( "Bad Request", 400, - "You may not exceed 10 dashboards on your current plan." + "You may not exceed 10 dashboards on your current plan.", ); try { await enrichDashboardError(apiErr, { @@ -761,7 +761,7 @@ describe("enrichDashboardError", () => { expect(msg).toContain("Dashboard create failed"); expect(msg).toContain("'my-org'"); expect((error as ApiError).detail).toContain( - "You may not exceed 10 dashboards" + "You may not exceed 10 dashboards", ); } }); @@ -769,7 +769,7 @@ describe("enrichDashboardError", () => { test("400 on non-create/update operation re-throws unchanged", async () => { const apiErr = new ApiError("Bad Request", 400, "some detail"); await expect( - enrichDashboardError(apiErr, { orgSlug: "my-org", operation: "list" }) + enrichDashboardError(apiErr, { orgSlug: "my-org", operation: "list" }), ).rejects.toThrow(apiErr); }); }); @@ -823,7 +823,7 @@ describe("validateWidgetEnums (with normalizeDataset)", () => { // Pipeline: caller runs normalizeDataset first, then passes the canonical // value to validateWidgetEnums. This simulates the wiring in add/edit. expect(() => - validateWidgetEnums("bar", normalizeDataset("errors")) + validateWidgetEnums("bar", normalizeDataset("errors")), ).not.toThrow(); }); @@ -835,13 +835,13 @@ describe("validateWidgetEnums (with normalizeDataset)", () => { test("rejects unknown datasets (no such alias)", () => { expect(() => validateWidgetEnums(undefined, "bogus-dataset")).toThrow( - ValidationError + ValidationError, ); }); test("rejects unknown display types", () => { expect(() => validateWidgetEnums("pie-chart", undefined)).toThrow( - ValidationError + ValidationError, ); }); }); @@ -857,10 +857,10 @@ describe("autoDefaultGroupLimit", () => { test("returns DEFAULT_GROUP_BY_LIMIT for grouped widgets with no limit", () => { expect(autoDefaultGroupLimit(["browser.name"], undefined)).toBe( - DEFAULT_GROUP_BY_LIMIT + DEFAULT_GROUP_BY_LIMIT, ); expect(autoDefaultGroupLimit(["browser.name"], null)).toBe( - DEFAULT_GROUP_BY_LIMIT + DEFAULT_GROUP_BY_LIMIT, ); }); @@ -879,19 +879,19 @@ describe("applyGroupLimitAutoDefault", () => { // Auto-defaulted columns (e.g., ["issue"] for issue/table) should NOT // trigger the auto-default limit — caller signals intent via userGroupBy. expect( - applyGroupLimitAutoDefault(undefined, ["issue"], undefined) + applyGroupLimitAutoDefault(undefined, ["issue"], undefined), ).toBeUndefined(); }); test("applies default when --group-by passed without limit", () => { expect( - applyGroupLimitAutoDefault(["browser.name"], ["browser.name"], undefined) + applyGroupLimitAutoDefault(["browser.name"], ["browser.name"], undefined), ).toBe(DEFAULT_GROUP_BY_LIMIT); }); test("preserves explicit limit", () => { expect( - applyGroupLimitAutoDefault(["browser.name"], ["browser.name"], 25) + applyGroupLimitAutoDefault(["browser.name"], ["browser.name"], 25), ).toBe(25); }); diff --git a/packages/cli/test/commands/dashboard/restore.test.ts b/packages/cli/test/commands/dashboard/restore.test.ts index 17881ec0c..05707ed9f 100644 --- a/packages/cli/test/commands/dashboard/restore.test.ts +++ b/packages/cli/test/commands/dashboard/restore.test.ts @@ -6,12 +6,12 @@ */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolve from "../../../src/commands/dashboard/resolve.js"; import { restoreCommand } from "../../../src/commands/dashboard/restore.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as polling from "../../../src/lib/polling.js"; import type { DashboardDetail } from "../../../src/types/dashboard.js"; @@ -77,7 +77,7 @@ describe("dashboard restore command", () => { beforeEach(() => { restoreDashboardRevisionSpy = vi.spyOn( apiClient, - "restoreDashboardRevision" + "restoreDashboardRevision", ); resolveOrgFromTargetSpy = vi.spyOn(resolve, "resolveOrgFromTarget"); resolveDashboardIdSpy = vi.spyOn(resolve, "resolveDashboardId"); @@ -87,7 +87,7 @@ describe("dashboard restore command", () => { .mockImplementation((_opts, fn) => fn(() => { /* no-op setMessage */ - }) + }), ); // Default mocks @@ -113,13 +113,13 @@ describe("dashboard restore command", () => { await func.call( context, defaultFlags({ json: true, revision: "42" }), - "123" + "123", ); expect(restoreDashboardRevisionSpy).toHaveBeenCalledWith( "test-org", "123", - "42" + "42", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -164,14 +164,14 @@ describe("dashboard restore command", () => { await func.call( context, defaultFlags({ json: true, revision: "5" }), - "456" + "456", ); expect(resolveDashboardIdSpy).toHaveBeenCalledWith("test-org", "456"); expect(restoreDashboardRevisionSpy).toHaveBeenCalledWith( "test-org", "123", - "5" + "5", ); }); @@ -182,7 +182,7 @@ describe("dashboard restore command", () => { context, defaultFlags({ json: true, revision: "10" }), "my-org/", - "789" + "789", ); expect(resolveDashboardIdSpy).toHaveBeenCalledWith("test-org", "789"); @@ -194,12 +194,12 @@ describe("dashboard restore command", () => { await func.call( context, defaultFlags({ json: true, revision: "3" }), - "My Dashboard Title" + "My Dashboard Title", ); expect(resolveDashboardIdSpy).toHaveBeenCalledWith( "test-org", - "My Dashboard Title" + "My Dashboard Title", ); }); @@ -221,7 +221,7 @@ describe("dashboard restore command", () => { expect(restoreDashboardRevisionSpy).toHaveBeenCalledWith( "test-org", "123", - "1" + "1", ); }); @@ -233,7 +233,7 @@ describe("dashboard restore command", () => { const func = await restoreCommand.loader(); await expect( - func.call(context, defaultFlags({ revision: "999" }), "123") + func.call(context, defaultFlags({ revision: "999" }), "123"), ).rejects.toThrow(); }); diff --git a/packages/cli/test/commands/dashboard/revisions.test.ts b/packages/cli/test/commands/dashboard/revisions.test.ts index 45ee1f752..ec2f9e260 100644 --- a/packages/cli/test/commands/dashboard/revisions.test.ts +++ b/packages/cli/test/commands/dashboard/revisions.test.ts @@ -6,14 +6,14 @@ */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolve from "../../../src/commands/dashboard/resolve.js"; import { revisionsCommand } from "../../../src/commands/dashboard/revisions.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as polling from "../../../src/lib/polling.js"; import type { DashboardRevision } from "../../../src/types/dashboard.js"; @@ -106,7 +106,7 @@ describe("dashboard revisions command", () => { beforeEach(() => { listDashboardRevisionsPaginatedSpy = vi.spyOn( apiClient, - "listDashboardRevisionsPaginated" + "listDashboardRevisionsPaginated", ); resolveOrgFromTargetSpy = vi.spyOn(resolve, "resolveOrgFromTarget"); resolveDashboardIdSpy = vi.spyOn(resolve, "resolveDashboardId"); @@ -116,7 +116,7 @@ describe("dashboard revisions command", () => { .mockImplementation((_opts, fn) => fn(() => { /* no-op setMessage */ - }) + }), ); advancePaginationStateSpy = vi .spyOn(paginationDb, "advancePaginationState") @@ -312,7 +312,7 @@ describe("dashboard revisions command", () => { expect(listDashboardRevisionsPaginatedSpy).toHaveBeenCalledWith( "test-org", "123", - { perPage: 25, cursor: undefined } + { perPage: 25, cursor: undefined }, ); }); @@ -340,12 +340,12 @@ describe("dashboard revisions command", () => { await func.call( context, defaultFlags({ json: true }), - "My Dashboard Title" + "My Dashboard Title", ); expect(resolveDashboardIdSpy).toHaveBeenCalledWith( "test-org", - "My Dashboard Title" + "My Dashboard Title", ); }); }); diff --git a/packages/cli/test/commands/dashboard/widget/add.test.ts b/packages/cli/test/commands/dashboard/widget/add.test.ts index a3f77e647..dfecc6f45 100644 --- a/packages/cli/test/commands/dashboard/widget/add.test.ts +++ b/packages/cli/test/commands/dashboard/widget/add.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; import { ValidationError } from "../../../../src/lib/errors.js"; @@ -33,11 +33,11 @@ vi.mock("../../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import type { DashboardDetail } from "../../../../src/types/dashboard.js"; @@ -135,7 +135,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "line", query: ["count"] }, "123", - "New Widget" + "New Widget", ); expect(getDashboardSpy).toHaveBeenCalledWith("acme-corp", "123"); @@ -147,7 +147,7 @@ describe("dashboard widget add", () => { widgets: expect.arrayContaining([ expect.objectContaining({ title: "New Widget", displayType: "line" }), ]), - }) + }), ); // Original widgets should be preserved plus the new one const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -161,7 +161,7 @@ describe("dashboard widget add", () => { context, { json: true, display: "big_number", query: ["count"] }, "123", - "My Counter" + "My Counter", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -179,7 +179,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "line", query: ["count"] }, "123", - "Error Rate" + "Error Rate", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -207,7 +207,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "invalid_type" }, "123", - "Bad Widget" + "Bad Widget", ) .catch((e: Error) => e); expect(err).toBeInstanceOf(ValidationError); @@ -223,7 +223,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "line", query: ["not_a_function"] }, "123", - "Bad Widget" + "Bad Widget", ) .catch((e: Error) => e); expect(err).toBeInstanceOf(ValidationError); @@ -237,7 +237,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "line", dataset: "issue" }, "123", - "Issues Over Time" + "Issues Over Time", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -252,7 +252,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "table", dataset: "issue" }, "123", - "Top Issues" + "Top Issues", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -274,7 +274,7 @@ describe("dashboard widget add", () => { query: ["count"], }, "123", - "Error Count" + "Error Count", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -294,7 +294,7 @@ describe("dashboard widget add", () => { query: ["p50(value,completion.duration_ms,distribution,none)"], }, "123", - "Latency" + "Latency", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -317,7 +317,7 @@ describe("dashboard widget add", () => { query: ["last_seen"], }, "123", - "Last Seen" + "Last Seen", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -338,7 +338,7 @@ describe("dashboard widget add", () => { query: ["count"], }, "123", - "Errors" + "Errors", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -360,7 +360,7 @@ describe("dashboard widget add", () => { query: ["count"], }, "123", - "Bad" + "Bad", ) .catch((e: Error) => e); @@ -383,7 +383,7 @@ describe("dashboard widget add", () => { limit: 5, }, "123", - "Issues by Project" + "Issues by Project", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -410,7 +410,7 @@ describe("dashboard widget add", () => { height: 3, }, "123", - "Full Width Widget" + "Full Width Widget", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -433,7 +433,7 @@ describe("dashboard widget add", () => { col: 4, }, "123", - "Positioned Counter" + "Positioned Counter", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -452,7 +452,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "line", query: ["count"], width: 7 }, "123", - "Too Wide" + "Too Wide", ) .catch((e: Error) => e); expect(err).toBeInstanceOf(ValidationError); @@ -468,7 +468,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "table", query: ["count"], col: 1 }, "123", - "Wide Table" + "Wide Table", ) .catch((e: Error) => e); expect(err).toBeInstanceOf(ValidationError); @@ -483,7 +483,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "line", query: ["count"], row: -1 }, "123", - "Bad Y" + "Bad Y", ) .catch((e: Error) => e); expect(err).toBeInstanceOf(ValidationError); @@ -503,7 +503,7 @@ describe("dashboard widget add", () => { limit: 5, }, "123", - "Top Browsers" + "Top Browsers", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -523,7 +523,7 @@ describe("dashboard widget add", () => { "group-by": ["browser.name"], }, "123", - "Top Browsers" + "Top Browsers", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -546,7 +546,7 @@ describe("dashboard widget add", () => { limit: 10, }, "123", - "Top Browsers" + "Top Browsers", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -561,7 +561,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "big_number", query: ["count"] }, "123", - "Total Count" + "Total Count", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -579,7 +579,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "table", dataset: "issue" }, "123", - "Top Issues" + "Top Issues", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -596,7 +596,7 @@ describe("dashboard widget add", () => { context, { json: false, display: "big_number", query: ["count"], layout: "dense" }, "123", - "Dense Widget" + "Dense Widget", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -619,7 +619,7 @@ describe("dashboard widget add", () => { layout: "sequential", }, "123", - "Sequential Widget" + "Sequential Widget", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -643,7 +643,7 @@ describe("dashboard widget add", () => { layout: "invalid", }, "123", - "Bad Layout" + "Bad Layout", ) .catch((e: unknown) => e); diff --git a/packages/cli/test/commands/dashboard/widget/delete.test.ts b/packages/cli/test/commands/dashboard/widget/delete.test.ts index cc2fd53b1..a07125872 100644 --- a/packages/cli/test/commands/dashboard/widget/delete.test.ts +++ b/packages/cli/test/commands/dashboard/widget/delete.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; import { ValidationError } from "../../../../src/lib/errors.js"; @@ -33,11 +33,11 @@ vi.mock("../../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import type { DashboardDetail } from "../../../../src/types/dashboard.js"; @@ -141,7 +141,7 @@ describe("dashboard widget delete", () => { widgets: expect.not.arrayContaining([ expect.objectContaining({ title: "Error Count" }), ]), - }) + }), ); // Only one widget should remain after deleting index 0 const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -155,7 +155,7 @@ describe("dashboard widget delete", () => { await func.call( context, { json: false, yes: true, title: "Slow Spans" }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; diff --git a/packages/cli/test/commands/dashboard/widget/edit.test.ts b/packages/cli/test/commands/dashboard/widget/edit.test.ts index 2f66cf812..377c58b9c 100644 --- a/packages/cli/test/commands/dashboard/widget/edit.test.ts +++ b/packages/cli/test/commands/dashboard/widget/edit.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../../src/lib/api-client.js"; import { ValidationError } from "../../../../src/lib/errors.js"; @@ -33,11 +33,11 @@ vi.mock("../../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../../src/lib/resolve-target.js"; import type { DashboardDetail } from "../../../../src/types/dashboard.js"; @@ -144,7 +144,7 @@ describe("dashboard widget edit", () => { displayType: "line", }), ]), - }) + }), ); }); @@ -154,7 +154,7 @@ describe("dashboard widget edit", () => { await func.call( context, { json: false, title: "error count", display: "bar" }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -229,7 +229,7 @@ describe("dashboard widget edit", () => { await func.call( context, { json: false, index: 0, dataset: "issue" }, - "123" + "123", ); expect(updateDashboardSpy).toHaveBeenCalledTimes(1); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -263,7 +263,7 @@ describe("dashboard widget edit", () => { await func.call( context, { json: false, index: 0, display: "table" }, - "123" + "123", ); expect(updateDashboardSpy).toHaveBeenCalledTimes(1); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -325,7 +325,7 @@ describe("dashboard widget edit", () => { await func.call( context, { json: false, index: 1, col: 0, width: 6, height: 4 }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -422,7 +422,7 @@ describe("dashboard widget edit", () => { dataset: "error-events", query: ["last_seen"], }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -436,7 +436,7 @@ describe("dashboard widget edit", () => { await func.call( context, { json: false, index: 0, dataset: "errors" }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -459,7 +459,7 @@ describe("dashboard widget edit", () => { dataset: "errors", query: ["last_seen"], }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -489,7 +489,7 @@ describe("dashboard widget edit", () => { display: "line", "group-by": ["browser.name"], }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -512,7 +512,7 @@ describe("dashboard widget edit", () => { "group-by": ["browser.name"], limit: 25, }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -543,7 +543,7 @@ describe("dashboard widget edit", () => { display: "line", "group-by": ["browser.name"], }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; @@ -557,7 +557,7 @@ describe("dashboard widget edit", () => { await func.call( context, { json: false, index: 0, query: ["p95:span.duration"] }, - "123" + "123", ); const body = updateDashboardSpy.mock.calls[0]?.[2]; diff --git a/packages/cli/test/commands/debug-files/bundle-jvm.test.ts b/packages/cli/test/commands/debug-files/bundle-jvm.test.ts index 0897661ab..c7176936c 100644 --- a/packages/cli/test/commands/debug-files/bundle-jvm.test.ts +++ b/packages/cli/test/commands/debug-files/bundle-jvm.test.ts @@ -25,7 +25,7 @@ type BundleJvmFlags = { type CmdFunc = ( this: unknown, flags: BundleJvmFlags, - sourcePath: string + sourcePath: string, ) => Promise<unknown>; function makeContext() { @@ -56,7 +56,7 @@ describe("sentry debug-files bundle-jvm", () => { }); await writeFile( join(tempDir, "src", "main", "java", "com", "example", "Main.java"), - "public class Main {}" + "public class Main {}", ); const ctx = makeContext(); @@ -66,7 +66,7 @@ describe("sentry debug-files bundle-jvm", () => { output: outputDir, "debug-id": VALID_DEBUG_ID, }, - tempDir + tempDir, ); // Verify the ZIP was created @@ -93,7 +93,7 @@ describe("sentry debug-files bundle-jvm", () => { output: outputDir, "debug-id": "not-a-uuid", }, - tempDir + tempDir, ); expect.unreachable("should have thrown"); } catch (err) { @@ -113,7 +113,7 @@ describe("sentry debug-files bundle-jvm", () => { output: outputDir, "debug-id": VALID_DEBUG_ID, }, - join(tempDir, "nonexistent") + join(tempDir, "nonexistent"), ); expect.unreachable("should have thrown"); } catch (err) { @@ -133,7 +133,7 @@ describe("sentry debug-files bundle-jvm", () => { output: outputDir, "debug-id": VALID_DEBUG_ID, }, - tempDir + tempDir, ); const writeCall = ctx.stdout.write.mock.calls[0]; @@ -155,7 +155,7 @@ describe("sentry debug-files bundle-jvm", () => { await mkdir(join(tempDir, "src", "main", "java"), { recursive: true }); await writeFile( join(tempDir, "src", "main", "java", "App.java"), - "class App {}" + "class App {}", ); const ctx = makeContext(); @@ -166,7 +166,7 @@ describe("sentry debug-files bundle-jvm", () => { "debug-id": VALID_DEBUG_ID, exclude: ["generated"], }, - tempDir + tempDir, ); const writeCall = ctx.stdout.write.mock.calls[0]; @@ -184,7 +184,7 @@ describe("sentry debug-files bundle-jvm", () => { await mkdir(join(tempDir, "src", "main", "java"), { recursive: true }); await writeFile( join(tempDir, "src", "main", "java", "App.java"), - "class App {}" + "class App {}", ); const ctx = makeContext(); @@ -194,7 +194,7 @@ describe("sentry debug-files bundle-jvm", () => { output: outputDir, "debug-id": VALID_DEBUG_ID, }, - tempDir + tempDir, ); expect(existsSync(join(outputDir, `${VALID_DEBUG_ID}.zip`))).toBe(true); diff --git a/packages/cli/test/commands/debug-files/bundle-sources.test.ts b/packages/cli/test/commands/debug-files/bundle-sources.test.ts index e56b748f2..c25726c38 100644 --- a/packages/cli/test/commands/debug-files/bundle-sources.test.ts +++ b/packages/cli/test/commands/debug-files/bundle-sources.test.ts @@ -47,7 +47,7 @@ const KNOWN_DEBUG_ID = "0f13a5da-412a-fbf7-c866-2048f3294f3d"; /** Run `debug-files bundle-sources` and capture stdout + exit code. */ async function runBundleSources( - args: string[] + args: string[], ): Promise<{ output: string; exitCode: number | undefined }> { let output = ""; const mockContext: SentryContext = { diff --git a/packages/cli/test/commands/debug-files/check.test.ts b/packages/cli/test/commands/debug-files/check.test.ts index 98ff2ee66..46cbf0a41 100644 --- a/packages/cli/test/commands/debug-files/check.test.ts +++ b/packages/cli/test/commands/debug-files/check.test.ts @@ -37,7 +37,7 @@ const KNOWN_DEBUG_ID = "0f13a5da-412a-fbf7-c866-2048f3294f3d"; /** Run `debug-files check` and capture stdout + exit code. */ async function runCheck( - args: string[] + args: string[], ): Promise<{ output: string; exitCode: number | undefined }> { let output = ""; const mockContext: SentryContext = { @@ -83,7 +83,7 @@ describe("sentry debug-files check", () => { expect(parsed.objects[0]).toHaveProperty("debugId", KNOWN_DEBUG_ID); expect(parsed.objects[0]).toHaveProperty( "codeId", - "daa5130f2a41f7fbc8662048f3294f3d439ca7ff" + "daa5130f2a41f7fbc8662048f3294f3d439ca7ff", ); }); @@ -111,7 +111,7 @@ describe("sentry debug-files check", () => { const path = join(tempDir, "unusable.sym"); await writeFile( path, - "MODULE Linux x86_64 000000000000000000000000000000000 x\nPUBLIC 1000 0 sym" + "MODULE Linux x86_64 000000000000000000000000000000000 x\nPUBLIC 1000 0 sym", ); const { output, exitCode } = await runCheck([path, "--json"]); diff --git a/packages/cli/test/commands/debug-files/find.test.ts b/packages/cli/test/commands/debug-files/find.test.ts index ddf575168..d03d883e8 100644 --- a/packages/cli/test/commands/debug-files/find.test.ts +++ b/packages/cli/test/commands/debug-files/find.test.ts @@ -36,7 +36,7 @@ const BREAKPAD_FIXTURE = [ const BREAKPAD_ID = "0f13a5da-412a-fbf7-c866-2048f3294f3d"; async function runFind( - args: string[] + args: string[], ): Promise<{ output: string; exitCode: number | undefined }> { let output = ""; const mockContext: SentryContext = { @@ -79,7 +79,7 @@ describe("sentry debug-files find", () => { }); // A breakpad match does not satisfy the request. expect(parsed.missing.map((m: { id: string }) => m.id)).toContain( - BREAKPAD_ID + BREAKPAD_ID, ); expect(exitCode).toBe(1); }); diff --git a/packages/cli/test/commands/debug-files/print-sources.test.ts b/packages/cli/test/commands/debug-files/print-sources.test.ts index 1d91e8f8e..db4e85c1e 100644 --- a/packages/cli/test/commands/debug-files/print-sources.test.ts +++ b/packages/cli/test/commands/debug-files/print-sources.test.ts @@ -50,7 +50,7 @@ const BREAKPAD_NO_SOURCES = [ /** Run `debug-files print-sources` and capture stdout + exit code. */ async function runPrintSources( - args: string[] + args: string[], ): Promise<{ output: string; exitCode: number | undefined }> { let output = ""; const mockContext: SentryContext = { diff --git a/packages/cli/test/commands/debug-files/upload.test.ts b/packages/cli/test/commands/debug-files/upload.test.ts index 94caf8b66..7af8d006e 100644 --- a/packages/cli/test/commands/debug-files/upload.test.ts +++ b/packages/cli/test/commands/debug-files/upload.test.ts @@ -16,9 +16,9 @@ import { zipSync } from "fflate"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { app } from "../../../src/app.js"; import type { SentryContext } from "../../../src/context.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as chunkUpload from "../../../src/lib/api/chunk-upload.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as debugFilesApi from "../../../src/lib/api/debug-files.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -80,7 +80,7 @@ async function writeBreakpadZip(name = "symbols.zip"): Promise<string> { const path = join(tempDir, name); await writeFile( path, - zipSync({ "example.sym": new TextEncoder().encode(BREAKPAD_FIXTURE) }) + zipSync({ "example.sym": new TextEncoder().encode(BREAKPAD_FIXTURE) }), ); return path; } @@ -91,7 +91,7 @@ const EMBEDDED_PE_DEBUG_ID = "d8eb7dca-4883-4b10-a1f7-048ea1ea388b-cfb0fc89"; /** Copy a committed binary DIF fixture into `tempDir` under `name`. */ async function writeDifFixture(fixture: string, name: string): Promise<string> { const bytes = readFileSync( - new URL(`../../fixtures/dif/${fixture}`, import.meta.url) + new URL(`../../fixtures/dif/${fixture}`, import.meta.url), ); const path = join(tempDir, name); await writeFile(path, bytes); @@ -117,7 +117,7 @@ async function writeIl2cppFixture(): Promise<void> { /** Run `debug-files upload` and capture stdout + exit code. */ async function runUpload( - args: string[] + args: string[], ): Promise<{ output: string; error: string; exitCode: number | undefined }> { let output = ""; let error = ""; @@ -200,7 +200,7 @@ describe("sentry debug-files upload", () => { async () => { const { exitCode } = await runUpload(["--derived-data", "--no-upload"]); expect(exitCode).not.toBe(0); - } + }, ); // ── --no-upload (dry-run) ──────────────────────────────────────── @@ -387,7 +387,7 @@ describe("sentry debug-files upload", () => { "INFO CODE_ID DAA5130F2A41F7FBC8662048F3294F3D439CA7FF", ].join("\n")}\n${Array.from( { length: 500 }, - (_, i) => `PUBLIC ${i.toString(16)} 0 sym_${i}` + (_, i) => `PUBLIC ${i.toString(16)} 0 sym_${i}`, ).join("\n")}`; await writeFile(join(tempDir, "big.sym"), bigBody); diff --git a/packages/cli/test/commands/docs.test.ts b/packages/cli/test/commands/docs.test.ts index cc9fd4cab..8cd0c7949 100644 --- a/packages/cli/test/commands/docs.test.ts +++ b/packages/cli/test/commands/docs.test.ts @@ -9,7 +9,7 @@ const { detectDocsContext, listDocs, queryDocs, withProgress } = vi.hoisted( listDocs: vi.fn(), queryDocs: vi.fn(), withProgress: vi.fn(), - }) + }), ); vi.mock("../../src/lib/docs-context.js", () => ({ detectDocsContext })); @@ -41,7 +41,7 @@ function createContext(): { async function runDocsRoute( context: SentryContext, - args: string[] + args: string[], ): Promise<void> { const app = buildApplication(docsRoute, { name: "sentry docs" }); await run(app, args, context); @@ -52,7 +52,7 @@ beforeEach(() => { withProgress.mockImplementation(async (_options, fn) => fn(() => { /* Progress rendering is covered by polling tests. */ - }) + }), ); detectDocsContext.mockResolvedValue({ frameworks: ["nextjs"], @@ -88,7 +88,7 @@ describe("docs commands", () => { sentryConfigured: true, }); expect( - JSON.parse(stdoutWrite.mock.calls.map((call) => call[0]).join("")) + JSON.parse(stdoutWrite.mock.calls.map((call) => call[0]).join("")), ).toEqual({ answer: "Use [tracing](https://docs.sentry.io/platforms/javascript/tracing/).\n\n## Sources\n\n- <https://docs.sentry.io/platforms/javascript/tracing/>", @@ -112,7 +112,7 @@ describe("docs commands", () => { ], rotationIntervalMs: 4000, }, - expect.any(Function) + expect.any(Function), ); }); @@ -140,7 +140,7 @@ describe("docs commands", () => { expect(listDocs).toHaveBeenCalledWith("nextjs tracing", 5); expect(stdoutWrite.mock.calls.map((call) => call[0]).join("")).toContain( - "Tracing" + "Tracing", ); expect(withProgress).not.toHaveBeenCalled(); }); diff --git a/packages/cli/test/commands/dsn/list.test.ts b/packages/cli/test/commands/dsn/list.test.ts index 2ae8ef217..1cc8c73f8 100644 --- a/packages/cli/test/commands/dsn/list.test.ts +++ b/packages/cli/test/commands/dsn/list.test.ts @@ -123,14 +123,14 @@ const DEFAULT_PROJECTS = [ /** Serve organization discovery and project metadata before the key endpoint. */ function mockProjectApi( projects: Project[], - fetchKeys: (target: Project, url: URL) => Response + fetchKeys: (target: Project, url: URL) => Response, ) { return mockFetch(async (input, init) => { const request = new Request(input, init); const url = new URL(request.url); const organizations = [ ...new Map( - projects.map((item) => [item.organization.slug, item.organization]) + projects.map((item) => [item.organization.slug, item.organization]), ).values(), ].map((org) => ({ ...org, @@ -143,7 +143,7 @@ function mockProjectApi( for (const org of organizations) { if ( [org.slug, org.id, `o${org.id}`].some( - (value) => url.pathname === `/api/0/organizations/${value}/` + (value) => url.pathname === `/api/0/organizations/${value}/`, ) ) { return response(org); @@ -158,7 +158,7 @@ function mockProjectApi( item.organization.id, `o${item.organization.id}`, ].includes(match[1] ?? "") && - [item.slug, item.id].includes(match[2] ?? "") + [item.slug, item.id].includes(match[2] ?? ""), ); if (target) { return match[3] ? fetchKeys(target, url) : response(target); @@ -183,7 +183,7 @@ function keyPage(target: Project, url: URL, count: number) { const next = offset + limit; return response( keys.slice(offset, next), - next < count ? `0:${next}:0` : undefined + next < count ? `0:${next}:0` : undefined, ); } @@ -195,7 +195,7 @@ function cacheOrganizations(projects: Project[]) { orgId: organization.id, orgName: organization.slug, regionUrl: "https://us.sentry.io", - })) + })), ); } @@ -206,7 +206,7 @@ async function invoke( limit?: number; cursor?: string; fields?: string[]; - } = {} + } = {}, ) { const ctx = createContext(); const func = await listCommand.loader(); @@ -219,7 +219,7 @@ async function invoke( cursor: options.cursor, fields: options.fields, }, - options.target + options.target, ); return ctx; } @@ -233,7 +233,7 @@ describe("dsn list", () => { const projects = CROSS_ORG_PROJECTS; cacheOrganizations(projects); globalThis.fetch = mockProjectApi(projects, (target, url) => - keyPage(target, url, target.organization.slug === "org-one" ? 5 : 1) + keyPage(target, url, target.organization.slug === "org-one" ? 5 : 1), ); const first = await invokeJson({ target: "frontend", limit: 4 }); @@ -244,7 +244,7 @@ describe("dsn list", () => { "org-one/frontend 2", "org-one/frontend 3", "org-two/frontend 1", - ]) + ]), ); expect(first).toMatchObject({ hasMore: true, hasPrev: false }); @@ -259,82 +259,82 @@ describe("dsn list", () => { ]); expect(next).toMatchObject({ hasMore: false, hasPrev: true }); expect( - await invokeJson({ target: "frontend", limit: 4, cursor: "prev" }) + await invokeJson({ target: "frontend", limit: 4, cursor: "prev" }), ).toEqual(first); }); - test.each([ - true, - false, - ])("detects all monorepo projects with public slugs from cold caches (json=%s)", async (json) => { - const projects = [ - project("test-org", "1", "frontend", "42"), - project("test-org", "1", "backend", "43"), - ]; - setDefaultOrganization(null); - setDefaultProject(null); - clearOrgRegions(); - await writeFile( - join(configDir(), "package.json"), - JSON.stringify({ private: true, workspaces: ["apps/*"] }) - ); - for (const target of projects) { - const directory = join(configDir(), "apps", target.slug); - await mkdir(directory, { recursive: true }); + test.each([true, false])( + "detects all monorepo projects with public slugs from cold caches (json=%s)", + async (json) => { + const projects = [ + project("test-org", "1", "frontend", "42"), + project("test-org", "1", "backend", "43"), + ]; + setDefaultOrganization(null); + setDefaultProject(null); + clearOrgRegions(); await writeFile( - join(directory, ".env"), - `SENTRY_DSN=https://${"a".repeat(32)}@o1.ingest.us.sentry.io/${target.id}\n` + join(configDir(), "package.json"), + JSON.stringify({ private: true, workspaces: ["apps/*"] }), + ); + for (const target of projects) { + const directory = join(configDir(), "apps", target.slug); + await mkdir(directory, { recursive: true }); + await writeFile( + join(directory, ".env"), + `SENTRY_DSN=https://${"a".repeat(32)}@o1.ingest.us.sentry.io/${target.id}\n`, + ); + } + globalThis.fetch = mockProjectApi(projects, (target, url) => + keyPage(target, url, 2), ); - } - globalThis.fetch = mockProjectApi(projects, (target, url) => - keyPage(target, url, 2) - ); - const first = await invoke({ limit: 2, json }); - if (json) { - const result = JSON.parse(first.output()); - expect(result).toMatchObject({ hasMore: true, hasPrev: false }); - expect(result.data).toHaveLength(2); - expect(result.data).toEqual( - expect.arrayContaining( - projects.map((target) => ({ - org: "test-org", - project: target.slug, - name: `test-org/${target.slug} 1`, - isActive: true, - dateCreated: KEY.dateCreated, - dsn: `https://${"a".repeat(32)}@o1.ingest.us.sentry.io/${target.id}`, - })) - ) + const first = await invoke({ limit: 2, json }); + if (json) { + const result = JSON.parse(first.output()); + expect(result).toMatchObject({ hasMore: true, hasPrev: false }); + expect(result.data).toHaveLength(2); + expect(result.data).toEqual( + expect.arrayContaining( + projects.map((target) => ({ + org: "test-org", + project: target.slug, + name: `test-org/${target.slug} 1`, + isActive: true, + dateCreated: KEY.dateCreated, + dsn: `https://${"a".repeat(32)}@o1.ingest.us.sentry.io/${target.id}`, + })), + ), + ); + } else { + expect(first.output()).toContain("test-org/frontend"); + expect(first.output()).toContain("test-org/backend"); + } + expect(`${first.output()}\n${first.diagnostics()}`).not.toMatch( + /\b(?:test-org|1)\s*\/\s*(?:42|43)\b|\b1\s*\/\s*(?:frontend|backend)\b/, ); - } else { - expect(first.output()).toContain("test-org/frontend"); - expect(first.output()).toContain("test-org/backend"); - } - expect(`${first.output()}\n${first.diagnostics()}`).not.toMatch( - /\b(?:test-org|1)\s*\/\s*(?:42|43)\b|\b1\s*\/\s*(?:frontend|backend)\b/ - ); - // The cold listing populated the cache; navigation must keep the same history. - const next = await invokeJson({ limit: 2, cursor: "next" }); - expect(next).toMatchObject({ hasMore: false, hasPrev: true }); - expect(next.data.map((item: { name: string }) => item.name)).toEqual( - expect.arrayContaining(["test-org/frontend 2", "test-org/backend 2"]) - ); - expect(next.data).toHaveLength(2); - const previous = await invokeJson({ limit: 2, cursor: "prev" }); - expect(previous.data.map((item: { name: string }) => item.name)).toEqual( - expect.arrayContaining(["test-org/frontend 1", "test-org/backend 1"]) - ); - expect(previous).toMatchObject({ hasMore: true, hasPrev: false }); - }); + // The cold listing populated the cache; navigation must keep the same history. + const next = await invokeJson({ limit: 2, cursor: "next" }); + expect(next).toMatchObject({ hasMore: false, hasPrev: true }); + expect(next.data.map((item: { name: string }) => item.name)).toEqual( + expect.arrayContaining(["test-org/frontend 2", "test-org/backend 2"]), + ); + expect(next.data).toHaveLength(2); + const previous = await invokeJson({ limit: 2, cursor: "prev" }); + expect(previous.data.map((item: { name: string }) => item.name)).toEqual( + expect.arrayContaining(["test-org/frontend 1", "test-org/backend 1"]), + ); + expect(previous).toMatchObject({ hasMore: true, hasPrev: false }); + }, + ); test("explains when detected DSNs cannot be resolved", async () => { setDefaultOrganization(null); setDefaultProject(null); await writeFile( join(configDir(), ".env"), - `SENTRY_DSN=https://${"a".repeat(32)}@sentry.example.com/42\n` + `SENTRY_DSN=https://${"a".repeat(32)}@sentry.example.com/42\n`, ); globalThis.fetch = mockFetch(async (input, init) => { const url = new URL(new Request(input, init).url); @@ -349,55 +349,55 @@ describe("dsn list", () => { name: "ContextError", command: "sentry dsn list <org>/<project>", message: expect.stringContaining( - "Found 1 DSN(s) that could not be resolved — you may not have access to these projects" + "Found 1 DSN(s) that could not be resolved — you may not have access to these projects", ), }); }); - test.each([ - false, - true, - ])("surfaces failed projects without treating them as empty (allDenied=%s)", async (allDenied) => { - const projects = CROSS_ORG_PROJECTS; - cacheOrganizations(projects); - globalThis.fetch = mockProjectApi(projects, (target, url) => { - if (allDenied || target.organization.slug === "org-two") { - return response({ detail: "Permission denied" }, undefined, 403); + test.each([false, true])( + "surfaces failed projects without treating them as empty (allDenied=%s)", + async (allDenied) => { + const projects = CROSS_ORG_PROJECTS; + cacheOrganizations(projects); + globalThis.fetch = mockProjectApi(projects, (target, url) => { + if (allDenied || target.organization.slug === "org-two") { + return response({ detail: "Permission denied" }, undefined, 403); + } + return keyPage(target, url, 1); + }); + if (allDenied) { + await expect(invoke({ target: "frontend" })).rejects.toThrow(ApiError); + return; } - return keyPage(target, url, 1); - }); - if (allDenied) { - await expect(invoke({ target: "frontend" })).rejects.toThrow(ApiError); - return; - } - const result = await invokeJson({ target: "frontend", limit: 4 }); - expect(result.data).toMatchObject([ - { org: "org-one", project: "frontend", name: "org-one/frontend 1" }, - ]); - expect(result.errors).toMatchObject([ - { project: "org-two/frontend", status: 403 }, - ]); - }); + const result = await invokeJson({ target: "frontend", limit: 4 }); + expect(result.data).toMatchObject([ + { org: "org-one", project: "frontend", name: "org-one/frontend 1" }, + ]); + expect(result.errors).toMatchObject([ + { project: "org-two/frontend", status: 403 }, + ]); + }, + ); test("does not advertise an unsafe cursor when the limit cannot include every target", async () => { const projects = CROSS_ORG_PROJECTS; cacheOrganizations(projects); globalThis.fetch = mockProjectApi(projects, (target, url) => - keyPage(target, url, 2) + keyPage(target, url, 2), ); const result = await invokeJson({ target: "frontend", limit: 1 }); expect(result.data).toHaveLength(1); expect(result.hasMore).toBe(true); expect(result.nextCursor).toBeUndefined(); await expect( - invoke({ target: "frontend", limit: 1, cursor: "next" }) + invoke({ target: "frontend", limit: 1, cursor: "next" }), ).rejects.toThrow("No next page"); }); test("returns an empty organization page without project lookups", async () => { globalThis.fetch = mockFetch(async (input, init) => { expect(new URL(new Request(input, init).url).pathname).toBe( - "/api/0/organizations/test-org/project-keys/" + "/api/0/organizations/test-org/project-keys/", ); return response([]); }); @@ -408,22 +408,22 @@ describe("dsn list", () => { }); }); - test.each([ - "0", - "1001", - ])("rejects --limit %s before any API call", async (limit) => { - const fetch = vi.fn(mockFetch(async () => response([]))); - globalThis.fetch = fetch; - const ctx = createContext(); - const cliProcess = { ...process, exitCode: undefined }; - await run( - buildApplication(dsnRoute, { name: "sentry dsn" }), - ["list", "test-org/", "--limit", limit], - { ...ctx.context, process: cliProcess } - ); - expect(cliProcess.exitCode).toBe(ExitCode.InvalidArgument); - expect(fetch).not.toHaveBeenCalled(); - }); + test.each(["0", "1001"])( + "rejects --limit %s before any API call", + async (limit) => { + const fetch = vi.fn(mockFetch(async () => response([]))); + globalThis.fetch = fetch; + const ctx = createContext(); + const cliProcess = { ...process, exitCode: undefined }; + await run( + buildApplication(dsnRoute, { name: "sentry dsn" }), + ["list", "test-org/", "--limit", limit], + { ...ctx.context, process: cliProcess }, + ); + expect(cliProcess.exitCode).toBe(ExitCode.InvalidArgument); + expect(fetch).not.toHaveBeenCalled(); + }, + ); test("ls honors an explicit project over defaults and exposes only public JSON fields", async () => { globalThis.fetch = mockProjectApi(DEFAULT_PROJECTS, (_target, url) => { @@ -434,7 +434,7 @@ describe("dsn list", () => { await run( buildApplication(dsnRoute, { name: "sentry dsn" }), ["ls", "test-org/other-project", "--json"], - ctx.context + ctx.context, ); expect(JSON.parse(ctx.output())).toEqual({ data: [ @@ -462,7 +462,7 @@ describe("dsn list", () => { response([ KEY, { ...KEY, name: "Old browser", isActive: false, dateCreated: null }, - ]) + ]), ); const ctx = await invoke({ json: false, target: "test-org/test-project" }); for (const visible of [ @@ -476,7 +476,7 @@ describe("dsn list", () => { expect(ctx.output()).toContain(visible); } expect(ctx.output()).not.toMatch( - /internal-key-id|private-key|private-dsn|internal-purpose/ + /internal-key-id|private-key|private-dsn|internal-purpose/, ); }); @@ -502,7 +502,7 @@ describe("dsn list", () => { return response({ detail: "Not found" }, undefined, 404); } const target = DEFAULT_PROJECTS.find( - (item) => url.pathname === `/api/0/projects/test-org/${item.id}/` + (item) => url.pathname === `/api/0/projects/test-org/${item.id}/`, ); if (target) { return response(target); @@ -542,13 +542,13 @@ describe("dsn list", () => { }); expect(prev).toEqual(first); await expect( - invoke({ target: "test-org/test-project", limit: 2, cursor: "next" }) + invoke({ target: "test-org/test-project", limit: 2, cursor: "next" }), ).rejects.toThrow("No next page"); }); test("uses the configured project and scopes its pagination history to project and page size", async () => { globalThis.fetch = mockProjectApi(DEFAULT_PROJECTS, (target, url) => - keyPage(target, url, 2) + keyPage(target, url, 2), ); const first = await invokeJson({ limit: 1 }); expect(first).toMatchObject({ @@ -566,10 +566,10 @@ describe("dsn list", () => { const previous = await invokeJson({ limit: 1, cursor: "prev" }); expect(previous).toEqual(first); await expect( - invoke({ target: "test-org/other-project", limit: 1, cursor: "next" }) + invoke({ target: "test-org/other-project", limit: 1, cursor: "next" }), ).rejects.toThrow("No next page"); await expect( - invoke({ target: "test-org/test-project", limit: 2, cursor: "next" }) + invoke({ target: "test-org/test-project", limit: 2, cursor: "next" }), ).rejects.toThrow("No next page"); }); }); diff --git a/packages/cli/test/commands/event/list.func.test.ts b/packages/cli/test/commands/event/list.func.test.ts index bec2f99d6..43ec38233 100644 --- a/packages/cli/test/commands/event/list.func.test.ts +++ b/packages/cli/test/commands/event/list.func.test.ts @@ -18,11 +18,11 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { @@ -32,11 +32,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { @@ -46,11 +46,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; import { parsePeriod } from "../../../src/lib/time-range.js"; import type { IssueEvent, SentryIssue } from "../../../src/types/sentry.js"; @@ -176,7 +176,7 @@ describe("event list command func()", () => { await func.call( context, { limit: 25, json: true, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -200,7 +200,7 @@ describe("event list command func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -227,7 +227,7 @@ describe("event list command func()", () => { await func.call( context, { limit: 2, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -252,14 +252,14 @@ describe("event list command func()", () => { await func.call( context, { limit: 25, json: true, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); expect(advancePaginationStateSpy).toHaveBeenCalledWith( "event-list", expect.any(String), "next", - "cursor123" + "cursor123", ); }); @@ -275,14 +275,14 @@ describe("event list command func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); expect(resolveIssueSpy).toHaveBeenCalledWith( expect.objectContaining({ command: "list", commandBase: "sentry event", - }) + }), ); }); @@ -299,8 +299,8 @@ describe("event list command func()", () => { func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "123456789" - ) + "123456789", + ), ).rejects.toThrow("organization"); }); }); diff --git a/packages/cli/test/commands/event/send.test.ts b/packages/cli/test/commands/event/send.test.ts index 441fe1195..9d10f7c8d 100644 --- a/packages/cli/test/commands/event/send.test.ts +++ b/packages/cli/test/commands/event/send.test.ts @@ -4,11 +4,11 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { sendCommand } from "../../../src/commands/event/send.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn import * as dsnIndex from "../../../src/lib/dsn/index.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn import * as eventSendDsn from "../../../src/lib/envelope/event-send-dsn.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn import * as transport from "../../../src/lib/envelope/transport.js"; import { ConfigError, ValidationError } from "../../../src/lib/errors.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -63,7 +63,7 @@ describe("sendCommand.func()", () => { level: "error", "no-environ": true, }, - SAAS_DSN + SAAS_DSN, ); expect(sendSpy).toHaveBeenCalledTimes(1); @@ -85,7 +85,7 @@ describe("sendCommand.func()", () => { level: "fatal", "no-environ": true, }, - SAAS_DSN + SAAS_DSN, ); const body = sendSpy.mock.calls[0]?.[1] as string; @@ -101,7 +101,7 @@ describe("sendCommand.func()", () => { tag: ["env:prod", "region:us"], "no-environ": true, }, - SAAS_DSN + SAAS_DSN, ); const body = sendSpy.mock.calls[0]?.[1] as string; @@ -112,7 +112,7 @@ describe("sendCommand.func()", () => { test("missing DSN throws ConfigError", async () => { const { ctx } = makeContext(); await expect(func.call(ctx, { "no-environ": true })).rejects.toBeInstanceOf( - ConfigError + ConfigError, ); }); @@ -148,7 +148,7 @@ describe("sendCommand.func()", () => { level: "error", "no-environ": true, }, - "grow-together-therapy/javascript-react" + "grow-together-therapy/javascript-react", ); expect(resolveSpy).toHaveBeenCalledWith("/tmp", { kind: "project", @@ -170,7 +170,7 @@ describe("sendCommand.func()", () => { json: true, "no-environ": true, }, - SAAS_DSN + SAAS_DSN, ); const output = writes.join(""); @@ -187,8 +187,8 @@ describe("sendCommand.func()", () => { ctx, { "no-environ": true }, SAAS_DSN, - "/nonexistent/missing.json" - ) + "/nonexistent/missing.json", + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -196,7 +196,7 @@ describe("sendCommand.func()", () => { const { ctx } = makeContext(); await expect( - func.call(ctx, { raw: true, "no-environ": true }, SAAS_DSN) + func.call(ctx, { raw: true, "no-environ": true }, SAAS_DSN), ).rejects.toBeInstanceOf(ValidationError); }); }); diff --git a/packages/cli/test/commands/event/view.test.ts b/packages/cli/test/commands/event/view.test.ts index 7cd1bbf2b..310aa9bae 100644 --- a/packages/cli/test/commands/event/view.test.ts +++ b/packages/cli/test/commands/event/view.test.ts @@ -5,7 +5,7 @@ * and viewCommand func() body in src/commands/event/view.ts */ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { @@ -30,11 +30,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.mocked access import * as apiClient from "../../../src/lib/api-client.js"; import { ProjectSpecificationType } from "../../../src/lib/arg-parsing.js"; @@ -45,11 +45,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; @@ -68,11 +68,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { resolveProjectBoundSlug } from "../../../src/lib/resolve-target.js"; @@ -83,11 +83,11 @@ vi.mock("../../../src/lib/span-tree.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as spanTree from "../../../src/lib/span-tree.js"; import type { SentryEvent } from "../../../src/types/index.js"; @@ -185,7 +185,7 @@ describe("parsePositionalArgs", () => { // "my-org/my-project" is a normal org/project target, not an issue short ID. // parseSlashSeparatedArg will throw ContextError as expected. expect(() => parsePositionalArgs(["my-org/my-project"])).toThrow( - "Event ID" + "Event ID", ); }); }); @@ -276,7 +276,7 @@ describe("parsePositionalArgs", () => { test("'latest' as second arg throws ContextError", () => { expect(() => parsePositionalArgs(["my-org/frontend", "latest"])).toThrow( - ContextError + ContextError, ); }); @@ -431,9 +431,8 @@ describe("parsePositionalArgs", () => { savedSentryHost = process.env.SENTRY_HOST; delete process.env.SENTRY_URL; delete process.env.SENTRY_HOST; - const { resetEnvTokenHostForTesting } = await import( - "../../../src/lib/env-token-host.js" - ); + const { resetEnvTokenHostForTesting } = + await import("../../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); }); @@ -448,9 +447,8 @@ describe("parsePositionalArgs", () => { } else { delete process.env.SENTRY_HOST; } - const { resetEnvTokenHostForTesting } = await import( - "../../../src/lib/env-token-host.js" - ); + const { resetEnvTokenHostForTesting } = + await import("../../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); }); @@ -483,7 +481,7 @@ describe("parsePositionalArgs", () => { test("org-only URL throws ContextError", () => { expect(() => - parsePositionalArgs(["https://sentry.io/organizations/my-org/"]) + parsePositionalArgs(["https://sentry.io/organizations/my-org/"]), ).toThrow(ContextError); }); }); @@ -512,7 +510,7 @@ describe("resolveProjectBoundSlug", () => { findProjectsBySlugSpy.mockResolvedValue({ projects: [], orgs: [] }); await expect(resolveProjectBoundSlug("my-project", HINT)).rejects.toThrow( - ResolutionError + ResolutionError, ); }); @@ -525,10 +523,10 @@ describe("resolveProjectBoundSlug", () => { } catch (error) { expect(error).toBeInstanceOf(ResolutionError); expect((error as ResolutionError).message).toContain( - 'Project "frontend"' + 'Project "frontend"', ); expect((error as ResolutionError).message).toContain( - "Check that you have access" + "Check that you have access", ); // Message says "not found", not "is required" expect((error as ResolutionError).message).toContain("not found"); @@ -604,7 +602,7 @@ describe("resolveProjectBoundSlug", () => { }); await expect(resolveProjectBoundSlug("frontend", HINT)).rejects.toThrow( - ValidationError + ValidationError, ); }); @@ -621,7 +619,7 @@ describe("resolveProjectBoundSlug", () => { await resolveProjectBoundSlug( "frontend", HINT, - "sentry event view <org>/frontend event-456" + "sentry event view <org>/frontend event-456", ); expect.unreachable("Should have thrown"); } catch (error) { @@ -648,14 +646,14 @@ describe("resolveProjectBoundSlug", () => { await resolveProjectBoundSlug( "api", HINT, - "sentry event view <org>/api abc123" + "sentry event view <org>/api abc123", ); expect.unreachable("Should have thrown"); } catch (error) { expect(error).toBeInstanceOf(ValidationError); const message = (error as ValidationError).message; expect(message).toContain( - "Example: sentry event view <org>/api abc123" + "Example: sentry event view <org>/api abc123", ); } }); @@ -766,7 +764,7 @@ describe("resolveEventTarget", () => { resolveOrgAndProjectSpy = vi.spyOn(resolveTarget, "resolveOrgAndProject"); resolveProjectBoundSlugSpy = vi.spyOn( resolveTarget, - "resolveProjectBoundSlug" + "resolveProjectBoundSlug", ); setTagSpy = vi.spyOn(Sentry, "setTag"); setOrgRegion("acme", DEFAULT_SENTRY_URL); @@ -900,7 +898,7 @@ describe("resolveOrgAllTarget", () => { resolveEventInOrgSpy.mockResolvedValue(null); await expect( - resolveOrgAllTarget("acme", "notfound", "/tmp") + resolveOrgAllTarget("acme", "notfound", "/tmp"), ).rejects.toBeInstanceOf(ResolutionError); }); @@ -909,7 +907,7 @@ describe("resolveOrgAllTarget", () => { resolveEventInOrgSpy.mockRejectedValue(err); await expect(resolveOrgAllTarget("acme", "abc123", "/tmp")).rejects.toBe( - err + err, ); }); }); @@ -1034,7 +1032,7 @@ describe("viewCommand.func", () => { openInBrowserSpy = vi.spyOn(browser, "openInBrowser"); resolveProjectBoundSlugSpy = vi.spyOn( resolveTarget, - "resolveProjectBoundSlug" + "resolveProjectBoundSlug", ); listEventAttachmentsSpy = vi .spyOn(apiClient, "listEventAttachments") @@ -1071,7 +1069,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 0 }, VALID_EVENT_ID, - "test-org/test-proj" + "test-org/test-proj", ); // Command should complete without error (warning goes to consola, not stdout) @@ -1093,7 +1091,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 0 }, "test-org/test-proj", - VALID_EVENT_ID + VALID_EVENT_ID, ); expect(result?.hint).toBeUndefined(); @@ -1115,16 +1113,16 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 0 }, "test-org/test-proj", - VALID_EVENT_ID + VALID_EVENT_ID, ); expect(listEventAttachmentsSpy).toHaveBeenCalledWith( "test-org", "test-proj", - VALID_EVENT_ID + VALID_EVENT_ID, ); const output = JSON.parse( - stdoutWrite.mock.calls.map((call) => call[0]).join("") + stdoutWrite.mock.calls.map((call) => call[0]).join(""), ); expect(output.attachments).toEqual([ expect.objectContaining({ @@ -1153,7 +1151,7 @@ describe("viewCommand.func", () => { context, { json: false, web: false, spans: 0 }, "test-org/test-proj", - VALID_EVENT_ID + VALID_EVENT_ID, ); const output = stdoutWrite.mock.calls @@ -1173,7 +1171,7 @@ describe("viewCommand.func", () => { success: false, }); listEventAttachmentsSpy.mockRejectedValue( - new Error("attachments unavailable") + new Error("attachments unavailable"), ); const { context, stdoutWrite } = createMockContext(); @@ -1182,11 +1180,11 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 0 }, "test-org/test-proj", - VALID_EVENT_ID + VALID_EVENT_ID, ); const output = JSON.parse( - stdoutWrite.mock.calls.map((call) => call[0]).join("") + stdoutWrite.mock.calls.map((call) => call[0]).join(""), ); expect(output.eventID).toBe(VALID_EVENT_ID); expect(output.attachments).toEqual([]); @@ -1220,7 +1218,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 0 }, "CAM-82X", - "95fd7f5a" + "95fd7f5a", ); // Should NOT go through resolveProjectBoundSlug (the old buggy path) @@ -1263,7 +1261,7 @@ describe("viewCommand.func", () => { expect(listEventAttachmentsSpy).toHaveBeenCalledWith( "auto-org", "auto-project", - VALID_EVENT_ID + VALID_EVENT_ID, ); resolveOrgSpy.mockRestore(); @@ -1289,11 +1287,11 @@ describe("viewCommand.func", () => { await func.call( context, { json: true, web: false, spans: 0 }, - "my-org/17370" + "my-org/17370", ); expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: "my-org" }) + expect.objectContaining({ org: "my-org" }), ); expect(getLatestEventSpy).toHaveBeenCalledWith("my-org", "17370"); @@ -1327,11 +1325,11 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 0 }, "my-org/my-project", - "CAM-82X" + "CAM-82X", ); expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: "my-org" }) + expect.objectContaining({ org: "my-org" }), ); expect(getLatestEventSpy).toHaveBeenCalled(); @@ -1359,7 +1357,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 0 }, "test_org/test_proj", - VALID_EVENT_ID + VALID_EVENT_ID, ); // parseOrgProjectArg normalizes "test_org/test_proj" → "test-org/test-proj" @@ -1378,7 +1376,7 @@ describe("viewCommand.func", () => { const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false, spans: 0 }, "--h") + func.call(context, { json: false, web: false, spans: 0 }, "--h"), ).rejects.toThrow(); }); @@ -1387,7 +1385,7 @@ describe("viewCommand.func", () => { const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false, spans: 0 }, "not-a-hex-id") + func.call(context, { json: false, web: false, spans: 0 }, "not-a-hex-id"), ).rejects.toThrow(); }); }); @@ -1415,7 +1413,7 @@ describe("fetchEventWithContext", () => { mockEvent, "my-org", "my-project", - "abc123" + "abc123", ); expect(result).toEqual({ org: "my-org", @@ -1433,7 +1431,7 @@ describe("fetchEventWithContext", () => { null, "my-org", "my-project", - "abc123" + "abc123", ); expect(result).toEqual({ org: "my-org", @@ -1445,7 +1443,7 @@ describe("fetchEventWithContext", () => { test("falls back to org-wide search on 404 and finds event", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); const resolvedEvent = { ...mockEvent, @@ -1461,7 +1459,7 @@ describe("fetchEventWithContext", () => { null, "my-org", "my-project", - "abc123" + "abc123", ); expect(result).toEqual({ org: "my-org", @@ -1472,19 +1470,19 @@ describe("fetchEventWithContext", () => { test("throws ResolutionError when project-scoped, org-wide, and cross-org all fail", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); vi.spyOn(apiClient, "resolveEventInOrg").mockResolvedValue(null); vi.spyOn(apiClient, "findEventAcrossOrgs").mockResolvedValue(null); await expect( - fetchEventWithContext(null, "my-org", "my-project", "abc123") + fetchEventWithContext(null, "my-org", "my-project", "abc123"), ).rejects.toThrow(ResolutionError); }); test("falls back to cross-org search when org-wide returns null", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); vi.spyOn(apiClient, "resolveEventInOrg").mockResolvedValue(null); const crossOrgEvent = { @@ -1501,7 +1499,7 @@ describe("fetchEventWithContext", () => { null, "my-org", "my-project", - "abc123" + "abc123", ); expect(result).toEqual({ org: "other-org", @@ -1512,7 +1510,7 @@ describe("fetchEventWithContext", () => { test("cross-org fallback passes excludeOrgs when same-org search succeeded", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); // Same-org search completed successfully (returned null = definitive "not found") vi.spyOn(apiClient, "resolveEventInOrg").mockResolvedValue(null); @@ -1521,7 +1519,7 @@ describe("fetchEventWithContext", () => { .mockResolvedValue(null); await expect( - fetchEventWithContext(null, "my-org", "my-project", "abc123") + fetchEventWithContext(null, "my-org", "my-project", "abc123"), ).rejects.toThrow(ResolutionError); expect(findSpy).toHaveBeenCalledWith("abc123", { @@ -1531,18 +1529,18 @@ describe("fetchEventWithContext", () => { test("cross-org does not exclude org when same-org search threw", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); // Same-org search threw a transient error — org was NOT definitively searched vi.spyOn(apiClient, "resolveEventInOrg").mockRejectedValue( - new Error("500 Internal Server Error") + new Error("500 Internal Server Error"), ); const findSpy = vi .spyOn(apiClient, "findEventAcrossOrgs") .mockResolvedValue(null); await expect( - fetchEventWithContext(null, "my-org", "my-project", "abc123") + fetchEventWithContext(null, "my-org", "my-project", "abc123"), ).rejects.toThrow(ResolutionError); // excludeOrgs should be undefined so cross-org retries the same org @@ -1553,18 +1551,18 @@ describe("fetchEventWithContext", () => { test("cross-org excludes org when same-org search was rate-limited (429)", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); // Same-org search hit a rate limit — retrying it immediately is futile (CLI-2Y1) vi.spyOn(apiClient, "resolveEventInOrg").mockRejectedValue( - new ApiError("Too Many Requests", 429) + new ApiError("Too Many Requests", 429), ); const findSpy = vi .spyOn(apiClient, "findEventAcrossOrgs") .mockResolvedValue(null); await expect( - fetchEventWithContext(null, "my-org", "my-project", "abc123") + fetchEventWithContext(null, "my-org", "my-project", "abc123"), ).rejects.toThrow(ResolutionError); // org must be excluded — re-querying a rate-limited endpoint immediately @@ -1576,29 +1574,29 @@ describe("fetchEventWithContext", () => { test("swallows non-auth cross-org errors and throws ResolutionError", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); vi.spyOn(apiClient, "resolveEventInOrg").mockResolvedValue(null); vi.spyOn(apiClient, "findEventAcrossOrgs").mockRejectedValue( - new Error("Network timeout") + new Error("Network timeout"), ); await expect( - fetchEventWithContext(null, "my-org", "my-project", "abc123") + fetchEventWithContext(null, "my-org", "my-project", "abc123"), ).rejects.toThrow(ResolutionError); }); test("propagates AuthError from cross-org fallback", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); vi.spyOn(apiClient, "resolveEventInOrg").mockResolvedValue(null); vi.spyOn(apiClient, "findEventAcrossOrgs").mockRejectedValue( - new AuthError("expired", "Token expired") + new AuthError("expired", "Token expired"), ); await expect( - fetchEventWithContext(null, "my-org", "my-project", "abc123") + fetchEventWithContext(null, "my-org", "my-project", "abc123"), ).rejects.toThrow(AuthError); }); @@ -1606,18 +1604,18 @@ describe("fetchEventWithContext", () => { // that event/view.ts captured at module load time. The resolveEventInOrg // mock doesn't take effect, so AuthError isn't thrown before cross-org // fallback. Tested via the Bun test suite. - // biome-ignore lint/suspicious/noSkippedTests: vitest barrel-mock limitation — mock doesn't intercept module-load binding + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest barrel-mock limitation — mock doesn't intercept module-load binding test.skip("propagates AuthError from same-org fallback", async () => { vi.mocked(apiClient.getEvent).mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); vi.mocked(apiClient.resolveEventInOrg).mockRejectedValue( - new AuthError("expired", "Token expired") + new AuthError("expired", "Token expired"), ); const findSpy = vi.mocked(apiClient.findEventAcrossOrgs); await expect( - fetchEventWithContext(null, "my-org", "my-project", "abc123") + fetchEventWithContext(null, "my-org", "my-project", "abc123"), ).rejects.toThrow(AuthError); // Cross-org should never be attempted when auth is broken expect(findSpy).not.toHaveBeenCalled(); @@ -1625,10 +1623,10 @@ describe("fetchEventWithContext", () => { test("tries cross-org fallback even when org-wide search throws", async () => { vi.spyOn(apiClient, "getEvent").mockRejectedValue( - new ApiError("Not found", 404) + new ApiError("Not found", 404), ); vi.spyOn(apiClient, "resolveEventInOrg").mockRejectedValue( - new Error("500 Internal Server Error") + new Error("500 Internal Server Error"), ); const crossOrgEvent = { ...mockEvent, @@ -1646,7 +1644,7 @@ describe("fetchEventWithContext", () => { null, "my-org", "my-project", - "abc123" + "abc123", ); expect(result).toEqual({ org: "other-org", @@ -1658,15 +1656,15 @@ describe("fetchEventWithContext", () => { // Skip: same vitest barrel-mock limitation — getEvent mock doesn't // intercept the binding captured by event/view.ts at module load time. - // biome-ignore lint/suspicious/noSkippedTests: vitest barrel-mock limitation — mock doesn't intercept module-load binding + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest barrel-mock limitation — mock doesn't intercept module-load binding test.skip("propagates non-404 errors without fallback", async () => { vi.mocked(apiClient.getEvent).mockRejectedValue( - new ApiError("Server error", 500) + new ApiError("Server error", 500), ); const resolveEventSpy = vi.mocked(apiClient.resolveEventInOrg); await expect( - fetchEventWithContext(null, "my-org", "my-project", "abc123") + fetchEventWithContext(null, "my-org", "my-project", "abc123"), ).rejects.toThrow("Server error"); expect(resolveEventSpy).not.toHaveBeenCalled(); }); @@ -1849,7 +1847,7 @@ describe("jsonTransformEventView", () => { requestedCount: 1, }); expect(result).toEqual( - expect.objectContaining({ eventID: "abc123", trace: null }) + expect.objectContaining({ eventID: "abc123", trace: null }), ); // Should NOT be an array expect(Array.isArray(result)).toBe(false); @@ -1887,7 +1885,7 @@ describe("jsonTransformEventView", () => { events: [{ event: mockEvent("abc123"), trace: null }], requestedCount: 1, }, - ["eventID"] + ["eventID"], ); expect(result).toEqual({ eventID: "abc123" }); }); @@ -1901,7 +1899,7 @@ describe("jsonTransformEventView", () => { ], requestedCount: 2, }, - ["eventID"] + ["eventID"], ); expect(result).toEqual([{ eventID: "event1" }, { eventID: "event2" }]); }); @@ -1952,7 +1950,7 @@ describe("fetchMultipleEvents", () => { const event2 = mockEvent("event2"); vi.spyOn(apiClient, "getEvent").mockImplementation( (_org: string, _proj: string, id: string) => - Promise.resolve(id === "event1" ? event1 : event2) + Promise.resolve(id === "event1" ? event1 : event2), ); const result = await fetchMultipleEvents({ @@ -1973,7 +1971,7 @@ describe("fetchMultipleEvents", () => { (_org: string, _proj: string, id: string) => id === "event1" ? Promise.resolve(event1) - : Promise.reject(new Error("not found")) + : Promise.reject(new Error("not found")), ); const result = await fetchMultipleEvents({ @@ -2000,7 +1998,7 @@ describe("fetchMultipleEvents", () => { project: "my-project", prefetchedEvent: null, primaryId: "event1", - }) + }), ).rejects.toThrow("Server error"); }); }); diff --git a/packages/cli/test/commands/explore.test.ts b/packages/cli/test/commands/explore.test.ts index 6ae658390..4c25cef7f 100644 --- a/packages/cli/test/commands/explore.test.ts +++ b/packages/cli/test/commands/explore.test.ts @@ -15,11 +15,11 @@ vi.mock("../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../src/lib/api-client.js"; vi.mock("../../src/lib/db/pagination.js", async (importOriginal) => { @@ -29,11 +29,11 @@ vi.mock("../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../src/lib/db/pagination.js"; import { ContextError, ValidationError } from "../../src/lib/errors.js"; import { DEFAULT_REPLAY_EXPLORE_FIELDS } from "../../src/lib/replay-search.js"; @@ -45,16 +45,16 @@ vi.mock("../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../src/lib/resolve-target.js"; import { parsePeriod } from "../../src/lib/time-range.js"; import { useTestConfigDir } from "../helpers.js"; -// Keep namespace references alive so biome doesn't strip the imports +// Keep namespace references alive so linting doesn't strip the imports const _apiRef = apiClient; const _paginationRef = paginationDb; const _resolveRef = resolveTarget; @@ -190,12 +190,12 @@ const parseExploreDataset = ( describe("sentry explore", () => { describe("dataset parsing", () => { - test.each([ - "transaction", - "transactions", - ])("accepts %s as the legacy transactions view", (dataset) => { - expect(parseExploreDataset(dataset)).toBe(dataset); - }); + test.each(["transaction", "transactions"])( + "accepts %s as the legacy transactions view", + (dataset) => { + expect(parseExploreDataset(dataset)).toBe(dataset); + }, + ); }); describe("target resolution", () => { @@ -208,11 +208,11 @@ describe("sentry explore", () => { expect(resolveTargetSpy).toHaveBeenCalledWith( "my-org/", "/tmp/test-explore", - "explore" + "explore", ); expect(queryEventsSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ query: undefined }) + expect.objectContaining({ query: undefined }), ); }); @@ -224,7 +224,7 @@ describe("sentry explore", () => { expect(queryEventsSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ query: "project:cli" }) + expect.objectContaining({ query: "project:cli" }), ); }); @@ -235,12 +235,12 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, query: "level:error" }, - "my-org/cli" + "my-org/cli", ); expect(queryEventsSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ query: "project:cli level:error" }) + expect.objectContaining({ query: "project:cli level:error" }), ); }); @@ -256,11 +256,11 @@ describe("sentry explore", () => { expect(resolveTargetSpy).toHaveBeenCalledWith( "cli", "/tmp/test-explore", - "explore" + "explore", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ query: "project:test-project" }) + expect.objectContaining({ query: "project:test-project" }), ); }); @@ -273,11 +273,11 @@ describe("sentry explore", () => { expect(resolveTargetSpy).toHaveBeenCalledWith( undefined, "/tmp/test-explore", - "explore" + "explore", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ query: undefined }) + expect.objectContaining({ query: undefined }), ); }); @@ -286,12 +286,12 @@ describe("sentry explore", () => { new ContextError("Organization", "sentry explore <target>", [ "SENTRY_ORG environment variable", "sentry cli defaults", - ]) + ]), ); const { context } = createContext(); await expect(func.call(context, DEFAULT_FLAGS)).rejects.toThrow( - "Organization" + "Organization", ); }); }); @@ -308,7 +308,7 @@ describe("sentry explore", () => { expect.objectContaining({ fields: ["title", "count()"], dataset: "errors", - }) + }), ); }); @@ -322,14 +322,14 @@ describe("sentry explore", () => { ...DEFAULT_FLAGS, field: ["transaction", "p50(span.duration)"], }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", expect.objectContaining({ fields: ["transaction", "p50(span.duration)"], - }) + }), ); }); @@ -340,12 +340,12 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, dataset: "spans" }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ dataset: "spans" }) + expect.objectContaining({ dataset: "spans" }), ); }); @@ -360,7 +360,7 @@ describe("sentry explore", () => { dataset: "transactions", query: "environment:production", }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( @@ -369,7 +369,7 @@ describe("sentry explore", () => { dataset: "spans", fields: ["transaction", "count()"], query: "environment:production is_transaction:true", - }) + }), ); }); @@ -384,7 +384,7 @@ describe("sentry explore", () => { dataset: "transactions", query: "is_transaction:true", }, - "test-org/cli" + "test-org/cli", ); expect(queryEventsSpy).toHaveBeenCalledWith( @@ -392,7 +392,7 @@ describe("sentry explore", () => { expect.objectContaining({ dataset: "spans", query: "project:cli is_transaction:true", - }) + }), ); }); @@ -403,12 +403,12 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, query: "is:unresolved" }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ query: "is:unresolved" }) + expect.objectContaining({ query: "is:unresolved" }), ); }); @@ -420,7 +420,7 @@ describe("sentry explore", () => { expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ limit: 100 }) + expect.objectContaining({ limit: 100 }), ); }); @@ -437,7 +437,7 @@ describe("sentry explore", () => { field: ["id", "user.email", "count_errors", "url"], sort: "-count_errors", }, - "test-org/cli" + "test-org/cli", ); expect(listReplaysSpy).toHaveBeenCalledWith("test-org", { @@ -464,7 +464,7 @@ describe("sentry explore", () => { dataset: "replays", query: "count_errors:>0", }, - "test-org/cli" + "test-org/cli", ); expect(listReplaysSpy).toHaveBeenCalledWith( @@ -472,7 +472,7 @@ describe("sentry explore", () => { expect.objectContaining({ projectSlugs: ["cli"], query: "count_errors:>0", - }) + }), ); }); @@ -487,14 +487,14 @@ describe("sentry explore", () => { dataset: "replays", field: ["id", "count_traces"], }, - "test-org/" + "test-org/", ); expect(listReplaysSpy).toHaveBeenCalledWith( "test-org", expect.objectContaining({ fields: ["id", "trace_ids"], - }) + }), ); }); }); @@ -510,7 +510,7 @@ describe("sentry explore", () => { // sort and needs a stable order for correct cursor pagination (#1519). expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ sort: "-count()" }) + expect.objectContaining({ sort: "-count()" }), ); }); @@ -521,12 +521,12 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, dataset: "spans", sort: "-count()" }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ sort: "-count()", dataset: "spans" }) + expect.objectContaining({ sort: "-count()", dataset: "spans" }), ); }); @@ -541,13 +541,13 @@ describe("sentry explore", () => { dataset: "tracemetrics", field: ["sum(value,llm.token_usage,distribution,none)"], }, - "test-org/" + "test-org/", ); // metrics/logs reject sort with 400, so it stays unset. expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ sort: undefined }) + expect.objectContaining({ sort: undefined }), ); }); @@ -562,12 +562,12 @@ describe("sentry explore", () => { dataset: "errors", field: ["task.name", "failure.node", "count()"], }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ sort: "-count()", dataset: "errors" }) + expect.objectContaining({ sort: "-count()", dataset: "errors" }), ); }); @@ -578,12 +578,12 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, dataset: "replays" }, - "test-org/" + "test-org/", ); expect(listReplaysSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ sort: "-started_at" }) + expect.objectContaining({ sort: "-started_at" }), ); }); @@ -594,7 +594,7 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, dataset: "replays" }, - "test-org/" + "test-org/", ); expect(listReplaysSpy).toHaveBeenCalledWith( @@ -610,7 +610,7 @@ describe("sentry explore", () => { "urls", "user", ], - }) + }), ); }); }); @@ -627,7 +627,7 @@ describe("sentry explore", () => { dataset: "tracemetrics", field: ["title", "count()"], }, - "test-org/" + "test-org/", ); await expect(promise).rejects.toThrow(ValidationError); @@ -648,12 +648,12 @@ describe("sentry explore", () => { "sum(value,llm.token_usage,distribution,none)", ], }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ dataset: "tracemetrics" }) + expect.objectContaining({ dataset: "tracemetrics" }), ); }); @@ -664,12 +664,12 @@ describe("sentry explore", () => { const promise = func.call( context, { ...DEFAULT_FLAGS, dataset: "tracemetrics" }, - "test-org/" + "test-org/", ); await expect(promise).rejects.toThrow(ValidationError); await expect(promise).rejects.toThrow( - /requires --metric or explicit --field/ + /requires --metric or explicit --field/, ); }); @@ -684,7 +684,7 @@ describe("sentry explore", () => { dataset: "tracemetrics", field: ["gen_ai.request.model"], }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalled(); @@ -701,7 +701,7 @@ describe("sentry explore", () => { dataset: "tracemetrics", metric: "llm.token_usage", }, - "test-org/" + "test-org/", ); expect(queryMetricsMetaSpy).toHaveBeenCalledWith("test-org", { @@ -713,7 +713,7 @@ describe("sentry explore", () => { expect.objectContaining({ fields: ["sum(value,llm.token_usage,distribution,none)"], dataset: "tracemetrics", - }) + }), ); }); @@ -729,7 +729,7 @@ describe("sentry explore", () => { metric: "llm.token_usage", field: ["gen_ai.request.model"], }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( @@ -739,7 +739,7 @@ describe("sentry explore", () => { "gen_ai.request.model", "sum(value,llm.token_usage,distribution,none)", ], - }) + }), ); }); @@ -755,14 +755,14 @@ describe("sentry explore", () => { metric: "cache.hit_rate", agg: "avg", }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", expect.objectContaining({ fields: ["avg(value,cache.hit_rate,distribution,none)"], - }) + }), ); }); @@ -777,7 +777,7 @@ describe("sentry explore", () => { dataset: "errors", metric: "llm.token_usage", }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( @@ -785,7 +785,7 @@ describe("sentry explore", () => { expect.objectContaining({ dataset: "tracemetrics", fields: ["sum(value,llm.token_usage,distribution,none)"], - }) + }), ); }); }); @@ -836,7 +836,7 @@ describe("sentry explore", () => { ...DEFAULT_FLAGS, field: ["title", "count()", "count_unique(user)"], }, - "test-org/" + "test-org/", ); const output = getStdout(); @@ -880,7 +880,7 @@ describe("sentry explore", () => { field: ["id", "user.email", "count_errors", "url"], json: true, }, - "test-org/" + "test-org/", ); const parsed = JSON.parse(getStdout()); @@ -937,7 +937,7 @@ describe("sentry explore", () => { "explore", expect.any(String), "next", - "cursor123" + "cursor123", ); }); @@ -952,11 +952,11 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, dataset: "transactions" }, - "test-org/" + "test-org/", ); expect(getStdout()).toContain( - "sentry explore test-org/ -c next --dataset transactions" + "sentry explore test-org/ -c next --dataset transactions", ); }); @@ -975,12 +975,12 @@ describe("sentry explore", () => { dataset: "replays", field: [...DEFAULT_REPLAY_EXPLORE_FIELDS], }, - "test-org/" + "test-org/", ); const output = getStdout(); expect(output).toContain( - "sentry explore test-org/ -c next --dataset replays" + "sentry explore test-org/ -c next --dataset replays", ); expect(output).not.toContain('-F "id"'); expect(output).not.toContain('-F "started_at"'); @@ -1000,12 +1000,12 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, environment: ["production"] }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", - expect.objectContaining({ query: "environment:production" }) + expect.objectContaining({ query: "environment:production" }), ); }); @@ -1020,14 +1020,14 @@ describe("sentry explore", () => { await func.call( context, { ...DEFAULT_FLAGS, environment: ["production", "canary"] }, - "test-org/" + "test-org/", ); expect(queryEventsSpy).toHaveBeenCalledWith( "test-org", expect.objectContaining({ query: "environment:[production,canary]", - }) + }), ); }); @@ -1039,8 +1039,8 @@ describe("sentry explore", () => { func.call( context, { ...DEFAULT_FLAGS, dataset: "replays", field: ["replay_type"] }, - "test-org/" - ) + "test-org/", + ), ).rejects.toThrow(ValidationError); }); }); diff --git a/packages/cli/test/commands/feedback/list.test.ts b/packages/cli/test/commands/feedback/list.test.ts index 924f05797..aa986e69a 100644 --- a/packages/cli/test/commands/feedback/list.test.ts +++ b/packages/cli/test/commands/feedback/list.test.ts @@ -15,11 +15,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { @@ -29,11 +29,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -43,15 +43,15 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; function sampleFeedback( - overrides: Partial<SentryFeedback> = {} + overrides: Partial<SentryFeedback> = {}, ): SentryFeedback { return { id: "5146636313", @@ -140,7 +140,7 @@ describe("feedback list", () => { json: true, fresh: false, }, - "test-org/test-project" + "test-org/test-project", ); expect(listFeedbackSpy).toHaveBeenCalledWith("test-org", "test-project", { @@ -184,7 +184,7 @@ describe("feedback list", () => { json: false, fresh: false, }, - "test-org/" + "test-org/", ); expect(listFeedbackSpy).toHaveBeenCalledWith("test-org", "", { @@ -199,16 +199,16 @@ describe("feedback list", () => { "feedback-list", expect.any(String), "prev", - "next" + "next", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); expect(output).toContain("Spam"); expect(output).toContain("Read"); expect(output).toContain( - 'sentry feedback list test-org/ -c prev --status spam --limit 250 -q "browser:Chrome" --period 30d' + 'sentry feedback list test-org/ -c prev --status spam --limit 250 -q "browser:Chrome" --period 30d', ); expect(output).toContain( - 'sentry feedback list test-org/ -c next --status spam --limit 250 -q "browser:Chrome" --period 30d' + 'sentry feedback list test-org/ -c next --status spam --limit 250 -q "browser:Chrome" --period 30d', ); }); @@ -232,7 +232,7 @@ describe("feedback list", () => { expect(resolveTargetSpy).toHaveBeenCalledWith( undefined, "/tmp", - "feedback list" + "feedback list", ); }); @@ -255,18 +255,18 @@ describe("feedback list", () => { json: true, fresh: false, }, - "test-project" + "test-project", ); expect(resolveTargetSpy).toHaveBeenCalledWith( "test-project", "/tmp", - "feedback list" + "feedback list", ); expect(listFeedbackSpy).toHaveBeenCalledWith( "test-org", "test-project", - expect.objectContaining({ projectId: 42 }) + expect.objectContaining({ projectId: 42 }), ); expect(getProjectSpy).not.toHaveBeenCalled(); }); @@ -293,7 +293,7 @@ describe("feedback list", () => { test("converts user query 400 responses to ValidationError", async () => { resolveTargetSpy.mockResolvedValue({ org: "test-org" }); listFeedbackSpy.mockRejectedValue( - new ApiError("bad query", 400, "Error parsing search query") + new ApiError("bad query", 400, "Error parsing search query"), ); const { context } = createMockContext(); @@ -306,7 +306,7 @@ describe("feedback list", () => { period: parsePeriod("14d"), json: true, fresh: false, - }) + }), ).rejects.toBeInstanceOf(ValidationError); }); }); diff --git a/packages/cli/test/commands/feedback/status.test.ts b/packages/cli/test/commands/feedback/status.test.ts index cb3d70faa..59ecc8b7b 100644 --- a/packages/cli/test/commands/feedback/status.test.ts +++ b/packages/cli/test/commands/feedback/status.test.ts @@ -116,7 +116,7 @@ describe.each([ const func = await command.loader(); await expect( - func.call(context, { json: false }, "TEST-PROJECT-1A") + func.call(context, { json: false }, "TEST-PROJECT-1A"), ).rejects.toMatchObject({ name: "ResolutionError", hint: "sentry issue view test-org/TEST-PROJECT-1A", @@ -132,7 +132,7 @@ describe.each([ const func = await command.loader(); await expect( - func.call(context, { json: false }, "TEST-PROJECT-1A") + func.call(context, { json: false }, "TEST-PROJECT-1A"), ).rejects.toBe(error); expect(output()).toBe(""); }); @@ -152,7 +152,7 @@ test("feedback unresolve restores Feedback marked as spam", async () => { expect(updateIssueStatus).toHaveBeenCalledExactlyOnceWith( "123", "unresolved", - { orgSlug: "test-org" } + { orgSlug: "test-org" }, ); expect(JSON.parse(output())).toEqual(feedback()); }); diff --git a/packages/cli/test/commands/feedback/utils.test.ts b/packages/cli/test/commands/feedback/utils.test.ts index b9031d55c..45cb6a1d0 100644 --- a/packages/cli/test/commands/feedback/utils.test.ts +++ b/packages/cli/test/commands/feedback/utils.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; function issue(overrides: Partial<SentryIssue> = {}): SentryIssue { @@ -67,7 +67,7 @@ describe("resolveFeedback", () => { }); await expect( - resolveFeedback("TEST-PROJECT-1A", "/tmp") + resolveFeedback("TEST-PROJECT-1A", "/tmp"), ).rejects.toMatchObject<Partial<ResolutionError>>({ name: "ResolutionError", hint: "sentry issue view test-org/TEST-PROJECT-1A", @@ -76,11 +76,11 @@ describe("resolveFeedback", () => { test("translates short-ID 404s to feedback-specific recovery", async () => { vi.spyOn(issueUtils, "resolveIssue").mockRejectedValue( - new ApiError("Short ID not found", 404) + new ApiError("Short ID not found", 404), ); await expect( - resolveFeedback("TEST-PROJECT-404", "/tmp") + resolveFeedback("TEST-PROJECT-404", "/tmp"), ).rejects.toMatchObject<Partial<ResolutionError>>({ name: "ResolutionError", hint: "sentry feedback view <org>/TEST-PROJECT-404", @@ -94,7 +94,7 @@ describe("resolveFeedback", () => { .mockRejectedValue(new ApiError("Short ID not found", 404)); await expect( - resolveFeedback("TEST-PROJECT-404", "/tmp", "resolve") + resolveFeedback("TEST-PROJECT-404", "/tmp", "resolve"), ).rejects.toMatchObject<Partial<ResolutionError>>({ name: "ResolutionError", hint: "sentry feedback resolve <org>/TEST-PROJECT-404", diff --git a/packages/cli/test/commands/feedback/view.test.ts b/packages/cli/test/commands/feedback/view.test.ts index fed40aaa8..9e571cabd 100644 --- a/packages/cli/test/commands/feedback/view.test.ts +++ b/packages/cli/test/commands/feedback/view.test.ts @@ -15,11 +15,11 @@ vi.mock("../../../src/commands/feedback/utils.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as feedbackUtils from "../../../src/commands/feedback/utils.js"; vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { @@ -29,11 +29,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -43,11 +43,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; const REPLAY_ID = "346789a703f6454384f1de473b8b9fcc"; @@ -152,13 +152,13 @@ describe("feedback view", () => { web: false, fresh: false, }, - "TEST-PROJECT-2SDJ" + "TEST-PROJECT-2SDJ", ); expect(listAttachmentsSpy).toHaveBeenCalledWith( "test-org", "test-project", - "abc123def456abc123def456abc12345" + "abc123def456abc123def456abc12345", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); expect(JSON.parse(output)).toMatchObject({ @@ -180,7 +180,7 @@ describe("feedback view", () => { web: false, fresh: false, }, - "TEST-PROJECT-2SDJ" + "TEST-PROJECT-2SDJ", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); @@ -205,7 +205,7 @@ describe("feedback view", () => { web: false, fresh: false, }, - "5146636313" + "5146636313", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); @@ -230,7 +230,7 @@ describe("feedback view", () => { web: false, fresh: false, }, - "TEST-PROJECT-2SDJ" + "TEST-PROJECT-2SDJ", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); @@ -251,12 +251,12 @@ describe("feedback view", () => { web: true, fresh: false, }, - "TEST-PROJECT-2SDJ" + "TEST-PROJECT-2SDJ", ); expect(openInBrowserSpy).toHaveBeenCalledWith( feedback().permalink, - "feedback" + "feedback", ); expect(getLatestEventSpy).not.toHaveBeenCalled(); expect(listReplayIdsSpy).not.toHaveBeenCalled(); diff --git a/packages/cli/test/commands/help.test.ts b/packages/cli/test/commands/help.test.ts index 3f603fa77..fdd79033b 100644 --- a/packages/cli/test/commands/help.test.ts +++ b/packages/cli/test/commands/help.test.ts @@ -73,12 +73,12 @@ describe("sentry help --json", () => { // Find a command that should have framework-injected flags const issueRoute = parsed.routes.find( - (r: { name: string }) => r.name === "issue" + (r: { name: string }) => r.name === "issue", ); expect(issueRoute).toBeDefined(); const listCmd = issueRoute.commands.find( - (c: { path: string }) => c.path === "sentry issue list" + (c: { path: string }) => c.path === "sentry issue list", ); expect(listCmd).toBeDefined(); @@ -146,7 +146,7 @@ describe("sentry help --json <group> <command>", () => { const output = await runHelp(["--json", "event", "view"]); const parsed = JSON.parse(output); const attachments = parsed.jsonFields?.find( - (field: { name: string }) => field.name === "attachments" + (field: { name: string }) => field.name === "attachments", ); expect(attachments).toMatchObject({ diff --git a/packages/cli/test/commands/info.test.ts b/packages/cli/test/commands/info.test.ts index 51d82bff4..dff0f91d9 100644 --- a/packages/cli/test/commands/info.test.ts +++ b/packages/cli/test/commands/info.test.ts @@ -7,11 +7,11 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { infoCommand } from "../../src/commands/info.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as users from "../../src/lib/api/users.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as auth from "../../src/lib/db/auth.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as defaults from "../../src/lib/db/defaults.js"; function createContext(env: NodeJS.ProcessEnv = {}) { @@ -21,7 +21,7 @@ function createContext(env: NodeJS.ProcessEnv = {}) { stdout: { write: (data: string | Uint8Array) => { writes.push( - typeof data === "string" ? data : new TextDecoder().decode(data) + typeof data === "string" ? data : new TextDecoder().decode(data), ); return true; }, diff --git a/packages/cli/test/commands/init.test.ts b/packages/cli/test/commands/init.test.ts index c508a7062..41ae6056b 100644 --- a/packages/cli/test/commands/init.test.ts +++ b/packages/cli/test/commands/init.test.ts @@ -9,19 +9,19 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { initCommand } from "../../src/commands/init.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as projectsApi from "../../src/lib/api/projects.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as authModule from "../../src/lib/db/auth.js"; import { AuthError, ContextError, ValidationError, } from "../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as prefetchNs from "../../src/lib/init/org-prefetch.js"; import { resetPrefetch } from "../../src/lib/init/org-prefetch.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as wizardRunner from "../../src/lib/init/wizard-runner.js"; /** Minimal org shape for mock returns */ @@ -48,12 +48,12 @@ const func = (await initCommand.loader()) as unknown as ( }, flags: Record<string, unknown>, first?: string, - second?: string + second?: string, ) => Promise<void>; function makeContext( cwd = "/projects/app", - tty: { stdinTTY?: boolean; stdoutTTY?: boolean } = {} + tty: { stdinTTY?: boolean; stdoutTTY?: boolean } = {}, ) { return { cwd, @@ -85,8 +85,8 @@ beforeEach(() => { // Spy on warmOrgDetection to verify it's called/skipped appropriately. // The mock prevents real DSN scans and API calls from the background. warmSpy = vi.spyOn(prefetchNs, "warmOrgDetection").mockImplementation( - // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op mock - () => {} + // intentional no-op mock + () => {}, ); refreshTokenSpy = vi .spyOn(authModule, "refreshToken") @@ -222,45 +222,44 @@ describe("init command func", () => { }); await expect(promise).rejects.toThrow(ValidationError); await expect(promise).rejects.toThrow( - "Supported features: errors, tracing, logs, replay, profiling, crons, agent-tracing, mcp-observability" + "Supported features: errors, tracing, logs, replay, profiling, crons, agent-tracing, mcp-observability", ); expect(runWizardSpy).not.toHaveBeenCalled(); expect(findProjectsSpy).not.toHaveBeenCalled(); expect(warmSpy).not.toHaveBeenCalled(); }); - test.each([ - "user-feedback", - "userFeedback", - ])("rejects %s because init cannot configure User Feedback placement", async (feature) => { - const ctx = makeContext(); - const promise = func.call(ctx, { - ...DEFAULT_FLAGS, - features: [feature], - }); - await expect(promise).rejects.toThrow(ValidationError); - await expect(promise).rejects.toThrow( - `Unknown init feature "${feature}"` - ); - expect(runWizardSpy).not.toHaveBeenCalled(); - }); - - test.each([ - "metrics", - "sourcemaps", - "attachments", - ])("rejects %s because init does not yet automate its setup", async (feature) => { - const ctx = makeContext(); - const promise = func.call(ctx, { - ...DEFAULT_FLAGS, - features: [feature], - }); - await expect(promise).rejects.toThrow(ValidationError); - await expect(promise).rejects.toThrow( - `Unknown init feature "${feature}"` - ); - expect(runWizardSpy).not.toHaveBeenCalled(); - }); + test.each(["user-feedback", "userFeedback"])( + "rejects %s because init cannot configure User Feedback placement", + async (feature) => { + const ctx = makeContext(); + const promise = func.call(ctx, { + ...DEFAULT_FLAGS, + features: [feature], + }); + await expect(promise).rejects.toThrow(ValidationError); + await expect(promise).rejects.toThrow( + `Unknown init feature "${feature}"`, + ); + expect(runWizardSpy).not.toHaveBeenCalled(); + }, + ); + + test.each(["metrics", "sourcemaps", "attachments"])( + "rejects %s because init does not yet automate its setup", + async (feature) => { + const ctx = makeContext(); + const promise = func.call(ctx, { + ...DEFAULT_FLAGS, + features: [feature], + }); + await expect(promise).rejects.toThrow(ValidationError); + await expect(promise).rejects.toThrow( + `Unknown init feature "${feature}"`, + ); + expect(runWizardSpy).not.toHaveBeenCalled(); + }, + ); test("passes undefined when features not provided", async () => { const ctx = makeContext(); @@ -275,7 +274,7 @@ describe("init command func", () => { test("requires --yes before project lookup or wizard startup", async () => { const ctx = makeContext("/projects/app", { stdinTTY: false }); await expect( - func.call(ctx, { yes: false, "dry-run": false }, "my-app") + func.call(ctx, { yes: false, "dry-run": false }, "my-app"), ).rejects.toThrow(ContextError); expect(findProjectsSpy).not.toHaveBeenCalled(); expect(warmSpy).not.toHaveBeenCalled(); @@ -287,7 +286,7 @@ describe("init command func", () => { const promise = func.call(ctx, DEFAULT_FLAGS); await expect(promise).rejects.toThrow(ContextError); await expect(promise).rejects.toThrow( - "sentry init --yes --features errors,tracing,replay [target] [directory]" + "sentry init --yes --features errors,tracing,replay [target] [directory]", ); expect(warmSpy).not.toHaveBeenCalled(); expect(runWizardSpy).not.toHaveBeenCalled(); @@ -369,7 +368,7 @@ describe("init command func", () => { const ctx = makeContext("/projects/app"); await func.call(ctx, DEFAULT_FLAGS, "./subdir"); expect(capturedArgs?.directory).toBe( - path.resolve("/projects/app", "./subdir") + path.resolve("/projects/app", "./subdir"), ); expect(capturedArgs?.org).toBeUndefined(); }); @@ -378,7 +377,7 @@ describe("init command func", () => { const ctx = makeContext("/projects/app"); await func.call(ctx, DEFAULT_FLAGS, "../other"); expect(capturedArgs?.directory).toBe( - path.resolve("/projects/app", "../other") + path.resolve("/projects/app", "../other"), ); expect(capturedArgs?.org).toBeUndefined(); }); @@ -394,7 +393,7 @@ describe("init command func", () => { const ctx = makeContext("/projects/app"); await func.call(ctx, DEFAULT_FLAGS, "~/projects/other"); expect(capturedArgs?.directory).toBe( - path.resolve("/projects/app", "~/projects/other") + path.resolve("/projects/app", "~/projects/other"), ); expect(capturedArgs?.org).toBeUndefined(); }); @@ -479,7 +478,7 @@ describe("init command func", () => { })); const ctx = makeContext(); await expect(func.call(ctx, DEFAULT_FLAGS, "my-app")).rejects.toThrow( - ValidationError + ValidationError, ); }); }); @@ -493,7 +492,7 @@ describe("init command func", () => { expect(capturedArgs?.org).toBe("acme"); expect(capturedArgs?.project).toBeUndefined(); expect(capturedArgs?.directory).toBe( - path.resolve("/projects/app", "./subdir") + path.resolve("/projects/app", "./subdir"), ); }); @@ -503,7 +502,7 @@ describe("init command func", () => { expect(capturedArgs?.org).toBe("acme"); expect(capturedArgs?.project).toBe("my-app"); expect(capturedArgs?.directory).toBe( - path.resolve("/projects/app", "./subdir") + path.resolve("/projects/app", "./subdir"), ); }); @@ -514,7 +513,7 @@ describe("init command func", () => { expect(capturedArgs?.org).toBe("resolved-org"); expect(capturedArgs?.project).toBe("my-app"); expect(capturedArgs?.directory).toBe( - path.resolve("/projects/app", "./subdir") + path.resolve("/projects/app", "./subdir"), ); }); }); @@ -536,7 +535,7 @@ describe("init command func", () => { expect(capturedArgs?.org).toBe("acme"); expect(capturedArgs?.project).toBe("my-app"); expect(capturedArgs?.directory).toBe( - path.resolve("/projects/app", "./subdir") + path.resolve("/projects/app", "./subdir"), ); }); }); @@ -549,7 +548,7 @@ describe("init command func", () => { const promise = func.call(ctx, DEFAULT_FLAGS, "./dir1", "./dir2"); await expect(promise).rejects.toThrow(ValidationError); await expect(promise).rejects.toThrow( - '"./dir1" and "./dir2" are both directory paths' + '"./dir1" and "./dir2" are both directory paths', ); }); @@ -558,7 +557,7 @@ describe("init command func", () => { const promise = func.call(ctx, DEFAULT_FLAGS, "acme/", "other/"); await expect(promise).rejects.toThrow(ValidationError); await expect(promise).rejects.toThrow( - '"acme/" and "other/" are both treated as targets' + '"acme/" and "other/" are both treated as targets', ); }); @@ -567,7 +566,7 @@ describe("init command func", () => { // ValidationError — normalizeSlug no longer converts them. const ctx = makeContext(); await expect(func.call(ctx, DEFAULT_FLAGS, "acme corp/")).rejects.toThrow( - ValidationError + ValidationError, ); }); }); @@ -587,7 +586,7 @@ describe("init command func", () => { await func.call( ctx, { ...DEFAULT_FLAGS, team: "backend" }, - "acme/my-app" + "acme/my-app", ); expect(capturedArgs?.org).toBe("acme"); expect(capturedArgs?.project).toBe("my-app"); @@ -646,7 +645,7 @@ describe("init command func", () => { const ctx = makeContext("/projects/app"); await func.call(ctx, DEFAULT_FLAGS, "./subdir"); expect(warmSpy).toHaveBeenCalledWith( - path.resolve("/projects/app", "./subdir") + path.resolve("/projects/app", "./subdir"), ); }); }); diff --git a/packages/cli/test/commands/issue/archive.func.test.ts b/packages/cli/test/commands/issue/archive.func.test.ts index 920021784..4098c1208 100644 --- a/packages/cli/test/commands/issue/archive.func.test.ts +++ b/packages/cli/test/commands/issue/archive.func.test.ts @@ -20,11 +20,11 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { @@ -34,11 +34,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ValidationError } from "../../../src/lib/errors.js"; import type { SentryIssue } from "../../../src/types/sentry.js"; diff --git a/packages/cli/test/commands/issue/events.func.test.ts b/packages/cli/test/commands/issue/events.func.test.ts index e20f0d03b..976fcaaa5 100644 --- a/packages/cli/test/commands/issue/events.func.test.ts +++ b/packages/cli/test/commands/issue/events.func.test.ts @@ -19,11 +19,11 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { @@ -33,11 +33,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { @@ -47,11 +47,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; import { parsePeriod } from "../../../src/lib/time-range.js"; import type { IssueEvent, SentryIssue } from "../../../src/types/sentry.js"; @@ -177,7 +177,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: true, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -201,7 +201,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: true, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -224,7 +224,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -243,7 +243,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -269,7 +269,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 2, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -289,7 +289,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 100, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -309,7 +309,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -335,7 +335,7 @@ describe("eventsCommand.func()", () => { query: "user.email:test@example.com", period: parsePeriod("24h"), }, - "CLI-G5" + "CLI-G5", ); expect(listIssueEventsSpy).toHaveBeenCalledWith("test-org", "123456789", { @@ -359,13 +359,13 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("30d") }, - "CLI-G5" + "CLI-G5", ); expect(listIssueEventsSpy).toHaveBeenCalledWith( "test-org", "123456789", - expect.objectContaining({ statsPeriod: "30d" }) + expect.objectContaining({ statsPeriod: "30d" }), ); }); @@ -384,7 +384,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 2, json: true, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -405,7 +405,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: true, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -428,14 +428,14 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: true, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); expect(advancePaginationStateSpy).toHaveBeenCalledWith( "issue-events", expect.any(String), "next", - "cursor123" + "cursor123", ); }); @@ -453,7 +453,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -475,7 +475,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -498,7 +498,7 @@ describe("eventsCommand.func()", () => { await func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "CLI-G5" + "CLI-G5", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -521,8 +521,8 @@ describe("eventsCommand.func()", () => { func.call( context, { limit: 25, json: false, full: false, period: parsePeriod("7d") }, - "123456789" - ) + "123456789", + ), ).rejects.toThrow("organization"); }); }); diff --git a/packages/cli/test/commands/issue/explain.func.test.ts b/packages/cli/test/commands/issue/explain.func.test.ts index 6407da569..34ff1f647 100644 --- a/packages/cli/test/commands/issue/explain.func.test.ts +++ b/packages/cli/test/commands/issue/explain.func.test.ts @@ -13,12 +13,12 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); import { explainCommand } from "../../../src/commands/issue/explain.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; import { ContextError } from "../../../src/lib/errors.js"; import type { AutofixState, RootCause } from "../../../src/types/seer.js"; @@ -93,7 +93,7 @@ describe("issue explain multiple IDs", () => { issueId: options.issueArg === "IOS-1" ? "1" : "2", })); analyzeSpy.mockImplementation(async (options: { issueId: string }) => - sampleState(sampleCause(`Cause ${options.issueId}`)) + sampleState(sampleCause(`Cause ${options.issueId}`)), ); const { context, stdoutWrite } = createMockContext(); @@ -128,7 +128,7 @@ describe("issue explain multiple IDs", () => { createMockContext().context, { json: false, force: false, fresh: false }, "IOS-1", - "IOS-2" + "IOS-2", ); expect(analyzeSpy).toHaveBeenCalledTimes(2); @@ -161,14 +161,14 @@ describe("issue explain multiple IDs", () => { const func = await explainCommand.loader(); await expect( - func.call(createMockContext().context, EXPLAIN_FLAGS, "IOS-1", "IOS-2") + func.call(createMockContext().context, EXPLAIN_FLAGS, "IOS-1", "IOS-2"), ).rejects.toBe(error); }); test("throws ContextError when no issue ID is provided", async () => { const func = await explainCommand.loader(); await expect( - func.call(createMockContext().context, EXPLAIN_FLAGS) + func.call(createMockContext().context, EXPLAIN_FLAGS), ).rejects.toThrow(ContextError); }); }); diff --git a/packages/cli/test/commands/issue/link.func.test.ts b/packages/cli/test/commands/issue/link.func.test.ts index 6493b8f89..016ee4fb7 100644 --- a/packages/cli/test/commands/issue/link.func.test.ts +++ b/packages/cli/test/commands/issue/link.func.test.ts @@ -70,7 +70,7 @@ describe("issue link", () => { context, { ...defaultFlags, integration: "99" }, "test-org/APP-42", - externalUrl + externalUrl, ); expect(resolveOrgAndIssueId).toHaveBeenCalledExactlyOnceWith({ @@ -104,7 +104,7 @@ describe("issue link", () => { field: ["team=team-1", "query=key=value", "optional="], }, "APP-42", - "https://tracker.example/issues/42" + "https://tracker.example/issues/42", ); expect(linkExternalIssue).toHaveBeenCalledExactlyOnceWith({ @@ -126,23 +126,26 @@ describe("issue link", () => { ["__proto__=value"], ["constructor=value"], ["prototype=value"], - ])("rejects malformed or ambiguous --field input %j before resolving or writing", async (...fields) => { - const { context, output } = createMockContext(); - const func = await linkCommand.loader(); - - await expect( - func.call( - context, - { ...defaultFlags, app: "custom-tracker", field: fields }, - "APP-42", - externalUrl - ) - ).rejects.toBeInstanceOf(ValidationError); - - expect(resolveOrgAndIssueId).not.toHaveBeenCalled(); - expect(linkExternalIssue).not.toHaveBeenCalled(); - expect(output()).toBe(""); - }); + ])( + "rejects malformed or ambiguous --field input %j before resolving or writing", + async (...fields) => { + const { context, output } = createMockContext(); + const func = await linkCommand.loader(); + + await expect( + func.call( + context, + { ...defaultFlags, app: "custom-tracker", field: fields }, + "APP-42", + externalUrl, + ), + ).rejects.toBeInstanceOf(ValidationError); + + expect(resolveOrgAndIssueId).not.toHaveBeenCalled(); + expect(linkExternalIssue).not.toHaveBeenCalled(); + expect(output()).toBe(""); + }, + ); test("renders a dry-run preview while forwarding the no-write flag", async () => { vi.mocked(linkExternalIssue).mockResolvedValue({ @@ -157,11 +160,11 @@ describe("issue link", () => { context, { ...defaultFlags, "dry-run": true }, "APP-42", - externalUrl + externalUrl, ); expect(linkExternalIssue).toHaveBeenCalledWith( - expect.objectContaining({ dryRun: true }) + expect.objectContaining({ dryRun: true }), ); expect(output()).toContain("Would link"); expect(output()).toContain("dry run"); @@ -174,7 +177,7 @@ describe("issue link", () => { context, { ...defaultFlags, json: true }, "APP-42", - externalUrl + externalUrl, ); expect(JSON.parse(output())).toEqual(linkedResult); diff --git a/packages/cli/test/commands/issue/list.property.test.ts b/packages/cli/test/commands/issue/list.property.test.ts index 39ace4e06..2719ede0a 100644 --- a/packages/cli/test/commands/issue/list.property.test.ts +++ b/packages/cli/test/commands/issue/list.property.test.ts @@ -47,7 +47,7 @@ const { /** Generates a slug-like string: lowercase alpha + digits + dashes */ const slugArb = array( constantFrom(..."abcdefghijklmnopqrstuvwxyz0123456789-".split("")), - { minLength: 1, maxLength: 12 } + { minLength: 1, maxLength: 12 }, ).map((chars) => chars.join("")); /** Generates an IssueWithOptions for testing trimWithProjectGuarantee */ @@ -63,20 +63,20 @@ const issueWithOptionsArb = tuple(slugArb, slugArb).map( projectSlug: proj, isMultiProject: true, }, - }) + }), ); /** Generates a Sentry cursor-like string (e.g. "1735689600:0:0") */ const cursorStringArb = tuple( integer({ min: 1_700_000_000, max: 1_800_000_000 }), nat({ max: 10 }), - nat({ max: 1 }) + nat({ max: 1 }), ).map(([ts, offset, flag]) => `${ts}:${offset}:${flag}`); /** Generates a cursor value: string or null (exhausted) */ const cursorValueArb = oneof( cursorStringArb.map((c) => c as string | null), - constant(null) + constant(null), ); /** Sort value arbitrary */ @@ -95,7 +95,7 @@ const sentryIssueArb = tuple( isoDateArb, isoDateArb, nat({ max: 100_000 }), - nat({ max: 50_000 }) + nat({ max: 50_000 }), ).map( ([lastSeen, firstSeen, count, userCount]) => ({ @@ -106,7 +106,7 @@ const sentryIssueArb = tuple( firstSeen, count: `${count}`, userCount, - }) as SentryIssue + }) as SentryIssue, ); // --- trimWithProjectGuarantee --- @@ -120,9 +120,9 @@ describe("property: trimWithProjectGuarantee", () => { (issues, limit) => { const result = trimWithProjectGuarantee(issues, limit); expect(result.length).toBeLessThanOrEqual(limit); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -139,9 +139,9 @@ describe("property: trimWithProjectGuarantee", () => { expect(idx).toBeGreaterThan(lastIdx); lastIdx = idx; } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -152,9 +152,9 @@ describe("property: trimWithProjectGuarantee", () => { (issues) => { const result = trimWithProjectGuarantee(issues, issues.length + 10); expect(result.length).toBe(issues.length); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -171,14 +171,14 @@ describe("property: trimWithProjectGuarantee", () => { const inputProjects = new Set( issues.map( - (i) => `${i.orgSlug}/${i.formatOptions.projectSlug ?? ""}` - ) + (i) => `${i.orgSlug}/${i.formatOptions.projectSlug ?? ""}`, + ), ); const resultProjects = new Set( result.map( - (i) => `${i.orgSlug}/${i.formatOptions.projectSlug ?? ""}` - ) + (i) => `${i.orgSlug}/${i.formatOptions.projectSlug ?? ""}`, + ), ); // If limit >= number of unique projects, every project is represented @@ -188,9 +188,9 @@ describe("property: trimWithProjectGuarantee", () => { // If limit < projects, we should have exactly limit items expect(result.length).toBe(limit); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -203,9 +203,9 @@ describe("property: trimWithProjectGuarantee", () => { const first = trimWithProjectGuarantee(issues, limit); const second = trimWithProjectGuarantee(first, limit); expect(second).toEqual(first); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -219,15 +219,15 @@ describe("property: compound cursor encode/decode", () => { // Need at least one non-null cursor: all-null encodes to "" which decodes to [] // (by design — all-exhausted is equivalent to "start fresh") array(cursorValueArb, { minLength: 1, maxLength: 20 }).filter((cs) => - cs.some((c) => c !== null) + cs.some((c) => c !== null), ), (cursors) => { const encoded = encodeCompoundCursor(cursors); const decoded = decodeCompoundCursor(encoded); expect(decoded).toEqual(cursors); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -248,14 +248,14 @@ describe("property: compound cursor encode/decode", () => { { minLength: 1, maxLength: 10, - } + }, ), (cursors) => { const encoded = encodeCompoundCursor(cursors); expect(encoded).not.toContain(`${CURSOR_SEP}${CURSOR_SEP}`); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -265,7 +265,7 @@ describe("property: compound cursor encode/decode", () => { test("legacy JSON cursor returns empty array (fresh start)", () => { expect( - decodeCompoundCursor('[{"org":"a","project":"b","cursor":"1:0:0"}]') + decodeCompoundCursor('[{"org":"a","project":"b","cursor":"1:0:0"}]'), ).toEqual([]); }); @@ -295,7 +295,7 @@ describe("property: getComparator", () => { const cmp = getComparator(sort); expect(typeof cmp).toBe("function"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -309,7 +309,7 @@ describe("property: getComparator", () => { // (Object.is(0, -0) is false, but 0 + -0 === 0) expect(ab + ba).toBe(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -319,7 +319,7 @@ describe("property: getComparator", () => { const cmp = getComparator(sort); expect(cmp(issue, issue)).toBe(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -408,9 +408,9 @@ describe("property: compareDates", () => { // ab + ba === 0 is the anti-symmetry check that handles 0/-0 correctly // (0 + -0 === 0, and 5 + -5 === 0) expect(ab + ba).toBe(0); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -424,7 +424,7 @@ describe("property: compareDates", () => { property(isoDateArb, (d) => { expect(compareDates(d, d)).toBe(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -444,9 +444,9 @@ describe("property: parseSort", () => { string().filter((s) => !VALID_SORT_VALUES.includes(s as SortValue)), (s) => { expect(() => parseSort(s)).toThrow(/Invalid sort value/); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -479,9 +479,9 @@ describe("property: buildProjectAliasMap", () => { } expect(Object.keys(entries).length).toBe(results.length); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -499,9 +499,9 @@ describe("property: buildProjectAliasMap", () => { const aliases = [...aliasMap.values()]; expect(new Set(aliases).size).toBe(aliases.length); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/commands/issue/list.test.ts b/packages/cli/test/commands/issue/list.test.ts index 8c0cd58e5..1197df989 100644 --- a/packages/cli/test/commands/issue/list.test.ts +++ b/packages/cli/test/commands/issue/list.test.ts @@ -7,7 +7,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { listCommand } from "../../../src/commands/issue/list.js"; -// biome-ignore lint/performance/noNamespaceImport: namespace needed for vi.spyOn on mocked module +// oxlint-disable-next-line sentry-cli/no-namespace-import -- namespace needed for vi.spyOn on mocked module import * as issueUtils from "../../../src/commands/issue/utils.js"; vi.mock("../../../src/lib/api/issues.js", async (importOriginal) => { @@ -39,9 +39,9 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { }; }); -// biome-ignore lint/performance/noNamespaceImport: namespace needed for vi.spyOn on mocked module +// oxlint-disable-next-line sentry-cli/no-namespace-import -- namespace needed for vi.spyOn on mocked module import * as issuesApi from "../../../src/lib/api/issues.js"; -// biome-ignore lint/performance/noNamespaceImport: namespace needed for vi.spyOn on mocked module +// oxlint-disable-next-line sentry-cli/no-namespace-import -- namespace needed for vi.spyOn on mocked module import * as projectsApi from "../../../src/lib/api/projects.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; import { setAuthToken } from "../../../src/lib/db/auth.js"; @@ -49,7 +49,7 @@ import { setDefaultOrganization, setDefaultProject, } from "../../../src/lib/db/defaults.js"; -// biome-ignore lint/performance/noNamespaceImport: namespace needed for vi.spyOn on mocked module +// oxlint-disable-next-line sentry-cli/no-namespace-import -- namespace needed for vi.spyOn on mocked module import * as paginationDb from "../../../src/lib/db/pagination.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; import { ApiError, ValidationError } from "../../../src/lib/errors.js"; @@ -73,7 +73,7 @@ type ListFlags = { type ListFunc = ( this: unknown, flags: ListFlags, - target?: string + target?: string, ) => Promise<void>; const getConfigDir = useTestConfigDir("test-issue-list-", { @@ -177,11 +177,11 @@ describe("issue list: short ID auto-recovery", () => { period: parsePeriod("90d"), json: true, }, - "javascript-react-mr-1b" + "javascript-react-mr-1b", ); expect(resolveIssueSpy).toHaveBeenCalledWith( - expect.objectContaining({ issueArg: "javascript-react-mr-1b" }) + expect.objectContaining({ issueArg: "javascript-react-mr-1b" }), ); }); @@ -200,11 +200,11 @@ describe("issue list: short ID auto-recovery", () => { period: parsePeriod("90d"), json: true, }, - "/CLI-G" + "/CLI-G", ); expect(resolveIssueSpy).toHaveBeenCalledWith( - expect.objectContaining({ issueArg: "CLI-G" }) + expect.objectContaining({ issueArg: "CLI-G" }), ); }); }); @@ -220,7 +220,7 @@ describe("issue list: error propagation", () => { if (req.url.includes("/issues/")) { return new Response( JSON.stringify({ detail: "Invalid query: unknown field" }), - { status: 400 } + { status: 400 }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -317,7 +317,7 @@ describe("issue list: error propagation", () => { if (req.url.includes("/issues/")) { return new Response( JSON.stringify({ detail: "Invalid search query: bad syntax" }), - { status: 400 } + { status: 400 }, ); } return new Response(JSON.stringify([]), { @@ -353,7 +353,7 @@ describe("issue list: error propagation", () => { JSON.stringify({ detail: "Error parsing search query: invalid status value of '403'", }), - { status: 400 } + { status: 400 }, ); } return new Response(JSON.stringify([]), { @@ -392,7 +392,7 @@ describe("issue list: error propagation", () => { JSON.stringify({ detail: "Error parsing search query: invalid status value of '403'", }), - { status: 400 } + { status: 400 }, ); } return new Response(JSON.stringify([]), { @@ -461,12 +461,12 @@ describe("issue list: org-as-project detection", () => { await func.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: false }, - "acme-corp" + "acme-corp", ); expect(listIssuesAllPagesMock).toHaveBeenCalledWith( "acme-corp", "", - expect.any(Object) + expect.any(Object), ); } finally { listIssuesAllPagesMock.mockReset(); @@ -505,12 +505,12 @@ describe("issue list: org-as-project detection", () => { await func.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: true }, - "acme-corp" + "acme-corp", ); expect(listIssuesAllPagesMock).toHaveBeenCalledWith( "other-org", "acme-corp", - expect.any(Object) + expect.any(Object), ); expect(findProjectsBySlugMock).toHaveBeenCalledTimes(1); } finally { @@ -553,7 +553,7 @@ describe("issue list: partial failure handling", () => { { slug: "org-one", name: "Org One" }, { slug: "org-two", name: "Org Two" }, ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -561,13 +561,13 @@ describe("issue list: partial failure handling", () => { if (url.includes("/projects/org-one/myproj/")) { return new Response( JSON.stringify({ id: "1", slug: "myproj", name: "My Project" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } if (url.includes("/projects/org-two/myproj/")) { return new Response( JSON.stringify({ id: "2", slug: "myproj", name: "My Project" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -581,7 +581,7 @@ describe("issue list: partial failure handling", () => { if (url.includes("/organizations/org-two/issues/")) { return new Response( JSON.stringify({ detail: "Invalid query syntax" }), - { status: 400 } + { status: 400 }, ); } @@ -598,7 +598,7 @@ describe("issue list: partial failure handling", () => { await func.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: true }, - "myproj" + "myproj", ); const output = JSON.parse(stdout.output); @@ -628,7 +628,7 @@ describe("issue list: partial failure handling", () => { { slug: "org-one", name: "Org One" }, { slug: "org-two", name: "Org Two" }, ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -636,13 +636,13 @@ describe("issue list: partial failure handling", () => { if (url.includes("/projects/org-one/myproj/")) { return new Response( JSON.stringify({ id: "1", slug: "myproj", name: "My Project" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } if (url.includes("/projects/org-two/myproj/")) { return new Response( JSON.stringify({ id: "2", slug: "myproj", name: "My Project" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -673,7 +673,7 @@ describe("issue list: partial failure handling", () => { await func.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: false }, - "myproj" + "myproj", ); // Partial failures are logged as warnings via logger (→ process.stderr) @@ -748,7 +748,7 @@ describe("issue list: server sort order preservation", () => { lastSeen: "2025-01-01T00:00:00Z", }), ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify([]), { @@ -779,7 +779,7 @@ const listIssuesPaginatedMock = vi.mocked(issuesApi.listIssuesPaginated); const listIssuesAllPagesMock = vi.mocked(issuesApi.listIssuesAllPages); const resolveCursorMock = vi.mocked(paginationDb.resolveCursor); const advancePaginationStateMock = vi.mocked( - paginationDb.advancePaginationState + paginationDb.advancePaginationState, ); describe("issue list: org-all mode (cursor pagination)", () => { @@ -832,7 +832,7 @@ describe("issue list: org-all mode (cursor pagination)", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; listIssuesAllPagesMock.mockResolvedValue({ @@ -868,8 +868,8 @@ describe("issue list: org-all mode (cursor pagination)", () => { json: false, cursor: "1735689600:0:0", }, - "test-org/test-project" - ) + "test-org/test-project", + ), ).resolves.toBeUndefined(); }); @@ -882,14 +882,14 @@ describe("issue list: org-all mode (cursor pagination)", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context, stdoutWrite } = createOrgAllContext(); await orgAllFunc.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: true }, - "my-org/" + "my-org/", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -908,14 +908,14 @@ describe("issue list: org-all mode (cursor pagination)", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context, stdoutWrite } = createOrgAllContext(); await orgAllFunc.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: true }, - "my-org/" + "my-org/", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -934,14 +934,14 @@ describe("issue list: org-all mode (cursor pagination)", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context, stdoutWrite } = createOrgAllContext(); await orgAllFunc.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: false }, - "my-org/" + "my-org/", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -959,14 +959,14 @@ describe("issue list: org-all mode (cursor pagination)", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context, stdoutWrite } = createOrgAllContext(); await orgAllFunc.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: false }, - "my-org/" + "my-org/", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -987,7 +987,7 @@ describe("issue list: org-all mode (cursor pagination)", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context } = createOrgAllContext(); @@ -1000,13 +1000,13 @@ describe("issue list: org-all mode (cursor pagination)", () => { json: false, cursor: "last", }, - "my-org/" + "my-org/", ); expect(listIssuesPaginatedMock).toHaveBeenCalledWith( "my-org", "", - expect.objectContaining({ cursor: "cached:cursor:789" }) + expect.objectContaining({ cursor: "cached:cursor:789" }), ); }); @@ -1014,14 +1014,14 @@ describe("issue list: org-all mode (cursor pagination)", () => { resolveCursorMock.mockImplementation(() => { throw new ValidationError( "No next page saved for this query. Run without --cursor first.", - "cursor" + "cursor", ); }); const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context } = createOrgAllContext(); @@ -1036,8 +1036,8 @@ describe("issue list: org-all mode (cursor pagination)", () => { json: false, cursor: "last", }, - "my-org/" - ) + "my-org/", + ), ).rejects.toThrow("No next page saved"); }); @@ -1055,7 +1055,7 @@ describe("issue list: org-all mode (cursor pagination)", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context } = createOrgAllContext(); @@ -1068,13 +1068,13 @@ describe("issue list: org-all mode (cursor pagination)", () => { json: false, cursor: "explicit:cursor:val", }, - "my-org/" + "my-org/", ); expect(listIssuesPaginatedMock).toHaveBeenCalledWith( "my-org", "", - expect.objectContaining({ cursor: "explicit:cursor:val" }) + expect.objectContaining({ cursor: "explicit:cursor:val" }), ); }); }); @@ -1150,7 +1150,7 @@ describe("issue list: Phase 2 budget redistribution", () => { { slug: "org-one", name: "Org One" }, { slug: "org-two", name: "Org Two" }, ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1158,13 +1158,13 @@ describe("issue list: Phase 2 budget redistribution", () => { if (url.includes("/projects/org-one/myproj/")) { return new Response( JSON.stringify({ id: "1", slug: "myproj", name: "My Project" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } if (url.includes("/projects/org-two/myproj/")) { return new Response( JSON.stringify({ id: "2", slug: "myproj", name: "My Project" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1190,7 +1190,7 @@ describe("issue list: Phase 2 budget redistribution", () => { "Content-Type": "application/json", Link: '<https://sentry.io/api/0/>; rel="next"; results="true"; cursor="phase2-cursor:0:0"', }, - } + }, ); } @@ -1217,7 +1217,7 @@ describe("issue list: Phase 2 budget redistribution", () => { await func.call( context, { limit: 6, sort: "date", period: parsePeriod("90d"), json: true }, - "myproj" + "myproj", ); const output = JSON.parse(stdout.output); @@ -1307,7 +1307,7 @@ describe("issue list: compound cursor resume", () => { json: true, cursor: "last", }, - "test-org/proj-a" + "test-org/proj-a", ); const output = JSON.parse(stdout.output); @@ -1352,7 +1352,7 @@ describe("issue list: collapse parameter optimization", () => { /** Run org-all issue list and return the API options passed to listIssuesAllPages. */ async function runOrgAll( - callFlags: Record<string, unknown> + callFlags: Record<string, unknown>, ): Promise<Record<string, unknown>> { listIssuesAllPagesMock.mockResolvedValue({ issues: [sampleIssue], @@ -1362,7 +1362,7 @@ describe("issue list: collapse parameter optimization", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context } = createOrgAllContext(); @@ -1416,14 +1416,14 @@ describe("issue list: collapse parameter optimization", () => { const orgAllFunc = (await listCommand.loader()) as unknown as ( this: unknown, flags: Record<string, unknown>, - target?: string + target?: string, ) => Promise<void>; const { context } = createOrgAllContext(); // Note: no `sort` key — the command must fill in the default. await orgAllFunc.call( context, { limit: 10, period: parsePeriod("90d"), json: true }, - "my-org/" + "my-org/", ); return (listIssuesAllPagesMock.mock.calls[0]?.[2] ?? {}) as Record< string, @@ -1617,14 +1617,14 @@ describe("issue list: multi-target cursor-safe budget", () => { issues: Array.from({ length: limit }, (_, i) => issue(org, i + 1)), nextCursor: `${org}-next:0:0`, }; - } + }, ); const { context, stdout } = createContext(); await func.call( context, { limit: 10, sort: "date", period: parsePeriod("90d"), json: true }, - "myproj" + "myproj", ); const output = JSON.parse(stdout.output); @@ -1645,14 +1645,14 @@ describe("issue list: multi-target cursor-safe budget", () => { issues: Array.from({ length: limit }, (_, i) => issue(org, i + 1)), nextCursor: `${org}-next:0:0`, }; - } + }, ); const { context, stdout } = createContext(); await func.call( context, { limit: 2, sort: "date", period: parsePeriod("90d"), json: true }, - "myproj" + "myproj", ); const output = JSON.parse(stdout.output); @@ -1662,7 +1662,7 @@ describe("issue list: multi-target cursor-safe budget", () => { "issue-list", expect.any(String), "first", - undefined + undefined, ); }); }); @@ -1824,7 +1824,7 @@ describe("build400Detail", () => { test("gives a sort-specific hint (not the generic trio) for an unsupported sort", () => { const detail = build400Detail( "Sort key 'recommended' not supported.", - flags + flags, ); expect(detail).toContain("does not support the 'recommended' sort"); expect(detail).toContain("--sort date"); @@ -1875,13 +1875,13 @@ describe("appendIssueFlags", () => { appendIssueFlags("sentry issue list org/", { ...baseFlags, sort: "recommended", - }) + }), ).toBe("sentry issue list org/"); expect( appendIssueFlags("sentry issue list org/", { ...baseFlags, sort: "date", - }) + }), ).toContain("--sort date"); }); @@ -1891,13 +1891,13 @@ describe("appendIssueFlags", () => { appendIssueFlags("sentry issue list org/", { ...baseFlags, sort: "date", - }) + }), ).toBe("sentry issue list org/"); expect( appendIssueFlags("sentry issue list org/", { ...baseFlags, sort: "recommended", - }) + }), ).toContain("--sort recommended"); }); }); diff --git a/packages/cli/test/commands/issue/merge.func.test.ts b/packages/cli/test/commands/issue/merge.func.test.ts index 3d1b256af..32dc72b40 100644 --- a/packages/cli/test/commands/issue/merge.func.test.ts +++ b/packages/cli/test/commands/issue/merge.func.test.ts @@ -17,11 +17,11 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { @@ -31,11 +31,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ApiError, @@ -133,7 +133,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg, id: issueArg.replace("CLI-", "10"), }), - }) + }), ); // Sentry honors the --into preference in this case mergeSpy.mockResolvedValue({ parent: "10B", children: ["10A"] }); @@ -158,7 +158,7 @@ describe("mergeCommand.func()", () => { Promise.resolve({ org: "test-org", issue: makeMockIssue({ shortId: "CLI-A", id: "100" }), - }) + }), ); const { context } = createMockContext(); @@ -204,7 +204,7 @@ describe("mergeCommand.func()", () => { Promise.resolve({ org: issueArg === "CLI-A" ? "org-one" : "org-two", issue: makeMockIssue({ shortId: issueArg, id: issueArg }), - }) + }), ); const { context } = createMockContext(); @@ -225,7 +225,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg, id: issueArg.replace("CLI-", "10"), }), - }) + }), ); mergeSpy.mockResolvedValue({ parent: "10A", children: ["10B", "10C"] }); @@ -244,7 +244,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg, id: issueArg.replace("CLI-", "10"), }), - }) + }), ); mergeSpy.mockResolvedValue({ parent: "10B", children: ["10A", "10C"] }); @@ -256,7 +256,7 @@ describe("mergeCommand.func()", () => { { json: false, into: "CLI-B" }, "CLI-A", "CLI-B", - "CLI-C" + "CLI-C", ); const callArgs = mergeSpy.mock.calls[0] as [string, string[]]; @@ -313,7 +313,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg.split("/").pop() as string, id: (issueArg.split("/").pop() as string).replace("CLI-", "10"), }), - }) + }), ); mergeSpy.mockResolvedValue({ parent: "10B", children: ["10A", "10C"] }); @@ -325,7 +325,7 @@ describe("mergeCommand.func()", () => { { json: false, into: "my-org/CLI-B" }, "CLI-A", "CLI-B", - "CLI-C" + "CLI-C", ); const callArgs = mergeSpy.mock.calls[0] as [string, string[]]; @@ -343,7 +343,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg, id: issueArg.replace("CLI-", "10"), }), - }) + }), ); mergeSpy.mockResolvedValue({ parent: "10C", children: ["10A", "10B"] }); @@ -367,7 +367,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg, id: issueArg.replace("CLI-", "10"), }), - }) + }), ); mergeSpy.mockResolvedValue({ parent: "10A", children: ["10B"] }); @@ -398,7 +398,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg, id: issueArg.replace("CLI-", "10"), }), - }) + }), ); // User asked for CLI-B, but Sentry picked CLI-A (e.g. larger by count) mergeSpy.mockResolvedValue({ parent: "10A", children: ["10B"] }); @@ -413,7 +413,7 @@ describe("mergeCommand.func()", () => { context, { json: false, into: "CLI-B" }, "CLI-A", - "CLI-B" + "CLI-B", ); const stderr = stderrSpy.mock.calls.map((c) => String(c[0])).join(""); @@ -432,7 +432,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg, id: issueArg.replace("CLI-", "10"), }), - }) + }), ); // User asked for CLI-B, Sentry agreed. mergeSpy.mockResolvedValue({ parent: "10B", children: ["10A"] }); @@ -445,7 +445,7 @@ describe("mergeCommand.func()", () => { context, { json: false, into: "CLI-B" }, "CLI-A", - "CLI-B" + "CLI-B", ); const stderr = stderrSpy.mock.calls.map((c) => String(c[0])).join(""); @@ -463,7 +463,7 @@ describe("mergeCommand.func()", () => { Promise.resolve({ org: "test-org", issue: makeMockIssue({ shortId: "CLI-A", id: "100" }), - }) + }), ); const { context } = createMockContext(); @@ -546,8 +546,8 @@ describe("mergeCommand.func()", () => { new ResolutionError( "Issue 'XYZ'", "not found", - "sentry issue view XYZ" - ) + "sentry issue view XYZ", + ), ); } return Promise.resolve({ @@ -582,7 +582,7 @@ describe("mergeCommand.func()", () => { shortId: issueArg.toUpperCase(), id: issueArg.toUpperCase().replace("CLI-", "10"), }), - }) + }), ); mergeSpy.mockResolvedValue({ parent: "10B", children: ["10A"] }); diff --git a/packages/cli/test/commands/issue/plan.func.test.ts b/packages/cli/test/commands/issue/plan.func.test.ts index 1c93a5ae5..e6dcd7b4a 100644 --- a/packages/cli/test/commands/issue/plan.func.test.ts +++ b/packages/cli/test/commands/issue/plan.func.test.ts @@ -14,13 +14,13 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); import { isatty } from "node:tty"; import { planCommand } from "../../../src/commands/issue/plan.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; import { triggerSolutionPlanning } from "../../../src/lib/api-client.js"; import { CliError } from "../../../src/lib/errors.js"; @@ -101,7 +101,7 @@ describe("issue plan waiting-for-user-response", () => { test("confirms the root cause and continues to planning when interactive", async () => { vi.mocked(isatty).mockReturnValue(true); analyzeSpy.mockResolvedValue( - waitingState([sampleCause(0, "First"), sampleCause(1, "Second")]) + waitingState([sampleCause(0, "First"), sampleCause(1, "Second")]), ); promptSpy.mockResolvedValue(true); const solutionSpy = vi @@ -112,7 +112,7 @@ describe("issue plan waiting-for-user-response", () => { expect(promptSpy).toHaveBeenCalledWith( "Generate a solution plan for it?", - expect.objectContaining({ type: "confirm" }) + expect.objectContaining({ type: "confirm" }), ); expect(triggerSolutionPlanning).toHaveBeenCalledWith("test-org", "1", 42); expect(solutionSpy).toHaveBeenCalled(); @@ -186,7 +186,7 @@ describe("issue plan after solution planning", () => { pollSpy.mockResolvedValue({ status: "ERROR", run_id: 42, steps: [] }); await expect(runPlan(createMockContext())).rejects.toThrow( - /Plan creation failed/ + /Plan creation failed/, ); }); @@ -194,7 +194,7 @@ describe("issue plan after solution planning", () => { pollSpy.mockResolvedValue({ status: "CANCELLED", run_id: 42, steps: [] }); await expect(runPlan(createMockContext())).rejects.toThrow( - /Plan creation was cancelled/ + /Plan creation was cancelled/, ); }); diff --git a/packages/cli/test/commands/issue/resolve-commit-spec.test.ts b/packages/cli/test/commands/issue/resolve-commit-spec.test.ts index 82bd70c0a..ded2cae14 100644 --- a/packages/cli/test/commands/issue/resolve-commit-spec.test.ts +++ b/packages/cli/test/commands/issue/resolve-commit-spec.test.ts @@ -7,10 +7,10 @@ import { describe, expect, test, vi } from "vitest"; import { resolveCommitSpec } from "../../../src/commands/issue/resolve-commit-spec.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ValidationError } from "../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as gitLib from "../../../src/lib/git.js"; import type { SentryRepository } from "../../../src/types/sentry.js"; @@ -36,7 +36,7 @@ describe("resolveCommitSpec — explicit mode", () => { const result = await resolveCommitSpec( { kind: "explicit", repository: "getsentry/cli", commit: "abc123" }, "sentry", - "/tmp" + "/tmp", ); expect(result).toEqual({ commit: "abc123", @@ -61,7 +61,7 @@ describe("resolveCommitSpec — explicit mode", () => { const result = await resolveCommitSpec( { kind: "explicit", repository: "getsentry/sentry", commit: "abc" }, "sentry", - "/tmp" + "/tmp", ); // API expects the canonical `name`, not the externalSlug expect(result.repository).toBe("Sentry Monolith"); @@ -79,8 +79,8 @@ describe("resolveCommitSpec — explicit mode", () => { resolveCommitSpec( { kind: "explicit", repository: "unknown/repo", commit: "abc" }, "sentry", - "/tmp" - ) + "/tmp", + ), ).rejects.toBeInstanceOf(ValidationError); } finally { listSpy.mockRestore(); @@ -95,7 +95,7 @@ describe("resolveCommitSpec — auto-detect mode", () => { .mockReturnValue(false); try { await expect( - resolveCommitSpec({ kind: "auto" }, "sentry", "/tmp") + resolveCommitSpec({ kind: "auto" }, "sentry", "/tmp"), ).rejects.toThrow(/requires a git repository/); } finally { gitSpy.mockRestore(); @@ -111,7 +111,7 @@ describe("resolveCommitSpec — auto-detect mode", () => { }); try { await expect( - resolveCommitSpec({ kind: "auto" }, "sentry", "/tmp") + resolveCommitSpec({ kind: "auto" }, "sentry", "/tmp"), ).rejects.toThrow(/could not read HEAD/); } finally { gitSpy.mockRestore(); @@ -149,7 +149,7 @@ describe("resolveCommitSpec — auto-detect mode", () => { const result = await resolveCommitSpec( { kind: "auto" }, "sentry", - process.cwd() + process.cwd(), ); expect(result).toEqual({ commit: "abc123def456", @@ -176,7 +176,7 @@ describe("resolveCommitSpec — error messages are actionable", () => { const err = await resolveCommitSpec( { kind: "explicit", repository: "typo/repo", commit: "abc" }, "sentry", - "/tmp" + "/tmp", ).catch((e: Error) => e); expect(err).toBeInstanceOf(ValidationError); expect(err.message).toContain("getsentry/cli"); diff --git a/packages/cli/test/commands/issue/resolve.func.test.ts b/packages/cli/test/commands/issue/resolve.func.test.ts index 8523cf162..ab5d2c9bf 100644 --- a/packages/cli/test/commands/issue/resolve.func.test.ts +++ b/packages/cli/test/commands/issue/resolve.func.test.ts @@ -18,12 +18,12 @@ vi.mock( Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); - } + }, ); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as commitSpec from "../../../src/commands/issue/resolve-commit-spec.js"; import { unresolveCommand } from "../../../src/commands/issue/unresolve.js"; @@ -36,11 +36,11 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { @@ -50,11 +50,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import type { SentryIssue } from "../../../src/types/sentry.js"; @@ -174,7 +174,7 @@ describe("resolveCommand.func()", () => { expect(commitSpy).toHaveBeenCalledWith( { kind: "auto" }, "test-org", - "/tmp" + "/tmp", ); expect(updateSpy).toHaveBeenCalledWith("123456789", "resolved", { statusDetails: { @@ -206,13 +206,13 @@ describe("resolveCommand.func()", () => { await func.call( context, { json: false, in: "@commit:getsentry/cli@abc123" }, - "CLI-G5" + "CLI-G5", ); expect(commitSpy).toHaveBeenCalledWith( { kind: "explicit", repository: "getsentry/cli", commit: "abc123" }, "test-org", - "/tmp" + "/tmp", ); expect(updateSpy).toHaveBeenCalledWith("123456789", "resolved", { statusDetails: { diff --git a/packages/cli/test/commands/issue/short-id-resolution.test.ts b/packages/cli/test/commands/issue/short-id-resolution.test.ts index 31756bf41..a3c4295b5 100644 --- a/packages/cli/test/commands/issue/short-id-resolution.test.ts +++ b/packages/cli/test/commands/issue/short-id-resolution.test.ts @@ -85,7 +85,7 @@ beforeEach(async () => { if (url.pathname === "/api/0/organizations/") { return Response.json( listingStatus === 200 ? listedOrgs : { detail: "Forbidden" }, - { status: listingStatus } + { status: listingStatus }, ); } for (const [key, issue] of issues) { @@ -120,26 +120,26 @@ describe("short IDs with configured organization context", () => { test.each([ { envOrg: secondOrg.slug, expectedOrg: secondOrg.slug }, { envOrg: undefined, expectedOrg: targetOrg.slug }, - ])("honors env and .sentryclirc precedence: $expectedOrg", async ({ - envOrg, - expectedOrg, - }) => { - setDefaultOrganization(cachedOrg.slug); - writeFileSync( - join(getConfigDir(), ".sentryclirc"), - `[defaults]\norg = ${targetOrg.slug}\n` - ); - if (envOrg) { - process.env.SENTRY_ORG = envOrg; - } - for (const org of [cachedOrg, targetOrg, secondOrg]) { - addIssue(org.slug); - } + ])( + "honors env and .sentryclirc precedence: $expectedOrg", + async ({ envOrg, expectedOrg }) => { + setDefaultOrganization(cachedOrg.slug); + writeFileSync( + join(getConfigDir(), ".sentryclirc"), + `[defaults]\norg = ${targetOrg.slug}\n`, + ); + if (envOrg) { + process.env.SENTRY_ORG = envOrg; + } + for (const org of [cachedOrg, targetOrg, secondOrg]) { + addIssue(org.slug); + } - expect((await resolve()).org).toBe(expectedOrg); - expect(listingRequests()).toHaveLength(0); - expect(shortIdRequests()).toHaveLength(1); - }); + expect((await resolve()).org).toBe(expectedOrg); + expect(listingRequests()).toHaveLength(0); + expect(shortIdRequests()).toHaveLength(1); + }, + ); test("does not fall through to another org when the configured org returns 404", async () => { setDefaultOrganization(targetOrg.slug); @@ -157,7 +157,7 @@ describe("short IDs with configured organization context", () => { setDefaultOrganization(targetOrg.slug); setProjectAliases( { f: { orgSlug: cachedOrg.slug, projectSlug: "frontend" } }, - "" + "", ); addIssue(cachedOrg.slug, "FRONTEND-5BS"); @@ -181,26 +181,27 @@ describe("short IDs missing from cached organizations", () => { `/api/0/organizations/target-org/shortids/${SHORT_ID}/`, ]); expect(shortIdRequests().every((url) => url.origin === REGION_URL)).toBe( - true + true, ); }); test.each([ { label: "unchanged", inventory: [cachedOrg] }, { label: "new organization", inventory: [cachedOrg, targetOrg] }, - ])("keeps a still-missing lookup bounded after refresh: $label", async ({ - inventory, - }) => { - listedOrgs = inventory; + ])( + "keeps a still-missing lookup bounded after refresh: $label", + async ({ inventory }) => { + listedOrgs = inventory; - await expect(resolve()).rejects.toBeInstanceOf(ResolutionError); + await expect(resolve()).rejects.toBeInstanceOf(ResolutionError); - expect(listingRequests()).toHaveLength(1); - expect(shortIdRequests()).toHaveLength(inventory.length); - expect(new Set(shortIdRequests().map((url) => url.pathname)).size).toBe( - inventory.length - ); - }); + expect(listingRequests()).toHaveLength(1); + expect(shortIdRequests()).toHaveLength(inventory.length); + expect(new Set(shortIdRequests().map((url) => url.pathname)).size).toBe( + inventory.length, + ); + }, + ); test("reports ambiguity when the refresh discovers two matching orgs", async () => { listedOrgs = [cachedOrg, targetOrg, secondOrg]; @@ -226,7 +227,7 @@ describe("short IDs missing from cached organizations", () => { test("does not scope the search to a DSN for a different project", async () => { writeFileSync( join(getConfigDir(), ".env"), - "SENTRY_DSN=https://abc@o123.ingest.de.sentry.io/456" + "SENTRY_DSN=https://abc@o123.ingest.de.sentry.io/456", ); setCachedProject("123", "456", { orgSlug: cachedOrg.slug, diff --git a/packages/cli/test/commands/issue/unlink.func.test.ts b/packages/cli/test/commands/issue/unlink.func.test.ts index 7a28c16ae..1ab9fcdd2 100644 --- a/packages/cli/test/commands/issue/unlink.func.test.ts +++ b/packages/cli/test/commands/issue/unlink.func.test.ts @@ -93,42 +93,42 @@ describe("issue unlink", () => { selector: { app: "custom-tracker" }, expected: { integrationId: undefined, appSlug: "custom-tracker" }, }, - ])("forwards resolved issue context and selector $selector", async ({ - selector, - expected, - }) => { - const { context, output } = createMockContext(); - const func = await unlinkCommand.loader(); - await func.call( - context, - { ...defaultFlags, ...selector, yes: true }, - "test-org/APP-42", - externalUrl - ); - - expect(resolveOrgAndIssueId).toHaveBeenCalledExactlyOnceWith({ - issueArg: "test-org/APP-42", - cwd: "/tmp/example-project", - command: "unlink", - }); - expect(unlinkExternalIssue).toHaveBeenCalledExactlyOnceWith({ - orgSlug: "test-org", - issueId: "123456789", - url: externalUrl, - ...expected, - dryRun: false, - }); - expect(confirmByTyping).not.toHaveBeenCalled(); - expect(output()).toContain("Unlinked"); - expect(output()).toContain("external issue was not deleted"); - }); + ])( + "forwards resolved issue context and selector $selector", + async ({ selector, expected }) => { + const { context, output } = createMockContext(); + const func = await unlinkCommand.loader(); + await func.call( + context, + { ...defaultFlags, ...selector, yes: true }, + "test-org/APP-42", + externalUrl, + ); + + expect(resolveOrgAndIssueId).toHaveBeenCalledExactlyOnceWith({ + issueArg: "test-org/APP-42", + cwd: "/tmp/example-project", + command: "unlink", + }); + expect(unlinkExternalIssue).toHaveBeenCalledExactlyOnceWith({ + orgSlug: "test-org", + issueId: "123456789", + url: externalUrl, + ...expected, + dryRun: false, + }); + expect(confirmByTyping).not.toHaveBeenCalled(); + expect(output()).toContain("Unlinked"); + expect(output()).toContain("external issue was not deleted"); + }, + ); test("refuses non-interactive mutation without explicit confirmation before resolving", async () => { const { context, output } = createMockContext(); const func = await unlinkCommand.loader(); await expect( - func.call(context, defaultFlags, "APP-42", externalUrl) + func.call(context, defaultFlags, "APP-42", externalUrl), ).rejects.toThrow("Use --yes or --force to confirm."); expect(resolveOrgAndIssueId).not.toHaveBeenCalled(); @@ -137,29 +137,29 @@ describe("issue unlink", () => { expect(output()).toBe(""); }); - test.each([ - "yes", - "force", - ] as const)("allows non-interactive --%s without prompting", async (flag) => { - const { context } = createMockContext(); - const func = await unlinkCommand.loader(); - await func.call( - context, - { ...defaultFlags, [flag]: true }, - "APP-42", - externalUrl - ); - - expect(confirmByTyping).not.toHaveBeenCalled(); - expect(unlinkExternalIssue).toHaveBeenCalledExactlyOnceWith({ - orgSlug: "test-org", - issueId: "123456789", - url: externalUrl, - integrationId: undefined, - appSlug: undefined, - dryRun: false, - }); - }); + test.each(["yes", "force"] as const)( + "allows non-interactive --%s without prompting", + async (flag) => { + const { context } = createMockContext(); + const func = await unlinkCommand.loader(); + await func.call( + context, + { ...defaultFlags, [flag]: true }, + "APP-42", + externalUrl, + ); + + expect(confirmByTyping).not.toHaveBeenCalled(); + expect(unlinkExternalIssue).toHaveBeenCalledExactlyOnceWith({ + orgSlug: "test-org", + issueId: "123456789", + url: externalUrl, + integrationId: undefined, + appSlug: undefined, + dryRun: false, + }); + }, + ); test("confirms the selected issue and external URL before unlinking interactively", async () => { mockIsatty.mockReturnValue(true); @@ -169,11 +169,11 @@ describe("issue unlink", () => { expect(confirmByTyping).toHaveBeenCalledExactlyOnceWith( "test-org/APP-42", - `Type 'test-org/APP-42' to unlink ${externalUrl}:` + `Type 'test-org/APP-42' to unlink ${externalUrl}:`, ); expect(unlinkExternalIssue).toHaveBeenCalledOnce(); expect(vi.mocked(confirmByTyping).mock.invocationCallOrder[0]).toBeLessThan( - vi.mocked(unlinkExternalIssue).mock.invocationCallOrder[0] + vi.mocked(unlinkExternalIssue).mock.invocationCallOrder[0], ); }); @@ -203,12 +203,12 @@ describe("issue unlink", () => { context, { ...defaultFlags, "dry-run": true, json: true }, "APP-42", - externalUrl + externalUrl, ); expect(confirmByTyping).not.toHaveBeenCalled(); expect(unlinkExternalIssue).toHaveBeenCalledWith( - expect.objectContaining({ dryRun: true }) + expect.objectContaining({ dryRun: true }), ); expect(JSON.parse(output())).toEqual(result); }); diff --git a/packages/cli/test/commands/issue/utils.test.ts b/packages/cli/test/commands/issue/utils.test.ts index 7b75c206c..9efc81db3 100644 --- a/packages/cli/test/commands/issue/utils.test.ts +++ b/packages/cli/test/commands/issue/utils.test.ts @@ -42,7 +42,7 @@ describe("mapIssueArgsConcurrently", () => { } return issueArg; }, - onError + onError, ); expect(result).toEqual(["found"]); @@ -61,8 +61,8 @@ describe("mapIssueArgsConcurrently", () => { async (issueArg) => { throw issueArg === "first" ? primary : secondary; }, - onError - ) + onError, + ), ).rejects.toBe(primary); expect(onError).not.toHaveBeenCalled(); }); @@ -71,82 +71,82 @@ describe("mapIssueArgsConcurrently", () => { describe("buildCommandHint", () => { test("suggests <org>/ID for numeric IDs", () => { expect(buildCommandHint("view", "123456789")).toBe( - "sentry issue view <org>/123456789" + "sentry issue view <org>/123456789", ); expect(buildCommandHint("explain", "0")).toBe( - "sentry issue explain <org>/0" + "sentry issue explain <org>/0", ); }); test("suggests <project>-suffix for short suffixes", () => { expect(buildCommandHint("view", "G")).toBe("sentry issue view <project>-G"); expect(buildCommandHint("explain", "4Y")).toBe( - "sentry issue explain <project>-4Y" + "sentry issue explain <project>-4Y", ); expect(buildCommandHint("plan", "ABC")).toBe( - "sentry issue plan <project>-ABC" + "sentry issue plan <project>-ABC", ); }); test("suggests <org>/ID for IDs with dashes", () => { expect(buildCommandHint("view", "cli-G")).toBe( - "sentry issue view <org>/cli-G" + "sentry issue view <org>/cli-G", ); expect(buildCommandHint("explain", "PROJECT-ABC")).toBe( - "sentry issue explain <org>/PROJECT-ABC" + "sentry issue explain <org>/PROJECT-ABC", ); }); test("suggests <org>/@selector for selectors", () => { expect(buildCommandHint("view", "@latest")).toBe( - "sentry issue view <org>/@latest" + "sentry issue view <org>/@latest", ); expect(buildCommandHint("explain", "@most_frequent")).toBe( - "sentry issue explain <org>/@most_frequent" + "sentry issue explain <org>/@most_frequent", ); }); test("shows as-is when input already contains a slash (CLI-8C)", () => { // org/numeric — don't add another <org>/ prefix expect(buildCommandHint("view", "saber-ut/103103195")).toBe( - "sentry issue view saber-ut/103103195" + "sentry issue view saber-ut/103103195", ); // org/project-suffix — already has full context expect(buildCommandHint("view", "sentry/cli-G")).toBe( - "sentry issue view sentry/cli-G" + "sentry issue view sentry/cli-G", ); // org/project/suffix — three-level path, show as-is expect(buildCommandHint("explain", "sentry/cli/CLI-A1")).toBe( - "sentry issue explain sentry/cli/CLI-A1" + "sentry issue explain sentry/cli/CLI-A1", ); }); test("suggests org/<project>-suffix for bare org/suffix form", () => { // org/SUFFIX with no project prefix — guide user to supply a project expect(buildCommandHint("view", "sentry/SERVER")).toBe( - "sentry issue view sentry/<project>-SERVER" + "sentry issue view sentry/<project>-SERVER", ); expect(buildCommandHint("explain", "my-org/ABC")).toBe( - "sentry issue explain my-org/<project>-ABC" + "sentry issue explain my-org/<project>-ABC", ); }); test("shows as-is for slash forms that aren't a bare org/suffix", () => { // org/@selector — a special selector, not a project suffix (CLI-RD review) expect(buildCommandHint("view", "sentry/@latest")).toBe( - "sentry issue view sentry/@latest" + "sentry issue view sentry/@latest", ); // org/project/suffix — multi-segment path is already fully specified expect(buildCommandHint("view", "sentry/cli/A1")).toBe( - "sentry issue view sentry/cli/A1" + "sentry issue view sentry/cli/A1", ); // org/project#suffix — GitHub-style separator, already has project context expect(buildCommandHint("view", "sentry/cli#A1")).toBe( - "sentry issue view sentry/cli#A1" + "sentry issue view sentry/cli#A1", ); // leading slash with no org — don't fabricate an org/<project> template expect(buildCommandHint("view", "/SERVER")).toBe( - "sentry issue view /SERVER" + "sentry issue view /SERVER", ); }); @@ -154,20 +154,20 @@ describe("buildCommandHint", () => { const shareUrl = "https://gibush-kq.sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/"; expect(buildCommandHint("view", shareUrl)).toBe( - `sentry issue view ${shareUrl}` + `sentry issue view ${shareUrl}`, ); }); test("returns URL as-is for regular issue URLs", () => { const issueUrl = "https://sentry.io/organizations/my-org/issues/12345/"; expect(buildCommandHint("view", issueUrl)).toBe( - `sentry issue view ${issueUrl}` + `sentry issue view ${issueUrl}`, ); }); test("supports a custom command domain", () => { expect(buildCommandHint("view", "PROJECT-ABC", "sentry feedback")).toBe( - "sentry feedback view <org>/PROJECT-ABC" + "sentry feedback view <org>/PROJECT-ABC", ); }); }); @@ -184,7 +184,7 @@ beforeEach(async () => { // won't produce "unexpected fetch" warnings from the preload trap. globalThis.fetch = mockFetch( async () => - new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }) + new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }), ); await setAuthToken("test-token"); // Pre-populate region cache for orgs used in tests to avoid region resolution API calls @@ -237,7 +237,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -252,7 +252,7 @@ describe("resolveOrgAndIssueId", () => { issueArg: "123456789", cwd: getConfigDir(), command: "explain", - }) + }), ).rejects.toThrow("organization"); }); @@ -279,7 +279,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -322,7 +322,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -366,7 +366,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -387,15 +387,14 @@ describe("resolveOrgAndIssueId", () => { test("resolves alias-suffix format (e.g., 'f-g') using cached aliases", async () => { // Empty fingerprint matches detectAllDsns on empty dir - const { setProjectAliases } = await import( - "../../../src/lib/db/project-aliases.js" - ); + const { setProjectAliases } = + await import("../../../src/lib/db/project-aliases.js"); setProjectAliases( { f: { orgSlug: "cached-org", projectSlug: "frontend" }, b: { orgSlug: "cached-org", projectSlug: "backend" }, }, - "" + "", ); // @ts-expect-error - partial mock @@ -423,7 +422,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -469,7 +468,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -489,9 +488,8 @@ describe("resolveOrgAndIssueId", () => { }); test("resolves short suffix format (e.g., 'G') using project context from defaults", async () => { - const { setDefaultOrganization, setDefaultProject } = await import( - "../../../src/lib/db/defaults.js" - ); + const { setDefaultOrganization, setDefaultProject } = + await import("../../../src/lib/db/defaults.js"); setDefaultOrganization("my-org"); setDefaultProject("my-project"); @@ -520,7 +518,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -549,14 +547,13 @@ describe("resolveOrgAndIssueId", () => { issueArg: "G", cwd: getConfigDir(), command: "explain", - }) + }), ).rejects.toThrow("could not be resolved"); }); test("searches projects across orgs for project-suffix format", async () => { - const { clearProjectAliases } = await import( - "../../../src/lib/db/project-aliases.js" - ); + const { clearProjectAliases } = + await import("../../../src/lib/db/project-aliases.js"); clearProjectAliases(); // @ts-expect-error - partial mock @@ -584,7 +581,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -600,7 +597,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -624,7 +621,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -644,9 +641,8 @@ describe("resolveOrgAndIssueId", () => { }); test("throws when project not found in any org", async () => { - const { clearProjectAliases } = await import( - "../../../src/lib/db/project-aliases.js" - ); + const { clearProjectAliases } = + await import("../../../src/lib/db/project-aliases.js"); clearProjectAliases(); // @ts-expect-error - partial mock @@ -674,7 +670,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -701,7 +697,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -715,14 +711,13 @@ describe("resolveOrgAndIssueId", () => { issueArg: "nonexistent-g", cwd: getConfigDir(), command: "explain", - }) + }), ).rejects.toThrow("Issue 'NONEXISTENT-G'"); }); test("throws when project found in multiple orgs without explicit org", async () => { - const { clearProjectAliases } = await import( - "../../../src/lib/db/project-aliases.js" - ); + const { clearProjectAliases } = + await import("../../../src/lib/db/project-aliases.js"); clearProjectAliases(); setOrgRegion("org2", DEFAULT_SENTRY_URL); @@ -755,7 +750,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -771,7 +766,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -787,7 +782,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -801,14 +796,13 @@ describe("resolveOrgAndIssueId", () => { issueArg: "common-g", cwd: getConfigDir(), command: "explain", - }) + }), ).rejects.toThrow("is ambiguous"); }); test("short suffix auth error (401) propagates", async () => { - const { setDefaultOrganization, setDefaultProject } = await import( - "../../../src/lib/db/defaults.js" - ); + const { setDefaultOrganization, setDefaultProject } = + await import("../../../src/lib/db/defaults.js"); setDefaultOrganization("my-org"); setDefaultProject("my-project"); @@ -824,14 +818,13 @@ describe("resolveOrgAndIssueId", () => { issueArg: "G", cwd: getConfigDir(), command: "explain", - }) + }), ).rejects.toThrow(); }); test("short suffix server error (500) propagates", async () => { - const { setDefaultOrganization, setDefaultProject } = await import( - "../../../src/lib/db/defaults.js" - ); + const { setDefaultOrganization, setDefaultProject } = + await import("../../../src/lib/db/defaults.js"); setDefaultOrganization("my-org"); setDefaultProject("my-project"); @@ -847,14 +840,13 @@ describe("resolveOrgAndIssueId", () => { issueArg: "G", cwd: getConfigDir(), command: "explain", - }) + }), ).rejects.toThrow("500"); }); test("fast path: ambiguous when shortid resolves in multiple orgs", async () => { - const { clearProjectAliases } = await import( - "../../../src/lib/db/project-aliases.js" - ); + const { clearProjectAliases } = + await import("../../../src/lib/db/project-aliases.js"); clearProjectAliases(); setOrgRegion("org2", DEFAULT_SENTRY_URL); @@ -879,7 +871,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); // @ts-expect-error - partial mock @@ -908,7 +900,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -930,14 +922,13 @@ describe("resolveOrgAndIssueId", () => { issueArg: "shared-g", cwd: getConfigDir(), command: "explain", - }) + }), ).rejects.toThrow("is ambiguous"); }); test("fast path: surfaces 403 when all orgs return forbidden", async () => { - const { clearProjectAliases } = await import( - "../../../src/lib/db/project-aliases.js" - ); + const { clearProjectAliases } = + await import("../../../src/lib/db/project-aliases.js"); clearProjectAliases(); // @ts-expect-error - partial mock @@ -963,7 +954,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -971,7 +962,7 @@ describe("resolveOrgAndIssueId", () => { if (url.includes("/shortids/")) { return new Response( JSON.stringify({ detail: "You do not have permission" }), - { status: 403 } + { status: 403 }, ); } @@ -991,9 +982,8 @@ describe("resolveOrgAndIssueId", () => { }); test("fast path: surfaces 500 when all orgs return server error", async () => { - const { clearProjectAliases } = await import( - "../../../src/lib/db/project-aliases.js" - ); + const { clearProjectAliases } = + await import("../../../src/lib/db/project-aliases.js"); clearProjectAliases(); // @ts-expect-error - partial mock @@ -1019,7 +1009,7 @@ describe("resolveOrgAndIssueId", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1027,7 +1017,7 @@ describe("resolveOrgAndIssueId", () => { if (url.includes("/shortids/")) { return new Response( JSON.stringify({ detail: "Internal Server Error" }), - { status: 500 } + { status: 500 }, ); } @@ -1049,9 +1039,8 @@ describe("resolveOrgAndIssueId", () => { describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { test("uses cached org on warm cache (single org-scoped call)", async () => { - const { setCachedIssueOrg, getCachedIssueOrg } = await import( - "../../../src/lib/db/issue-org-cache.js" - ); + const { setCachedIssueOrg, getCachedIssueOrg } = + await import("../../../src/lib/db/issue-org-cache.js"); setCachedIssueOrg("77777777", "cached-org"); // Track which endpoint is hit — must be org-scoped, not legacy unscoped. @@ -1068,7 +1057,7 @@ describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { permalink: "https://sentry.io/organizations/cached-org/issues/77777777/", }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -1085,22 +1074,21 @@ describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { expect(result.org).toBe("cached-org"); // Should NOT have hit the legacy unscoped /api/0/issues/{id}/ endpoint. expect(calls.some((u) => /\/api\/0\/issues\/77777777\//.test(u))).toBe( - false + false, ); // The org-scoped endpoint is what got called. expect( calls.some((u) => - u.includes("/organizations/cached-org/issues/77777777/") - ) + u.includes("/organizations/cached-org/issues/77777777/"), + ), ).toBe(true); // Cache stays populated (it was a valid hit). expect(getCachedIssueOrg("77777777")).toBe("cached-org"); }); test("evicts stale cache entry on 404 and uses permalink org (does not leak stale slug)", async () => { - const { setCachedIssueOrg, getCachedIssueOrg } = await import( - "../../../src/lib/db/issue-org-cache.js" - ); + const { setCachedIssueOrg, getCachedIssueOrg } = + await import("../../../src/lib/db/issue-org-cache.js"); // Seed a stale mapping that will 404 on the org-scoped endpoint. setCachedIssueOrg("88888888", "stale-org"); setOrgRegion("stale-org", DEFAULT_SENTRY_URL); @@ -1125,7 +1113,7 @@ describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { permalink: "https://sentry.io/organizations/correct-org/issues/88888888/", }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -1149,9 +1137,8 @@ describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { }); test("writes numeric-id → org mapping after legacy unscoped fallback", async () => { - const { getCachedIssueOrg } = await import( - "../../../src/lib/db/issue-org-cache.js" - ); + const { getCachedIssueOrg } = + await import("../../../src/lib/db/issue-org-cache.js"); setOrgRegion("fresh-org", DEFAULT_SENTRY_URL); // @ts-expect-error - partial mock @@ -1166,7 +1153,7 @@ describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { permalink: "https://sentry.io/organizations/fresh-org/issues/99999999/", }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -1187,9 +1174,8 @@ describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { }); test("5xx on cached-org fetch propagates the error WITHOUT evicting the cache", async () => { - const { setCachedIssueOrg, getCachedIssueOrg } = await import( - "../../../src/lib/db/issue-org-cache.js" - ); + const { setCachedIssueOrg, getCachedIssueOrg } = + await import("../../../src/lib/db/issue-org-cache.js"); // Seed a valid mapping — the org-scoped endpoint will return 500 (transient). setCachedIssueOrg("66666666", "still-valid-org"); setOrgRegion("still-valid-org", DEFAULT_SENTRY_URL); @@ -1212,7 +1198,7 @@ describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { permalink: "https://sentry.io/organizations/still-valid-org/issues/66666666/", }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -1226,7 +1212,7 @@ describe("resolveOrgAndIssueId: issue-id → org cache (Pattern D)", () => { issueArg: "66666666", cwd: getConfigDir(), command: "view", - }) + }), ).rejects.toThrow(ApiError); // Cache must NOT have been evicted — the failure was transient, the @@ -1255,7 +1241,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; @@ -1284,7 +1270,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); const result = await pollAutofixState({ @@ -1311,7 +1297,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); const result = await pollAutofixState({ @@ -1345,7 +1331,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1360,7 +1346,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; @@ -1423,7 +1409,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1438,7 +1424,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; @@ -1474,7 +1460,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); await expect( @@ -1486,7 +1472,7 @@ describe("pollAutofixState", () => { timeoutMs: 50, pollIntervalMs: 20, timeoutMessage: "Custom timeout message", - }) + }), ).rejects.toThrow("Custom timeout message"); }); @@ -1516,7 +1502,7 @@ describe("pollAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; @@ -1551,7 +1537,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; @@ -1583,7 +1569,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; @@ -1621,7 +1607,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } // Before trigger, return null @@ -1679,7 +1665,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response( @@ -1693,7 +1679,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1745,7 +1731,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1760,7 +1746,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1802,7 +1788,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1851,7 +1837,7 @@ describe("ensureRootCauseAnalysis", () => { // @ts-expect-error - partial mock globalThis.fetch = async ( input: RequestInfo | URL, - init?: RequestInit + init?: RequestInit, ) => { const req = new Request(input, init); const url = req.url; @@ -1869,7 +1855,7 @@ describe("ensureRootCauseAnalysis", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ autofix: null }), { @@ -1912,9 +1898,8 @@ describe("ensureRootCauseAnalysis", () => { describe("resolveOrgAndIssueId: magic @ selectors", () => { test("resolves @latest to the most recent unresolved issue", async () => { - const { setDefaultOrganization } = await import( - "../../../src/lib/db/defaults.js" - ); + const { setDefaultOrganization } = + await import("../../../src/lib/db/defaults.js"); setDefaultOrganization("test-org"); // @ts-expect-error - partial mock @@ -1943,7 +1928,7 @@ describe("resolveOrgAndIssueId: magic @ selectors", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1963,9 +1948,8 @@ describe("resolveOrgAndIssueId: magic @ selectors", () => { }); test("resolves @most_frequent to the highest frequency issue", async () => { - const { setDefaultOrganization } = await import( - "../../../src/lib/db/defaults.js" - ); + const { setDefaultOrganization } = + await import("../../../src/lib/db/defaults.js"); setDefaultOrganization("test-org"); // @ts-expect-error - partial mock @@ -1994,7 +1978,7 @@ describe("resolveOrgAndIssueId: magic @ selectors", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -2039,7 +2023,7 @@ describe("resolveOrgAndIssueId: magic @ selectors", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -2059,9 +2043,8 @@ describe("resolveOrgAndIssueId: magic @ selectors", () => { }); test("throws ResolutionError when no unresolved issues found", async () => { - const { setDefaultOrganization } = await import( - "../../../src/lib/db/defaults.js" - ); + const { setDefaultOrganization } = + await import("../../../src/lib/db/defaults.js"); setDefaultOrganization("test-org"); // @ts-expect-error - partial mock @@ -2102,7 +2085,7 @@ describe("resolveOrgAndIssueId: magic @ selectors", () => { }).catch((error) => error); expect(eventError).toBeInstanceOf(ResolutionError); expect(eventError.hint).toBe( - 'sentry issue list test-org/ -q "is:resolved"' + 'sentry issue list test-org/ -q "is:resolved"', ); }); @@ -2116,7 +2099,7 @@ describe("resolveOrgAndIssueId: magic @ selectors", () => { issueArg: "@latest", cwd: getConfigDir(), command: "view", - }) + }), ).rejects.toThrow("organization"); }); }); @@ -2174,7 +2157,7 @@ describe("resolveIssue: numeric 404 error handling", () => { issueArg: "123456789", cwd: getResolveIssueConfigDir(), command: "view", - }) + }), ).rejects.not.toBeInstanceOf(ResolutionError); }); @@ -2216,7 +2199,7 @@ describe("resolveIssue: numeric 404 error handling", () => { issueArg: "my-org/999999999", cwd: getResolveIssueConfigDir(), command: "view", - }) + }), ).rejects.not.toBeInstanceOf(ResolutionError); }); }); @@ -2255,7 +2238,7 @@ describe("resolveIssue: project-search DSN shortcut", () => { // Write a DSN so detectDsn finds it writeFileSync( join(cwd, ".env"), - "SENTRY_DSN=https://abc@o123.ingest.us.sentry.io/456" + "SENTRY_DSN=https://abc@o123.ingest.us.sentry.io/456", ); const requests: string[] = []; @@ -2283,7 +2266,7 @@ describe("resolveIssue: project-search DSN shortcut", () => { userCount: 1, }, }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -2307,8 +2290,8 @@ describe("resolveIssue: project-search DSN shortcut", () => { expect(requests.some((r) => r.includes("/users/me/regions/"))).toBe(false); expect( requests.some( - (r) => r.includes("/organizations/") && !r.includes("/shortids/") - ) + (r) => r.includes("/organizations/") && !r.includes("/shortids/"), + ), ).toBe(false); }); }); @@ -2353,62 +2336,66 @@ describe("resolveIssue with share URLs", () => { "test-org", `/share/issue/${shareId}/`, ], - ])("resolves %s using the shared issue id", async (name, baseUrl, org, path) => { - const { setDefaultOrganization } = await import( - "../../../src/lib/db/defaults.js" - ); - setDefaultOrganization( - name === "legacy URL with default org" ? org : "other-org" - ); - const apiBaseUrl = - baseUrl === "https://sentry.example.com" ? baseUrl : DEFAULT_SENTRY_URL; - setAuthToken("test-token", undefined, undefined, { host: apiBaseUrl }); - setOrgRegion(org, apiBaseUrl); - const requests: Request[] = []; - globalThis.fetch = mockFetch(async (input, init) => { - const request = new Request(input, init); - requests.push(request); - if ( - request.url === - `${baseUrl}/api/0/organizations/${org}/shared/issues/${shareId}/` - ) { - return Response.json({ - id: "12345", - title: "Shared issue", - project: { slug: "backend" }, - }); - } - if ( - new URL(request.url).pathname === - `/api/0/organizations/${org}/issues/12345/` - ) { - return Response.json({ - id: "12345", - shortId: "BACKEND-A1", - title: "Shared issue", - status: "unresolved", - platform: "python", - type: "error", - count: "5", - userCount: 3, - }); - } - return Response.json({ detail: "Not found" }, { status: 404 }); - }); + ])( + "resolves %s using the shared issue id", + async (name, baseUrl, org, path) => { + const { setDefaultOrganization } = + await import("../../../src/lib/db/defaults.js"); + setDefaultOrganization( + name === "legacy URL with default org" ? org : "other-org", + ); + const apiBaseUrl = + baseUrl === "https://sentry.example.com" ? baseUrl : DEFAULT_SENTRY_URL; + setAuthToken("test-token", undefined, undefined, { host: apiBaseUrl }); + setOrgRegion(org, apiBaseUrl); + const requests: Request[] = []; + globalThis.fetch = mockFetch(async (input, init) => { + const request = new Request(input, init); + requests.push(request); + if ( + request.url === + `${baseUrl}/api/0/organizations/${org}/shared/issues/${shareId}/` + ) { + return Response.json({ + id: "12345", + title: "Shared issue", + project: { slug: "backend" }, + }); + } + if ( + new URL(request.url).pathname === + `/api/0/organizations/${org}/issues/12345/` + ) { + return Response.json({ + id: "12345", + shortId: "BACKEND-A1", + title: "Shared issue", + status: "unresolved", + platform: "python", + type: "error", + count: "5", + userCount: 3, + }); + } + return Response.json({ detail: "Not found" }, { status: 404 }); + }); - const result = await resolveIssue({ - issueArg: `${baseUrl}${path}`, - cwd: getConfigDir(), - command: "view", - }); + const result = await resolveIssue({ + issueArg: `${baseUrl}${path}`, + cwd: getConfigDir(), + command: "view", + }); - expect(result.org).toBe(org); - expect(result.issue.id).toBe("12345"); - expect(result.issue.shortId).toBe("BACKEND-A1"); - expect(requests).toHaveLength(2); - expect(requests[0]?.headers.has("Authorization")).toBe(false); - expect(requests[1]?.headers.get("Authorization")).toBe("Bearer test-token"); - }); + expect(result.org).toBe(org); + expect(result.issue.id).toBe("12345"); + expect(result.issue.shortId).toBe("BACKEND-A1"); + expect(requests).toHaveLength(2); + expect(requests[0]?.headers.has("Authorization")).toBe(false); + expect(requests[1]?.headers.get("Authorization")).toBe( + "Bearer test-token", + ); + }, + ); test("requires organization context before requesting a legacy share URL", async () => { const requests: string[] = []; @@ -2422,7 +2409,7 @@ describe("resolveIssue with share URLs", () => { issueArg: `https://sentry.io/share/issue/${shareId}/`, cwd: getConfigDir(), command: "view", - }) + }), ).rejects.toBeInstanceOf(ContextError); expect(requests).toEqual([]); }); @@ -2439,7 +2426,7 @@ describe("resolveIssue with share URLs", () => { issueArg: `https://test-org.sentry.io/share/issue/${shareId}/`, cwd: getConfigDir(), command: "view", - }) + }), ).rejects.toMatchObject({ name: "ApiError", message: "Share link not found or expired", diff --git a/packages/cli/test/commands/issue/view.func.test.ts b/packages/cli/test/commands/issue/view.func.test.ts index 0880efa9b..37623da96 100644 --- a/packages/cli/test/commands/issue/view.func.test.ts +++ b/packages/cli/test/commands/issue/view.func.test.ts @@ -13,11 +13,11 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; import { fetchMultipleIssueViews, @@ -32,11 +32,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -46,11 +46,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { ContextError } from "../../../src/lib/errors.js"; import type { SentryEvent, SentryIssue } from "../../../src/types/index.js"; @@ -150,7 +150,7 @@ describe("issue view replay integration", () => { await func.call( context, { json: false, web: false, force: false, spans: 0, fresh: false }, - "CLI-123" + "CLI-123", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); @@ -195,7 +195,7 @@ describe("issue view multiple IDs", () => { shortId: options.issueArg, title: options.issueArg, }), - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -204,7 +204,7 @@ describe("issue view multiple IDs", () => { expect(resolveIssueSpy).toHaveBeenCalledTimes(2); const parsed = JSON.parse( - stdoutWrite.mock.calls.map((call) => call[0]).join("") + stdoutWrite.mock.calls.map((call) => call[0]).join(""), ); expect(Array.isArray(parsed)).toBe(true); expect(parsed).toHaveLength(2); @@ -220,7 +220,7 @@ describe("issue view multiple IDs", () => { id: options.issueArg, shortId: options.issueArg, }), - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -229,7 +229,7 @@ describe("issue view multiple IDs", () => { expect(resolveIssueSpy).toHaveBeenCalledTimes(3); const parsed = JSON.parse( - stdoutWrite.mock.calls.map((call) => call[0]).join("") + stdoutWrite.mock.calls.map((call) => call[0]).join(""), ); expect(parsed.map((row: { shortId: string }) => row.shortId)).toEqual([ "IOS-1", @@ -250,7 +250,7 @@ describe("issue view multiple IDs", () => { expect(resolveIssueSpy).toHaveBeenCalledTimes(1); expect(resolveIssueSpy).toHaveBeenCalledWith( - expect.objectContaining({ issueArg: "IOS-1,IOS-2" }) + expect.objectContaining({ issueArg: "IOS-1,IOS-2" }), ); }); @@ -264,7 +264,7 @@ describe("issue view multiple IDs", () => { org: "test-org", issue: sampleIssue({ shortId: options.issueArg }), }; - } + }, ); const { context, stdoutWrite } = createMockContext(); @@ -272,7 +272,7 @@ describe("issue view multiple IDs", () => { await func.call(context, VIEW_FLAGS, "IOS-1", "MISSING"); const parsed = JSON.parse( - stdoutWrite.mock.calls.map((call) => call[0]).join("") + stdoutWrite.mock.calls.map((call) => call[0]).join(""), ); expect(Array.isArray(parsed)).toBe(true); expect(parsed).toHaveLength(1); @@ -286,7 +286,7 @@ describe("issue view multiple IDs", () => { const { context } = createMockContext(); const func = await viewCommand.loader(); await expect(func.call(context, VIEW_FLAGS, "IOS-1", "IOS-2")).rejects.toBe( - error + error, ); }); @@ -304,7 +304,7 @@ describe("issue view multiple IDs", () => { shortId: options.issueArg, permalink: `https://sentry.io/issues/${options.issueArg}/`, }), - }) + }), ); const { context } = createMockContext(); @@ -313,19 +313,19 @@ describe("issue view multiple IDs", () => { context, { json: false, web: true, force: false, spans: 0, fresh: false }, "IOS-1", - "IOS-2" + "IOS-2", ); expect(resolveIssueSpy).toHaveBeenCalledTimes(2); expect(openInBrowserSpy).toHaveBeenNthCalledWith( 1, "https://sentry.io/issues/IOS-1/", - "issue" + "issue", ); expect(openInBrowserSpy).toHaveBeenNthCalledWith( 2, "https://sentry.io/issues/IOS-2/", - "issue" + "issue", ); }); @@ -334,18 +334,18 @@ describe("issue view multiple IDs", () => { async (options: { issueArg: string }) => ({ org: "test-org", issue: sampleIssue({ shortId: options.issueArg }), - }) + }), ); const issueArgs = Array.from( { length: MAX_WEB_ISSUES + 2 }, - (_, index) => `IOS-${index + 1}` + (_, index) => `IOS-${index + 1}`, ); const func = await viewCommand.loader(); await func.call( createMockContext().context, { json: false, web: true, force: false, spans: 0, fresh: false }, - ...issueArgs + ...issueArgs, ); expect(openInBrowserSpy).toHaveBeenCalledTimes(MAX_WEB_ISSUES); @@ -354,7 +354,7 @@ describe("issue view multiple IDs", () => { await func.call( createMockContext().context, { json: false, web: true, force: true, spans: 0, fresh: false }, - ...issueArgs + ...issueArgs, ); expect(openInBrowserSpy).toHaveBeenCalledTimes(issueArgs.length); }); @@ -378,7 +378,7 @@ describe("fetchMultipleIssueViews", () => { async (options: { issueArg: string }) => ({ org: "test-org", issue: sampleIssue({ shortId: options.issueArg }), - }) + }), ); const result = await fetchMultipleIssueViews({ @@ -401,7 +401,7 @@ describe("fetchMultipleIssueViews", () => { org: "test-org", issue: sampleIssue({ shortId: options.issueArg }), }; - } + }, ); const result = await fetchMultipleIssueViews({ @@ -422,7 +422,7 @@ describe("fetchMultipleIssueViews", () => { issueArgs: ["IOS-1", "IOS-2"], cwd: "/tmp", spans: 0, - }) + }), ).rejects.toBe(error); }); }); diff --git a/packages/cli/test/commands/issue/view.test.ts b/packages/cli/test/commands/issue/view.test.ts index 0596e7dd1..df973f5e4 100644 --- a/packages/cli/test/commands/issue/view.test.ts +++ b/packages/cli/test/commands/issue/view.test.ts @@ -95,7 +95,7 @@ describe("jsonTransformIssueView", () => { event: null, replayIds: [], trace: null, - }) + }), ); }); @@ -131,7 +131,7 @@ describe("jsonTransformIssueView", () => { issues: [sampleView()], requestedCount: 1, }, - ["shortId"] + ["shortId"], ); expect(result).toEqual({ shortId: "CLI-123" }); }); @@ -145,7 +145,7 @@ describe("jsonTransformIssueView", () => { ], requestedCount: 2, }, - ["shortId"] + ["shortId"], ); expect(result).toEqual([{ shortId: "IOS-1" }, { shortId: "IOS-2" }]); }); diff --git a/packages/cli/test/commands/local/run.test.ts b/packages/cli/test/commands/local/run.test.ts index 56001bcdf..9f432ba2f 100644 --- a/packages/cli/test/commands/local/run.test.ts +++ b/packages/cli/test/commands/local/run.test.ts @@ -40,7 +40,7 @@ vi.mock("node:child_process", async (importOriginal) => { spawn: ( cmd: string, args: readonly string[], - options: Parameters<typeof actual.spawn>[2] + options: Parameters<typeof actual.spawn>[2], ) => { spawnCapture.args = args; spawnCapture.env = (options as { env?: NodeJS.ProcessEnv })?.env; @@ -114,7 +114,7 @@ describe("sentry local run", () => { } catch (err) { expect(err).toBeInstanceOf(ValidationError); expect((err as ValidationError).message).toContain( - "No command provided and could not auto-detect" + "No command provided and could not auto-detect", ); } }); @@ -126,7 +126,7 @@ describe("sentry local run", () => { await func.call( ctx, { port: 0, host: "localhost", verify: false, timeout: 0 }, - "--" + "--", ); expect.unreachable("should have thrown"); } catch (err) { @@ -137,7 +137,7 @@ describe("sentry local run", () => { test("auto-detects dev command from package.json", async () => { await writeFile( join(tmpDir, "package.json"), - JSON.stringify({ scripts: { dev: "echo hello" } }) + JSON.stringify({ scripts: { dev: "echo hello" } }), ); const func = (await runCommand.loader()) as unknown as RunFunc; @@ -163,7 +163,7 @@ describe("sentry local run", () => { ctx, { port, host: "127.0.0.1", verify: false, timeout: 0 }, "echo", - "ok" + "ok", ); }); @@ -181,13 +181,13 @@ describe("sentry local run", () => { timeout: 0, open: true, }, - "true" + "true", ); expect(openBrowserMock).toHaveBeenCalledWith( expect.stringMatching( - /^http:\/\/localhost:5173\/#stream=http%3A%2F%2F127\.0\.0\.1%3A\d+%2Fstream$/ - ) + /^http:\/\/localhost:5173\/#stream=http%3A%2F%2F127\.0\.0\.1%3A\d+%2Fstream$/, + ), ); }); @@ -204,8 +204,8 @@ describe("sentry local run", () => { timeout: 0, open: true, }, - "true" - ) + "true", + ), ).rejects.toMatchObject({ field: "open", message: "--open cannot be used with --verify.", @@ -225,8 +225,8 @@ describe("sentry local run", () => { timeout: 0, open: true, }, - "true" - ) + "true", + ), ).rejects.toMatchObject({ field: "host", message: @@ -245,14 +245,14 @@ describe("sentry local run", () => { makeContext(), { port: 0, host: "127.0.0.1", verify: false, timeout: 0 }, fakeWrangler, - "dev" + "dev", ); expect(spawnCapture.args).toEqual([ "dev", "--var", expect.stringMatching( - /^SENTRY_SPOTLIGHT:http:\/\/127\.0\.0\.1:\d+\/stream$/ + /^SENTRY_SPOTLIGHT:http:\/\/127\.0\.0\.1:\d+\/stream$/, ), ]); }); @@ -269,7 +269,7 @@ describe("sentry local run", () => { ctx, { port: 19_878, host: "127.0.0.1", verify: false, timeout: 0 }, "printenv", - "SENTRY_TRACES_SAMPLE_RATE" + "SENTRY_TRACES_SAMPLE_RATE", ); } finally { if (originalRate === undefined) { @@ -289,7 +289,7 @@ describe("sentry local run", () => { await func.call( ctx, { port, host: "127.0.0.1", verify: false, timeout: 0 }, - "false" + "false", ); expect.unreachable("should have thrown"); } catch (err) { @@ -308,7 +308,7 @@ describe("sentry local run", () => { ctx, { port: 0, host: "127.0.0.1", verify: false, timeout: 1 }, "sleep", - "60" + "60", ); expect.unreachable("should have thrown"); } catch (err) { @@ -326,13 +326,13 @@ describe("sentry local run", () => { await func.call( ctx, { port: 0, host: "127.0.0.1", verify: true, timeout: 0 }, - "true" + "true", ); expect.unreachable("should have thrown"); } catch (err) { expect(err).toBeInstanceOf(CliError); expect((err as CliError).message).toContain( - "Process exited before sending any events" + "Process exited before sending any events", ); expect((err as CliError).exitCode).toBe(64); } @@ -347,7 +347,7 @@ describe("sentry local run", () => { ctx, { port: 0, host: "127.0.0.1", verify: true, timeout: 1 }, "sleep", - "60" + "60", ); expect.unreachable("should have thrown"); } catch (err) { @@ -365,13 +365,13 @@ describe("sentry local run", () => { await func.call( ctx, { port: 19_879, host: "127.0.0.1", verify: false, timeout: 0 }, - "nonexistent-command-that-does-not-exist" + "nonexistent-command-that-does-not-exist", ); expect.unreachable("should have thrown"); } catch (err) { expect(err).toBeInstanceOf(Error); expect((err as Error).message).toMatch( - /exited with code|Failed to start|ENOENT|spawn/i + /exited with code|Failed to start|ENOENT|spawn/i, ); } }); @@ -385,7 +385,7 @@ describe("sentry local run", () => { ctx, { port: 19_880, host: "127.0.0.1", verify: false, timeout: 0 }, "--", - "true" + "true", ); }); @@ -427,7 +427,7 @@ describe("sentry local run", () => { makeContext(), { port, host: "127.0.0.1", verify: false, timeout: 0 }, "sleep", - "1" + "1", ); } finally { spy.mockRestore(); @@ -482,7 +482,7 @@ describe("sentry local run", () => { filter: ["error"], }, "sleep", - "1" + "1", ); } finally { stdoutSpy.mockRestore(); @@ -517,7 +517,7 @@ describe("sentry local run", () => { format: "json", attributes: false, }, - "true" + "true", ); expect(spawnCapture.stdio).toEqual(["inherit", "pipe", "pipe"]); @@ -554,7 +554,7 @@ describe("sentry local run", () => { makeContext(), { port, host: "127.0.0.1", verify: false, timeout: 0 }, "sleep", - "1" + "1", ); } finally { spy.mockRestore(); diff --git a/packages/cli/test/commands/local/server.test.ts b/packages/cli/test/commands/local/server.test.ts index 491f774a4..a0ed06db2 100644 --- a/packages/cli/test/commands/local/server.test.ts +++ b/packages/cli/test/commands/local/server.test.ts @@ -35,7 +35,7 @@ type ServeFunc = ( format: "human" | "json"; attributes: boolean; open: boolean; - } + }, ) => Promise<void>; function interceptShutdownSignal() { @@ -44,7 +44,7 @@ function interceptShutdownSignal() { const originalOnce = process.once.bind(process); const onSpy = vi.spyOn(process, "on").mockImplementation((( event: string, - listener: () => void + listener: () => void, ) => { if (event === "SIGINT") { handler = listener; @@ -54,7 +54,7 @@ function interceptShutdownSignal() { }) as typeof process.on); const onceSpy = vi.spyOn(process, "once").mockImplementation((( event: string, - listener: () => void + listener: () => void, ) => { if (event === "SIGINT") { handler = listener; @@ -128,8 +128,8 @@ describe("sentry local serve --open", () => { expect(openBrowserMock).toHaveBeenCalledWith( expect.stringMatching( - /^http:\/\/localhost:5173\/#stream=http%3A%2F%2F127\.0\.0\.1%3A\d+%2Fstream$/ - ) + /^http:\/\/localhost:5173\/#stream=http%3A%2F%2F127\.0\.0\.1%3A\d+%2Fstream$/, + ), ); }); @@ -147,7 +147,7 @@ describe("sentry local serve --open", () => { const { server, port } = await tryListen( buildApp(createSpotlightBuffer(10)), 0, - "127.0.0.1" + "127.0.0.1", ); const func = (await serverCommand.loader()) as unknown as ServeFunc; @@ -163,7 +163,7 @@ describe("sentry local serve --open", () => { } expect(openBrowserMock).toHaveBeenCalledWith( - `http://localhost:5173/#stream=http%3A%2F%2F127.0.0.1%3A${port}%2Fstream` + `http://localhost:5173/#stream=http%3A%2F%2F127.0.0.1%3A${port}%2Fstream`, ); }); }); @@ -179,7 +179,7 @@ describe("feedSSELine", () => { feedSSELine( "event: application/x-sentry-envelope", state, - (type, data, id) => events.push({ type, data, id }) + (type, data, id) => events.push({ type, data, id }), ); expect(state.eventType).toBe("application/x-sentry-envelope"); expect(events).toHaveLength(0); @@ -189,7 +189,7 @@ describe("feedSSELine", () => { const state = makeState(); const events: Array<{ type: string; data: string; id: string }> = []; feedSSELine("data: hello", state, (type, data, id) => - events.push({ type, data, id }) + events.push({ type, data, id }), ); expect(state.dataLines).toEqual(["hello"]); expect(events).toHaveLength(0); @@ -199,7 +199,7 @@ describe("feedSSELine", () => { const state = makeState(); const events: Array<{ type: string; data: string; id: string }> = []; feedSSELine("id: abc-123", state, (type, data, id) => - events.push({ type, data, id }) + events.push({ type, data, id }), ); expect(state.id).toBe("abc-123"); }); @@ -412,7 +412,7 @@ describe("buildApp", () => { headers: { Origin: "http://localhost:3000" }, }); expect(res.headers.get("access-control-allow-origin")).toBe( - "http://localhost:3000" + "http://localhost:3000", ); }); @@ -424,7 +424,7 @@ describe("buildApp", () => { headers: { Origin: "https://local.sentry.dev" }, }); expect(res.headers.get("access-control-allow-origin")).toBe( - "https://local.sentry.dev" + "https://local.sentry.dev", ); if (res.body) { await res.body.cancel(); @@ -489,13 +489,13 @@ describe("buildApp", () => { }, }); expect(res.headers.get("access-control-allow-headers")).toContain( - "Last-Event-ID" + "Last-Event-ID", ); expect(res.headers.get("access-control-allow-private-network")).toBe( - "true" + "true", ); expect(res.headers.get("vary")).toContain( - "Access-Control-Request-Private-Network" + "Access-Control-Request-Private-Network", ); }); @@ -530,7 +530,7 @@ describe("buildApp", () => { expect((await app.request("/capabilities")).status).toBe(403); expect((await app.request("/clear", { method: "DELETE" })).status).toBe( - 403 + 403, ); }); @@ -658,7 +658,7 @@ describe("isServerRunning", () => { try { await expect(isServerRunning(`http://127.0.0.1:${port}`)).resolves.toBe( - true + true, ); } finally { globalThis.fetch = savedFetch; diff --git a/packages/cli/test/commands/local/ui.test.ts b/packages/cli/test/commands/local/ui.test.ts index 6c41a1983..b59575e52 100644 --- a/packages/cli/test/commands/local/ui.test.ts +++ b/packages/cli/test/commands/local/ui.test.ts @@ -25,19 +25,19 @@ describe("local UI URLs", () => { test("formats IPv6 loopback URLs correctly", () => { expect(formatLocalServerUrl("::1", 8969)).toBe("http://[::1]:8969"); expect(buildLocalUiUrl("http://[::1]:8969")).toBe( - "http://localhost:5173/#stream=http%3A%2F%2F%5B%3A%3A1%5D%3A8969%2Fstream" + "http://localhost:5173/#stream=http%3A%2F%2F%5B%3A%3A1%5D%3A8969%2Fstream", ); }); test("refuses to send the browser to a non-loopback receiver", () => { expect(() => buildLocalUiUrl("http://example.com:8969")).toThrow( - "--open requires a loopback --host" + "--open requires a loopback --host", ); }); test("validates an --open host before a receiver is started", () => { expect(() => assertLoopbackHostForUi("0.0.0.0")).toThrow( - "--open requires a loopback --host" + "--open requires a loopback --host", ); }); diff --git a/packages/cli/test/commands/log/list.test.ts b/packages/cli/test/commands/log/list.test.ts index 258960389..edafd7370 100644 --- a/packages/cli/test/commands/log/list.test.ts +++ b/packages/cli/test/commands/log/list.test.ts @@ -29,11 +29,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { @@ -43,11 +43,11 @@ vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbAuth from "../../../src/lib/db/auth.js"; import { ApiError, @@ -65,11 +65,11 @@ vi.mock("../../../src/lib/formatters/index.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as formatters from "../../../src/lib/formatters/index.js"; vi.mock("../../../src/lib/polling.js", async (importOriginal) => { @@ -79,11 +79,11 @@ vi.mock("../../../src/lib/polling.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as polling from "../../../src/lib/polling.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -93,11 +93,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { parsePeriod } from "../../../src/lib/time-range.js"; @@ -108,11 +108,11 @@ vi.mock("../../../src/lib/trace-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as traceTarget from "../../../src/lib/trace-target.js"; vi.mock("../../../src/lib/version-check.js", async (importOriginal) => { @@ -122,11 +122,11 @@ vi.mock("../../../src/lib/version-check.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as versionCheck from "../../../src/lib/version-check.js"; import type { SentryLog, TraceLog } from "../../../src/types/sentry.js"; @@ -151,7 +151,7 @@ function interceptSigint() { const originalOnce = process.once.bind(process); const spy = vi.spyOn(process, "once").mockImplementation((( event: string, - fn: (...args: unknown[]) => void + fn: (...args: unknown[]) => void, ) => { if (event === "SIGINT") { handler = fn; @@ -164,7 +164,7 @@ function interceptSigint() { const originalRemoveListener = process.removeListener.bind(process); const removeSpy = vi.spyOn(process, "removeListener").mockImplementation((( event: string, - fn: (...args: unknown[]) => void + fn: (...args: unknown[]) => void, ) => { if (event === "SIGINT" && fn === handler) { handler = null; @@ -213,7 +213,7 @@ function noop() { /** Passthrough mock for `withProgress` — bypasses spinner, calls fn directly */ function mockWithProgress( _opts: unknown, - fn: (setMessage: () => void) => unknown + fn: (setMessage: () => void) => unknown, ) { return fn(noop); } @@ -350,7 +350,7 @@ describe("listCommand.func — standard mode", () => { listLogsSpy = vi.spyOn(apiClient, "listLogs"); resolveOrgProjectSpy = vi.spyOn( resolveTarget, - "resolveProjectBoundFromArg" + "resolveProjectBoundFromArg", ); vi.spyOn(resolveTarget, "resolveLogProjectId").mockResolvedValue(undefined); withProgressSpy = vi @@ -406,14 +406,14 @@ describe("listCommand.func — standard mode", () => { await func.call( context, { ...BATCH_FLAGS, sort: "oldest" }, - `${ORG}/${PROJECT}` + `${ORG}/${PROJECT}`, ); // sort=oldest is passed to API for server-side sorting expect(listLogsSpy).toHaveBeenCalledWith( ORG, PROJECT, - expect.objectContaining({ sort: "oldest" }) + expect.objectContaining({ sort: "oldest" }), ); }); @@ -489,7 +489,7 @@ describe("listCommand.func — standard mode", () => { await func.call( context, { json: false, limit: 50, query: "severity:error", sort: "newest" }, - `${ORG}/${PROJECT}` + `${ORG}/${PROJECT}`, ); expect(listLogsSpy).toHaveBeenCalledWith(ORG, PROJECT, { @@ -502,7 +502,7 @@ describe("listCommand.func — standard mode", () => { test("converts a search-query parse 400 to a ValidationError when --query is set (project single-fetch)", async () => { listLogsSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad field") + new ApiError("bad", 400, "Error parsing search query: bad field"), ); resolveOrgProjectSpy.mockResolvedValue({ org: ORG, project: PROJECT }); @@ -512,14 +512,14 @@ describe("listCommand.func — standard mode", () => { func.call( context, { json: false, limit: 50, query: "bad:::query", sort: "newest" }, - `${ORG}/${PROJECT}` - ) + `${ORG}/${PROJECT}`, + ), ).rejects.toBeInstanceOf(ValidationError); }); test("keeps a search-query parse 400 as a reported ApiError with no --query (project single-fetch)", async () => { listLogsSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad field") + new ApiError("bad", 400, "Error parsing search query: bad field"), ); resolveOrgProjectSpy.mockResolvedValue({ org: ORG, project: PROJECT }); @@ -529,8 +529,8 @@ describe("listCommand.func — standard mode", () => { func.call( context, { json: false, limit: 50, sort: "newest" }, - `${ORG}/${PROJECT}` - ) + `${ORG}/${PROJECT}`, + ), ).rejects.toBeInstanceOf(ApiError); }); @@ -609,27 +609,27 @@ describe("listCommand.func — trace mode", () => { // executeTraceSingleFetch must wrap listTraceLogs with toSearchQueryError // just like executeSingleFetch does for the project path. listTraceLogsSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad field") + new ApiError("bad", 400, "Error parsing search query: bad field"), ); resolveTraceOrgSpy.mockResolvedValue({ traceId: TRACE_ID, org: ORG }); const { context } = createMockContext(); const func = await listCommand.loader(); await expect( - func.call(context, { ...BATCH_FLAGS, query: "bad:::query" }, TRACE_ID) + func.call(context, { ...BATCH_FLAGS, query: "bad:::query" }, TRACE_ID), ).rejects.toBeInstanceOf(ValidationError); }); test("keeps a search-query parse 400 as a reported ApiError with no --query (trace single-fetch)", async () => { listTraceLogsSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad field") + new ApiError("bad", 400, "Error parsing search query: bad field"), ); resolveTraceOrgSpy.mockResolvedValue({ traceId: TRACE_ID, org: ORG }); const { context } = createMockContext(); const func = await listCommand.loader(); await expect( - func.call(context, BATCH_FLAGS, TRACE_ID) + func.call(context, BATCH_FLAGS, TRACE_ID), ).rejects.toBeInstanceOf(ApiError); }); @@ -710,7 +710,7 @@ describe("listCommand.func — trace mode", () => { await func.call( context, { json: false, limit: 50, query: "severity:error", sort: "newest" }, - TRACE_ID + TRACE_ID, ); expect(listTraceLogsSpy).toHaveBeenCalledWith(ORG, TRACE_ID, { @@ -737,7 +737,7 @@ describe("listCommand.func — trace mode", () => { traceId: TRACE_ID, }), "/tmp", - expect.any(String) + expect.any(String), ); }); }); @@ -863,7 +863,7 @@ describe("listCommand.func — period flag", () => { await func.call( context, { json: true, limit: 100, sort: "newest" }, - TRACE_ID + TRACE_ID, ); expect(listTraceLogsSpy).toHaveBeenCalledWith(ORG, TRACE_ID, { @@ -880,7 +880,7 @@ describe("listCommand.func — period flag", () => { await func.call( context, { json: true, limit: 100, period: parsePeriod("30d"), sort: "newest" }, - TRACE_ID + TRACE_ID, ); expect(listTraceLogsSpy).toHaveBeenCalledWith(ORG, TRACE_ID, { @@ -919,20 +919,20 @@ describe("listCommand.func — trace mode org resolution failure", () => { test("throws ContextError when org cannot be resolved", async () => { resolveTraceOrgSpy.mockRejectedValue( - new ContextError("Organization", "sentry log list [<org>/]<trace-id>") + new ContextError("Organization", "sentry log list [<org>/]<trace-id>"), ); const { context } = createMockContext(); const func = await listCommand.loader(); await expect(func.call(context, HUMAN_FLAGS, TRACE_ID)).rejects.toThrow( - ContextError + ContextError, ); }); test("ContextError mentions Organization", async () => { resolveTraceOrgSpy.mockRejectedValue( - new ContextError("Organization", "sentry log list [<org>/]<trace-id>") + new ContextError("Organization", "sentry log list [<org>/]<trace-id>"), ); const { context } = createMockContext(); @@ -965,7 +965,7 @@ describe("listCommand.func — trace mode org resolution failure", () => { * Handles both string and Buffer arguments from consola/logger. */ function collectProcessStderr( - spy: ReturnType<typeof spyOn<typeof process.stderr, "write">> + spy: ReturnType<typeof spyOn<typeof process.stderr, "write">>, ): string { return spy.mock.calls .map((c) => { @@ -993,8 +993,8 @@ describe("listCommand.func — flag validation", () => { func.call( context, { json: false, limit: 100, follow: 2, sort: "oldest" }, - "my-org/my-project" - ) + "my-org/my-project", + ), ).rejects.toThrow(ValidationError); }); @@ -1005,7 +1005,7 @@ describe("listCommand.func — flag validation", () => { const resolveOrgProjectSpy = vi .spyOn(resolveTarget, "resolveProjectBoundFromArg") .mockRejectedValueOnce( - new ContextError("Organization", "sentry log list") + new ContextError("Organization", "sentry log list"), ); const { context } = createMockContext(); const func = await listCommand.loader(); @@ -1013,8 +1013,8 @@ describe("listCommand.func — flag validation", () => { func.call( context, { json: false, limit: 100, follow: 2, sort: "newest" }, - "my-org/my-project" - ) + "my-org/my-project", + ), ).rejects.not.toThrow(ValidationError); resolveOrgProjectSpy.mockRestore(); }); @@ -1037,7 +1037,7 @@ describe("listCommand.func — follow mode (standard)", () => { listLogsSpy = vi.spyOn(apiClient, "listLogs"); resolveOrgProjectSpy = vi.spyOn( resolveTarget, - "resolveProjectBoundFromArg" + "resolveProjectBoundFromArg", ); vi.spyOn(resolveTarget, "resolveLogProjectId").mockResolvedValue(undefined); isPlainSpy = vi.spyOn(formatters, "isPlainOutput").mockReturnValue(true); @@ -1087,7 +1087,7 @@ describe("listCommand.func — follow mode (standard)", () => { // a bad user --query surfaces as a ValidationError on the initial fetch and // terminates the stream instead of being reported as a CLI bug. listLogsSpy.mockRejectedValueOnce( - new ApiError("bad", 400, "Error parsing search query: bad field") + new ApiError("bad", 400, "Error parsing search query: bad field"), ); resolveOrgProjectSpy.mockResolvedValue({ org: ORG, project: PROJECT }); @@ -1098,8 +1098,8 @@ describe("listCommand.func — follow mode (standard)", () => { func.call( context, { ...followFlags, query: "bad:::query" }, - `${ORG}/${PROJECT}` - ) + `${ORG}/${PROJECT}`, + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -1109,7 +1109,7 @@ describe("listCommand.func — follow mode (standard)", () => { listLogsSpy.mockReturnValueOnce( new Promise((resolve) => { resolveFetch = resolve; - }) + }), ); resolveOrgProjectSpy.mockResolvedValue({ org: ORG, project: PROJECT }); @@ -1162,7 +1162,7 @@ describe("listCommand.func — follow mode (standard)", () => { const promise = func.call( context, { ...followFlags, json: true }, - `${ORG}/${PROJECT}` + `${ORG}/${PROJECT}`, ); await sleep(50); sigint.trigger(); @@ -1205,7 +1205,7 @@ describe("listCommand.func — follow mode (standard)", () => { const promise = func.call( context, { ...followFlags, json: true }, - `${ORG}/${PROJECT}` + `${ORG}/${PROJECT}`, ); await sleep(50); sigint.trigger(); @@ -1232,7 +1232,7 @@ describe("listCommand.func — follow mode (standard)", () => { const func = await listCommand.loader(); await expect( - func.call(context, followFlags, `${ORG}/${PROJECT}`) + func.call(context, followFlags, `${ORG}/${PROJECT}`), ).rejects.toThrow(AuthError); }); @@ -1246,7 +1246,7 @@ describe("listCommand.func — follow mode (standard)", () => { const func = await listCommand.loader(); await expect( - func.call(context, followFlags, `${ORG}/${PROJECT}`) + func.call(context, followFlags, `${ORG}/${PROJECT}`), ).rejects.toThrow(AuthError); }); @@ -1453,7 +1453,7 @@ describe("listCommand.func — follow mode (trace)", () => { const promise = func.call( context, { ...traceFollowFlags, json: true }, - TRACE_ID + TRACE_ID, ); // Wait for initial fetch + poll timer (1s) + poll execution await sleep(1200); @@ -1492,7 +1492,7 @@ describe("listCommand.func — follow mode (trace)", () => { const func = await listCommand.loader(); await expect( - func.call(context, traceFollowFlags, TRACE_ID) + func.call(context, traceFollowFlags, TRACE_ID), ).rejects.toThrow(AuthError); }); diff --git a/packages/cli/test/commands/log/view.func.test.ts b/packages/cli/test/commands/log/view.func.test.ts index 9609b14d1..8283f48d7 100644 --- a/packages/cli/test/commands/log/view.func.test.ts +++ b/packages/cli/test/commands/log/view.func.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -30,11 +30,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { ContextError, ResolutionError } from "../../../src/lib/errors.js"; @@ -45,11 +45,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { DetailedSentryLog } from "../../../src/types/sentry.js"; @@ -110,7 +110,7 @@ describe("viewCommand.func", () => { resolveOrgAndProjectSpy = vi.spyOn(resolveTarget, "resolveOrgAndProject"); resolveProjectBoundSlugSpy = vi.spyOn( resolveTarget, - "resolveProjectBoundSlug" + "resolveProjectBoundSlug", ); vi.spyOn(resolveTarget, "resolveLogProjectId").mockResolvedValue(1234); openInBrowserSpy = vi.spyOn(browser, "openInBrowser"); @@ -162,14 +162,14 @@ describe("viewCommand.func", () => { context, { json: false, web: false }, "my-org/proj", - ID1 + ID1, ); expect.unreachable("Should have thrown"); } catch (error) { expect(error).toBeInstanceOf(ResolutionError); expect((error as ResolutionError).message).toContain(ID1); expect((error as ResolutionError).message).toContain( - "not found in my-org/proj" + "not found in my-org/proj", ); } }); @@ -187,7 +187,7 @@ describe("viewCommand.func", () => { { json: true, web: false }, "my-org/proj", ID1, - ID2 + ID2, ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -209,7 +209,7 @@ describe("viewCommand.func", () => { { json: false, web: false }, "my-org/proj", ID1, - ID2 + ID2, ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -229,7 +229,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false }, "my-org/proj", - combined + combined, ); // getLogs should have been called with both IDs @@ -255,7 +255,7 @@ describe("viewCommand.func", () => { "my-org/proj", ID1, ID2, - ID3 + ID3, ); // Should still output the found log as JSON @@ -278,7 +278,7 @@ describe("viewCommand.func", () => { { json: false, web: false }, "my-org/proj", ID1, - ID2 + ID2, ); expect.unreachable("Should have thrown"); } catch (error) { @@ -317,7 +317,7 @@ describe("viewCommand.func", () => { { json: false, web: true }, "my-org/proj", ID1, - ID2 + ID2, ); // Non-interactive (no TTY in tests) — should warn and not open any tabs @@ -342,7 +342,7 @@ describe("viewCommand.func", () => { "resolved-org", "resolved-proj", [ID1], - { extraFields: undefined, projectId: 1234 } + { extraFields: undefined, projectId: 1234 }, ); }); @@ -377,7 +377,7 @@ describe("viewCommand.func", () => { "detected-org", "detected-proj", [ID1], - { extraFields: undefined, projectId: 1234 } + { extraFields: undefined, projectId: 1234 }, ); // Human output should include the detected-from hint @@ -397,7 +397,7 @@ describe("viewCommand.func", () => { } catch (error) { expect(error).toBeInstanceOf(ContextError); expect((error as ContextError).message).toContain( - "organization and project" + "organization and project", ); } }); @@ -416,7 +416,7 @@ describe("viewCommand.func", () => { "my-org", "proj", ID1, - log.trace + log.trace, ); }); diff --git a/packages/cli/test/commands/log/view.property.test.ts b/packages/cli/test/commands/log/view.property.test.ts index fe79f95fd..51282efb4 100644 --- a/packages/cli/test/commands/log/view.property.test.ts +++ b/packages/cli/test/commands/log/view.property.test.ts @@ -31,7 +31,7 @@ describe("parsePositionalArgs properties", () => { expect(result.rawLogIds).toEqual([input]); expect(result.targetArg).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -43,7 +43,7 @@ describe("parsePositionalArgs properties", () => { expect(result.targetArg).toBe(`${org}/${project}`); expect(result.rawLogIds).toEqual([logId]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -51,10 +51,10 @@ describe("parsePositionalArgs properties", () => { await fcAssert( property(tuple(slugArb, slugArb), ([org, project]) => { expect(() => parsePositionalArgs([`${org}/${project}`])).toThrow( - ContextError + ContextError, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -65,7 +65,7 @@ describe("parsePositionalArgs properties", () => { expect(result.targetArg).toBe(slug); expect(result.rawLogIds).toEqual([logId]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -78,7 +78,7 @@ describe("parsePositionalArgs properties", () => { expect(result.targetArg).toBe(target); expect(result.rawLogIds).toEqual([logId]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -92,9 +92,9 @@ describe("parsePositionalArgs properties", () => { expect(result.targetArg).toBe(slug); expect(result.rawLogIds).toEqual(ids); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -108,9 +108,9 @@ describe("parsePositionalArgs properties", () => { expect(result.targetArg).toBe(slug); expect(result.rawLogIds).toEqual(ids); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -121,7 +121,7 @@ describe("parsePositionalArgs properties", () => { const result2 = parsePositionalArgs([slug, logId]); expect(result1).toEqual(result2); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -138,7 +138,7 @@ describe("parsePositionalArgs properties", () => { expect(typeof id).toBe("string"); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -149,7 +149,7 @@ describe("parsePositionalArgs properties", () => { const result = parsePositionalArgs([input]); expect(result.targetArg).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); // Two+ args case @@ -159,7 +159,7 @@ describe("parsePositionalArgs properties", () => { expect(result.targetArg).toBeDefined(); expect(typeof result.targetArg).toBe("string"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/commands/log/view.test.ts b/packages/cli/test/commands/log/view.test.ts index c4570227e..46db6e8ae 100644 --- a/packages/cli/test/commands/log/view.test.ts +++ b/packages/cli/test/commands/log/view.test.ts @@ -75,9 +75,8 @@ vi.mock("../../../src/lib/logger.js", () => ({ // Dynamic import: must load AFTER vi.mock() registrations above so the // `log = logger.withTag(...)` binding inside view.ts picks up fakeLog. -const { parsePositionalArgs, viewCommand } = await import( - "../../../src/commands/log/view.js" -); +const { parsePositionalArgs, viewCommand } = + await import("../../../src/commands/log/view.js"); import type { ProjectWithOrg } from "../../../src/lib/api-client.js"; @@ -88,11 +87,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -102,11 +101,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; @@ -115,7 +114,7 @@ import { ResolutionError, ValidationError, } from "../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { resolveProjectBoundSlug } from "../../../src/lib/resolve-target.js"; import type { DetailedSentryLog } from "../../../src/types/index.js"; @@ -234,7 +233,7 @@ describe("parsePositionalArgs", () => { test("throws ContextError for empty log ID after target", () => { expect(() => parsePositionalArgs(["my-org/frontend", ""])).toThrow( - ContextError + ContextError, ); }); }); @@ -332,7 +331,7 @@ describe("resolveProjectBoundSlug", () => { findProjectsBySlugSpy.mockResolvedValue({ projects: [], orgs: [] }); await expect(resolveProjectBoundSlug("my-project", HINT)).rejects.toThrow( - ResolutionError + ResolutionError, ); }); @@ -345,10 +344,10 @@ describe("resolveProjectBoundSlug", () => { } catch (error) { expect(error).toBeInstanceOf(ResolutionError); expect((error as ResolutionError).message).toContain( - 'Project "frontend"' + 'Project "frontend"', ); expect((error as ResolutionError).message).toContain( - "Check that you have access" + "Check that you have access", ); // Message says "not found", not "is required" expect((error as ResolutionError).message).toContain("not found"); @@ -367,7 +366,7 @@ describe("resolveProjectBoundSlug", () => { }); await expect(resolveProjectBoundSlug("frontend", HINT)).rejects.toThrow( - ValidationError + ValidationError, ); }); @@ -384,7 +383,7 @@ describe("resolveProjectBoundSlug", () => { await resolveProjectBoundSlug( "frontend", HINT, - "sentry log view <org>/frontend log-456" + "sentry log view <org>/frontend log-456", ); expect.unreachable("Should have thrown"); } catch (error) { @@ -411,7 +410,7 @@ describe("resolveProjectBoundSlug", () => { await resolveProjectBoundSlug( "api", HINT, - "sentry log view <org>/api abc123" + "sentry log view <org>/api abc123", ); expect.unreachable("Should have thrown"); } catch (error) { @@ -537,7 +536,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false }, "968c763c740cfda8b6728f27fb9e9b01", - "test-org/test-proj" + "test-org/test-proj", ); // Should resolve correctly despite swapped args @@ -561,7 +560,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false }, "frontend", - "968c763c740cfda8b6728f27fb9e9b01" + "968c763c740cfda8b6728f27fb9e9b01", ); expect(findProjectsBySlugSpy).toHaveBeenCalledWith("frontend"); @@ -584,7 +583,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false }, "CAM-82X", - "968c763c740cfda8b6728f27fb9e9b01" + "968c763c740cfda8b6728f27fb9e9b01", ); // The suggestion path fires (looksLikeIssueShortId("CAM-82X") → true) @@ -608,7 +607,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false }, "test-org/test-project", - expiredLogId + expiredLogId, ); expect.unreachable("Should have thrown"); } catch (err) { @@ -667,7 +666,7 @@ describe("log view --web interactive prompt", () => { { json: false, web: true }, "my-org/proj", PROMPT_ID1, - PROMPT_ID2 + PROMPT_ID2, ); expect(mockPrompt).toHaveBeenCalled(); @@ -689,7 +688,7 @@ describe("log view --web interactive prompt", () => { { json: false, web: true }, "my-org/proj", PROMPT_ID1, - PROMPT_ID2 + PROMPT_ID2, ); expect(mockPrompt).toHaveBeenCalled(); @@ -710,7 +709,7 @@ describe("log view --web interactive prompt", () => { { json: false, web: true }, "my-org/proj", PROMPT_ID1, - PROMPT_ID2 + PROMPT_ID2, ); expect(mockPrompt).toHaveBeenCalled(); diff --git a/packages/cli/test/commands/monitor/list.test.ts b/packages/cli/test/commands/monitor/list.test.ts index c487e65d6..d64423b67 100644 --- a/packages/cli/test/commands/monitor/list.test.ts +++ b/packages/cli/test/commands/monitor/list.test.ts @@ -24,13 +24,13 @@ describe("formatSchedule", () => { test("renders a crontab schedule verbatim", () => { expect(formatSchedule(monitor({ schedule: "0 * * * *" }))).toBe( - "0 * * * *" + "0 * * * *", ); }); test("renders a well-formed interval schedule", () => { expect(formatSchedule(monitor({ schedule: [1, "hour"] }))).toBe( - "every 1 hour" + "every 1 hour", ); }); diff --git a/packages/cli/test/commands/monitor/run.test.ts b/packages/cli/test/commands/monitor/run.test.ts index 0e76fc232..72967bfe5 100644 --- a/packages/cli/test/commands/monitor/run.test.ts +++ b/packages/cli/test/commands/monitor/run.test.ts @@ -8,7 +8,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { runCommand } from "../../../src/commands/monitor/run.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn import * as transport from "../../../src/lib/envelope/transport.js"; import { CliError, ValidationError } from "../../../src/lib/errors.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -52,7 +52,7 @@ describe("monitor runCommand.func()", () => { "my-job", NODE, "-e", - "process.exit(0)" + "process.exit(0)", ); expect(sendSpy).toHaveBeenCalledTimes(2); @@ -75,8 +75,8 @@ describe("monitor runCommand.func()", () => { "my-job", NODE, "-e", - "process.exit(3)" - ) + "process.exit(3)", + ), ).rejects.toMatchObject({ exitCode: 3 }); expect(sendSpy).toHaveBeenCalledTimes(2); @@ -93,7 +93,7 @@ describe("monitor runCommand.func()", () => { "env-check-job", NODE, "-e", - "process.exit(process.env.SENTRY_MONITOR_SLUG === 'env-check-job' ? 0 : 1)" + "process.exit(process.env.SENTRY_MONITOR_SLUG === 'env-check-job' ? 0 : 1)", ); // No throw => exit 0 => env var was present and correct. const closeBody = sendSpy.mock.calls[1]?.[1] as string; @@ -111,8 +111,8 @@ describe("monitor runCommand.func()", () => { "my-job", NODE, "-e", - "process.exit(0)" - ) + "process.exit(0)", + ), ).resolves.toBeUndefined(); expect(sendSpy).toHaveBeenCalledTimes(2); }); @@ -130,7 +130,7 @@ describe("monitor runCommand.func()", () => { "scheduled-job", NODE, "-e", - "process.exit(0)" + "process.exit(0)", ); const openBody = sendSpy.mock.calls[0]?.[1] as string; @@ -143,14 +143,14 @@ describe("monitor runCommand.func()", () => { test("missing command throws ValidationError", async () => { const { ctx } = makeContext(); await expect( - func.call(ctx, { dsn: SAAS_DSN, environment: "production" }, "my-job") + func.call(ctx, { dsn: SAAS_DSN, environment: "production" }, "my-job"), ).rejects.toBeInstanceOf(ValidationError); }); test("missing monitor slug throws ValidationError", async () => { const { ctx } = makeContext(); await expect( - func.call(ctx, { dsn: SAAS_DSN, environment: "production" }) + func.call(ctx, { dsn: SAAS_DSN, environment: "production" }), ).rejects.toBeInstanceOf(ValidationError); }); @@ -163,8 +163,8 @@ describe("monitor runCommand.func()", () => { "my-job", NODE, "-e", - "process.exit(0)" - ) + "process.exit(0)", + ), ).rejects.toBeInstanceOf(ValidationError); // No check-ins sent because validation fails before any send. expect(sendSpy).not.toHaveBeenCalled(); @@ -177,8 +177,8 @@ describe("monitor runCommand.func()", () => { ctx, { dsn: SAAS_DSN, environment: "production" }, "my-job", - "this-binary-does-not-exist-xyz" - ) + "this-binary-does-not-exist-xyz", + ), ).rejects.toBeInstanceOf(CliError); // Both check-ins are still sent; the close one reports an error. expect(sendSpy).toHaveBeenCalledTimes(2); diff --git a/packages/cli/test/commands/proguard/upload.test.ts b/packages/cli/test/commands/proguard/upload.test.ts index 2d367e5a5..54eba9c63 100644 --- a/packages/cli/test/commands/proguard/upload.test.ts +++ b/packages/cli/test/commands/proguard/upload.test.ts @@ -12,7 +12,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { uploadCommand } from "../../../src/commands/proguard/upload.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as proguardApi from "../../../src/lib/api/proguard.js"; import { ContextError, ValidationError } from "../../../src/lib/errors.js"; @@ -99,7 +99,7 @@ describe("sentry proguard upload", () => { ctx, { uuid: "5db7294d-87fc-5726-a5c0-4a90679657a5" }, f1, - f2 + f2, ); expect.unreachable("should have thrown"); } catch (err) { @@ -168,7 +168,7 @@ describe("sentry proguard upload", () => { const ctx = makeContext(); // Should succeed without org/project set await expect( - func.call(ctx, { "no-upload": true }, f) + func.call(ctx, { "no-upload": true }, f), ).resolves.toBeUndefined(); }); @@ -191,7 +191,7 @@ describe("sentry proguard upload", () => { expect(callArgs?.project).toBe("test-project"); expect(callArgs?.mappings).toHaveLength(1); expect(callArgs?.mappings[0]?.uuid).toBe( - "5db7294d-87fc-5726-a5c0-4a90679657a5" + "5db7294d-87fc-5726-a5c0-4a90679657a5", ); expect(callArgs?.mappings[0]?.content).toBeInstanceOf(Buffer); } finally { diff --git a/packages/cli/test/commands/project/create.test.ts b/packages/cli/test/commands/project/create.test.ts index 43b44b6cf..0c0a68d10 100644 --- a/packages/cli/test/commands/project/create.test.ts +++ b/packages/cli/test/commands/project/create.test.ts @@ -21,11 +21,11 @@ vi.mock("../../../src/lib/api/teams.js"); vi.mock("../../../src/lib/api/organizations.js"); vi.mock("../../../src/lib/resolve-target.js"); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.spyOn mocking import * as orgsApi from "../../../src/lib/api/organizations.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for vi.spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.spyOn mocking import * as projectsApi from "../../../src/lib/api/projects.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for vi.spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.spyOn mocking import * as teamsApi from "../../../src/lib/api/teams.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; @@ -38,7 +38,7 @@ import { ValidationError, } from "../../../src/lib/errors.js"; import type { ProjectCreatedResult } from "../../../src/lib/formatters/human.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for vi.spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { slugify } from "../../../src/lib/utils.js"; import type { SentryProject, SentryTeam } from "../../../src/types/index.js"; @@ -109,7 +109,7 @@ describe("project create", () => { // doesn't internally call createProject, so we assert on this spy. const createProjectWithDsnSpy = vi.mocked(projectsApi.createProjectWithDsn); const createProjectWithAutoTeamSpy = vi.mocked( - projectsApi.createProjectWithAutoTeam + projectsApi.createProjectWithAutoTeam, ); const createTeamSpy = vi.mocked(teamsApi.createTeam); const tryGetPrimaryDsnSpy = vi.mocked(projectsApi.tryGetPrimaryDsn); @@ -136,12 +136,12 @@ describe("project create", () => { new ApiError( "Forbidden", 403, - "Your organization has disabled this feature for members." - ) + "Your organization has disabled this feature for members.", + ), ); createTeamSpy.mockResolvedValue(sampleTeam); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc@o123.ingest.us.sentry.io/999" + "https://abc@o123.ingest.us.sentry.io/999", ); listOrgsSpy.mockResolvedValue([ { slug: "acme-corp", name: "Acme Corp" }, @@ -170,7 +170,7 @@ describe("project create", () => { { name: "my-app", platform: "node", - } + }, ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -192,41 +192,42 @@ describe("project create", () => { }); }); - test.each([ - "python-flask", - "node-mastra", - "node-flue", - "node-eve", - ])("passes the paired platform %s to createProject", async (platform) => { - const { context } = createMockContext(); - const func = await createCommand.loader(); - await func.call(context, { json: false }, `my-app:${platform}`); + test.each(["python-flask", "node-mastra", "node-flue", "node-eve"])( + "passes the paired platform %s to createProject", + async (platform) => { + const { context } = createMockContext(); + const func = await createCommand.loader(); + await func.call(context, { json: false }, `my-app:${platform}`); - expect(createProjectWithDsnSpy).toHaveBeenCalledWith( - "acme-corp", - "engineering", - { - name: "my-app", - platform, - } - ); - }); + expect(createProjectWithDsnSpy).toHaveBeenCalledWith( + "acme-corp", + "engineering", + { + name: "my-app", + platform, + }, + ); + }, + ); test.each([ ["without an org", ["my-app", "python-flask"]], ["with an explicit org", ["my-org/my-app", "python-flask"]], - ])("rejects the space-separated form %s — platform must use :", async (_label, args) => { - const { context } = createMockContext(); - const func = await createCommand.loader(); - - const err = await func - .call(context, { json: false }, ...args) - .catch((error: Error) => error); - - expect(err).toBeInstanceOf(ValidationError); - expect(err.message).toContain("must use <name>:<platform> syntax"); - expect(createProjectWithDsnSpy).not.toHaveBeenCalled(); - }); + ])( + "rejects the space-separated form %s — platform must use :", + async (_label, args) => { + const { context } = createMockContext(); + const func = await createCommand.loader(); + + const err = await func + .call(context, { json: false }, ...args) + .catch((error: Error) => error); + + expect(err).toBeInstanceOf(ValidationError); + expect(err.message).toContain("must use <name>:<platform> syntax"); + expect(createProjectWithDsnSpy).not.toHaveBeenCalled(); + }, + ); test("rejects the space-separated form through Stricli", async () => { const { context, stdoutWrite } = createMockContext(); @@ -246,7 +247,7 @@ describe("project create", () => { "backend", "--dry-run", ], - context + context, ); expect(createProjectWithDsnSpy).not.toHaveBeenCalled(); @@ -274,7 +275,7 @@ describe("project create", () => { { name: "api:europe", platform: "node", - } + }, ); }); @@ -293,7 +294,7 @@ describe("project create", () => { { name: "my-app", platform: "go", - } + }, ); }); @@ -312,7 +313,7 @@ describe("project create", () => { { name: "my-app", platform: "node", - } + }, ); }); @@ -391,7 +392,7 @@ describe("project create", () => { { name: "my-app", platform: "node", - } + }, ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -406,7 +407,7 @@ describe("project create", () => { const func = await createCommand.loader(); await expect( - func.call(context, { json: false }, "my-app:node") + func.call(context, { json: false }, "my-app:node"), ).rejects.toThrow(ContextError); }); @@ -415,8 +416,8 @@ describe("project create", () => { new ApiError( "API request failed: 409 Conflict", 409, - "Project already exists" - ) + "Project already exists", + ), ); const { context } = createMockContext(); @@ -432,7 +433,7 @@ describe("project create", () => { test("handles 404 from createProject as team-not-found with available teams", async () => { createProjectWithDsnSpy.mockRejectedValue( - new ApiError("API request failed: 404 Not Found", 404) + new ApiError("API request failed: 404 Not Found", 404), ); const { context } = createMockContext(); @@ -454,7 +455,7 @@ describe("project create", () => { // This used to produce a contradictory "Team 'engineering' not found" // while listing "engineering" as an available team. createProjectWithDsnSpy.mockRejectedValue( - new ApiError("API request failed: 404 Not Found", 404) + new ApiError("API request failed: 404 Not Found", 404), ); // Default listTeams returns [sampleTeam] (slug: "engineering") // resolveOrCreateTeam auto-selects "engineering", then handleCreateProject404 @@ -475,11 +476,11 @@ describe("project create", () => { test("handles 404 from createProject with bad org — shows user's orgs", async () => { createProjectWithDsnSpy.mockRejectedValue( - new ApiError("API request failed: 404 Not Found", 404) + new ApiError("API request failed: 404 Not Found", 404), ); // listTeams also fails → org is bad listTeamsSpy.mockRejectedValue( - new ApiError("API request failed: 404 Not Found", 404) + new ApiError("API request failed: 404 Not Found", 404), ); const { context } = createMockContext(); @@ -496,11 +497,11 @@ describe("project create", () => { test("handles 404 with non-404 listTeams failure — shows generic error", async () => { createProjectWithDsnSpy.mockRejectedValue( - new ApiError("API request failed: 404 Not Found", 404) + new ApiError("API request failed: 404 Not Found", 404), ); // listTeams returns 403 (not 404) — can't tell if org or team is wrong listTeamsSpy.mockRejectedValue( - new ApiError("API request failed: 403 Forbidden", 403) + new ApiError("API request failed: 403 Forbidden", 403), ); const { context } = createMockContext(); @@ -537,8 +538,8 @@ describe("project create", () => { new ApiError( "API request failed: 400 Bad Request", 400, - '{"platform":["Invalid platform"]}' - ) + '{"platform":["Invalid platform"]}', + ), ); const { context } = createMockContext(); @@ -557,7 +558,7 @@ describe("project create", () => { // createProjectWithDsn fails with a non-403 server error (e.g. 500). // The fallback is only attempted on 403; this should surface directly. createProjectWithDsnSpy.mockRejectedValue( - new ApiError("Internal Server Error", 500, "Something went wrong") + new ApiError("Internal Server Error", 500, "Something went wrong"), ); const { context } = createMockContext(); @@ -579,7 +580,7 @@ describe("project create", () => { // Simulate: member has a team (via listTeams) but can't create projects on it. // The fallback to POST /organizations/{org}/projects/ should kick in. createProjectWithDsnSpy.mockRejectedValue( - new ApiError("Forbidden", 403, "You do not have permission") + new ApiError("Forbidden", 403, "You do not have permission"), ); createProjectWithAutoTeamSpy.mockResolvedValue({ project: sampleProject, @@ -603,8 +604,8 @@ describe("project create", () => { new ApiError( "Bad Request", 400, - '{"name":["Ensure this field has no more than 50 characters."]}' - ) + '{"name":["Ensure this field has no more than 50 characters."]}', + ), ); const { context } = createMockContext(); const func = await createCommand.loader(); @@ -619,14 +620,14 @@ describe("project create", () => { test("preserves unrelated API 400 errors on the org-scoped fallback", async () => { createProjectWithDsnSpy.mockRejectedValue( - new ApiError("Forbidden", 403, "You do not have permission") + new ApiError("Forbidden", 403, "You do not have permission"), ); createProjectWithAutoTeamSpy.mockRejectedValue( new ApiError( "Bad Request", 400, - '{"name":["Ensure this field has no more than 50 characters."]}' - ) + '{"name":["Ensure this field has no more than 50 characters."]}', + ), ); const { context } = createMockContext(); const func = await createCommand.loader(); @@ -642,14 +643,14 @@ describe("project create", () => { test("handles API platform errors on the org-scoped fallback", async () => { createProjectWithDsnSpy.mockRejectedValue( - new ApiError("Forbidden", 403, "You do not have permission") + new ApiError("Forbidden", 403, "You do not have permission"), ); createProjectWithAutoTeamSpy.mockRejectedValue( new ApiError( "API request failed: 400 Bad Request", 400, - '{"platform":["Invalid platform"]}' - ) + '{"platform":["Invalid platform"]}', + ), ); const { context } = createMockContext(); const func = await createCommand.loader(); @@ -666,7 +667,7 @@ describe("project create", () => { // Both paths 403: team-based creation fails, and the fallback returns // the org-level policy error ("disabled this feature"). createProjectWithDsnSpy.mockRejectedValue( - new ApiError("Forbidden", 403, "You do not have permission") + new ApiError("Forbidden", 403, "You do not have permission"), ); // createProjectWithAutoTeamSpy already defaults to "disabled this feature" 403 @@ -717,7 +718,7 @@ describe("project create", () => { // Missing name after slash await expect( - func.call(context, { json: false }, "acme-corp/:node") + func.call(context, { json: false }, "acme-corp/:node"), ).rejects.toThrow(ContextError); }); @@ -737,7 +738,7 @@ describe("project create", () => { const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); expect(output).toContain( - "sentry.io/organizations/acme-corp/projects/my-app/" + "sentry.io/organizations/acme-corp/projects/my-app/", ); }); @@ -778,44 +779,44 @@ describe("project create", () => { expect(err).toBeInstanceOf(ContextError); expect(err.message).toContain("Project specification is required"); expect(err.message).toContain( - "sentry project create [<org>/]<name>:<platform>..." + "sentry project create [<org>/]<name>:<platform>...", ); }); - test.each([ - "", - " ", - ])("rejects an empty project name argument (%j)", async (name) => { - const { context } = createMockContext(); - const func = await createCommand.loader(); - - const err = await func - .call(context, { json: false }, `${name}:node`) - .catch((error: Error) => error); - expect(err).toBeInstanceOf(ValidationError); - expect(err.message).toContain("Project name cannot be empty"); - }); - - test.each([ - "my-app", - "my-app:", - ])("shows a helpful error when platform is missing from %s", async (projectArg) => { - const { context } = createMockContext(); - const func = await createCommand.loader(); + test.each(["", " "])( + "rejects an empty project name argument (%j)", + async (name) => { + const { context } = createMockContext(); + const func = await createCommand.loader(); + + const err = await func + .call(context, { json: false }, `${name}:node`) + .catch((error: Error) => error); + expect(err).toBeInstanceOf(ValidationError); + expect(err.message).toContain("Project name cannot be empty"); + }, + ); - const err = await func - .call(context, { json: false }, projectArg) - .catch((e: Error) => e); - expect(err).toBeInstanceOf(ValidationError); - expect((err as ValidationError).exitCode).toBe(EXIT.VALIDATION); - expect(err.message).toContain("Platform is required"); - expect(err.message).toContain("Common platforms:"); - expect(err.message).toContain("javascript-nextjs"); - expect(err.message).toContain("python"); - expect(err.message).toContain("Run 'sentry platform list'"); - // There is no space-separated form at all, so it must never be suggested. - expect(err.message).not.toContain(" <platform>"); - }); + test.each(["my-app", "my-app:"])( + "shows a helpful error when platform is missing from %s", + async (projectArg) => { + const { context } = createMockContext(); + const func = await createCommand.loader(); + + const err = await func + .call(context, { json: false }, projectArg) + .catch((e: Error) => e); + expect(err).toBeInstanceOf(ValidationError); + expect((err as ValidationError).exitCode).toBe(EXIT.VALIDATION); + expect(err.message).toContain("Platform is required"); + expect(err.message).toContain("Common platforms:"); + expect(err.message).toContain("javascript-nextjs"); + expect(err.message).toContain("python"); + expect(err.message).toContain("Run 'sentry platform list'"); + // There is no space-separated form at all, so it must never be suggested. + expect(err.message).not.toContain(" <platform>"); + }, + ); test("rejects trailing-platform batches that were never supported", async () => { const { context } = createMockContext(); @@ -831,7 +832,7 @@ describe("project create", () => { test("wraps listTeams API failure with org list", async () => { listTeamsSpy.mockRejectedValue( - new ApiError("API request failed: 404 Not Found", 404) + new ApiError("API request failed: 404 Not Found", 404), ); const { context } = createMockContext(); @@ -854,7 +855,7 @@ describe("project create", () => { detectedFrom: "test/mocks/routes.ts", }); listTeamsSpy.mockRejectedValue( - new ApiError("API request failed: 404 Not Found", 404) + new ApiError("API request failed: 404 Not Found", 404), ); const { context } = createMockContext(); @@ -875,7 +876,7 @@ describe("project create", () => { // The default beforeEach mock has createProjectWithAutoTeam reject with // "disabled this feature", so the final error is the policy-disabled message. listTeamsSpy.mockRejectedValue( - new ApiError("API request failed: 403 Forbidden", 403) + new ApiError("API request failed: 403 Forbidden", 403), ); const { context } = createMockContext(); @@ -901,7 +902,7 @@ describe("project create", () => { { name: "my-app", platform: "javascript-nextjs", - } + }, ); }); @@ -917,7 +918,7 @@ describe("project create", () => { { name: "my-app", platform: "javascript-nextjs", - } + }, ); }); @@ -941,7 +942,7 @@ describe("project create", () => { { json: false }, "web:javascript", "api:python-django", - "worker:node" + "worker:node", ); expect(createProjectWithDsnSpy).toHaveBeenCalledTimes(3); @@ -953,7 +954,7 @@ describe("project create", () => { expect(createProjectWithDsnSpy).toHaveBeenCalledWith( "acme-corp", "engineering", - { name, platform } + { name, platform }, ); } const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); @@ -963,7 +964,7 @@ describe("project create", () => { test("creates one hundred project pairs in one command", async () => { const specs = Array.from( { length: 100 }, - (_, index) => `project-${index}:node` + (_, index) => `project-${index}:node`, ); const { context } = createMockContext(); const func = await createCommand.loader(); @@ -974,7 +975,7 @@ describe("project create", () => { expect(createProjectWithDsnSpy).toHaveBeenLastCalledWith( "acme-corp", "engineering", - { name: "project-99", platform: "node" } + { name: "project-99", platform: "node" }, ); }); @@ -1042,21 +1043,22 @@ describe("project create", () => { expect(err.message).toContain("sentry project create proj2:<platform>"); }); - test.each([ - ["paired", ["My Cool App:node"]], - ])("rejects whitespace in a %s project name", async (_syntax, args) => { - const { context } = createMockContext(); - const func = await createCommand.loader(); - const err = await func - .call(context, { json: false }, ...args) - .catch((error: Error) => error); - - expect(err).toBeInstanceOf(ValidationError); - expect(err.message).toContain( - "Project name 'My Cool App' cannot contain whitespace" - ); - expect(createProjectWithDsnSpy).not.toHaveBeenCalled(); - }); + test.each([["paired", ["My Cool App:node"]]])( + "rejects whitespace in a %s project name", + async (_syntax, args) => { + const { context } = createMockContext(); + const func = await createCommand.loader(); + const err = await func + .call(context, { json: false }, ...args) + .catch((error: Error) => error); + + expect(err).toBeInstanceOf(ValidationError); + expect(err.message).toContain( + "Project name 'My Cool App' cannot contain whitespace", + ); + expect(createProjectWithDsnSpy).not.toHaveBeenCalled(); + }, + ); test("preserves commas inside a project name", async () => { const { context } = createMockContext(); @@ -1067,7 +1069,7 @@ describe("project create", () => { expect(createProjectWithDsnSpy).toHaveBeenCalledWith( "acme-corp", "engineering", - { name: "payments,eu", platform: "node" } + { name: "payments,eu", platform: "node" }, ); }); @@ -1079,7 +1081,7 @@ describe("project create", () => { expect(createProjectWithDsnSpy).toHaveBeenCalledWith( "acme-corp", "engineering", - { name: "api:europe", platform: "node" } + { name: "api:europe", platform: "node" }, ); }); @@ -1112,7 +1114,7 @@ describe("project create", () => { { json: false, "dry-run": true }, "web:javascript", "api:python-django", - "worker:node" + "worker:node", ); // Dry-run never creates teams or projects. @@ -1135,7 +1137,7 @@ describe("project create", () => { .call( context, { json: false, "dry-run": true }, - "my-app:invalid-platform" + "my-app:invalid-platform", ) .catch((e: Error) => e); expect(err).toBeInstanceOf(CliError); @@ -1148,7 +1150,7 @@ describe("project create", () => { await func.call( context, { json: false, "dry-run": true }, - "my-org/my-app:python" + "my-org/my-app:python", ); expect(resolveOrgSpy).toHaveBeenCalledWith({ @@ -1184,14 +1186,14 @@ describe("project create", () => { context, { json: true, "dry-run": true }, "web:node", - "api:node" + "api:node", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); const parsed = JSON.parse(output); expect(parsed).toHaveLength(2); expect( - parsed.map((result: ProjectCreatedResult) => result.project.name) + parsed.map((result: ProjectCreatedResult) => result.project.name), ).toEqual(["web", "api"]); }); @@ -1235,7 +1237,7 @@ describe("project create", () => { context, { json: false, "dry-run": true }, "!!!:node", - "my-app:python" + "my-app:python", ); expect(createTeamSpy).not.toHaveBeenCalled(); diff --git a/packages/cli/test/commands/project/delete.test.ts b/packages/cli/test/commands/project/delete.test.ts index 276e4827a..dcd839625 100644 --- a/packages/cli/test/commands/project/delete.test.ts +++ b/packages/cli/test/commands/project/delete.test.ts @@ -30,8 +30,8 @@ const { mockIsatty, ttyExports, noop, mockPrompt, fakeLog } = vi.hoisted(() => { // Mock the logger to intercept the .prompt() call made by the module-scoped // `log = logger.withTag("project.delete")` inside delete.ts. - const _mockPrompt = vi.fn( - (): Promise<string | symbol> => Promise.resolve("acme-corp/my-app") + const _mockPrompt = vi.fn((): Promise<string | symbol> => + Promise.resolve("acme-corp/my-app"), ); const _fakeLog: { prompt: typeof _mockPrompt; @@ -81,9 +81,8 @@ vi.mock("../../../src/lib/logger.js", () => ({ // Dynamic import: must run AFTER vi.mock() so the module-scoped logger // binding inside delete.ts picks up fakeLog. -const { deleteCommand } = await import( - "../../../src/commands/project/delete.js" -); +const { deleteCommand } = + await import("../../../src/commands/project/delete.js"); vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { const actual = @@ -92,11 +91,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ApiError, ContextError } from "../../../src/lib/errors.js"; @@ -107,11 +106,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryProject } from "../../../src/types/index.js"; @@ -152,7 +151,7 @@ describe("project delete", () => { getOrganizationSpy = vi.spyOn(apiClient, "getOrganization"); resolveProjectBoundTargetSpy = vi.spyOn( resolveTarget, - "resolveProjectBoundTarget" + "resolveProjectBoundTarget", ); // Default mocks @@ -202,7 +201,7 @@ describe("project delete", () => { project: "my-app", }), "/tmp", - "project delete" + "project delete", ); }); @@ -217,7 +216,7 @@ describe("project delete", () => { projectSlug: "my-app", }), "/tmp", - "project delete" + "project delete", ); expect(deleteProjectSpy).toHaveBeenCalledWith("acme-corp", "my-app"); }); @@ -228,7 +227,7 @@ describe("project delete", () => { // isatty(0) returns false in test environments (non-TTY) await expect( - func.call(context, { ...defaultFlags, yes: false }, "acme-corp/my-app") + func.call(context, { ...defaultFlags, yes: false }, "acme-corp/my-app"), ).rejects.toThrow("non-interactive mode"); expect(deleteProjectSpy).not.toHaveBeenCalled(); @@ -236,14 +235,14 @@ describe("project delete", () => { test("propagates 404 from getProject", async () => { getProjectSpy.mockRejectedValue( - new ApiError("Not found", 404, "Project not found") + new ApiError("Not found", 404, "Project not found"), ); const { context } = createMockContext(); const func = await deleteCommand.loader(); await expect( - func.call(context, defaultFlags, "acme-corp/my-app") + func.call(context, defaultFlags, "acme-corp/my-app"), ).rejects.toThrow(ApiError); expect(deleteProjectSpy).not.toHaveBeenCalled(); @@ -251,7 +250,7 @@ describe("project delete", () => { test("403 with member role suggests asking an admin", async () => { deleteProjectSpy.mockRejectedValue( - new ApiError("Forbidden", 403, "You do not have permission") + new ApiError("Forbidden", 403, "You do not have permission"), ); getOrganizationSpy.mockResolvedValue({ id: "1", @@ -278,7 +277,7 @@ describe("project delete", () => { test("403 with owner role suggests checking token scope", async () => { deleteProjectSpy.mockRejectedValue( - new ApiError("Forbidden", 403, "You do not have permission") + new ApiError("Forbidden", 403, "You do not have permission"), ); getOrganizationSpy.mockResolvedValue({ id: "1", @@ -305,7 +304,7 @@ describe("project delete", () => { test("403 with role fetch failure shows fallback message", async () => { deleteProjectSpy.mockRejectedValue( - new ApiError("Forbidden", 403, "You do not have permission") + new ApiError("Forbidden", 403, "You do not have permission"), ); getOrganizationSpy.mockRejectedValue(new Error("network error")); @@ -344,7 +343,7 @@ describe("project delete", () => { await func.call( context, { ...defaultFlags, "dry-run": true }, - "acme-corp/my-app" + "acme-corp/my-app", ); expect(getProjectSpy).toHaveBeenCalledWith("acme-corp", "my-app"); @@ -362,7 +361,7 @@ describe("project delete", () => { await func.call( context, { ...defaultFlags, "dry-run": true, json: true }, - "acme-corp/my-app" + "acme-corp/my-app", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -382,7 +381,7 @@ describe("project delete", () => { await func.call( context, { ...defaultFlags, json: true }, - "acme-corp/my-app" + "acme-corp/my-app", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -400,7 +399,7 @@ describe("project delete", () => { // Empty string triggers auto-detect in parseOrgProjectArg await expect(func.call(context, defaultFlags, "")).rejects.toThrow( - ContextError + ContextError, ); expect(deleteProjectSpy).not.toHaveBeenCalled(); @@ -437,7 +436,7 @@ describe("project delete — interactive confirmation", () => { deleteProjectSpy = vi.spyOn(apiClient, "deleteProject"); resolveProjectBoundTargetSpy = vi.spyOn( resolveTarget, - "resolveProjectBoundTarget" + "resolveProjectBoundTarget", ); getProjectSpy.mockResolvedValue(sampleProject); @@ -466,7 +465,7 @@ describe("project delete — interactive confirmation", () => { await func.call( context, { yes: false, "dry-run": false }, - "acme-corp/my-app" + "acme-corp/my-app", ); expect(deleteProjectSpy).toHaveBeenCalledWith("acme-corp", "my-app"); @@ -482,7 +481,7 @@ describe("project delete — interactive confirmation", () => { await func.call( context, { yes: false, "dry-run": false }, - "acme-corp/my-app" + "acme-corp/my-app", ); expect(deleteProjectSpy).not.toHaveBeenCalled(); @@ -498,7 +497,7 @@ describe("project delete — interactive confirmation", () => { await func.call( context, { yes: false, "dry-run": false }, - "acme-corp/my-app" + "acme-corp/my-app", ); expect(deleteProjectSpy).not.toHaveBeenCalled(); @@ -513,7 +512,7 @@ describe("project delete — interactive confirmation", () => { await func.call( context, { yes: false, "dry-run": false }, - "acme-corp/my-app" + "acme-corp/my-app", ); expect(deleteProjectSpy).not.toHaveBeenCalled(); @@ -527,12 +526,12 @@ describe("project delete — interactive confirmation", () => { await func.call( context, { yes: false, "dry-run": false }, - "acme-corp/my-app" + "acme-corp/my-app", ); expect(mockPrompt).toHaveBeenCalledWith( expect.stringContaining("acme-corp/my-app"), - expect.objectContaining({ type: "text" }) + expect.objectContaining({ type: "text" }), ); }); }); diff --git a/packages/cli/test/commands/project/list.test.ts b/packages/cli/test/commands/project/list.test.ts index cace32f8c..7aac665f0 100644 --- a/packages/cli/test/commands/project/list.test.ts +++ b/packages/cli/test/commands/project/list.test.ts @@ -5,7 +5,7 @@ * Handlers are tested with fetch mocking for API isolation. */ -// biome-ignore-all lint/suspicious/noMisplacedAssertion: Property tests use expect() inside fast-check callbacks. +// Property tests use expect() inside fast-check callbacks. import { array, @@ -51,7 +51,7 @@ useTestConfigDir("test-project-list-", { isolateProjectRoot: true }); /** Create a minimal project for testing */ function makeProject( - overrides: Partial<SentryProject> & { orgSlug?: string } = {} + overrides: Partial<SentryProject> & { orgSlug?: string } = {}, ): SentryProject & { orgSlug?: string } { return { id: "1", @@ -71,7 +71,7 @@ const slugArb = array( { minLength: 1, maxLength: 12, - } + }, ).map((chars) => chars.join("")); const platformArb = constantFrom( @@ -82,7 +82,7 @@ const platformArb = constantFrom( "ruby", "php", "javascript-react", - "python-django" + "python-django", ); // Tests @@ -97,7 +97,7 @@ describe("buildContextKey", () => { const key = buildContextKey(parsed, {}, host); expect(key).toBe(`host:${host}|type:org:${org}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -113,7 +113,7 @@ describe("buildContextKey", () => { const key = buildContextKey(parsed, {}, host); expect(key).toBe(`host:${host}|type:explicit:${org}/${project}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -127,7 +127,7 @@ describe("buildContextKey", () => { const key = buildContextKey(parsed, {}, host); expect(key).toBe(`host:${host}|type:search:${projectSlug}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -138,7 +138,7 @@ describe("buildContextKey", () => { const key = buildContextKey(parsed, { platform }, host); expect(key).toBe(`host:${host}|type:org:${org}|platform:${platform}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -150,13 +150,13 @@ describe("buildContextKey", () => { const selfHosted = buildContextKey( parsed, {}, - "https://sentry.example.com" + "https://sentry.example.com", ); expect(saas).not.toBe(selfHosted); expect(saas).toStartWith("host:https://sentry.io|"); expect(selfHosted).toStartWith("host:https://sentry.example.com|"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -209,7 +209,7 @@ describe("filterByPlatform", () => { const twice = filterByPlatform(once, platform); expect(twice).toEqual(once); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -227,7 +227,7 @@ describe("filterByPlatform", () => { expect(projects).toContain(p); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -243,7 +243,7 @@ describe("resolveCursor", () => { const result = resolveCursor( "1735689600000:100:0", PAGINATION_KEY, - "org:sentry" + "org:sentry", ); expect(result.cursor).toBe("1735689600000:100:0"); expect(result.direction).toBe("next"); @@ -251,10 +251,10 @@ describe("resolveCursor", () => { test("'next' with no saved state throws ValidationError", () => { expect(() => resolveCursor("next", PAGINATION_KEY, "org:sentry")).toThrow( - ValidationError + ValidationError, ); expect(() => resolveCursor("next", PAGINATION_KEY, "org:sentry")).toThrow( - /No next page/ + /No next page/, ); }); @@ -276,10 +276,10 @@ describe("resolveCursor", () => { advancePaginationState(PAGINATION_KEY, contextKey, "next", "some-cursor"); expect(() => resolveCursor("prev", PAGINATION_KEY, contextKey)).toThrow( - ValidationError + ValidationError, ); expect(() => resolveCursor("prev", PAGINATION_KEY, contextKey)).toThrow( - /first page/ + /first page/, ); }); @@ -297,7 +297,7 @@ let originalFetch: typeof globalThis.fetch; /** Create a mock fetch for project API calls */ function mockProjectFetch( projects: SentryProject[], - options: { hasMore?: boolean; nextCursor?: string } = {} + options: { hasMore?: boolean; nextCursor?: string } = {}, ): typeof globalThis.fetch { const { hasMore = false, nextCursor } = options; // @ts-expect-error - partial mock @@ -332,7 +332,7 @@ function mockProjectFetch( ]; if (hasMore && nextCursor) { linkParts.push( - `<${url}>; rel="next"; results="true"; cursor="${nextCursor}"` + `<${url}>; rel="next"; results="true"; cursor="${nextCursor}"`, ); } else { linkParts.push(`<${url}>; rel="next"; results="false"; cursor="0:0:0"`); @@ -357,7 +357,7 @@ function mockProjectFetch( { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -543,7 +543,7 @@ describe("handleOrgAll", () => { PAGINATION_KEY, "type:org:test-org", "next", - "old-cursor" + "old-cursor", ); globalThis.fetch = mockProjectFetch(sampleProjects); @@ -657,7 +657,7 @@ describe("handleOrgAll", () => { id: String(i + 1), slug: `proj-${i}`, name: `Project ${i}`, - }) + }), ); const perPageValues: number[] = []; @@ -749,7 +749,7 @@ describe("handleProjectSearch", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -765,7 +765,7 @@ describe("handleProjectSearch", () => { limit: 30, json: false, fresh: false, - }) + }), ).rejects.toThrow(ResolutionError); }); @@ -781,7 +781,7 @@ describe("handleProjectSearch", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -849,7 +849,7 @@ describe("handleProjectSearch", () => { dateCreated: "2024-01-01T00:00:00Z", status: "active", }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -864,7 +864,7 @@ describe("handleProjectSearch", () => { { id: "10", slug: "org-a", name: "Org A" }, { id: "20", slug: "org-b", name: "Org B" }, ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -876,7 +876,7 @@ describe("handleProjectSearch", () => { const result = await handleProjectSearch( "frontend", { limit: 30, json: false, fresh: false }, - { scopedOrg: "org-a" } + { scopedOrg: "org-a" }, ); expect(result.items.every((i) => i.orgSlug === "org-a")).toBe(true); @@ -918,7 +918,7 @@ describe("handleProjectSearch", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -972,7 +972,7 @@ describe("handleProjectSearch", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1150,7 +1150,7 @@ describe("fetchAllOrgProjects", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -1238,7 +1238,7 @@ describe("handleAutoDetect", () => { test("respects --limit flag and indicates truncation", async () => { const manyProjects = Array.from({ length: 5 }, (_, i) => - makeProject({ id: String(i), slug: `proj-${i}`, name: `Project ${i}` }) + makeProject({ id: String(i), slug: `proj-${i}`, name: `Project ${i}` }), ); globalThis.fetch = mockProjectFetch(manyProjects); @@ -1271,7 +1271,7 @@ describe("handleAutoDetect", () => { test("shows limit message when more projects exist", async () => { const manyProjects = Array.from({ length: 5 }, (_, i) => - makeProject({ id: String(i), slug: `proj-${i}`, name: `Project ${i}` }) + makeProject({ id: String(i), slug: `proj-${i}`, name: `Project ${i}` }), ); globalThis.fetch = mockProjectFetch(manyProjects); @@ -1378,7 +1378,7 @@ describe("handleAutoDetect", () => { limit: 30, json: true, fresh: false, - }) + }), ).rejects.toThrow(AuthError); } finally { if (savedAuthToken !== undefined) { diff --git a/packages/cli/test/commands/project/view.func.test.ts b/packages/cli/test/commands/project/view.func.test.ts index 19fc139b8..5d385b588 100644 --- a/packages/cli/test/commands/project/view.func.test.ts +++ b/packages/cli/test/commands/project/view.func.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -30,11 +30,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { AuthError, ContextError } from "../../../src/lib/errors.js"; @@ -45,11 +45,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { ProjectKey, SentryProject } from "../../../src/types/sentry.js"; @@ -91,7 +91,7 @@ describe("viewCommand.func", () => { const tryGetPrimaryDsnSpy = vi.mocked(apiClient.tryGetPrimaryDsn); const resolveAllTargetsSpy = vi.mocked(resolveTarget.resolveAllTargets); const resolveProjectBoundSlugSpy = vi.mocked( - resolveTarget.resolveProjectBoundSlug + resolveTarget.resolveProjectBoundSlug, ); const openInBrowserSpy = vi.mocked(browser.openInBrowser); @@ -106,7 +106,7 @@ describe("viewCommand.func", () => { test("explicit org/project outputs JSON with DSN", async () => { getProjectSpy.mockResolvedValue(sampleProject); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context, stdoutWrite } = createMockContext(); @@ -124,7 +124,7 @@ describe("viewCommand.func", () => { test("explicit org/project outputs human-readable details", async () => { getProjectSpy.mockResolvedValue(sampleProject); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context, stdoutWrite } = createMockContext(); @@ -132,7 +132,7 @@ describe("viewCommand.func", () => { await func.call( context, { json: false, web: false }, - "my-org/test-project" + "my-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -190,7 +190,7 @@ describe("viewCommand.func", () => { }); getProjectSpy.mockResolvedValue({ ...sampleProject, slug: "frontend" }); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context, stdoutWrite } = createMockContext(); @@ -201,7 +201,7 @@ describe("viewCommand.func", () => { "frontend", "sentry project view <org>/<project>", "sentry project view <org>/frontend", - undefined + undefined, ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); const parsed = JSON.parse(output); @@ -220,7 +220,7 @@ describe("viewCommand.func", () => { expect(error).toBeInstanceOf(ContextError); expect((error as ContextError).message).toContain("Specific project"); expect((error as ContextError).message).toContain( - "not just the organization" + "not just the organization", ); } }); @@ -240,7 +240,7 @@ describe("viewCommand.func", () => { }); getProjectSpy.mockResolvedValue({ ...sampleProject, slug: "backend" }); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context, stdoutWrite } = createMockContext(); @@ -261,7 +261,7 @@ describe("viewCommand.func", () => { const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false }) + func.call(context, { json: false, web: false }), ).rejects.toThrow(ContextError); }); @@ -294,14 +294,14 @@ describe("viewCommand.func", () => { const apiErr = new Error("404 Not Found"); getProjectSpy.mockRejectedValue(apiErr); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context } = createMockContext(); const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false }, "my-org/bad-project") + func.call(context, { json: false, web: false }, "my-org/bad-project"), ).rejects.toThrow("404 Not Found"); expect(getProjectSpy).toHaveBeenCalledWith("my-org", "bad-project"); @@ -315,14 +315,14 @@ describe("viewCommand.func", () => { const apiErr = new Error("403 Forbidden"); getProjectSpy.mockRejectedValue(apiErr); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context } = createMockContext(); const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false }, "frontend") + func.call(context, { json: false, web: false }, "frontend"), ).rejects.toThrow("403 Forbidden"); }); @@ -344,28 +344,28 @@ describe("viewCommand.func", () => { }); getProjectSpy.mockRejectedValue(new Error("403 Forbidden")); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context } = createMockContext(); const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false }) + func.call(context, { json: false, web: false }), ).rejects.toThrow(ContextError); }); test("auth error from API is rethrown", async () => { getProjectSpy.mockRejectedValue(new AuthError("not_authenticated")); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context } = createMockContext(); const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false }, "my-org/test-project") + func.call(context, { json: false, web: false }, "my-org/test-project"), ).rejects.toThrow(AuthError); }); @@ -378,7 +378,7 @@ describe("viewCommand.func", () => { organization: { id: "1", slug: "my-org" }, }); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context, stdoutWrite } = createMockContext(); @@ -400,7 +400,7 @@ describe("viewCommand.func", () => { organization: { id: "1", slug: "my-org", name: "My Organization" }, }); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context, stdoutWrite } = createMockContext(); @@ -415,7 +415,7 @@ describe("viewCommand.func", () => { test("JSON output still strips detectedFrom (human-only field)", async () => { getProjectSpy.mockResolvedValue(sampleProject); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context, stdoutWrite } = createMockContext(); @@ -433,7 +433,7 @@ describe("viewCommand.func", () => { organization: { id: "1", slug: "my-org" }, }); tryGetPrimaryDsnSpy.mockResolvedValue( - "https://abc123@o1.ingest.sentry.io/42" + "https://abc123@o1.ingest.sentry.io/42", ); const { context, stdoutWrite } = createMockContext(); @@ -447,7 +447,7 @@ describe("viewCommand.func", () => { web: false, fields: ["slug", "organization.slug"], }, - "my-org/test-project" + "my-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); diff --git a/packages/cli/test/commands/react-native/gradle.test.ts b/packages/cli/test/commands/react-native/gradle.test.ts index edece831a..2635d085d 100644 --- a/packages/cli/test/commands/react-native/gradle.test.ts +++ b/packages/cli/test/commands/react-native/gradle.test.ts @@ -10,10 +10,10 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { gradleCommand } from "../../../src/commands/react-native/gradle.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as sourcemaps from "../../../src/lib/api/sourcemaps.js"; import { ValidationError } from "../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; let tmpDir: string; @@ -27,7 +27,7 @@ function createContext() { stdout: { write: (data: string | Uint8Array) => { writes.push( - typeof data === "string" ? data : new TextDecoder().decode(data) + typeof data === "string" ? data : new TextDecoder().decode(data), ); return true; }, @@ -57,7 +57,7 @@ describe("react-native gradle", () => { names: [], mappings: "", sourcesContent: ["console.log('hello');"], - }) + }), ); vi.spyOn(resolveTarget, "resolveOrgAndProject").mockResolvedValue({ org: "acme", @@ -106,7 +106,7 @@ describe("react-native gradle", () => { expect(uploadSpy).toHaveBeenCalledTimes(2); const dists = uploadSpy.mock.calls.map( - (c) => (c[0] as sourcemaps.UploadOptions).dist + (c) => (c[0] as sourcemaps.UploadOptions).dist, ); expect(dists).toEqual(["1000", "1001"]); for (const call of uploadSpy.mock.calls) { @@ -140,7 +140,7 @@ describe("react-native gradle", () => { func.call(createContext().context, { bundle: join(tmpDir, "nope.bundle"), sourcemap, - }) + }), ).rejects.toThrow(ValidationError); }); @@ -148,7 +148,7 @@ describe("react-native gradle", () => { await writeFile(bundle, Buffer.from([0xe5, 0xd1, 0x0b, 0xfb, 0x00, 0x00])); const func = await gradleCommand.loader(); await expect( - func.call(createContext().context, { bundle, sourcemap }) + func.call(createContext().context, { bundle, sourcemap }), ).rejects.toThrow(/RAM bundle/); }); }); diff --git a/packages/cli/test/commands/react-native/xcode.test.ts b/packages/cli/test/commands/react-native/xcode.test.ts index 1edcca195..c88929943 100644 --- a/packages/cli/test/commands/react-native/xcode.test.ts +++ b/packages/cli/test/commands/react-native/xcode.test.ts @@ -12,9 +12,9 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { xcodeCommand } from "../../../src/commands/react-native/xcode.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as sourcemaps from "../../../src/lib/api/sourcemaps.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; vi.mock("node:child_process", async (importOriginal) => { @@ -90,7 +90,7 @@ describe("react-native xcode", () => { ( _cmd: string, _args: readonly string[] | undefined, - spawnOpts?: { env?: NodeJS.ProcessEnv } + spawnOpts?: { env?: NodeJS.ProcessEnv }, ) => { const reportPath = spawnOpts?.env?.SENTRY_RN_SOURCEMAP_REPORT; if (reportPath) { @@ -99,7 +99,7 @@ describe("react-native xcode", () => { JSON.stringify({ packager_bundle_path: bundle, packager_sourcemap_path: map, - }) + }), ); } return { @@ -110,7 +110,7 @@ describe("react-native xcode", () => { output: [], signal: null, }; - } + }, ); // The sourcemap already carries a debug id (from the Metro plugin). @@ -163,46 +163,49 @@ describe("react-native xcode", () => { ["missing", 7], ["malformed", 0], ["malformed", 7], - ] as const)("skips upload for a %s report and preserves build status %i", async (reportState, status) => { - spawnMock.mockImplementation( - ( - _cmd: string, - _args: readonly string[] | undefined, - spawnOpts?: { env?: NodeJS.ProcessEnv } - ) => { - const reportPath = spawnOpts?.env?.SENTRY_RN_SOURCEMAP_REPORT; - if (reportPath) { - if (reportState === "missing") { - rmSync(reportPath); - } else { - writeFileSync(reportPath, '{"packager_bundle_path":'); + ] as const)( + "skips upload for a %s report and preserves build status %i", + async (reportState, status) => { + spawnMock.mockImplementation( + ( + _cmd: string, + _args: readonly string[] | undefined, + spawnOpts?: { env?: NodeJS.ProcessEnv }, + ) => { + const reportPath = spawnOpts?.env?.SENTRY_RN_SOURCEMAP_REPORT; + if (reportPath) { + if (reportState === "missing") { + rmSync(reportPath); + } else { + writeFileSync(reportPath, '{"packager_bundle_path":'); + } } - } - return { - status, - stdout: "", - stderr: "", - pid: 1, - output: [], - signal: null, - }; - } - ); - - const ctx = createContext({ CONFIGURATION: "Release" }); - const func = await xcodeCommand.loader(); - await func.call(ctx, { "build-script": script }); - - expect(ctx.process.exitCode).toBe(status === 0 ? undefined : status); - expect(sourcemaps.uploadSourcemaps).not.toHaveBeenCalled(); - }); + return { + status, + stdout: "", + stderr: "", + pid: 1, + output: [], + signal: null, + }; + }, + ); + + const ctx = createContext({ CONFIGURATION: "Release" }); + const func = await xcodeCommand.loader(); + await func.call(ctx, { "build-script": script }); + + expect(ctx.process.exitCode).toBe(status === 0 ? undefined : status); + expect(sourcemaps.uploadSourcemaps).not.toHaveBeenCalled(); + }, + ); test("warns and skips upload when the build produced no sourcemaps", async () => { spawnMock.mockImplementation( ( _cmd: string, _args: readonly string[] | undefined, - spawnOpts?: { env?: NodeJS.ProcessEnv } + spawnOpts?: { env?: NodeJS.ProcessEnv }, ) => { const reportPath = spawnOpts?.env?.SENTRY_RN_SOURCEMAP_REPORT; if (reportPath) { @@ -216,7 +219,7 @@ describe("react-native xcode", () => { output: [], signal: null, }; - } + }, ); await runXcode({ CONFIGURATION: "Release" }); expect(sourcemaps.uploadSourcemaps).not.toHaveBeenCalled(); diff --git a/packages/cli/test/commands/release/archive.test.ts b/packages/cli/test/commands/release/archive.test.ts index 9cb513d8f..13e2ad245 100644 --- a/packages/cli/test/commands/release/archive.test.ts +++ b/packages/cli/test/commands/release/archive.test.ts @@ -13,11 +13,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -27,11 +27,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryRelease } from "../../../src/types/index.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -114,7 +114,7 @@ describe("release archive", () => { const func = await archiveCommand.loader(); await expect( - func.call(context, { "dry-run": false, json: false }) + func.call(context, { "dry-run": false, json: false }), ).rejects.toThrow("Release version"); }); @@ -125,7 +125,7 @@ describe("release archive", () => { const func = await archiveCommand.loader(); await expect( - func.call(context, { "dry-run": false, json: false }, "1.0.0") + func.call(context, { "dry-run": false, json: false }, "1.0.0"), ).rejects.toThrow("organization"); }); }); @@ -174,7 +174,7 @@ describe("release restore", () => { const func = await restoreCommand.loader(); await expect( - func.call(context, { "dry-run": false, json: false }) + func.call(context, { "dry-run": false, json: false }), ).rejects.toThrow("Release version"); }); @@ -185,7 +185,7 @@ describe("release restore", () => { const func = await restoreCommand.loader(); await expect( - func.call(context, { "dry-run": false, json: false }, "1.0.0") + func.call(context, { "dry-run": false, json: false }, "1.0.0"), ).rejects.toThrow("organization"); }); }); diff --git a/packages/cli/test/commands/release/create.test.ts b/packages/cli/test/commands/release/create.test.ts index 7a9f899c1..928adeb27 100644 --- a/packages/cli/test/commands/release/create.test.ts +++ b/packages/cli/test/commands/release/create.test.ts @@ -12,11 +12,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -26,11 +26,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryRelease } from "../../../src/types/index.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -116,7 +116,7 @@ describe("release create", () => { await func.call( context, { finalize: false, project: "my-project", json: true }, - "1.0.0" + "1.0.0", ); expect(createReleaseSpy).toHaveBeenCalledWith("my-org", { @@ -151,7 +151,7 @@ describe("release create", () => { await func.call( context, { finalize: false, ref: "main", json: true }, - "1.0.0" + "1.0.0", ); expect(createReleaseSpy).toHaveBeenCalledWith("my-org", { @@ -165,7 +165,7 @@ describe("release create", () => { const func = await createCommand.loader(); await expect( - func.call(context, { finalize: false, json: false }) + func.call(context, { finalize: false, json: false }), ).rejects.toThrow("Release version"); }); }); diff --git a/packages/cli/test/commands/release/delete.test.ts b/packages/cli/test/commands/release/delete.test.ts index 0b616cc2b..62958522b 100644 --- a/packages/cli/test/commands/release/delete.test.ts +++ b/packages/cli/test/commands/release/delete.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ApiError, ContextError } from "../../../src/lib/errors.js"; @@ -31,11 +31,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; /** Minimal release shape returned by getRelease */ @@ -113,7 +113,7 @@ describe("release delete", () => { const func = await deleteCommand.loader(); await expect(func.call(context, defaultFlags, "")).rejects.toThrow( - ContextError + ContextError, ); expect(deleteReleaseSpy).not.toHaveBeenCalled(); }); @@ -124,7 +124,7 @@ describe("release delete", () => { await func.call( context, { ...defaultFlags, "dry-run": true }, - "my-org/1.0.0" + "my-org/1.0.0", ); expect(getReleaseSpy).toHaveBeenCalledWith("my-org", "1.0.0"); @@ -137,14 +137,14 @@ describe("release delete", () => { test("propagates 404 from getRelease", async () => { getReleaseSpy.mockRejectedValue( - new ApiError("Not found", 404, "Release not found") + new ApiError("Not found", 404, "Release not found"), ); const { context } = createMockContext(); const func = await deleteCommand.loader(); await expect( - func.call(context, defaultFlags, "my-org/1.0.0") + func.call(context, defaultFlags, "my-org/1.0.0"), ).rejects.toThrow(ApiError); expect(deleteReleaseSpy).not.toHaveBeenCalled(); @@ -157,8 +157,8 @@ describe("release delete", () => { "Failed to delete release '1.0.0': 400 Bad Request", 400, "This release has health data and cannot be removed.", - endpoint - ) + endpoint, + ), ); const { context } = createMockContext(); @@ -184,7 +184,7 @@ describe("release delete", () => { const originalError = new ApiError( "Failed to delete release '1.0.0': 400 Bad Request", 400, - "Some other validation error" + "Some other validation error", ); deleteReleaseSpy.mockRejectedValue(originalError); diff --git a/packages/cli/test/commands/release/deploy.test.ts b/packages/cli/test/commands/release/deploy.test.ts index e22d7514d..3ccb843a9 100644 --- a/packages/cli/test/commands/release/deploy.test.ts +++ b/packages/cli/test/commands/release/deploy.test.ts @@ -12,11 +12,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -26,11 +26,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryDeploy } from "../../../src/types/index.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -105,7 +105,7 @@ describe("release deploy", () => { expect(createRelaseDeploySpy).toHaveBeenCalledWith( "my-org", "1.0.0", - expect.objectContaining({ environment: "staging", name: "Deploy #42" }) + expect.objectContaining({ environment: "staging", name: "Deploy #42" }), ); }); @@ -119,7 +119,7 @@ describe("release deploy", () => { context, { url: "https://example.com", json: true }, "1.0.0", - "production" + "production", ); expect(createRelaseDeploySpy).toHaveBeenCalledWith( @@ -128,7 +128,7 @@ describe("release deploy", () => { expect.objectContaining({ environment: "production", url: "https://example.com", - }) + }), ); }); @@ -137,7 +137,7 @@ describe("release deploy", () => { const func = await deployCommand.loader(); await expect(func.call(context, { json: false }, "1.0.0")).rejects.toThrow( - "Release version and environment" + "Release version and environment", ); }); @@ -146,7 +146,7 @@ describe("release deploy", () => { const func = await deployCommand.loader(); await expect(func.call(context, { json: false })).rejects.toThrow( - "Release version and environment" + "Release version and environment", ); }); }); diff --git a/packages/cli/test/commands/release/finalize.test.ts b/packages/cli/test/commands/release/finalize.test.ts index 6bd3faccf..87c82b913 100644 --- a/packages/cli/test/commands/release/finalize.test.ts +++ b/packages/cli/test/commands/release/finalize.test.ts @@ -12,11 +12,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -26,11 +26,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryRelease } from "../../../src/types/index.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -92,7 +92,7 @@ describe("release finalize", () => { expect(updateReleaseSpy).toHaveBeenCalledWith( "my-org", "1.0.0", - expect.objectContaining({ dateReleased: expect.any(String) }) + expect.objectContaining({ dateReleased: expect.any(String) }), ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); const parsed = JSON.parse(output); @@ -104,7 +104,7 @@ describe("release finalize", () => { const func = await finalizeCommand.loader(); await expect(func.call(context, { json: false })).rejects.toThrow( - "Release version" + "Release version", ); }); @@ -115,7 +115,7 @@ describe("release finalize", () => { const func = await finalizeCommand.loader(); await expect(func.call(context, { json: false }, "1.0.0")).rejects.toThrow( - "organization" + "organization", ); }); }); diff --git a/packages/cli/test/commands/release/propose-version.test.ts b/packages/cli/test/commands/release/propose-version.test.ts index 18189be18..d317b9338 100644 --- a/packages/cli/test/commands/release/propose-version.test.ts +++ b/packages/cli/test/commands/release/propose-version.test.ts @@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { proposeVersionCommand } from "../../../src/commands/release/propose-version.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as git from "../../../src/lib/git.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -38,7 +38,7 @@ describe("release propose-version", () => { test("outputs HEAD SHA in JSON mode when no env vars set", async () => { getHeadCommitSpy.mockResolvedValue( - "abc123def456789012345678901234567890abcd" + "abc123def456789012345678901234567890abcd", ); const { context, stdoutWrite } = createMockContext(); @@ -52,7 +52,7 @@ describe("release propose-version", () => { test("outputs bare SHA in human mode", async () => { getHeadCommitSpy.mockResolvedValue( - "abc123def456789012345678901234567890abcd" + "abc123def456789012345678901234567890abcd", ); const { context, stdoutWrite } = createMockContext(); @@ -80,7 +80,7 @@ describe("release propose-version", () => { const func = await proposeVersionCommand.loader(); await expect(func.call(context, { json: false })).rejects.toThrow( - "Not a git repository" + "Not a git repository", ); }); diff --git a/packages/cli/test/commands/release/set-commits.test.ts b/packages/cli/test/commands/release/set-commits.test.ts index b3330378f..0b9752599 100644 --- a/packages/cli/test/commands/release/set-commits.test.ts +++ b/packages/cli/test/commands/release/set-commits.test.ts @@ -15,12 +15,12 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); import { NO_REPO_INTEGRATIONS_MESSAGE } from "../../../src/lib/api/releases.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ApiError, ValidationError } from "../../../src/lib/errors.js"; @@ -31,13 +31,13 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as gitLib from "../../../src/lib/git.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryRelease } from "../../../src/types/index.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -104,7 +104,7 @@ describe("release set-commits --commit", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsWithRefsSpy).toHaveBeenCalledWith("my-org", "1.0.0", [ @@ -128,7 +128,7 @@ describe("release set-commits --commit", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsWithRefsSpy).toHaveBeenCalledWith("my-org", "1.0.0", [ @@ -156,7 +156,7 @@ describe("release set-commits --commit", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsWithRefsSpy).toHaveBeenCalledWith("my-org", "1.0.0", [ @@ -182,8 +182,8 @@ describe("release set-commits --commit", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("Invalid commit format"); }); @@ -204,8 +204,8 @@ describe("release set-commits --commit", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("Only one of --auto, --local, or --commit"); }); }); @@ -243,7 +243,7 @@ describe("release set-commits --clear", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsWithRefsSpy).toHaveBeenCalledWith("my-org", "1.0.0", []); @@ -280,13 +280,13 @@ describe("release set-commits --auto", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsAutoSpy).toHaveBeenCalledWith( "my-org", "1.0.0", - "/my/project" + "/my/project", ); }); }); @@ -311,12 +311,12 @@ describe("release set-commits (default mode)", () => { test("propagates unrelated 400 errors from setCommitsAuto", async () => { resolveOrgSpy.mockResolvedValue({ org: "my-org" }); setCommitsAutoSpy.mockRejectedValue( - new ApiError("Invalid commit SHA.", 400, undefined, "releases/1.0.0/") + new ApiError("Invalid commit SHA.", 400, undefined, "releases/1.0.0/"), ); const repoRoot = new URL("../../..", import.meta.url).pathname.replace( /\/$/, - "" + "", ); const { context } = createMockContext(repoRoot); const func = await setCommitsCommand.loader(); @@ -331,8 +331,8 @@ describe("release set-commits (default mode)", () => { "initial-depth": 20, json: true, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("Invalid commit SHA."); expect(setCommitsAutoSpy).toHaveBeenCalled(); @@ -346,14 +346,14 @@ describe("release set-commits (default mode)", () => { NO_REPO_INTEGRATIONS_MESSAGE, 400, undefined, - "releases/1.0.0/" - ) + "releases/1.0.0/", + ), ); setCommitsLocalSpy.mockResolvedValue(sampleRelease); const repoRoot = new URL("../../..", import.meta.url).pathname.replace( /\/$/, - "" + "", ); const { context } = createMockContext(repoRoot); const func = await setCommitsCommand.loader(); @@ -367,7 +367,7 @@ describe("release set-commits (default mode)", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsAutoSpy).toHaveBeenCalled(); @@ -379,15 +379,15 @@ describe("release set-commits (default mode)", () => { setCommitsAutoSpy.mockRejectedValue( new ValidationError( "No Sentry repository matching 'foo/bar'.", - "repository" - ) + "repository", + ), ); setCommitsLocalSpy.mockResolvedValue(sampleRelease); // Use the actual repo root as cwd so getCommitLog can read git history const repoRoot = new URL("../../..", import.meta.url).pathname.replace( /\/$/, - "" + "", ); const { context } = createMockContext(repoRoot); const func = await setCommitsCommand.loader(); @@ -401,7 +401,7 @@ describe("release set-commits (default mode)", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsAutoSpy).toHaveBeenCalled(); @@ -417,7 +417,7 @@ describe("release set-commits --path", () => { // Use the actual repo root as cwd so getCommitLog can read git history const repoRoot = new URL("../../..", import.meta.url).pathname.replace( /\/$/, - "" + "", ); beforeEach(() => { @@ -449,7 +449,7 @@ describe("release set-commits --path", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsLocalSpy).toHaveBeenCalled(); @@ -473,7 +473,7 @@ describe("release set-commits --path", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsLocalSpy).toHaveBeenCalled(); @@ -498,8 +498,8 @@ describe("release set-commits --path", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("--path requires at least one non-empty path"); }); @@ -521,8 +521,8 @@ describe("release set-commits --path", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("--path cannot be combined with --auto or --commit"); }); @@ -544,8 +544,8 @@ describe("release set-commits --path", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("--path cannot be combined with --auto or --commit"); }); }); @@ -559,7 +559,7 @@ describe("release set-commits --from", () => { // Use the actual repo root as cwd so the git remote/shallow helpers resolve. const repoRoot = new URL("../../..", import.meta.url).pathname.replace( /\/$/, - "" + "", ); beforeEach(() => { @@ -603,7 +603,7 @@ describe("release set-commits --from", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsLocalSpy).toHaveBeenCalled(); @@ -611,7 +611,7 @@ describe("release set-commits --from", () => { // A range is self-bounding, so depth 0 (no cap) is passed. expect(getCommitLogSpy).toHaveBeenCalledWith( repoRoot, - expect.objectContaining({ from: "HEAD~1", depth: 0, paths: [] }) + expect.objectContaining({ from: "HEAD~1", depth: 0, paths: [] }), ); }); @@ -633,14 +633,14 @@ describe("release set-commits --from", () => { "initial-depth": 20, json: true, }, - "1.0.0" + "1.0.0", ); expect(setCommitsLocalSpy).toHaveBeenCalled(); expect(setCommitsAutoSpy).not.toHaveBeenCalled(); expect(getCommitLogSpy).toHaveBeenCalledWith( repoRoot, - expect.objectContaining({ from: "HEAD~3", paths: ["src", "test"] }) + expect.objectContaining({ from: "HEAD~3", paths: ["src", "test"] }), ); }); @@ -662,8 +662,8 @@ describe("release set-commits --from", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("--from must be a git ref, not a CLI flag"); }); @@ -685,8 +685,8 @@ describe("release set-commits --from", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("--from requires a non-empty git ref"); }); @@ -708,8 +708,8 @@ describe("release set-commits --from", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("--from cannot be combined with --auto or --commit"); }); @@ -731,8 +731,8 @@ describe("release set-commits --from", () => { "initial-depth": 20, json: false, }, - "1.0.0" - ) + "1.0.0", + ), ).rejects.toThrow("--from cannot be combined with --auto or --commit"); }); }); diff --git a/packages/cli/test/commands/release/view.test.ts b/packages/cli/test/commands/release/view.test.ts index 673a15399..6c8874455 100644 --- a/packages/cli/test/commands/release/view.test.ts +++ b/packages/cli/test/commands/release/view.test.ts @@ -15,11 +15,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -29,11 +29,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryRelease } from "../../../src/types/index.js"; import { useTestConfigDir } from "../../helpers.js"; @@ -204,7 +204,7 @@ describe("release view", () => { const func = await viewCommand.loader(); await expect( - func.call(context, { fresh: false, json: false }) + func.call(context, { fresh: false, json: false }), ).rejects.toThrow("Release version"); }); @@ -215,7 +215,7 @@ describe("release view", () => { const func = await viewCommand.loader(); await expect( - func.call(context, { fresh: false, json: false }, "1.0.0") + func.call(context, { fresh: false, json: false }, "1.0.0"), ).rejects.toThrow("organization"); }); diff --git a/packages/cli/test/commands/replay/download.test.ts b/packages/cli/test/commands/replay/download.test.ts index d732ee122..6d9d6f492 100644 --- a/packages/cli/test/commands/replay/download.test.ts +++ b/packages/cli/test/commands/replay/download.test.ts @@ -18,11 +18,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ApiError, @@ -37,11 +37,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { ReplayDetails } from "../../../src/types/index.js"; @@ -156,7 +156,7 @@ describe("replay download", () => { await func.call( createContext().context, { fresh: false, output: "nested/dir/replay.json" }, - REPLAY_ID + REPLAY_ID, ); const events = await readEvents(join(tmpDir, "nested/dir/replay.json")); @@ -185,13 +185,13 @@ describe("replay download", () => { await func.call( createContext().context, { fresh: false }, - `https://sentry.io/organizations/url-org/explore/replays/${REPLAY_ID}/` + `https://sentry.io/organizations/url-org/explore/replays/${REPLAY_ID}/`, ); expect(resolveTargetSpy).toHaveBeenCalledWith( "url-org/", tmpDir, - "replay download" + "replay download", ); }); @@ -209,12 +209,12 @@ describe("replay download", () => { test("rejects an archived replay without fetching segments", async () => { resolveReplaySpy.mockResolvedValue( - sampleReplay({ is_archived: true, project_id: null }) + sampleReplay({ is_archived: true, project_id: null }), ); const func = await downloadCommand.loader(); await expect( - func.call(createContext().context, { fresh: false }, REPLAY_ID) + func.call(createContext().context, { fresh: false }, REPLAY_ID), ).rejects.toThrow(ResolutionError); expect(segmentsSpy).not.toHaveBeenCalled(); }); @@ -231,8 +231,8 @@ describe("replay download", () => { func.call( createContext().context, { fresh: false }, - `test-org/other/${REPLAY_ID}` - ) + `test-org/other/${REPLAY_ID}`, + ), ).rejects.toThrow(/is not in project 'other'/); expect(segmentsSpy).not.toHaveBeenCalled(); }); @@ -242,10 +242,10 @@ describe("replay download", () => { const func = await downloadCommand.loader(); await expect( - func.call(createContext().context, { fresh: false }, REPLAY_ID) + func.call(createContext().context, { fresh: false }, REPLAY_ID), ).rejects.toThrow(/has no recording to download/); await expect( - readFile(join(tmpDir, `${REPLAY_ID}.rrweb.json`)) + readFile(join(tmpDir, `${REPLAY_ID}.rrweb.json`)), ).rejects.toThrow(); }); @@ -254,7 +254,7 @@ describe("replay download", () => { const func = await downloadCommand.loader(); await expect( - func.call(createContext().context, { fresh: false }, REPLAY_ID) + func.call(createContext().context, { fresh: false }, REPLAY_ID), ).rejects.toThrow(/not found/); }); diff --git a/packages/cli/test/commands/replay/list.test.ts b/packages/cli/test/commands/replay/list.test.ts index 5918759fd..05f8b5103 100644 --- a/packages/cli/test/commands/replay/list.test.ts +++ b/packages/cli/test/commands/replay/list.test.ts @@ -13,11 +13,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { @@ -27,11 +27,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; import { LIST_PERIOD_FLAG } from "../../../src/lib/list-command.js"; @@ -42,11 +42,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { parsePeriod } from "../../../src/lib/time-range.js"; import { @@ -139,7 +139,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 25, json: true, period: parsePeriod("7d"), sort: "-started_at" }, - "test-org/cli" + "test-org/cli", ); expect(listReplaysSpy).toHaveBeenCalledWith("test-org", { @@ -166,7 +166,7 @@ describe("listCommand.func", () => { // --query is a user mistake, not a reported CLI bug. resolveTargetSpy.mockResolvedValue({ org: "test-org", project: "cli" }); listReplaysSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad field") + new ApiError("bad", 400, "Error parsing search query: bad field"), ); const { context } = createMockContext(); @@ -181,15 +181,15 @@ describe("listCommand.func", () => { sort: "-started_at", query: "bad:::query", }, - "test-org/cli" - ) + "test-org/cli", + ), ).rejects.toBeInstanceOf(ValidationError); }); test("keeps a search-query parse 400 as a reported ApiError when no --query is set", async () => { resolveTargetSpy.mockResolvedValue({ org: "test-org", project: "cli" }); listReplaysSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad field") + new ApiError("bad", 400, "Error parsing search query: bad field"), ); const { context } = createMockContext(); @@ -203,8 +203,8 @@ describe("listCommand.func", () => { period: parsePeriod("7d"), sort: "-started_at", }, - "test-org/cli" - ) + "test-org/cli", + ), ).rejects.toBeInstanceOf(ApiError); }); @@ -226,7 +226,7 @@ describe("listCommand.func", () => { period: parsePeriod("7d"), sort: "-started_at", }, - "test-org/cli" + "test-org/cli", ); expect(listReplaysSpy).toHaveBeenCalledWith("test-org", { @@ -255,7 +255,7 @@ describe("listCommand.func", () => { period: parsePeriod("7d"), sort: "-started_at", }, - "test-org/cli" + "test-org/cli", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); @@ -282,7 +282,7 @@ describe("listCommand.func", () => { period: parsePeriod(LIST_PERIOD_FLAG.default), sort: "-started_at", }, - "test-org/cli" + "test-org/cli", ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); diff --git a/packages/cli/test/commands/replay/view.test.ts b/packages/cli/test/commands/replay/view.test.ts index 1df458bf0..38bd70435 100644 --- a/packages/cli/test/commands/replay/view.test.ts +++ b/packages/cli/test/commands/replay/view.test.ts @@ -15,11 +15,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -29,11 +29,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { ApiError, @@ -49,11 +49,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { ReplayDetails } from "../../../src/types/index.js"; @@ -135,7 +135,7 @@ describe("parsePositionalArgs", () => { test("throws ValidationError for extra positional args", () => { expect(() => - parsePositionalArgs(["test-org/cli", REPLAY_ID, "extra-arg"]) + parsePositionalArgs(["test-org/cli", REPLAY_ID, "extra-arg"]), ).toThrow(ValidationError); }); }); @@ -216,7 +216,7 @@ describe("viewCommand.func", () => { resolveReplaySpy.mockResolvedValue( sampleReplay({ error_ids: ["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"], - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -224,7 +224,7 @@ describe("viewCommand.func", () => { await func.call( context, { json: true, web: false, fresh: false }, - REPLAY_ID + REPLAY_ID, ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); @@ -239,7 +239,7 @@ describe("viewCommand.func", () => { "test-org", "42", REPLAY_ID, - { expectedSegments: 5 } + { expectedSegments: 5 }, ); expect(listIssuesPaginatedSpy).toHaveBeenCalledWith( "test-org", @@ -247,7 +247,7 @@ describe("viewCommand.func", () => { expect.objectContaining({ perPage: 1, query: "event.id:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - }) + }), ); }); @@ -260,12 +260,12 @@ describe("viewCommand.func", () => { await func.call( context, { json: false, web: true, fresh: false }, - `test-org/${REPLAY_ID}` + `test-org/${REPLAY_ID}`, ); expect(openInBrowserSpy).toHaveBeenCalledWith( "https://test-org.sentry.io/explore/replays/346789a703f6454384f1de473b8b9fcc/", - "replay" + "replay", ); }); @@ -278,12 +278,12 @@ describe("viewCommand.func", () => { await func.call( context, { json: false, web: true, fresh: false }, - `https://sentry.io/organizations/test-org/explore/replays/${REPLAY_ID}/` + `https://sentry.io/organizations/test-org/explore/replays/${REPLAY_ID}/`, ); expect(openInBrowserSpy).toHaveBeenCalledWith( "https://test-org.sentry.io/explore/replays/346789a703f6454384f1de473b8b9fcc/", - "replay" + "replay", ); }); @@ -296,26 +296,26 @@ describe("viewCommand.func", () => { await func.call( context, { json: false, web: true, fresh: false }, - TRACE_ID + TRACE_ID, ); expect(openInBrowserSpy).toHaveBeenCalledWith( "https://test-org.sentry.io/explore/replays/346789a703f6454384f1de473b8b9fcc/", - "replay" + "replay", ); }); test("converts missing replays into ResolutionError", async () => { resolveTargetSpy.mockResolvedValue({ org: "test-org", project: "cli" }); resolveReplaySpy.mockRejectedValue( - new ApiError("Failed to get replay", 404, "Not Found") + new ApiError("Failed to get replay", 404, "Not Found"), ); const { context } = createMockContext(); const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false, fresh: false }, REPLAY_ID) + func.call(context, { json: false, web: false, fresh: false }, REPLAY_ID), ).rejects.toThrow(ResolutionError); }); @@ -327,7 +327,7 @@ describe("viewCommand.func", () => { const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false, fresh: false }, REPLAY_ID) + func.call(context, { json: false, web: false, fresh: false }, REPLAY_ID), ).rejects.toThrow(ResolutionError); }); @@ -338,7 +338,7 @@ describe("viewCommand.func", () => { count_segments: 0, is_archived: true, project_id: null, - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -346,7 +346,7 @@ describe("viewCommand.func", () => { await func.call( context, { json: true, web: false, fresh: false }, - REPLAY_ID + REPLAY_ID, ); expect(getProjectSpy).not.toHaveBeenCalled(); @@ -361,7 +361,7 @@ describe("viewCommand.func", () => { sampleReplay({ error_ids: ["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"], urls: ["/checkout"], - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -369,7 +369,7 @@ describe("viewCommand.func", () => { await func.call( context, { json: false, web: false, fresh: false }, - REPLAY_ID + REPLAY_ID, ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); @@ -385,7 +385,7 @@ describe("viewCommand.func", () => { resolveReplaySpy.mockResolvedValue( sampleReplay({ started_at: "2025-01-01T00:00:00.000Z", - }) + }), ); getReplayRecordingSegmentsSpy.mockResolvedValue([ [ @@ -401,7 +401,7 @@ describe("viewCommand.func", () => { await func.call( context, { json: false, web: false, fresh: false }, - REPLAY_ID + REPLAY_ID, ); const output = stdoutWrite.mock.calls.map((call) => call[0]).join(""); diff --git a/packages/cli/test/commands/repo/list.test.ts b/packages/cli/test/commands/repo/list.test.ts index 01e4e5f6a..2e05adcac 100644 --- a/packages/cli/test/commands/repo/list.test.ts +++ b/packages/cli/test/commands/repo/list.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.mocked access import * as apiClient from "../../../src/lib/api-client.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; @@ -31,11 +31,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.mocked access import * as paginationDb from "../../../src/lib/db/pagination.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; import { ValidationError } from "../../../src/lib/errors.js"; @@ -47,11 +47,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.mocked access import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryRepository } from "../../../src/types/sentry.js"; @@ -275,7 +275,7 @@ describe("listCommand.func — auto-detect mode", () => { const listRepositoriesSpy = vi.mocked(apiClient.listRepositories); const listOrganizationsSpy = vi.mocked(apiClient.listOrganizations); const resolveOrgsForListingSpy = vi.mocked( - resolveTarget.resolveOrgsForListing + resolveTarget.resolveOrgsForListing, ); beforeEach(() => { @@ -367,10 +367,10 @@ describe("listCommand.func — auto-detect mode", () => { describe("listCommand.func — org-all mode (cursor pagination)", () => { const listRepositoriesPaginatedSpy = vi.mocked( - apiClient.listRepositoriesPaginated + apiClient.listRepositoriesPaginated, ); const advancePaginationStateSpy = vi.mocked( - paginationDb.advancePaginationState + paginationDb.advancePaginationState, ); const hasPreviousPageSpy = vi.mocked(paginationDb.hasPreviousPage); const resolveCursorSpy = vi.mocked(paginationDb.resolveCursor); @@ -486,12 +486,12 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { await func.call( context, { limit: 25, json: false, cursor: "explicit:cursor:value" }, - "my-org/" + "my-org/", ); expect(listRepositoriesPaginatedSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ cursor: "explicit:cursor:value" }) + expect.objectContaining({ cursor: "explicit:cursor:value" }), ); }); @@ -510,12 +510,12 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { await func.call( context, { limit: 25, json: false, cursor: "next" }, - "my-org/" + "my-org/", ); expect(listRepositoriesPaginatedSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ cursor: "cached:cursor:456" }) + expect.objectContaining({ cursor: "cached:cursor:456" }), ); }); @@ -523,7 +523,7 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { resolveCursorSpy.mockImplementation(() => { throw new ValidationError( "No next page saved for this query. Run without --cursor first.", - "cursor" + "cursor", ); }); @@ -531,7 +531,7 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { const func = await listCommand.loader(); await expect( - func.call(context, { limit: 25, json: false, cursor: "next" }, "my-org/") + func.call(context, { limit: 25, json: false, cursor: "next" }, "my-org/"), ).rejects.toThrow("No next page saved"); }); @@ -543,8 +543,8 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { func.call( context, { limit: 25, json: false, cursor: "some-cursor" }, - "my-org/my-project" - ) + "my-org/my-project", + ), ).rejects.toThrow(ValidationError); }); @@ -560,7 +560,7 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { expect(listRepositoriesPaginatedSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ perPage: 10 }) + expect.objectContaining({ perPage: 10 }), ); }); }); diff --git a/packages/cli/test/commands/schema-path.test.ts b/packages/cli/test/commands/schema-path.test.ts index 77ba31b3d..4b118d817 100644 --- a/packages/cli/test/commands/schema-path.test.ts +++ b/packages/cli/test/commands/schema-path.test.ts @@ -85,7 +85,7 @@ describe("sentry schema path lookup", () => { await func.call( context, { json: true, all: false, search: "GET" }, - ORG_ISSUES + ORG_ISSUES, ); const parsed = JSON.parse(stdoutOf(stdoutWrite)); @@ -121,7 +121,7 @@ describe("sentry schema path lookup", () => { expect(err).toBeInstanceOf(ResolutionError); const message = (err as ResolutionError).message; expect(message).toContain( - "Available methods at this path: DELETE, GET, PUT" + "Available methods at this path: DELETE, GET, PUT", ); expect(message).toContain(`sentry schema "GET ${ORG_ISSUES}"`); }); @@ -135,7 +135,7 @@ describe("sentry schema path lookup", () => { expect(Array.isArray(parsed)).toBe(true); expect(parsed.length).toBeGreaterThan(3); expect( - parsed.every((e: { resource: string }) => e.resource === "issues") + parsed.every((e: { resource: string }) => e.resource === "issues"), ).toBe(true); }); }); diff --git a/packages/cli/test/commands/schema.test.ts b/packages/cli/test/commands/schema.test.ts index 64392bc8f..bdb1cdb44 100644 --- a/packages/cli/test/commands/schema.test.ts +++ b/packages/cli/test/commands/schema.test.ts @@ -33,7 +33,7 @@ describe("resolveResourceQuery", () => { test("does not interpret a partial operation identifier as exact", () => { expect(() => resolveResourceQuery("OrganizationEvents")).toThrow( - ResolutionError + ResolutionError, ); }); test("no-match resource throws ResolutionError instead of listing everything", () => { diff --git a/packages/cli/test/commands/send-envelope.test.ts b/packages/cli/test/commands/send-envelope.test.ts index e43b4fa5f..1db3406b8 100644 --- a/packages/cli/test/commands/send-envelope.test.ts +++ b/packages/cli/test/commands/send-envelope.test.ts @@ -30,8 +30,8 @@ describe("sendEnvelopeCommand (deprecation shim)", () => { func.call( ctx, { dsn: "https://x@o1.ingest.sentry.io/1" }, - "file.envelope" - ) + "file.envelope", + ), ).rejects.toBeInstanceOf(CliError); }); @@ -41,7 +41,7 @@ describe("sendEnvelopeCommand (deprecation shim)", () => { await func.call( ctx, { dsn: "https://x@o1.ingest.sentry.io/1" }, - "my.envelope" + "my.envelope", ); expect.unreachable("should have thrown"); } catch (err) { @@ -57,7 +57,7 @@ describe("sendEnvelopeCommand (deprecation shim)", () => { expect.unreachable("should have thrown"); } catch (err) { expect((err as CliError).message).toContain( - "sentry event send --raw <file>" + "sentry event send --raw <file>", ); } }); diff --git a/packages/cli/test/commands/snapshots/diff.test.ts b/packages/cli/test/commands/snapshots/diff.test.ts index ae109ca87..e80595888 100644 --- a/packages/cli/test/commands/snapshots/diff.test.ts +++ b/packages/cli/test/commands/snapshots/diff.test.ts @@ -43,7 +43,7 @@ function createContext() { stdout: { write: (data: string | Uint8Array) => { writes.push( - typeof data === "string" ? data : new TextDecoder().decode(data) + typeof data === "string" ? data : new TextDecoder().decode(data), ); return true; }, @@ -102,7 +102,7 @@ describe("snapshots diff", () => { harness.context, { output: outDir, "fail-on-diff": true }, baseDir, - headDir + headDir, ); expect(harness.exitCode).toBe(1); @@ -118,7 +118,7 @@ describe("snapshots diff", () => { harness.context, { output: outDir, "fail-on-diff": true }, baseDir, - headDir + headDir, ); expect(harness.exitCode ?? 0).toBe(0); @@ -149,7 +149,7 @@ describe("snapshots diff", () => { removedHarness.context, { output: outDir, "fail-on-diff": true }, baseDir, - headDir + headDir, ); expect(removedHarness.exitCode).toBe(1); @@ -159,7 +159,7 @@ describe("snapshots diff", () => { skippedHarness.context, { output: outDir, "fail-on-diff": true, selective: true }, baseDir, - headDir + headDir, ); expect(skippedHarness.exitCode ?? 0).toBe(0); }); @@ -175,7 +175,7 @@ describe("snapshots diff", () => { harness.context, { output: outDir, "fail-on-diff": true }, baseDir, - headDir + headDir, ); // Errored images count toward --fail-on-diff, and the run still completes. @@ -193,7 +193,7 @@ describe("snapshots diff", () => { harness.context, { output: outDir, "no-antialiasing": true }, baseDir, - headDir + headDir, ); expect(existsSync(join(outDir, "a.png"))).toBe(true); @@ -203,7 +203,7 @@ describe("snapshots diff", () => { const harness = createContext(); const func = await diffCommand.loader(); await expect( - func.call(harness.context, {}, join(tmpDir, "nope"), headDir) + func.call(harness.context, {}, join(tmpDir, "nope"), headDir), ).rejects.toThrow(ValidationError); }); }); diff --git a/packages/cli/test/commands/snapshots/download.test.ts b/packages/cli/test/commands/snapshots/download.test.ts index 934563343..dae94ca7e 100644 --- a/packages/cli/test/commands/snapshots/download.test.ts +++ b/packages/cli/test/commands/snapshots/download.test.ts @@ -12,10 +12,10 @@ import { join } from "node:path"; import { strToU8, zipSync } from "fflate"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { downloadCommand } from "../../../src/commands/snapshots/download.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as preprod from "../../../src/lib/api/preprod-artifacts.js"; import { ContextError, ValidationError } from "../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; let tmpDir: string; @@ -27,7 +27,7 @@ function createContext() { stdout: { write: (data: string | Uint8Array) => { writes.push( - typeof data === "string" ? data : new TextDecoder().decode(data) + typeof data === "string" ? data : new TextDecoder().decode(data), ); return true; }, @@ -43,7 +43,7 @@ function createContext() { function snapshotZip(): Buffer { return Buffer.from( - zipSync({ "img1.png": strToU8("A"), "img2.png": strToU8("B") }) + zipSync({ "img1.png": strToU8("A"), "img2.png": strToU8("B") }), ); } @@ -76,7 +76,7 @@ describe("snapshots download", () => { ok: true, status: 200, body: streamOf(snapshotZip()), - } as unknown as Response) + } as unknown as Response), ); latestSpy = vi.spyOn(preprod, "getLatestBaseSnapshot").mockResolvedValue({ headArtifactId: "resolved-art", @@ -102,7 +102,7 @@ describe("snapshots download", () => { expect(waitSpy).toHaveBeenCalledWith( "test-org", "snap-1", - expect.any(Function) + expect.any(Function), ); expect(downloadSpy).toHaveBeenCalledWith("test-org", "snap-1"); expect(latestSpy).not.toHaveBeenCalled(); @@ -133,14 +133,14 @@ describe("snapshots download", () => { func.call(createContext().context, { "app-id": "a", "snapshot-id": "s", - }) + }), ).rejects.toThrow(ValidationError); }); test("rejects when neither --app-id nor --snapshot-id is given", async () => { const func = await downloadCommand.loader(); await expect(func.call(createContext().context, {})).rejects.toThrow( - ContextError + ContextError, ); }); @@ -150,7 +150,7 @@ describe("snapshots download", () => { func.call(createContext().context, { "snapshot-id": "s", branch: "main", - }) + }), ).rejects.toThrow(ValidationError); }); }); diff --git a/packages/cli/test/commands/snapshots/upload.test.ts b/packages/cli/test/commands/snapshots/upload.test.ts index 1ffc0c5a1..e44112377 100644 --- a/packages/cli/test/commands/snapshots/upload.test.ts +++ b/packages/cli/test/commands/snapshots/upload.test.ts @@ -12,12 +12,12 @@ import { join } from "node:path"; import { PNG } from "pngjs"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { uploadCommand } from "../../../src/commands/snapshots/upload.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as preprod from "../../../src/lib/api/preprod-artifacts.js"; import { ValidationError } from "../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as objectstore from "../../../src/lib/objectstore.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; let tmpDir: string; @@ -29,7 +29,7 @@ function createContext() { stdout: { write: (data: string | Uint8Array) => { writes.push( - typeof data === "string" ? data : new TextDecoder().decode(data) + typeof data === "string" ? data : new TextDecoder().decode(data), ); return true; }, @@ -104,50 +104,50 @@ describe("snapshots upload", () => { return dir; } - test.each([ - "preprod", - "preprod_snapshots", - ])("uploads images to %s and creates a snapshot with a correct manifest", async (usecase) => { - const config = { ...UPLOAD_OPTIONS.objectstore, usecase }; - uploadOptionsSpy.mockResolvedValue({ objectstore: config }); - const dir = await writeShots(); - const harness = createContext(); - const func = await uploadCommand.loader(); - - await func.call(harness.context, { "app-id": "com.example.app" }, dir); - - // Two images uploaded (none pre-existing). - expect(putSpy).toHaveBeenCalledTimes(2); - expect(createSpy).toHaveBeenCalledTimes(1); - - const [, , manifest] = createSpy.mock.calls[0] as [ - string, - string, - Record<string, unknown>, - ]; - expect(manifest.app_id).toBe("com.example.app"); - const images = manifest.images as Record<string, Record<string, unknown>>; - expect(Object.keys(images).sort()).toEqual(["a.png", "sub/b.png"]); - expect(images["a.png"]).toMatchObject({ - width: 4, - height: 3, - note: "hi", - }); - expect(images["a.png"].content_hash).toMatch(/^[0-9a-f]{64}$/); - // selective omitted when not requested. - expect(manifest.selective).toBeUndefined(); - expect(harness.output()).toContain("snap-1"); - - // The objectstore key is `{orgId}/{projectId}/{sha256}` from the scope. - const hash = images["a.png"].content_hash as string; - const key = putSpy.mock.calls.find(([, k]) => - (k as string).endsWith(hash) - )?.[1] as string; - expect(key).toMatch(/^1\/2\/[0-9a-f]{64}$/); - expect(key.endsWith(hash)).toBe(true); - expect(existsSpy).toHaveBeenCalledWith(config, key); - expect(putSpy).toHaveBeenCalledWith(config, key, expect.any(Uint8Array)); - }); + test.each(["preprod", "preprod_snapshots"])( + "uploads images to %s and creates a snapshot with a correct manifest", + async (usecase) => { + const config = { ...UPLOAD_OPTIONS.objectstore, usecase }; + uploadOptionsSpy.mockResolvedValue({ objectstore: config }); + const dir = await writeShots(); + const harness = createContext(); + const func = await uploadCommand.loader(); + + await func.call(harness.context, { "app-id": "com.example.app" }, dir); + + // Two images uploaded (none pre-existing). + expect(putSpy).toHaveBeenCalledTimes(2); + expect(createSpy).toHaveBeenCalledTimes(1); + + const [, , manifest] = createSpy.mock.calls[0] as [ + string, + string, + Record<string, unknown>, + ]; + expect(manifest.app_id).toBe("com.example.app"); + const images = manifest.images as Record<string, Record<string, unknown>>; + expect(Object.keys(images).sort()).toEqual(["a.png", "sub/b.png"]); + expect(images["a.png"]).toMatchObject({ + width: 4, + height: 3, + note: "hi", + }); + expect(images["a.png"].content_hash).toMatch(/^[0-9a-f]{64}$/); + // selective omitted when not requested. + expect(manifest.selective).toBeUndefined(); + expect(harness.output()).toContain("snap-1"); + + // The objectstore key is `{orgId}/{projectId}/{sha256}` from the scope. + const hash = images["a.png"].content_hash as string; + const key = putSpy.mock.calls.find(([, k]) => + (k as string).endsWith(hash), + )?.[1] as string; + expect(key).toMatch(/^1\/2\/[0-9a-f]{64}$/); + expect(key.endsWith(hash)).toBe(true); + expect(existsSpy).toHaveBeenCalledWith(config, key); + expect(putSpy).toHaveBeenCalledWith(config, key, expect.any(Uint8Array)); + }, + ); test("CLI width/height/content_hash override sidecar keys", async () => { const dir = join(tmpDir, "shots"); @@ -155,7 +155,12 @@ describe("snapshots upload", () => { await writeFile(join(dir, "a.png"), pngBytes(4, 3)); await writeFile( join(dir, "a.json"), - JSON.stringify({ width: 999, height: 888, content_hash: "nope", keep: 1 }) + JSON.stringify({ + width: 999, + height: 888, + content_hash: "nope", + keep: 1, + }), ); const harness = createContext(); const func = await uploadCommand.loader(); @@ -183,8 +188,8 @@ describe("snapshots upload", () => { func.call( createContext().context, { "app-id": "app", "pr-number": 7 }, - dir - ) + dir, + ), ).rejects.toThrow(ValidationError); }); @@ -241,7 +246,7 @@ describe("snapshots upload", () => { "app-id": "app", "all-image-file-names": "./a.png,sub\\b.jpg", }, - dir + dir, ); expect(uploadOptionsSpy).not.toHaveBeenCalled(); @@ -273,7 +278,7 @@ describe("snapshots upload", () => { await func.call( createContext().context, { "app-id": "app", "all-image-file-names-file": namesFile }, - dir + dir, ); expect(uploadOptionsSpy).not.toHaveBeenCalled(); @@ -311,8 +316,8 @@ describe("snapshots upload", () => { "app-id": "app", "all-image-file-names-file": join(tmpDir, "missing.txt"), }, - dir - ) + dir, + ), ).rejects.toThrow(ValidationError); expect(createSpy).not.toHaveBeenCalled(); }); @@ -328,8 +333,8 @@ describe("snapshots upload", () => { func.call( createContext().context, { "app-id": "app", "all-image-file-names-file": namesFile }, - dir - ) + dir, + ), ).rejects.toThrow(ValidationError); expect(createSpy).not.toHaveBeenCalled(); }); @@ -343,7 +348,7 @@ describe("snapshots upload", () => { const func = await uploadCommand.loader(); await expect( - func.call(createContext().context, { "app-id": "app", ...flag }, dir) + func.call(createContext().context, { "app-id": "app", ...flag }, dir), ).rejects.toThrow(ValidationError); expect(createSpy).not.toHaveBeenCalled(); }); @@ -353,7 +358,7 @@ describe("snapshots upload", () => { await writeFile(file, pngBytes(1, 1)); const func = await uploadCommand.loader(); await expect( - func.call(createContext().context, { "app-id": "app" }, file) + func.call(createContext().context, { "app-id": "app" }, file), ).rejects.toThrow(ValidationError); }); @@ -364,8 +369,8 @@ describe("snapshots upload", () => { func.call( createContext().context, { "app-id": "app", "all-image-file-names": "a.png" }, - dir - ) + dir, + ), ).rejects.toThrow(ValidationError); }); @@ -377,7 +382,7 @@ describe("snapshots upload", () => { await func.call( harness.context, { "app-id": "app", "diff-threshold": 0.05, selective: true }, - dir + dir, ); const [, , manifest] = createSpy.mock.calls[0] as [ diff --git a/packages/cli/test/commands/sourcemap/inject.test.ts b/packages/cli/test/commands/sourcemap/inject.test.ts index 20e037bf9..0edbc5472 100644 --- a/packages/cli/test/commands/sourcemap/inject.test.ts +++ b/packages/cli/test/commands/sourcemap/inject.test.ts @@ -19,7 +19,7 @@ type InjectFuncArgs = { type CmdFunc = ( this: unknown, flags: InjectFuncArgs, - dir: string + dir: string, ) => Promise<unknown>; function makeContext() { @@ -56,11 +56,11 @@ describe("sourcemap inject command — runs without authentication", () => { test("injects debug IDs with no stored credentials and no env token", async () => { await writeFile( join(dir, "app.js"), - "console.log(1);\n//# sourceMappingURL=app.js.map\n" + "console.log(1);\n//# sourceMappingURL=app.js.map\n", ); await writeFile( join(dir, "app.js.map"), - '{"version":3,"file":"app.js","sources":["app.ts"],"names":[],"mappings":"AAAA"}\n' + '{"version":3,"file":"app.js","sources":["app.ts"],"names":[],"mappings":"AAAA"}\n', ); const ctx = makeContext(); diff --git a/packages/cli/test/commands/sourcemap/upload.test.ts b/packages/cli/test/commands/sourcemap/upload.test.ts index 097a77624..cfb2a1fc0 100644 --- a/packages/cli/test/commands/sourcemap/upload.test.ts +++ b/packages/cli/test/commands/sourcemap/upload.test.ts @@ -11,7 +11,7 @@ import { runInNewContext } from "node:vm"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { injectCommand } from "../../../src/commands/sourcemap/inject.js"; import { uploadCommand } from "../../../src/commands/sourcemap/upload.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as sourcemapsApi from "../../../src/lib/api/sourcemaps.js"; import { ValidationError } from "../../../src/lib/errors.js"; import { getDebugIdSnippet } from "../../../src/lib/sourcemap/debug-id.js"; @@ -73,7 +73,7 @@ describe("sourcemap inject command — --allow-empty behavior", () => { test("empty directory with --allow-empty: succeeds silently", async () => { const ctx = makeContext(); await expect( - func.call(ctx, { "allow-empty": true }, dir) + func.call(ctx, { "allow-empty": true }, dir), ).resolves.toBeUndefined(); }); @@ -163,14 +163,14 @@ describe("sourcemap inject command — --allow-empty behavior", () => { test("--dry-run + empty directory: still errors (dry-run is not an escape hatch)", async () => { const ctx = makeContext(); await expect( - func.call(ctx, { "dry-run": true }, dir) + func.call(ctx, { "dry-run": true }, dir), ).rejects.toBeInstanceOf(ValidationError); }); test("--dry-run + --allow-empty: succeeds silently", async () => { const ctx = makeContext(); await expect( - func.call(ctx, { "dry-run": true, "allow-empty": true }, dir) + func.call(ctx, { "dry-run": true, "allow-empty": true }, dir), ).resolves.toBeUndefined(); }); @@ -178,12 +178,12 @@ describe("sourcemap inject command — --allow-empty behavior", () => { // JS file with sourceMappingURL pointing to a differently-named map await writeFile( join(dir, "bundle.js"), - "console.log(1)\n//# sourceMappingURL=bundle.abc123.js.map\n" + "console.log(1)\n//# sourceMappingURL=bundle.abc123.js.map\n", ); // Map file with non-convention name (no bundle.js.map exists) await writeFile( join(dir, "bundle.abc123.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); const ctx = makeContext(); await expect(func.call(ctx, {}, dir)).resolves.toBeUndefined(); @@ -193,17 +193,17 @@ describe("sourcemap inject command — --allow-empty behavior", () => { // JS file with sourceMappingURL pointing to a different file await writeFile( join(dir, "app.js"), - "console.log(1)\n//# sourceMappingURL=other.js.map\n" + "console.log(1)\n//# sourceMappingURL=other.js.map\n", ); // Convention map exists — should be used await writeFile( join(dir, "app.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); // The directive target also exists await writeFile( join(dir, "other.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); const ctx = makeContext(); // Should succeed (convention-based match found first) @@ -214,7 +214,7 @@ describe("sourcemap inject command — --allow-empty behavior", () => { // eyJ2ZXJzaW9uIjozfQ== === {"version":3} await writeFile( join(dir, "inline.js"), - "console.log(1)\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==\n" + "console.log(1)\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==\n", ); // No convention map — the inline map is discovered as a pair and injected. const ctx = makeContext(); @@ -224,12 +224,12 @@ describe("sourcemap inject command — --allow-empty behavior", () => { test("sourceMappingURL: invalid inline base64 is skipped (zero pairs)", async () => { await writeFile( join(dir, "bad-inline.js"), - "console.log(1)\n//# sourceMappingURL=data:application/json;base64,@@@not-base64@@@\n" + "console.log(1)\n//# sourceMappingURL=data:application/json;base64,@@@not-base64@@@\n", ); // Non-fatal skip → no pairs → actionable ValidationError, not a crash. const ctx = makeContext(); await expect(func.call(ctx, {}, dir)).rejects.toBeInstanceOf( - ValidationError + ValidationError, ); }); }); @@ -278,7 +278,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { test("empty directory with --allow-empty: succeeds silently", async () => { const ctx = makeContext(); await expect( - func.call(ctx, { "allow-empty": true }, dir) + func.call(ctx, { "allow-empty": true }, dir), ).resolves.toBeUndefined(); }); @@ -290,7 +290,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { delete process.env.SENTRY_PROJECT; const ctx = makeContext(); await expect( - func.call(ctx, { "allow-empty": true }, dir) + func.call(ctx, { "allow-empty": true }, dir), ).resolves.toBeUndefined(); }); @@ -299,7 +299,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { await writeFile(join(dir, "_astro", "app.js"), "console.log(1)\n"); const ctx = makeContext(); await expect(func.call(ctx, {}, dir)).rejects.toBeInstanceOf( - ValidationError + ValidationError, ); }); @@ -328,7 +328,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { await writeFile(jsPath, original); const ctx = makeContext(); await expect(func.call(ctx, {}, dir)).rejects.toBeInstanceOf( - ValidationError + ValidationError, ); const after = await readFile(jsPath, "utf-8"); expect(after).toBe(original); @@ -348,7 +348,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { sources: ["app.ts"], names: [], mappings: "", - }) + }), ); const uploadSpy = vi @@ -376,7 +376,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { const dataUrl = `data:application/json;base64,${Buffer.from(JSON.stringify(map)).toString("base64")}`; await writeFile( jsPath, - `console.log(1)\n//# sourceMappingURL=${dataUrl}\n` + `console.log(1)\n//# sourceMappingURL=${dataUrl}\n`, ); const uploadSpy = vi @@ -396,7 +396,8 @@ describe("sourcemap upload command — --allow-empty behavior", () => { expect(mapFile?.debugId).toBe(js?.debugId); // The uploaded map carries the injected debug ID. const uploaded = JSON.parse( - (mapFile?.content as Buffer).toString("utf-8") + // oxlint-disable-next-line no-unsafe-optional-chaining -- Existing test assumes the optional fixture is present. + (mapFile?.content as Buffer).toString("utf-8"), ); expect(uploaded.debug_id).toBe(js?.debugId); } finally { @@ -410,7 +411,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { const dataUrl = `data:application/json;base64,${Buffer.from(JSON.stringify(map)).toString("base64")}`; await writeFile( jsPath, - `console.log(1)\n//# sourceMappingURL=${dataUrl}\n` + `console.log(1)\n//# sourceMappingURL=${dataUrl}\n`, ); const uploadSpy = vi @@ -429,7 +430,8 @@ describe("sourcemap upload command — --allow-empty behavior", () => { // The source_map entry carries the original (un-injected) map content. expect(mapFile?.content).toBeInstanceOf(Buffer); const uploaded = JSON.parse( - (mapFile?.content as Buffer).toString("utf-8") + // oxlint-disable-next-line no-unsafe-optional-chaining -- Existing test assumes the optional fixture is present. + (mapFile?.content as Buffer).toString("utf-8"), ); expect(uploaded.version).toBe(3); expect(uploaded.debug_id).toBeUndefined(); @@ -448,7 +450,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { sources: ["app.ts"], names: [], mappings: "", - }) + }), ); const uploadSpy = vi @@ -479,7 +481,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { sources: ["app.ts"], names: [], mappings: "", - }) + }), ); const uploadSpy = vi @@ -512,7 +514,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { sources: ["app.ts"], names: [], mappings: "", - }) + }), ); // A .js file that should NOT be discovered when --ext is .ts await writeFile(join(dir, "other.js"), "console.log(2)\n"); @@ -540,13 +542,13 @@ describe("sourcemap upload command — --allow-empty behavior", () => { await writeFile(join(dir, "vendor", "lib.js"), "console.log(1)\n"); await writeFile( join(dir, "vendor", "lib.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); // File that should be included await writeFile(join(dir, "app.js"), "console.log(2)\n"); await writeFile( join(dir, "app.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); const uploadSpy = vi @@ -572,12 +574,12 @@ describe("sourcemap upload command — --allow-empty behavior", () => { await writeFile(join(dir, "vendor", "lib.js"), "console.log(1)\n"); await writeFile( join(dir, "vendor", "lib.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); await writeFile(join(dir, "app.js"), "console.log(2)\n"); await writeFile( join(dir, "app.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); // Write an ignore file const ignoreFilePath = join(dir, ".sourcemapignore"); @@ -604,7 +606,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { await writeFile(join(dir, "app.js"), "console.log(1)\n"); await writeFile( join(dir, "app.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); const ctx = makeContext(); try { @@ -621,7 +623,7 @@ describe("sourcemap upload command — --allow-empty behavior", () => { await writeFile(join(dir, "static", "js", "app.js"), "console.log(1)\n"); await writeFile( join(dir, "static", "js", "app.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); const uploadSpy = vi @@ -645,19 +647,19 @@ describe("sourcemap upload command — --allow-empty behavior", () => { await mkdir(join(dir, "build", "output"), { recursive: true }); await writeFile( join(dir, "build", "output", "main.js"), - "console.log(1)\n" + "console.log(1)\n", ); await writeFile( join(dir, "build", "output", "main.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); await writeFile( join(dir, "build", "output", "vendor.js"), - "console.log(2)\n" + "console.log(2)\n", ); await writeFile( join(dir, "build", "output", "vendor.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); const uploadSpy = vi @@ -682,15 +684,15 @@ describe("sourcemap upload command — --allow-empty behavior", () => { await writeFile(join(dir, "app.js"), "console.log(1)\n"); await writeFile( join(dir, "app.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); const ctx = makeContext(); await expect( func.call( ctx, { "strip-prefix": "foo/", "strip-common-prefix": true }, - dir - ) + dir, + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -703,16 +705,16 @@ describe("sourcemap upload command — --allow-empty behavior", () => { "console.log(1)\n" + "//# sourceMappingURL=wrong.js.map\n" + "console.log(2)\n" + - "//# sourceMappingURL=correct.js.map\n" + "//# sourceMappingURL=correct.js.map\n", ); await writeFile( join(dir, "correct.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); // wrong.js.map also exists — but the last directive should win await writeFile( join(dir, "wrong.js.map"), - JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }) + JSON.stringify({ version: 3, sources: [], names: [], mappings: "" }), ); const uploadSpy = vi @@ -737,80 +739,80 @@ describe("sourcemap upload command — --allow-empty behavior", () => { { mapKind: "external", idSource: "map only" }, { mapKind: "inline", idSource: "comment and map" }, { mapKind: "inline", idSource: "map only" }, - ])("$mapKind map with ID in $idSource: uploads runtime registration and shifted mappings once", async ({ - mapKind, - idSource, - }) => { - const debugId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; - const jsPath = join(dir, "app.js"); - const mapPath = join(dir, "app.js.map"); - const map = JSON.stringify({ - version: 3, - sources: ["app.ts"], - sourcesContent: ["globalThis.completed = true;"], - names: [], - mappings: "AAAA", - debug_id: debugId, - }); - const mapUrl = - mapKind === "inline" - ? `data:application/json;base64,${Buffer.from(map).toString("base64")}` - : "app.js.map"; - const comment = - idSource === "comment and map" ? `//# debugId=${debugId}\n` : ""; - await writeFile( - jsPath, - `globalThis.completed = true;\n${comment}//# sourceMappingURL=${mapUrl}\n` - ); - if (mapKind === "external") { - await writeFile(mapPath, map); - } - - const uploadSpy = vi - .spyOn(sourcemapsApi, "uploadSourcemaps") - .mockResolvedValue(undefined); - try { - const ctx = makeContext(); - await func.call(ctx, {}, dir); - const files = uploadSpy.mock.calls[0]?.[0]?.files ?? []; - expect(files).toHaveLength(2); - const jsFile = files.find((file) => file.type === "minified_source"); - const mapFile = files.find((file) => file.type === "source_map"); - expect(jsFile?.path).toBe(jsPath); - expect(jsFile?.debugId).toBe(debugId); - expect(mapFile?.debugId).toBe(debugId); - - const uploadedJs = await readFile(jsPath, "utf-8"); - const runtime: { - _sentryDebugIds?: Record<string, string>; - completed?: boolean; - } = {}; - runInNewContext(uploadedJs, runtime); - expect(runtime.completed).toBe(true); - expect(Object.values(runtime._sentryDebugIds ?? {})).toEqual([debugId]); - const uploadedMap = - mapFile?.content?.toString("utf-8") ?? - (await readFile(mapPath, "utf-8")); - expect(JSON.parse(uploadedMap)).toMatchObject({ + ])( + "$mapKind map with ID in $idSource: uploads runtime registration and shifted mappings once", + async ({ mapKind, idSource }) => { + const debugId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + const jsPath = join(dir, "app.js"); + const mapPath = join(dir, "app.js.map"); + const map = JSON.stringify({ + version: 3, + sources: ["app.ts"], + sourcesContent: ["globalThis.completed = true;"], + names: [], + mappings: "AAAA", debug_id: debugId, - debugId, - mappings: ";AAAA", }); - - await func.call(ctx, {}, dir); - expect(uploadSpy).toHaveBeenCalledTimes(2); - expect(await readFile(jsPath, "utf-8")).toBe(uploadedJs); - const secondMap = uploadSpy.mock.calls[1]?.[0]?.files.find( - (file) => file.type === "source_map" + const mapUrl = + mapKind === "inline" + ? `data:application/json;base64,${Buffer.from(map).toString("base64")}` + : "app.js.map"; + const comment = + idSource === "comment and map" ? `//# debugId=${debugId}\n` : ""; + await writeFile( + jsPath, + `globalThis.completed = true;\n${comment}//# sourceMappingURL=${mapUrl}\n`, ); - expect( - secondMap?.content?.toString("utf-8") ?? - (await readFile(mapPath, "utf-8")) - ).toBe(uploadedMap); - } finally { - uploadSpy.mockRestore(); - } - }); + if (mapKind === "external") { + await writeFile(mapPath, map); + } + + const uploadSpy = vi + .spyOn(sourcemapsApi, "uploadSourcemaps") + .mockResolvedValue(undefined); + try { + const ctx = makeContext(); + await func.call(ctx, {}, dir); + const files = uploadSpy.mock.calls[0]?.[0]?.files ?? []; + expect(files).toHaveLength(2); + const jsFile = files.find((file) => file.type === "minified_source"); + const mapFile = files.find((file) => file.type === "source_map"); + expect(jsFile?.path).toBe(jsPath); + expect(jsFile?.debugId).toBe(debugId); + expect(mapFile?.debugId).toBe(debugId); + + const uploadedJs = await readFile(jsPath, "utf-8"); + const runtime: { + _sentryDebugIds?: Record<string, string>; + completed?: boolean; + } = {}; + runInNewContext(uploadedJs, runtime); + expect(runtime.completed).toBe(true); + expect(Object.values(runtime._sentryDebugIds ?? {})).toEqual([debugId]); + const uploadedMap = + mapFile?.content?.toString("utf-8") ?? + (await readFile(mapPath, "utf-8")); + expect(JSON.parse(uploadedMap)).toMatchObject({ + debug_id: debugId, + debugId, + mappings: ";AAAA", + }); + + await func.call(ctx, {}, dir); + expect(uploadSpy).toHaveBeenCalledTimes(2); + expect(await readFile(jsPath, "utf-8")).toBe(uploadedJs); + const secondMap = uploadSpy.mock.calls[1]?.[0]?.files.find( + (file) => file.type === "source_map", + ); + expect( + secondMap?.content?.toString("utf-8") ?? + (await readFile(mapPath, "utf-8")), + ).toBe(uploadedMap); + } finally { + uploadSpy.mockRestore(); + } + }, + ); test("pre-existing map debug ID: uploaded on both entries, files untouched", async () => { // What a bundler plugin running with `sourcemaps.disable: 'disable-upload'` @@ -839,10 +841,10 @@ describe("sourcemap upload command — --allow-empty behavior", () => { const files = uploadSpy.mock.calls[0]?.[0]?.files ?? []; expect(files).toHaveLength(2); expect(files.find((f) => f.type === "minified_source")?.debugId).toBe( - pluginId + pluginId, ); expect(files.find((f) => f.type === "source_map")?.debugId).toBe( - pluginId + pluginId, ); expect(await readFile(jsPath, "utf-8")).toBe(js); expect(await readFile(mapPath, "utf-8")).toBe(map); @@ -875,11 +877,12 @@ describe("sourcemap upload command — --allow-empty behavior", () => { expect(files).toHaveLength(2); const mapFile = files.find((f) => f.type === "source_map"); expect(files.find((f) => f.type === "minified_source")?.debugId).toBe( - pluginId + pluginId, ); expect(mapFile?.debugId).toBe(pluginId); expect( - JSON.parse((mapFile?.content as Buffer).toString("utf-8")).debug_id + // oxlint-disable-next-line no-unsafe-optional-chaining -- Existing test assumes the optional fixture is present. + JSON.parse((mapFile?.content as Buffer).toString("utf-8")).debug_id, ).toBe(pluginId); expect(await readFile(jsPath, "utf-8")).toBe(js); } finally { diff --git a/packages/cli/test/commands/span/list.test.ts b/packages/cli/test/commands/span/list.test.ts index 127ab63d8..ef3c0237b 100644 --- a/packages/cli/test/commands/span/list.test.ts +++ b/packages/cli/test/commands/span/list.test.ts @@ -22,11 +22,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { @@ -36,11 +36,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -50,12 +50,12 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); import { ApiError, ValidationError } from "../../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { parsePeriod } from "../../../src/lib/time-range.js"; @@ -245,7 +245,7 @@ describe("listCommand.func (trace mode)", () => { period: parsePeriod("7d"), fresh: false, }, - VALID_TRACE_ID + VALID_TRACE_ID, ); expect(listSpansSpy).toHaveBeenCalledWith( @@ -253,7 +253,7 @@ describe("listCommand.func (trace mode)", () => { "test-project", expect.objectContaining({ query: `trace:${VALID_TRACE_ID}`, - }) + }), ); // Output should contain the span data (rendered by wrapper) @@ -275,7 +275,7 @@ describe("listCommand.func (trace mode)", () => { period: parsePeriod("7d"), fresh: false, }, - VALID_TRACE_ID + VALID_TRACE_ID, ); expect(listSpansSpy).toHaveBeenCalledWith( @@ -283,13 +283,13 @@ describe("listCommand.func (trace mode)", () => { "test-project", expect.objectContaining({ query: `trace:${VALID_TRACE_ID} span.op:db`, - }) + }), ); }); test("converts a search-query parse 400 to a ValidationError when --query is set (trace mode)", async () => { listSpansSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad op filter") + new ApiError("bad", 400, "Error parsing search query: bad op filter"), ); const { context } = createContext(); @@ -304,8 +304,8 @@ describe("listCommand.func (trace mode)", () => { period: parsePeriod("7d"), fresh: false, }, - VALID_TRACE_ID - ) + VALID_TRACE_ID, + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -322,13 +322,13 @@ describe("listCommand.func (trace mode)", () => { period: parsePeriod("7d"), fresh: false, }, - `my-org/my-project/${VALID_TRACE_ID}` + `my-org/my-project/${VALID_TRACE_ID}`, ); expect(listSpansSpy).toHaveBeenCalledWith( "my-org", "my-project", - expect.anything() + expect.anything(), ); // Should NOT have called resolveOrgAndProject expect(resolveOrgAndProjectSpy).not.toHaveBeenCalled(); @@ -362,7 +362,7 @@ describe("listCommand.func (trace mode)", () => { cursor: "1735689600:0:0", fresh: false, }, - VALID_TRACE_ID + VALID_TRACE_ID, ); expect(listSpansSpy).toHaveBeenCalledWith( @@ -370,7 +370,7 @@ describe("listCommand.func (trace mode)", () => { "test-project", expect.objectContaining({ cursor: "1735689600:0:0", - }) + }), ); }); @@ -398,7 +398,7 @@ describe("listCommand.func (trace mode)", () => { json: true, fresh: false, }, - VALID_TRACE_ID + VALID_TRACE_ID, ); const output = getStdout(); @@ -430,7 +430,7 @@ describe("listCommand.func (trace mode)", () => { period: parsePeriod("7d"), fresh: false, }, - VALID_TRACE_ID + VALID_TRACE_ID, ); const output = getStdout(); @@ -450,7 +450,7 @@ describe("listCommand.func (trace mode)", () => { period: parsePeriod("24h"), fresh: false, }, - VALID_TRACE_ID + VALID_TRACE_ID, ); expect(listSpansSpy).toHaveBeenCalledWith( @@ -458,7 +458,7 @@ describe("listCommand.func (trace mode)", () => { "test-project", expect.objectContaining({ statsPeriod: "24h", - }) + }), ); }); @@ -475,7 +475,7 @@ describe("listCommand.func (trace mode)", () => { period: parsePeriod("7d"), fresh: false, }, - VALID_TRACE_ID + VALID_TRACE_ID, ); expect(listSpansSpy).toHaveBeenCalledWith( @@ -483,7 +483,7 @@ describe("listCommand.func (trace mode)", () => { "test-project", expect.objectContaining({ allProjects: true, - }) + }), ); }); }); @@ -496,12 +496,12 @@ describe("listCommand.func (project mode)", () => { let func: ListFunc; // span/list.ts calls resolveProjectBoundFromArg (not resolveOrgAndProject) const resolveOrgAndProjectSpy = vi.mocked( - resolveTarget.resolveProjectBoundFromArg + resolveTarget.resolveProjectBoundFromArg, ); const listSpansSpy = vi.mocked(apiClient.listSpans); const resolveCursorSpy = vi.mocked(paginationDb.resolveCursor); const advancePaginationStateSpy = vi.mocked( - paginationDb.advancePaginationState + paginationDb.advancePaginationState, ); const hasPreviousPageSpy = vi.mocked(paginationDb.hasPreviousPage); @@ -536,7 +536,7 @@ describe("listCommand.func (project mode)", () => { return { org: org!, project: project! }; } return { org: "test-org", project: "test-project" }; - } + }, ); vi.spyOn(resolveTarget, "resolveLogProjectId").mockResolvedValue(4242); resolveCursorSpy.mockReturnValue({ @@ -603,19 +603,19 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("7d"), fresh: false, }, - "my-org/my-project" + "my-org/my-project", ); expect(listSpansSpy).toHaveBeenCalledWith( "my-org", "my-project", - expect.anything() + expect.anything(), ); // resolveProjectBoundFromArg is called with the explicit target expect(resolveOrgAndProjectSpy).toHaveBeenCalledWith( "my-org/my-project", expect.any(String), - expect.any(String) + expect.any(String), ); }); @@ -633,7 +633,7 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("7d"), fresh: false, }, - "my-org/my-project" + "my-org/my-project", ); expect(listSpansSpy).toHaveBeenCalledWith( @@ -641,7 +641,7 @@ describe("listCommand.func (project mode)", () => { "my-project", expect.objectContaining({ query: "span.op:db span.duration:>100ms", - }) + }), ); }); @@ -649,7 +649,7 @@ describe("listCommand.func (project mode)", () => { // Project mode must wrap listSpans with toSearchQueryError just like trace // mode does — a bad user --query is a user mistake, not a reported CLI bug. listSpansSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad op filter") + new ApiError("bad", 400, "Error parsing search query: bad op filter"), ); const { context } = createContext(); @@ -664,8 +664,8 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("7d"), fresh: false, }, - "my-org/my-project" - ) + "my-org/my-project", + ), ).rejects.toBeInstanceOf(ValidationError); }); @@ -673,7 +673,7 @@ describe("listCommand.func (project mode)", () => { // No user --query means the CLI built the bad query — stays a reported // ApiError(400), not reclassified as user input. listSpansSpy.mockRejectedValue( - new ApiError("bad", 400, "Error parsing search query: bad op filter") + new ApiError("bad", 400, "Error parsing search query: bad op filter"), ); const { context } = createContext(); @@ -687,8 +687,8 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("7d"), fresh: false, }, - "my-org/my-project" - ) + "my-org/my-project", + ), ).rejects.toBeInstanceOf(ApiError); }); @@ -705,7 +705,7 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("30d"), fresh: false, }, - "my-org/my-project" + "my-org/my-project", ); expect(listSpansSpy).toHaveBeenCalledWith( @@ -713,7 +713,7 @@ describe("listCommand.func (project mode)", () => { "my-project", expect.objectContaining({ statsPeriod: "30d", - }) + }), ); }); @@ -743,7 +743,7 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("7d"), fresh: false, }, - "my-org/my-project" + "my-org/my-project", ); const output = getStdout(); @@ -795,7 +795,7 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("7d"), fresh: false, }, - "my-org/my-project" + "my-org/my-project", ); const output = getStdout(); @@ -815,7 +815,7 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("7d"), fresh: false, }, - "my-org/my-project" + "my-org/my-project", ); const callArgs = listSpansSpy.mock.calls[0]; @@ -847,7 +847,7 @@ describe("listCommand.func (project mode)", () => { period: parsePeriod("7d"), fresh: false, }, - "my-org/my-project" + "my-org/my-project", ); const output = getStdout(); diff --git a/packages/cli/test/commands/span/view.test.ts b/packages/cli/test/commands/span/view.test.ts index 03c80e953..4326c75c3 100644 --- a/packages/cli/test/commands/span/view.test.ts +++ b/packages/cli/test/commands/span/view.test.ts @@ -18,11 +18,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; @@ -40,11 +40,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; const VALID_TRACE_ID = "aaaa1111bbbb2222cccc3333dddd4444"; @@ -74,7 +74,7 @@ describe("validateSpanId", () => { test("throws for too long", () => { expect(() => validateSpanId("a1b2c3d4e5f678901234")).toThrow( - ValidationError + ValidationError, ); }); @@ -149,7 +149,7 @@ describe("parsePositionalArgs", () => { expect(result.kind).toBe("resolved"); if (result.kind !== "resolved") throw new Error("unreachable"); expect(result.traceTarget.traceId).toBe( - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(result.traceTarget.type).toBe("auto-detect"); expect(result.rawSpanIds).toEqual(["a1b2c3d4e5f67890"]); @@ -162,7 +162,7 @@ describe("parsePositionalArgs", () => { expect(result.kind).toBe("resolved"); if (result.kind !== "resolved") throw new Error("unreachable"); expect(result.traceTarget.traceId).toBe( - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(result.rawSpanIds).toEqual(["a1b2c3d4e5f67890"]); }); @@ -176,7 +176,7 @@ describe("parsePositionalArgs", () => { expect(result.kind).toBe("deferred"); if (result.kind !== "deferred") throw new Error("unreachable"); expect(result.rawTraceArg).toBe( - "my-org/aaaa1111bbbb2222cccc3333dddd4444" + "my-org/aaaa1111bbbb2222cccc3333dddd4444", ); expect(result.rawSpanIds).toEqual(["a1b2c3d4e5f67890"]); }); @@ -187,7 +187,7 @@ describe("parsePositionalArgs", () => { // branch and throws ContextError. (A two-arg variant would defer // to the command layer — covered by other tests.) expect(() => - parsePositionalArgs(["not-a-hex-id/a1b2c3d4e5f67890"]) + parsePositionalArgs(["not-a-hex-id/a1b2c3d4e5f67890"]), ).toThrow(ContextError); }); @@ -198,7 +198,7 @@ describe("parsePositionalArgs", () => { expect(() => parsePositionalArgs([ "aaaa1111bbbb2222cccc3333dddd4444/bbbb2222cccc3333dddd4444eeee5555", - ]) + ]), ).toThrow(ContextError); }); @@ -224,7 +224,7 @@ describe("parsePositionalArgs", () => { expect(msg).toContain(VALID_SPAN_ID); // Does not misdirect the user to `span list` with the span ID. expect(msg).not.toContain( - `span list my-org/my-project/${VALID_SPAN_ID}` + `span list my-org/my-project/${VALID_SPAN_ID}`, ); } }); @@ -294,7 +294,7 @@ describe("parsePositionalArgs", () => { test("throws ContextError for bare span ID without trace ID (CLI-SC)", () => { expect(() => parsePositionalArgs(["a1b2c3d4e5f67890"])).toThrow( - ContextError + ContextError, ); }); @@ -314,7 +314,7 @@ describe("parsePositionalArgs", () => { test("bare span ID with dashes is still detected (CLI-SC)", () => { // Some tools format span IDs with dashes expect(() => parsePositionalArgs(["a1b2-c3d4-e5f6-7890"])).toThrow( - ContextError + ContextError, ); }); }); @@ -410,7 +410,7 @@ describe("viewCommand.func", () => { fresh: false, }, VALID_TRACE_ID, - VALID_SPAN_ID + VALID_SPAN_ID, ); const output = getStdout(); @@ -431,8 +431,8 @@ describe("viewCommand.func", () => { fresh: false, }, VALID_TRACE_ID, - VALID_SPAN_ID - ) + VALID_SPAN_ID, + ), ).rejects.toThrow(ResolutionError); }); @@ -449,8 +449,8 @@ describe("viewCommand.func", () => { fresh: false, }, VALID_TRACE_ID, - VALID_SPAN_ID - ) + VALID_SPAN_ID, + ), ).rejects.toThrow(ResolutionError); }); @@ -466,7 +466,7 @@ describe("viewCommand.func", () => { fresh: false, }, `my-org/my-project/${VALID_TRACE_ID}`, - VALID_SPAN_ID + VALID_SPAN_ID, ); expect(getDetailedTraceSpy).toHaveBeenCalledWith("my-org", VALID_TRACE_ID); @@ -486,7 +486,7 @@ describe("viewCommand.func", () => { { spans: 0, fresh: false }, VALID_TRACE_ID, FOUND_SPAN, - MISSING_SPAN + MISSING_SPAN, ); const output = getStdout(); @@ -505,7 +505,7 @@ describe("viewCommand.func", () => { context, { spans: 3, fresh: false }, VALID_TRACE_ID, - VALID_SPAN_ID + VALID_SPAN_ID, ); const output = getStdout(); @@ -523,7 +523,7 @@ describe("viewCommand.func", () => { context, { spans: 0, fresh: false, json: true }, VALID_TRACE_ID, - VALID_SPAN_ID + VALID_SPAN_ID, ); const output = getStdout(); @@ -543,9 +543,9 @@ describe("viewCommand.func", () => { func.call( context, { spans: 0, fresh: false }, - `my-org/my-project/${VALID_TRACE_ID}` + `my-org/my-project/${VALID_TRACE_ID}`, // No span IDs — but we need at least one - ) + ), ).rejects.toThrow(ContextError); }); @@ -560,8 +560,8 @@ describe("viewCommand.func", () => { { spans: 0, fresh: false }, VALID_TRACE_ID, VALID_SPAN_ID, - VALID_SPAN_ID_2 - ) + VALID_SPAN_ID_2, + ), ).rejects.toThrow(ResolutionError); }); }); diff --git a/packages/cli/test/commands/team/list.test.ts b/packages/cli/test/commands/team/list.test.ts index a90341738..fd3575a86 100644 --- a/packages/cli/test/commands/team/list.test.ts +++ b/packages/cli/test/commands/team/list.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.mocked access import * as apiClient from "../../../src/lib/api-client.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; @@ -31,11 +31,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.mocked access import * as paginationDb from "../../../src/lib/db/pagination.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; import { ValidationError } from "../../../src/lib/errors.js"; @@ -47,11 +47,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.mocked access import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { SentryTeam } from "../../../src/types/sentry.js"; @@ -263,7 +263,7 @@ describe("listCommand.func — auto-detect mode", () => { // Mocking resolveAllTargets doesn't work because resolveOrgsForListing calls // it internally (same-file call bypasses vi.mock). Mock the outer function. const resolveOrgsForListingSpy = vi.mocked( - resolveTarget.resolveOrgsForListing + resolveTarget.resolveOrgsForListing, ); beforeEach(() => { @@ -356,7 +356,7 @@ describe("listCommand.func — auto-detect mode", () => { describe("listCommand.func — org-all mode (cursor pagination)", () => { const listTeamsPaginatedSpy = vi.mocked(apiClient.listTeamsPaginated); const advancePaginationStateSpy = vi.mocked( - paginationDb.advancePaginationState + paginationDb.advancePaginationState, ); const hasPreviousPageSpy = vi.mocked(paginationDb.hasPreviousPage); const resolveCursorSpy = vi.mocked(paginationDb.resolveCursor); @@ -470,12 +470,12 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { await func.call( context, { limit: 25, json: false, cursor: "explicit:cursor:value" }, - "my-org/" + "my-org/", ); expect(listTeamsPaginatedSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ cursor: "explicit:cursor:value" }) + expect.objectContaining({ cursor: "explicit:cursor:value" }), ); }); @@ -494,12 +494,12 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { await func.call( context, { limit: 25, json: false, cursor: "next" }, - "my-org/" + "my-org/", ); expect(listTeamsPaginatedSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ cursor: "cached:cursor:456" }) + expect.objectContaining({ cursor: "cached:cursor:456" }), ); }); @@ -507,7 +507,7 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { resolveCursorSpy.mockImplementation(() => { throw new ValidationError( "No next page saved for this query. Run without --cursor first.", - "cursor" + "cursor", ); }); @@ -515,7 +515,7 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { const func = await listCommand.loader(); await expect( - func.call(context, { limit: 25, json: false, cursor: "next" }, "my-org/") + func.call(context, { limit: 25, json: false, cursor: "next" }, "my-org/"), ).rejects.toThrow("No next page saved"); }); @@ -527,8 +527,8 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { func.call( context, { limit: 25, json: false, cursor: "some-cursor" }, - "my-org/my-project" - ) + "my-org/my-project", + ), ).rejects.toThrow(ValidationError); }); @@ -544,7 +544,7 @@ describe("listCommand.func — org-all mode (cursor pagination)", () => { expect(listTeamsPaginatedSpy).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ perPage: 10 }) + expect.objectContaining({ perPage: 10 }), ); }); }); diff --git a/packages/cli/test/commands/trace/list.test.ts b/packages/cli/test/commands/trace/list.test.ts index 5da346dc9..cf05bd3f5 100644 --- a/packages/cli/test/commands/trace/list.test.ts +++ b/packages/cli/test/commands/trace/list.test.ts @@ -19,11 +19,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { validateLimit } from "../../../src/lib/arg-parsing.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; @@ -35,11 +35,11 @@ vi.mock("../../../src/lib/db/pagination.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as paginationDb from "../../../src/lib/db/pagination.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; import { ContextError, ResolutionError } from "../../../src/lib/errors.js"; @@ -51,11 +51,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { parsePeriod } from "../../../src/lib/time-range.js"; import type { TransactionListItem } from "../../../src/types/sentry.js"; @@ -139,7 +139,7 @@ describe("resolveProjectBoundFromArg", () => { const result = await resolveTarget.resolveProjectBoundFromArg( "my-org/my-project", "/tmp", - "trace list" + "trace list", ); expect(result).toEqual({ org: "my-org", project: "my-project" }); expect(findProjectsBySlugSpy).not.toHaveBeenCalled(); @@ -148,7 +148,7 @@ describe("resolveProjectBoundFromArg", () => { test("throws for org-all target (org/ without project)", async () => { await expect( - resolveTarget.resolveProjectBoundFromArg("my-org/", "/tmp", "trace list") + resolveTarget.resolveProjectBoundFromArg("my-org/", "/tmp", "trace list"), ).rejects.toThrow(ContextError); }); @@ -157,7 +157,7 @@ describe("resolveProjectBoundFromArg", () => { await resolveTarget.resolveProjectBoundFromArg( "my-org/", "/tmp", - "trace list" + "trace list", ); expect.unreachable("Should have thrown"); } catch (error) { @@ -178,7 +178,7 @@ describe("resolveProjectBoundFromArg", () => { const result = await resolveTarget.resolveProjectBoundFromArg( "frontend", "/tmp", - "trace list" + "trace list", ); expect(result).toMatchObject({ org: "acme", project: "frontend" }); expect(result.projectData).toBeDefined(); @@ -191,8 +191,8 @@ describe("resolveProjectBoundFromArg", () => { resolveTarget.resolveProjectBoundFromArg( "nonexistent", "/tmp", - "trace list" - ) + "trace list", + ), ).rejects.toThrow(ResolutionError); }); @@ -209,7 +209,7 @@ describe("resolveProjectBoundFromArg", () => { await resolveTarget.resolveProjectBoundFromArg( "frontend", "/tmp", - "trace list" + "trace list", ); expect.unreachable("Should have thrown"); } catch (error) { @@ -223,7 +223,7 @@ describe("resolveProjectBoundFromArg", () => { // Skip: resolveProjectBoundFromArg calls resolveOrgAndProject internally // (same-file call). vi.spyOn on the export doesn't intercept same-file // calls in vitest, so the mock has no effect and the real code runs. - // biome-ignore lint/suspicious/noSkippedTests: vitest can't intercept same-file internal calls + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest can't intercept same-file internal calls test.skip("uses auto-detect when no target provided", async () => { resolveOrgAndProjectSpy.mockResolvedValue({ org: "detected-org", @@ -233,7 +233,7 @@ describe("resolveProjectBoundFromArg", () => { const result = await resolveTarget.resolveProjectBoundFromArg( undefined, "/tmp", - "trace list" + "trace list", ); expect(result).toEqual({ org: "detected-org", @@ -246,12 +246,12 @@ describe("resolveProjectBoundFromArg", () => { }); // Skip: same reason — resolveOrgAndProject is an internal same-file call - // biome-ignore lint/suspicious/noSkippedTests: vitest can't intercept same-file internal calls + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest can't intercept same-file internal calls test.skip("throws when auto-detect returns null", async () => { resolveOrgAndProjectSpy.mockResolvedValue(null); await expect( - resolveTarget.resolveProjectBoundFromArg(undefined, "/tmp", "trace list") + resolveTarget.resolveProjectBoundFromArg(undefined, "/tmp", "trace list"), ).rejects.toThrow(ContextError); }); }); @@ -333,7 +333,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: true, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -353,7 +353,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: true, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -372,7 +372,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: false, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -387,7 +387,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: false, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -409,7 +409,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 2, sort: "date", json: false, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -425,7 +425,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 100, sort: "date", json: false, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -441,7 +441,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: false, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -463,7 +463,7 @@ describe("listCommand.func", () => { query: "transaction:GET", period: parsePeriod("7d"), }, - "test-org/test-project" + "test-org/test-project", ); expect(listTransactionsSpy).toHaveBeenCalledWith( @@ -476,7 +476,7 @@ describe("listCommand.func", () => { cursor: undefined, projectId: 4242, statsPeriod: "7d", - } + }, ); }); @@ -491,7 +491,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: true, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -514,7 +514,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "duration", json: false, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -531,7 +531,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: false, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const callArgs = listTransactionsSpy.mock.calls[0]; @@ -549,7 +549,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: false, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -574,7 +574,7 @@ describe("listCommand.func", () => { query: "transaction:POST", period: parsePeriod("7d"), }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -590,7 +590,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: true, period: parsePeriod("7d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -608,7 +608,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: false, period: parsePeriod("24h") }, - "test-org/test-project" + "test-org/test-project", ); expect(listTransactionsSpy).toHaveBeenCalledWith( @@ -616,7 +616,7 @@ describe("listCommand.func", () => { "test-project", expect.objectContaining({ statsPeriod: "24h", - }) + }), ); }); @@ -631,7 +631,7 @@ describe("listCommand.func", () => { await func.call( context, { limit: 20, sort: "date", json: false, period: parsePeriod("30d") }, - "test-org/test-project" + "test-org/test-project", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); diff --git a/packages/cli/test/commands/trace/logs.test.ts b/packages/cli/test/commands/trace/logs.test.ts index 4e87c1689..7c31cafa6 100644 --- a/packages/cli/test/commands/trace/logs.test.ts +++ b/packages/cli/test/commands/trace/logs.test.ts @@ -29,11 +29,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -43,11 +43,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { ContextError } from "../../../src/lib/errors.js"; @@ -58,11 +58,11 @@ vi.mock("../../../src/lib/polling.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as polling from "../../../src/lib/polling.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -72,11 +72,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import { parsePeriod } from "../../../src/lib/time-range.js"; import type { TraceLog } from "../../../src/types/sentry.js"; @@ -134,7 +134,7 @@ function createMockContext() { * Collect all output written to a mock write function. */ function collectMockOutput( - writeMock: ReturnType<typeof mock<() => boolean>> + writeMock: ReturnType<typeof mock<() => boolean>>, ): string { return writeMock.mock.calls .map((c) => { @@ -172,7 +172,7 @@ describe("logsCommand.func", () => { .mockImplementation((_opts, fn) => fn(() => { /* no-op setMessage */ - }) + }), ); }); @@ -200,7 +200,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -229,7 +229,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -254,7 +254,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -277,7 +277,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -299,7 +299,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -323,7 +323,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -346,7 +346,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -370,7 +370,7 @@ describe("logsCommand.func", () => { limit: 3, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -392,7 +392,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); const output = collectMockOutput(stdoutWrite); @@ -417,7 +417,7 @@ describe("logsCommand.func", () => { sort: "newest", }, ORG, - TRACE_ID + TRACE_ID, ); expect(resolveOrgOnlyTargetSpy).toHaveBeenCalled(); @@ -438,7 +438,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); expect(resolveOrgSpy).toHaveBeenCalledWith({ @@ -463,8 +463,8 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID - ) + TRACE_ID, + ), ).rejects.toThrow(ContextError); }); }); @@ -486,7 +486,7 @@ describe("logsCommand.func", () => { query: "severity:error", sort: "newest", }, - TRACE_ID + TRACE_ID, ); expect(listTraceLogsSpy).toHaveBeenCalledWith(ORG, TRACE_ID, { @@ -512,7 +512,7 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); expect(listTraceLogsSpy).toHaveBeenCalledWith(ORG, TRACE_ID, { @@ -539,12 +539,12 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); expect(openInBrowserSpy).toHaveBeenCalledWith( expect.stringContaining(TRACE_ID), - "trace" + "trace", ); expect(listTraceLogsSpy).not.toHaveBeenCalled(); }); @@ -566,13 +566,13 @@ describe("logsCommand.func", () => { limit: 100, sort: "newest", }, - TRACE_ID + TRACE_ID, ); expect(listTraceLogsSpy).toHaveBeenCalledWith( ORG, TRACE_ID, - expect.objectContaining({ sort: "newest" }) + expect.objectContaining({ sort: "newest" }), ); }); @@ -591,13 +591,13 @@ describe("logsCommand.func", () => { limit: 100, sort: "oldest", }, - TRACE_ID + TRACE_ID, ); expect(listTraceLogsSpy).toHaveBeenCalledWith( ORG, TRACE_ID, - expect.objectContaining({ sort: "oldest" }) + expect.objectContaining({ sort: "oldest" }), ); }); }); diff --git a/packages/cli/test/commands/trace/view.func.test.ts b/packages/cli/test/commands/trace/view.func.test.ts index d0cffcd29..d7322b3cd 100644 --- a/packages/cli/test/commands/trace/view.func.test.ts +++ b/packages/cli/test/commands/trace/view.func.test.ts @@ -22,11 +22,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -36,11 +36,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../../src/lib/browser.js"; import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; @@ -57,11 +57,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { TraceSpan } from "../../../src/types/sentry.js"; @@ -208,7 +208,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 100 }, "test-org/test-project", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -228,7 +228,7 @@ describe("viewCommand.func", () => { context, { json: false, web: false, spans: 100 }, "test-org/test-project", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -248,8 +248,8 @@ describe("viewCommand.func", () => { context, { json: false, web: false, spans: 100 }, "test-org/test-project", - "00000000000000000000000000000000" - ) + "00000000000000000000000000000000", + ), ).rejects.toThrow(ResolutionError); }); @@ -264,13 +264,13 @@ describe("viewCommand.func", () => { context, { json: false, web: false, spans: 100 }, "test-org/test-project", - "deadbeef12345678deadbeef12345678" + "deadbeef12345678deadbeef12345678", ); expect.unreachable("Should have thrown"); } catch (error) { expect(error).toBeInstanceOf(ResolutionError); expect((error as ResolutionError).message).toContain( - "deadbeef12345678deadbeef12345678" + "deadbeef12345678deadbeef12345678", ); } }); @@ -284,7 +284,7 @@ describe("viewCommand.func", () => { context, { json: false, web: true, spans: 100 }, "test-org/test-project", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(openInBrowserSpy).toHaveBeenCalled(); @@ -301,7 +301,7 @@ describe("viewCommand.func", () => { context, { json: false, web: false, spans: 0 }, "test-org/test-project", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -322,16 +322,16 @@ describe("viewCommand.func", () => { context, { json: false, web: false, spans: 100 }, "my-org/", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(getDetailedTraceSpy).toHaveBeenCalledWith( "my-org", "aaaa1111bbbb2222cccc3333dddd4444", - expect.objectContaining({ projectId: undefined }) + expect.objectContaining({ projectId: undefined }), ); expect(getDetailedTraceSpy.mock.calls[0]?.[2]).not.toHaveProperty( - "timestamp" + "timestamp", ); }); @@ -340,7 +340,7 @@ describe("viewCommand.func", () => { // error) also cannot resolve a target. resolveOrgAndProjectSpy.mockResolvedValue(null); vi.spyOn(resolveTarget, "guideOrgProjectFailure").mockRejectedValue( - new ContextError("Organization and project", "sentry trace view <id>") + new ContextError("Organization and project", "sentry trace view <id>"), ); const { context } = createMockContext(); @@ -350,8 +350,8 @@ describe("viewCommand.func", () => { func.call( context, { json: false, web: false, spans: 100 }, - "aaaa1111bbbb2222cccc3333dddd4444" - ) + "aaaa1111bbbb2222cccc3333dddd4444", + ), ).rejects.toThrow(ContextError); }); @@ -370,7 +370,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 100 }, "frontend", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(findProjectsBySlugSpy).toHaveBeenCalledWith("frontend"); @@ -386,7 +386,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 100 }, "aaaa1111bbbb2222cccc3333dddd4444", - "test-org/test-project" + "test-org/test-project", ); // Command should complete (warning goes to consola, not stdout) @@ -409,7 +409,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 100 }, "CAM-82X", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); // The suggestion path fires (looksLikeIssueShortId("CAM-82X") → true) @@ -441,7 +441,7 @@ describe("viewCommand.func", () => { expect(getDetailedTraceSpy).toHaveBeenCalledWith( "test-org", traceIdFromEvent, - { additionalAttributes: undefined, projectId: undefined } + { additionalAttributes: undefined, projectId: undefined }, ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); @@ -466,7 +466,7 @@ describe("viewCommand.func", () => { const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false, spans: 100 }, "CLI-G5") + func.call(context, { json: false, web: false, spans: 100 }, "CLI-G5"), ).rejects.toThrow(ValidationError); }); @@ -477,7 +477,7 @@ describe("viewCommand.func", () => { const func = await viewCommand.loader(); await expect( - func.call(context, { json: false, web: false, spans: 100 }, "CLI-G5") + func.call(context, { json: false, web: false, spans: 100 }, "CLI-G5"), ).rejects.toThrow(ContextError); }); @@ -490,7 +490,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 100 }, "test-org/test-project", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(fetchMultiSpanDetailsSpy).toHaveBeenCalled(); @@ -505,7 +505,7 @@ describe("viewCommand.func", () => { context, { json: false, full: true, web: false, spans: 100 }, "test-org/test-project", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(fetchMultiSpanDetailsSpy).toHaveBeenCalled(); @@ -520,7 +520,7 @@ describe("viewCommand.func", () => { context, { json: false, web: false, spans: 100 }, "test-org/test-project", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(fetchMultiSpanDetailsSpy).not.toHaveBeenCalled(); @@ -543,7 +543,7 @@ describe("viewCommand.func", () => { links: null, }, ], - ]) + ]), ); const { context, stdoutWrite } = createMockContext(); @@ -552,7 +552,7 @@ describe("viewCommand.func", () => { context, { json: true, web: false, spans: 100 }, "test-org/test-project", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); diff --git a/packages/cli/test/commands/trace/view.property.test.ts b/packages/cli/test/commands/trace/view.property.test.ts index 6095819f7..d8c4cf38c 100644 --- a/packages/cli/test/commands/trace/view.property.test.ts +++ b/packages/cli/test/commands/trace/view.property.test.ts @@ -27,7 +27,7 @@ const traceIdArb = stringMatching(/^[a-f0-9]{32}$/); /** Valid org/project slugs (no xn-- punycode prefix) */ const slugArb = stringMatching(/^[a-z][a-z0-9-]{1,20}[a-z0-9]$/).filter( - (s) => !s.startsWith("xn--") + (s) => !s.startsWith("xn--"), ); const HINT = "sentry trace view [<org>/<project>/]<trace-id>"; @@ -47,7 +47,7 @@ describe("parseTraceTarget properties", () => { expect(result.type).toBe("auto-detect"); expect(result.traceId).toBe(input); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -64,9 +64,9 @@ describe("parseTraceTarget properties", () => { expect(result.org).toBe(org); expect(result.project).toBe(project); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -81,7 +81,7 @@ describe("parseTraceTarget properties", () => { expect(result.org).toBe(org); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -94,9 +94,9 @@ describe("parseTraceTarget properties", () => { const result = parseTraceTarget([target, traceId], HINT); expect(result.type).toBe("explicit"); expect(result.traceId).toBe(traceId); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -108,7 +108,7 @@ describe("parseTraceTarget properties", () => { const result2 = parseTraceTarget(args, HINT); expect(result1).toEqual(result2); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -123,7 +123,7 @@ describe("parseTraceTarget properties", () => { expect(result.traceId).toBeDefined(); expect(typeof result.traceId).toBe("string"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -134,7 +134,7 @@ describe("parseTraceTarget properties", () => { const result = parseTraceTarget([uuid], HINT); expect(result.traceId).toBe(hex); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -144,7 +144,7 @@ describe("parseTraceTarget properties", () => { property(invalidIdArb, (badId) => { expect(() => parseTraceTarget([badId], HINT)).toThrow(ValidationError); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -158,7 +158,7 @@ describe("parseTraceTarget properties", () => { * parentIndex = -1 means root. Otherwise index into the flat list. */ function buildTree( - items: Array<{ id: string; parentIdx: number }> + items: Array<{ id: string; parentIdx: number }>, ): TraceSpan[] { const nodes: TraceSpan[] = items.map((item) => ({ span_id: item.id, @@ -201,9 +201,9 @@ const spanTreeArb = uniqueArray(stringMatching(/^[a-f0-9]{16}$/), { integer({ min: -1, max: Math.max(0, i - 1) }).map((parentIdx) => ({ id, parentIdx, - })) - ) - ) + })), + ), + ), ) .map((items) => buildTree(items)); @@ -240,7 +240,7 @@ describe("flattenSpanTree properties", () => { const result = flattenSpanTree(tree); expect(result).toHaveLength(countSpans(tree)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -253,7 +253,7 @@ describe("flattenSpanTree properties", () => { expect(originalIds.has(span.span_id)).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/commands/trace/view.test.ts b/packages/cli/test/commands/trace/view.test.ts index b23234e6d..e338f260c 100644 --- a/packages/cli/test/commands/trace/view.test.ts +++ b/packages/cli/test/commands/trace/view.test.ts @@ -18,11 +18,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; import { ResolutionError, ValidationError } from "../../../src/lib/errors.js"; import { resolveProjectBoundSlug } from "../../../src/lib/resolve-target.js"; @@ -100,7 +100,7 @@ describe("resolveProjectBoundSlug", () => { findProjectsBySlugSpy.mockResolvedValue({ projects: [], orgs: [] }); await expect(resolveProjectBoundSlug("my-project", HINT)).rejects.toThrow( - ResolutionError + ResolutionError, ); }); @@ -113,10 +113,10 @@ describe("resolveProjectBoundSlug", () => { } catch (error) { expect(error).toBeInstanceOf(ResolutionError); expect((error as ResolutionError).message).toContain( - 'Project "frontend"' + 'Project "frontend"', ); expect((error as ResolutionError).message).toContain( - "Check that you have access" + "Check that you have access", ); expect((error as ResolutionError).message).toContain("not found"); } @@ -134,7 +134,7 @@ describe("resolveProjectBoundSlug", () => { }); await expect(resolveProjectBoundSlug("frontend", HINT)).rejects.toThrow( - ValidationError + ValidationError, ); }); @@ -161,7 +161,7 @@ describe("resolveProjectBoundSlug", () => { await resolveProjectBoundSlug( "frontend", HINT, - "sentry trace view <org>/frontend trace-456" + "sentry trace view <org>/frontend trace-456", ); expect.unreachable("Should have thrown"); } catch (error) { diff --git a/packages/cli/test/commands/trial/list.test.ts b/packages/cli/test/commands/trial/list.test.ts index 61c1dcde3..268d87846 100644 --- a/packages/cli/test/commands/trial/list.test.ts +++ b/packages/cli/test/commands/trial/list.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -30,11 +30,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { CustomerTrialInfo, @@ -72,7 +72,7 @@ function daysFromNow(days: number): string { function makeCustomerInfo( overrides: Partial<CustomerTrialInfo> & { productTrials?: ProductTrial[] | null; - } = {} + } = {}, ): CustomerTrialInfo { return { productTrials: [], @@ -138,7 +138,7 @@ describe("trial list command", () => { getCustomerTrialInfoSpy.mockResolvedValue( makeCustomerInfo({ productTrials: [AVAILABLE_TRIAL, ACTIVE_TRIAL], - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -159,7 +159,7 @@ describe("trial list command", () => { test("excludes displayName from JSON output", async () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ productTrials: [AVAILABLE_TRIAL] }) + makeCustomerInfo({ productTrials: [AVAILABLE_TRIAL] }), ); const { context, stdoutWrite } = createMockContext(); @@ -176,7 +176,7 @@ describe("trial list command", () => { getCustomerTrialInfoSpy.mockResolvedValue( makeCustomerInfo({ productTrials: [AVAILABLE_TRIAL, ACTIVE_TRIAL, EXPIRED_TRIAL], - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -199,7 +199,7 @@ describe("trial list command", () => { test("shows empty state message when no trials and no plan trial", async () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ productTrials: [], canTrial: false }) + makeCustomerInfo({ productTrials: [], canTrial: false }), ); const { context, stdoutWrite } = createMockContext(); @@ -213,7 +213,7 @@ describe("trial list command", () => { test("outputs empty JSON array when no trials and no plan trial", async () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ productTrials: [], canTrial: false }) + makeCustomerInfo({ productTrials: [], canTrial: false }), ); const { context, stdoutWrite } = createMockContext(); @@ -227,7 +227,7 @@ describe("trial list command", () => { test("uses org from positional argument", async () => { resolveOrgSpy.mockResolvedValue({ org: "my-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ productTrials: [AVAILABLE_TRIAL] }) + makeCustomerInfo({ productTrials: [AVAILABLE_TRIAL] }), ); const { context } = createMockContext(); @@ -235,7 +235,7 @@ describe("trial list command", () => { await func.call(context, { json: true }, "my-org"); expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: "my-org" }) + expect.objectContaining({ org: "my-org" }), ); expect(getCustomerTrialInfoSpy).toHaveBeenCalledWith("my-org"); }); @@ -247,14 +247,14 @@ describe("trial list command", () => { const func = await listCommand.loader(); await expect( - func.call(context, { json: false }, undefined) + func.call(context, { json: false }, undefined), ).rejects.toThrow("organization"); }); test("includes hint about starting trial when available product trials exist", async () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ productTrials: [AVAILABLE_TRIAL] }) + makeCustomerInfo({ productTrials: [AVAILABLE_TRIAL] }), ); const { context, stdoutWrite } = createMockContext(); @@ -276,7 +276,7 @@ describe("trial list command", () => { productTrials: [], canTrial: true, planDetails: { name: "Developer", trialPlan: "am3_t" }, - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -298,7 +298,7 @@ describe("trial list command", () => { productTrials: [], canTrial: true, planDetails: { name: "Developer", trialPlan: "am3_t" }, - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -318,7 +318,7 @@ describe("trial list command", () => { isTrial: true, trialEnd: daysFromNow(10), planDetails: { name: "Business", trialPlan: null }, - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -341,7 +341,7 @@ describe("trial list command", () => { productTrials: [AVAILABLE_TRIAL], canTrial: true, planDetails: { name: "Developer", trialPlan: "am3_t" }, - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -362,7 +362,7 @@ describe("trial list command", () => { productTrials: [], canTrial: true, planDetails: { name: "Developer", trialPlan: "am3_t" }, - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -380,7 +380,7 @@ describe("trial list command", () => { productTrials: null, canTrial: true, planDetails: { name: "Developer", trialPlan: "am3_t" }, - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -401,7 +401,7 @@ describe("trial list command", () => { productTrials: [EXPIRED_TRIAL], canTrial: false, isTrial: false, - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -422,7 +422,7 @@ describe("trial list command", () => { { ...EXPIRED_TRIAL, category: "monitorSeats" }, { ...EXPIRED_TRIAL, category: "profileDurationUI" }, ], - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -443,7 +443,7 @@ describe("trial list command", () => { { ...EXPIRED_TRIAL, category: "profileDuration" }, { ...EXPIRED_TRIAL, category: "profileDurationUI" }, ], - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -454,7 +454,7 @@ describe("trial list command", () => { const parsed = JSON.parse(output); // Should show only one "profiling" entry, not two const profilingEntries = parsed.filter( - (e: { name: string }) => e.name === "profiling" + (e: { name: string }) => e.name === "profiling", ); expect(profilingEntries).toHaveLength(1); }); @@ -471,7 +471,7 @@ describe("trial list command", () => { endDate: daysFromNow(5), }, ], - }) + }), ); const { context, stdoutWrite } = createMockContext(); @@ -481,7 +481,7 @@ describe("trial list command", () => { const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); const parsed = JSON.parse(output); const profiling = parsed.find( - (e: { name: string }) => e.name === "profiling" + (e: { name: string }) => e.name === "profiling", ); expect(profiling.status).toBe("active"); }); @@ -494,7 +494,7 @@ describe("trial list command", () => { { ...EXPIRED_TRIAL, category: "profileDuration" }, { ...EXPIRED_TRIAL, category: "profileDurationUI" }, ], - }) + }), ); const { context, stdoutWrite } = createMockContext(); diff --git a/packages/cli/test/commands/trial/start.test.ts b/packages/cli/test/commands/trial/start.test.ts index 83e37d0ab..97a164792 100644 --- a/packages/cli/test/commands/trial/start.test.ts +++ b/packages/cli/test/commands/trial/start.test.ts @@ -16,11 +16,11 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/browser.js", async (importOriginal) => { @@ -30,11 +30,11 @@ vi.mock("../../../src/lib/browser.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browserMod from "../../../src/lib/browser.js"; import { ValidationError } from "../../../src/lib/errors.js"; @@ -45,11 +45,11 @@ vi.mock("../../../src/lib/qrcode.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as qrcodeMod from "../../../src/lib/qrcode.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -59,11 +59,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../../src/lib/resolve-target.js"; import type { CustomerTrialInfo, @@ -173,7 +173,7 @@ describe("trial start command", () => { const func = await startCommand.loader(); await expect( - func.call(context, { json: false }, "unknown-name") + func.call(context, { json: false }, "unknown-name"), ).rejects.toThrow(ValidationError); }); @@ -185,7 +185,7 @@ describe("trial start command", () => { const func = await startCommand.loader(); await expect(func.call(context, { json: false }, "seer")).rejects.toThrow( - ValidationError + ValidationError, ); }); @@ -212,7 +212,7 @@ describe("trial start command", () => { const func = await startCommand.loader(); await expect(func.call(context, { json: false }, "seer")).rejects.toThrow( - "organization" + "organization", ); }); @@ -225,7 +225,7 @@ describe("trial start command", () => { await func.call(context, { json: true }, "seer", "my-org"); expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: "my-org" }) + expect.objectContaining({ org: "my-org" }), ); }); @@ -240,7 +240,7 @@ describe("trial start command", () => { // Should resolve correctly despite swap expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: "my-org" }) + expect.objectContaining({ org: "my-org" }), ); expect(getProductTrialsSpy).toHaveBeenCalledWith("my-org"); }); @@ -263,7 +263,7 @@ describe("trial start command", () => { // Should resolve correctly despite swap expect(resolveOrgSpy).toHaveBeenCalledWith( - expect.objectContaining({ org: "my-org" }) + expect.objectContaining({ org: "my-org" }), ); getInfoSpy.mockRestore(); }); @@ -293,7 +293,7 @@ describe("trial start command", () => { // --------------------------------------------------------------------------- function makeCustomerInfo( - overrides: Partial<CustomerTrialInfo> = {} + overrides: Partial<CustomerTrialInfo> = {}, ): CustomerTrialInfo { return { productTrials: [], @@ -332,7 +332,7 @@ describe("trial start plan", () => { test("returns JSON with url and opened fields", async () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ canTrial: true }) + makeCustomerInfo({ canTrial: true }), ); const { context, stdoutWrite } = createMockContext(); @@ -351,7 +351,7 @@ describe("trial start plan", () => { test("shows billing URL in output", async () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ canTrial: true }) + makeCustomerInfo({ canTrial: true }), ); const { context, stdoutWrite } = createMockContext(); @@ -367,7 +367,7 @@ describe("trial start plan", () => { test("generates QR code for billing URL", async () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ canTrial: true }) + makeCustomerInfo({ canTrial: true }), ); const { context, stdoutWrite } = createMockContext(); @@ -387,7 +387,7 @@ describe("trial start plan", () => { canTrial: false, isTrial: true, planDetails: { name: "Business", trialPlan: null }, - }) + }), ); const { context } = createMockContext(); @@ -406,14 +406,14 @@ describe("trial start plan", () => { test("throws when no plan trial available", async () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ canTrial: false, isTrial: false }) + makeCustomerInfo({ canTrial: false, isTrial: false }), ); const { context } = createMockContext(); const func = await startCommand.loader(); await expect(func.call(context, { json: false }, "plan")).rejects.toThrow( - ValidationError + ValidationError, ); }); @@ -424,7 +424,7 @@ describe("trial start plan", () => { resolveOrgSpy.mockResolvedValue({ org: "test-org" }); getCustomerTrialInfoSpy.mockResolvedValue( - makeCustomerInfo({ canTrial: true }) + makeCustomerInfo({ canTrial: true }), ); const { context } = createMockContext(); @@ -441,7 +441,7 @@ describe("trial start plan", () => { makeCustomerInfo({ canTrial: true, planDetails: { name: "Team", trialPlan: "am3_t" }, - }) + }), ); const { context, stdoutWrite } = createMockContext(); diff --git a/packages/cli/test/commands/wasm-split.test.ts b/packages/cli/test/commands/wasm-split.test.ts index 9260ca300..9529fef2d 100644 --- a/packages/cli/test/commands/wasm-split.test.ts +++ b/packages/cli/test/commands/wasm-split.test.ts @@ -42,7 +42,7 @@ function createContext() { stdout: { write: (data: string | Uint8Array) => { writes.push( - typeof data === "string" ? data : new TextDecoder().decode(data) + typeof data === "string" ? data : new TextDecoder().decode(data), ); return true; }, @@ -59,7 +59,7 @@ function createContext() { /** Run the command, returning its stdout. */ async function run( flags: Record<string, unknown>, - input: string + input: string, ): Promise<string> { const harness = createContext(); const func = await wasmSplitCommand.loader(); @@ -95,7 +95,7 @@ describe("stdout contract", () => { const buildId = "a1b2c3d4-e5f6-4788-99aa-bbccddeeff00"; const output = await run( { "build-id": buildId }, - await writeModule(debugModule()) + await writeModule(debugModule()), ); expect(output).toBe("a1b2c3d4e5f6478899aabbccddeeff00\n"); @@ -111,7 +111,7 @@ describe("stdout contract", () => { test("--quiet cannot be used with --json", async () => { const path = await writeModule(debugModule()); await expect(run({ quiet: true, json: true }, path)).rejects.toThrow( - ValidationError + ValidationError, ); }); @@ -119,7 +119,7 @@ describe("stdout contract", () => { const path = await writeModule(debugModule()); const output = await run( { json: true, "build-id": "a1b2c3d4-e5f6-4788-99aa-bbccddeeff00" }, - path + path, ); expect(JSON.parse(output)).toMatchObject({ @@ -138,21 +138,21 @@ describe("build id", () => { expect(output.trim()).toMatch(/^[0-9a-f]{32}$/); expect( formatBuildId( - buildIdFromSections(parseSections(await readFile(path))) as Uint8Array - ) + buildIdFromSections(parseSections(await readFile(path))) as Uint8Array, + ), ).toBe(output.trim()); }); test("rejects a --build-id that is not a UUID", async () => { const path = await writeModule(debugModule()); await expect(run({ "build-id": "not-a-uuid" }, path)).rejects.toThrow( - /Invalid --build-id/ + /Invalid --build-id/, ); }); test("rejects a missing input file", async () => { await expect( - run({}, join(tmpdir(), "definitely-absent.wasm")) + run({}, join(tmpdir(), "definitely-absent.wasm")), ).rejects.toThrow(/does not exist/); }); }); @@ -229,11 +229,11 @@ describe("external_debug_info", () => { "debug-out": join(path, "..", "app.debug.wasm"), "external-dwarf-url": "https://cdn.example/debug/app.debug.wasm", }, - path + path, ); expect(await readExternalDebugInfo(path)).toBe( - "https://cdn.example/debug/app.debug.wasm" + "https://cdn.example/debug/app.debug.wasm", ); }); }); @@ -241,7 +241,7 @@ describe("external_debug_info", () => { /** Read the `external_debug_info` value out of a file on disk. */ async function readExternalDebugInfo(path: string): Promise<string | null> { const found = parseSections(await readFile(path)).find( - (entry) => entry.name === "external_debug_info" + (entry) => entry.name === "external_debug_info", ); return found?.contents ? readByteVectorString(found.contents) : null; } diff --git a/packages/cli/test/e2e/api.test.ts b/packages/cli/test/e2e/api.test.ts index d6a28602e..8366db5cd 100644 --- a/packages/cli/test/e2e/api.test.ts +++ b/packages/cli/test/e2e/api.test.ts @@ -131,7 +131,7 @@ describe("sentry api", () => { expect(result.exitCode).toBe(EXIT.OUTPUT_ERROR); expect(result.stdout).toContain( - "HTTP 404 Not Found — GET /api/0/empty-error/" + "HTTP 404 Not Found — GET /api/0/empty-error/", ); }); @@ -157,7 +157,7 @@ describe("sentry api", () => { const result = await ctx.run(["api", "nonexistent-endpoint-12345/"]); expect(result.exitCode).toBe(EXIT.OUTPUT_ERROR); - } + }, ); test("--silent flag suppresses output", { timeout: 15_000 }, async () => { @@ -183,7 +183,7 @@ describe("sentry api", () => { expect(result.exitCode).toBe(EXIT.OUTPUT_ERROR); expect(result.stdout).toBe(""); - } + }, ); test("supports custom HTTP method", { timeout: 15_000 }, async () => { @@ -268,7 +268,7 @@ describe("sentry api", () => { // stdout should still contain the response body const data = JSON.parse(result.stdout); expect(Array.isArray(data)).toBe(true); - } + }, ); // ───────────────────────────────────────────────────────────────────────────── @@ -311,7 +311,7 @@ describe("sentry api", () => { expect(result.exitCode).toBe(EXIT.VALIDATION); expect(result.stderr + result.stdout).toMatch(/file not found/i); - } + }, ); // ───────────────────────────────────────────────────────────────────────────── @@ -338,7 +338,7 @@ describe("sentry api", () => { expect(result.exitCode).toBe(0); const data = JSON.parse(result.stdout); expect(Array.isArray(data)).toBe(true); - } + }, ); test( @@ -363,7 +363,7 @@ describe("sentry api", () => { expect(result.exitCode).toBe(EXIT.OUTPUT_ERROR); // The error should be from the API, not a TypeError about body expect(result.stdout + result.stderr).not.toMatch(/cannot have body/i); - } + }, ); test( @@ -385,9 +385,9 @@ describe("sentry api", () => { expect(result.exitCode).toBe(EXIT.VALIDATION); expect(result.stderr + result.stdout).toMatch( - /--data.*--input|--input.*--data/i + /--data.*--input|--input.*--data/i, ); - } + }, ); test( @@ -409,9 +409,9 @@ describe("sentry api", () => { expect(result.exitCode).toBe(EXIT.VALIDATION); expect(result.stderr + result.stdout).toMatch( - /--data.*--field|--field.*--data/i + /--data.*--field|--field.*--data/i, ); - } + }, ); test( @@ -433,8 +433,8 @@ describe("sentry api", () => { expect(result.exitCode).toBe(EXIT.VALIDATION); expect(result.stderr + result.stdout).toMatch( - /--data.*--field|--field.*--data/i + /--data.*--field|--field.*--data/i, ); - } + }, ); }); diff --git a/packages/cli/test/e2e/auth.test.ts b/packages/cli/test/e2e/auth.test.ts index 260f834a8..040e0cc91 100644 --- a/packages/cli/test/e2e/auth.test.ts +++ b/packages/cli/test/e2e/auth.test.ts @@ -111,7 +111,7 @@ describe("sentry auth login --token", () => { expect(result.exitCode).toBe(EXIT.AUTH_HOST_SCOPE); expect(result.stderr + result.stdout).toMatch( - /invalid|unauthorized|error/i + /invalid|unauthorized|error/i, ); }); }); diff --git a/packages/cli/test/e2e/bundle.test.ts b/packages/cli/test/e2e/bundle.test.ts index c188426ec..3fb866a42 100644 --- a/packages/cli/test/e2e/bundle.test.ts +++ b/packages/cli/test/e2e/bundle.test.ts @@ -21,7 +21,7 @@ function noop(): void { async function spawnCollect( cmd: string, args: string[], - opts?: { cwd?: string; env?: NodeJS.ProcessEnv } + opts?: { cwd?: string; env?: NodeJS.ProcessEnv }, ): Promise<{ stdout: string; stderr: string; exitCode: number }> { const proc = spawn(cmd, args, { cwd: opts?.cwd, @@ -40,7 +40,7 @@ async function spawnCollect( }); const exitCode = await new Promise<number>((resolve) => - proc.on("close", (code) => resolve(code ?? 1)) + proc.on("close", (code) => resolve(code ?? 1)), ); return { stdout, stderr, exitCode }; } @@ -75,7 +75,7 @@ describe("npm bundle", () => { [BUNDLE_BIN_PATH, "--version"], { cwd: ROOT_DIR, - } + }, ); // Should not have shell syntax errors (the original bug) @@ -96,7 +96,7 @@ describe("npm bundle", () => { [BUNDLE_BIN_PATH, "--version"], { cwd: ROOT_DIR, - } + }, ); // Should not have any Node.js warnings @@ -121,7 +121,7 @@ describe("npm bundle", () => { ["--version"], { cwd: ROOT_DIR, - } + }, ); // This is the exact error from the bug report - shell interpreting JS as bash @@ -147,7 +147,7 @@ describe("npm bundle", () => { SENTRY_AUTH_TOKEN: "", SENTRY_TOKEN: "", }, - } + }, ); const output = stdout + stderr; @@ -175,7 +175,7 @@ describe("npm bundle", () => { SENTRY_AUTH_TOKEN: "", SENTRY_TOKEN: "", }, - } + }, ); const output = stdout + stderr; @@ -212,7 +212,7 @@ describe("npm bundle", () => { if (exitCode !== 0) { const output = stdout + stderr; throw new Error( - `Ink sidecar import failed (exit ${exitCode}): ${output}` + `Ink sidecar import failed (exit ${exitCode}): ${output}`, ); } expect(exitCode).toBe(0); diff --git a/packages/cli/test/e2e/completion.test.ts b/packages/cli/test/e2e/completion.test.ts index 1cfff7ccb..68e51d6c3 100644 --- a/packages/cli/test/e2e/completion.test.ts +++ b/packages/cli/test/e2e/completion.test.ts @@ -24,7 +24,7 @@ const cliDir = join(import.meta.dirname, "../.."); /** Spawn a CLI process and measure wall-clock duration. */ async function measureCommand( args: string[], - env?: Record<string, string | undefined> + env?: Record<string, string | undefined>, ): Promise<{ duration: number; exitCode: number; @@ -50,7 +50,7 @@ async function measureCommand( }); const exitCode = await new Promise<number>((resolve) => - proc.on("close", (code) => resolve(code ?? 1)) + proc.on("close", (code) => resolve(code ?? 1)), ); return { diff --git a/packages/cli/test/e2e/delta-upgrade.test.ts b/packages/cli/test/e2e/delta-upgrade.test.ts index b5e409b70..0480eae7b 100644 --- a/packages/cli/test/e2e/delta-upgrade.test.ts +++ b/packages/cli/test/e2e/delta-upgrade.test.ts @@ -64,11 +64,11 @@ describe.skipIf(!canRun)("e2e: delta upgrade", () => { const baseUrl = "https://github.com/getsentry/cli/releases/download"; await downloadBinary( `${baseUrl}/${OLD_VERSION}/${binaryName}`, - oldPath + oldPath, ); await downloadBinary( `${baseUrl}/${NEW_VERSION}/${binaryName}`, - newPath + newPath, ); // Generate TRDIFF10 patch @@ -76,7 +76,7 @@ describe.skipIf(!canRun)("e2e: delta upgrade", () => { `${BSDIFF_PATH} ${oldPath} ${newPath} ${patchPath} --use-zstd`, { stdio: "pipe", - } + }, ); // Apply patch with our implementation @@ -101,18 +101,18 @@ describe.skipIf(!canRun)("e2e: delta upgrade", () => { // the new binary while keeping the intermediate entirely in memory. execSync( `${BSDIFF_PATH} ${newPath} ${newPath} ${chainPatchPath} --use-zstd`, - { stdio: "pipe" } + { stdio: "pipe" }, ); const p1 = new Uint8Array(await readFile(patchPath)); const p2 = new Uint8Array(await readFile(chainPatchPath)); const chainSha = await applyPatchChainInMemory( oldPath, [p1, p2], - chainOutputPath + chainOutputPath, ); expect(chainSha).toBe(expectedHash); expect(new Uint8Array(await readFile(chainOutputPath))).toEqual( - expectedBytes + expectedBytes, ); // Intermediates never touch disk. expect(existsSync(`${chainOutputPath}.patching.a`)).toBe(false); @@ -134,7 +134,7 @@ describe.skipIf(!canRun)("e2e: delta upgrade", () => { } } } - } + }, ); }); diff --git a/packages/cli/test/e2e/event.test.ts b/packages/cli/test/e2e/event.test.ts index 64709f0c7..bb11cb872 100644 --- a/packages/cli/test/e2e/event.test.ts +++ b/packages/cli/test/e2e/event.test.ts @@ -80,7 +80,7 @@ describe("sentry event view", () => { expect(result.exitCode).toBe(EXIT.VALIDATION); expect(result.stderr + result.stdout).toMatch( - /invalid event id|32-character hexadecimal/i + /invalid event id|32-character hexadecimal/i, ); }); diff --git a/packages/cli/test/e2e/feedback.test.ts b/packages/cli/test/e2e/feedback.test.ts index 528cf1634..bca4e7d81 100644 --- a/packages/cli/test/e2e/feedback.test.ts +++ b/packages/cli/test/e2e/feedback.test.ts @@ -81,7 +81,7 @@ describe("sentry feedback routes", () => { expect(unresolveHelp.stdout).toContain("--json"); expect(spamHelp.exitCode, spamHelp.stderr).toBe(0); expect(spamHelp.stdout).toContain("--json"); - } + }, ); }); @@ -173,13 +173,13 @@ describe.each([ }, }, ], - { validTokens: [TEST_TOKEN] } + { validTokens: [TEST_TOKEN] }, ); await selectionServer.start(); try { const selectionContext = createE2EContext( testConfigDir, - selectionServer.url + selectionServer.url, ); await selectionContext.setAuthToken(TEST_TOKEN); const args = [`${TEST_ORG}/@latest`, "--json", "--fields", "id"]; @@ -193,7 +193,7 @@ describe.each([ ...args, ]); expect(cachedView.exitCode, cachedView.stderr + cachedView.stdout).toBe( - 0 + 0, ); expect(JSON.parse(cachedView.stdout)).toEqual({ id: TEST_FEEDBACK_ID }); expect(reads).toBe(1); @@ -286,7 +286,7 @@ describe("sentry feedback list", () => { ]); expect(firstPage.exitCode, firstPage.stderr + firstPage.stdout).toBe(0); expect(JSON.parse(firstPage.stdout).nextCursor).toBe( - `feedback-limit-${limit}-next` + `feedback-limit-${limit}-next`, ); } @@ -319,7 +319,7 @@ describe("sentry feedback view", () => { ]); expect( defaultsResult.exitCode, - defaultsResult.stderr + defaultsResult.stdout + defaultsResult.stderr + defaultsResult.stdout, ).toBe(0); for (const input of ["@latest", `${TEST_FEEDBACK_LATEST_ORG}/@latest`]) { @@ -394,10 +394,10 @@ describe("sentry feedback view", () => { expect( defaultResult.exitCode, - defaultResult.stderr + defaultResult.stdout + defaultResult.stderr + defaultResult.stdout, ).toBe(0); expect(aliasResult.exitCode, aliasResult.stderr + aliasResult.stdout).toBe( - 0 + 0, ); expect(JSON.parse(defaultResult.stdout)).toEqual({ id: TEST_FEEDBACK_ID }); expect(JSON.parse(aliasResult.stdout)).toEqual({ id: TEST_FEEDBACK_ID }); @@ -455,7 +455,7 @@ describe("sentry feedback view", () => { ]); expect(result.exitCode, result.stderr + result.stdout).toBe( - EXIT.RESOLUTION + EXIT.RESOLUTION, ); expect(result.stderr + result.stdout).toContain("is not User Feedback"); expect(result.stderr + result.stdout).toContain("sentry issue view"); diff --git a/packages/cli/test/e2e/library.test.ts b/packages/cli/test/e2e/library.test.ts index 6000e60ab..6d9d09a10 100644 --- a/packages/cli/test/e2e/library.test.ts +++ b/packages/cli/test/e2e/library.test.ts @@ -33,7 +33,7 @@ const ROOT_DIR = join(import.meta.dirname, "../.."); */ async function runNodeScript( script: string, - timeout = 15_000 + timeout = 15_000, ): Promise<{ stdout: string; stderr: string; exitCode: number }> { const env: Record<string, string | undefined> = { ...process.env, @@ -63,7 +63,7 @@ async function runNodeScript( }); const exitCode = await new Promise<number>((resolve) => - proc.on("close", (code) => resolve(code ?? 1)) + proc.on("close", (code) => resolve(code ?? 1)), ); clearTimeout(timer); @@ -76,12 +76,12 @@ async function runNodeScript( */ async function runNodeScriptOk( script: string, - timeout?: number + timeout?: number, ): Promise<{ stdout: string; stderr: string }> { const result = await runNodeScript(script, timeout); if (result.exitCode !== 0) { throw new Error( - `Node exited ${result.exitCode}.\nstdout: ${result.stdout}\nstderr: ${result.stderr}` + `Node exited ${result.exitCode}.\nstdout: ${result.stdout}\nstderr: ${result.stderr}`, ); } return result; @@ -209,7 +209,7 @@ describe("library mode (bundled)", () => { const directory = getConfigDir(); await writeFile( join(directory, "app.js"), - "console.log('fixture');\n//# sourceMappingURL=app.js.map\n" + "console.log('fixture');\n//# sourceMappingURL=app.js.map\n", ); await writeFile( join(directory, "app.js.map"), @@ -219,10 +219,10 @@ describe("library mode (bundled)", () => { sourcesContent: ["console.log('fixture');"], names: [], mappings: "AAAA", - }) + }), ); const claim = Buffer.from( - JSON.stringify({ org: "test-org", url: "http://localhost:9000" }) + JSON.stringify({ org: "test-org", url: "http://localhost:9000" }), ).toString("base64"); const token = `sntrys_${claim}_ab\nSYNTHETIC-SECRET-TAIL`; const { stdout, stderr } = await runNodeScriptOk(` diff --git a/packages/cli/test/e2e/migration.test.ts b/packages/cli/test/e2e/migration.test.ts index d9ef3bb89..cc6de98e0 100644 --- a/packages/cli/test/e2e/migration.test.ts +++ b/packages/cli/test/e2e/migration.test.ts @@ -83,7 +83,7 @@ describe("e2e: legacy ~/.sentry migration", () => { const xdgDb = join(home, ".config", "sentry", "cli.db"); expect(existsSync(xdgDb)).toBe(true); expect(existsSync(join(legacyDir, "cli.db"))).toBe(false); - } + }, ); test( @@ -111,6 +111,6 @@ describe("e2e: legacy ~/.sentry migration", () => { expect(existsSync(movedBin)).toBe(true); expect(await readFile(movedBin, "utf8")).toBe("#!/bin/sh\necho legacy\n"); expect(existsSync(legacyBin)).toBe(false); - } + }, ); }); diff --git a/packages/cli/test/e2e/multiregion.test.ts b/packages/cli/test/e2e/multiregion.test.ts index c32bc1762..f251cc010 100644 --- a/packages/cli/test/e2e/multiregion.test.ts +++ b/packages/cli/test/e2e/multiregion.test.ts @@ -72,7 +72,7 @@ describe("multi-region", () => { // In test environment, region URLs are localhost, so display shows LOCALHOST // The important thing is that REGION column appears when orgs span multiple regions // (In production, would show US/EU based on actual hostname like us.sentry.io) - } + }, ); test( @@ -92,7 +92,7 @@ describe("multi-region", () => { for (const orgSlug of EU_ORGS) { expect(result.stdout).toContain(orgSlug); } - } + }, ); test( @@ -112,7 +112,7 @@ describe("multi-region", () => { for (const orgSlug of [...US_ORGS, ...EU_ORGS]) { expect(slugs).toContain(orgSlug); } - } + }, ); }); @@ -132,7 +132,7 @@ describe("multi-region", () => { expect(result.exitCode).toBe(0); expect(result.stdout).toContain("acme-corp"); expect(result.stdout).toContain("Acme Corporation"); - } + }, ); test( @@ -150,7 +150,7 @@ describe("multi-region", () => { expect(result.exitCode).toBe(0); expect(result.stdout).toContain("euro-gmbh"); expect(result.stdout).toContain("Euro GmbH"); - } + }, ); }); @@ -171,7 +171,7 @@ describe("multi-region", () => { for (const projectSlug of US_PROJECTS["acme-corp"]) { expect(result.stdout).toContain(projectSlug); } - } + }, ); test( @@ -190,7 +190,7 @@ describe("multi-region", () => { for (const projectSlug of EU_PROJECTS["euro-gmbh"]) { expect(result.stdout).toContain(projectSlug); } - } + }, ); test( @@ -219,7 +219,7 @@ describe("multi-region", () => { for (const projectSlug of EU_PROJECTS["berlin-startup"]) { expect(slugs).toContain(projectSlug); } - } + }, ); }); @@ -242,9 +242,9 @@ describe("multi-region", () => { expect(result.exitCode).toBe(0); // Should contain the US issue (strip markdown bold markers from short ID) expect(result.stdout.replace(/\*\*/g, "")).toContain( - "ACME-FRONTEND-1A" + "ACME-FRONTEND-1A", ); - } + }, ); test( @@ -265,7 +265,7 @@ describe("multi-region", () => { expect(result.exitCode).toBe(0); // Should contain the EU issue (strip markdown bold markers from short ID) expect(result.stdout.replace(/\*\*/g, "")).toContain("EURO-PORTAL-1A"); - } + }, ); test( @@ -293,7 +293,7 @@ describe("multi-region", () => { // Should contain Berlin issue const shortIds = parsed.data.map((i: { shortId: string }) => i.shortId); expect(shortIds).toContain("BERLIN-APP-1A"); - } + }, ); }); }); @@ -340,7 +340,7 @@ describe("single-region", () => { // Should still contain US orgs expect(result.stdout).toContain("acme-corp"); expect(result.stdout).toContain("widgets-inc"); - } + }, ); }); }); @@ -386,7 +386,7 @@ describe("self-hosted fallback", () => { expect(result.stdout).toContain("SLUG"); // Should have orgs from the fallback (US fixtures served by control silo) expect(result.stdout).toContain("acme-corp"); - } + }, ); test( @@ -401,7 +401,7 @@ describe("self-hosted fallback", () => { const data = JSON.parse(result.stdout); expect(Array.isArray(data)).toBe(true); expect(data.length).toBeGreaterThan(0); - } + }, ); }); }); diff --git a/packages/cli/test/e2e/project.test.ts b/packages/cli/test/e2e/project.test.ts index b87a9d9c1..910e975c4 100644 --- a/packages/cli/test/e2e/project.test.ts +++ b/packages/cli/test/e2e/project.test.ts @@ -101,7 +101,7 @@ describe("sentry project list", () => { ]); expect(result.exitCode).toBe(0); - } + }, ); test("supports --json output", { timeout: 15_000 }, async () => { @@ -225,7 +225,7 @@ describe("sentry project view", () => { expect(result.exitCode).toBe(0); expect(result.stdout).toContain("DSN"); expect(result.stdout).toContain(TEST_DSN); - } + }, ); test("supports --json output", { timeout: 15_000 }, async () => { diff --git a/packages/cli/test/e2e/skill-eval.test.ts b/packages/cli/test/e2e/skill-eval.test.ts index 3a4c005ca..1ea152fbf 100644 --- a/packages/cli/test/e2e/skill-eval.test.ts +++ b/packages/cli/test/e2e/skill-eval.test.ts @@ -71,7 +71,7 @@ describe.skipIf(!provider)("skill eval", () => { client, model, skillContent, - testCase.prompt + testCase.prompt, ); const result = await judgePlan(client, testCase, plan); results.push(result); @@ -79,13 +79,13 @@ describe.skipIf(!provider)("skill eval", () => { const passed = results.filter((r) => r.passed).length; const score = passed / testCases.length; - // biome-ignore lint/suspicious/noMisplacedAssertion: called from test() via helper + // called from test() via helper expect(score).toBeGreaterThanOrEqual(threshold); } for (const model of AGENT_MODELS) { test(`${model} meets threshold`, { timeout: 120_000 }, () => - runEvalForModel(model) + runEvalForModel(model), ); } }); diff --git a/packages/cli/test/e2e/telemetry-exit.test.ts b/packages/cli/test/e2e/telemetry-exit.test.ts index 9cd8e08ff..7c066937c 100644 --- a/packages/cli/test/e2e/telemetry-exit.test.ts +++ b/packages/cli/test/e2e/telemetry-exit.test.ts @@ -33,7 +33,7 @@ describe("telemetry exit timing", () => { }); disabledProc.on("error", noop); const disabledExitCode = await new Promise<number>((resolve) => - disabledProc.on("close", (code) => resolve(code ?? 1)) + disabledProc.on("close", (code) => resolve(code ?? 1)), ); const disabledDuration = performance.now() - disabledStart; @@ -50,7 +50,7 @@ describe("telemetry exit timing", () => { }); enabledProc.on("error", noop); const enabledExitCode = await new Promise<number>((resolve) => - enabledProc.on("close", (code) => resolve(code ?? 1)) + enabledProc.on("close", (code) => resolve(code ?? 1)), ); const enabledDuration = performance.now() - enabledStart; diff --git a/packages/cli/test/eval-common/anthropic-client.ts b/packages/cli/test/eval-common/anthropic-client.ts index 373e4a62d..50aa0b929 100644 --- a/packages/cli/test/eval-common/anthropic-client.ts +++ b/packages/cli/test/eval-common/anthropic-client.ts @@ -37,7 +37,7 @@ export type EvalProvider = { chat: ( model: string, messages: ChatMessage[], - maxTokens: number + maxTokens: number, ) => Promise<string>; }; @@ -48,7 +48,7 @@ export type EvalProvider = { * skip (matching the historical `!apiKey` skip behavior). */ export function resolveEvalProvider( - env: NodeJS.ProcessEnv = process.env + env: NodeJS.ProcessEnv = process.env, ): EvalProvider | null { const openRouterKey = env.OPENROUTER_API_KEY?.trim(); const anthropicKey = env.ANTHROPIC_API_KEY?.trim(); @@ -144,7 +144,7 @@ async function anthropicChat({ anthropicModel = model.slice("anthropic/".length); } else if (model.startsWith("openai/")) { throw new Error( - `Anthropic direct provider cannot serve OpenAI model "${model}"` + `Anthropic direct provider cannot serve OpenAI model "${model}"`, ); } diff --git a/packages/cli/test/fixture.ts b/packages/cli/test/fixture.ts index 2a88a2489..669fa5720 100644 --- a/packages/cli/test/fixture.ts +++ b/packages/cli/test/fixture.ts @@ -78,7 +78,7 @@ export async function runCli( options?: { cwd?: string; env?: Record<string, string>; - } + }, ): Promise<CliResult> { const cliDir = join(import.meta.dirname, ".."); const [cmdBin, ...cmdArgs] = getCliCommand(); @@ -100,7 +100,7 @@ export async function runCli( }); const exitCode = await new Promise<number>((resolve) => - proc.on("close", (code) => resolve(code ?? 1)) + proc.on("close", (code) => resolve(code ?? 1)), ); return { @@ -128,7 +128,7 @@ export type E2EContext = { */ export function createE2EContext( configDir: string, - serverUrl: string + serverUrl: string, ): E2EContext { // Use the constant directly instead of require() to avoid .js→.ts // resolution issues in vitest Node workers. diff --git a/packages/cli/test/fixtures/bench/generate.ts b/packages/cli/test/fixtures/bench/generate.ts index 5f13b0e86..28f009964 100644 --- a/packages/cli/test/fixtures/bench/generate.ts +++ b/packages/cli/test/fixtures/bench/generate.ts @@ -73,12 +73,12 @@ export type FixtureMeta = { * plenty for fixture generation. The `next()` method returns a float * in [0, 1) matching Math.random()'s contract. * - * Note: the biome rule against bitwise operators is intentionally + * The lint rule against bitwise operators is intentionally * suppressed for this class. Xorshift's defining property is the * bitwise xor/shift sequence; rewriting with arithmetic would change * the output (and the "reproducible trees" property). */ -// biome-ignore-start lint/suspicious/noBitwiseOperators: xorshift32 requires bitwise ops +// oxlint-disable no-bitwise -- xorshift32 requires bitwise ops class XorShift32 { private state: number; @@ -113,7 +113,7 @@ class XorShift32 { return this.next() < p; } } -// biome-ignore-end lint/suspicious/noBitwiseOperators: xorshift32 requires bitwise ops +// oxlint-enable no-bitwise /** Synthetic DSN templates scattered throughout text files. */ const DSN_TEMPLATES = [ @@ -168,7 +168,7 @@ out/ */ export function generateFixture( spec: FixtureSpec, - options: { force?: boolean } = {} + options: { force?: boolean } = {}, ): FixtureMeta { const existing = readMeta(spec.rootDir); if (!options.force && existing && specMatches(existing.spec, spec)) { @@ -197,7 +197,7 @@ export function generateFixture( writeFileSync( join(spec.rootDir, "node_modules", "some-pkg", "index.js"), "// should never be scanned\n", - "utf8" + "utf8", ); if (spec.packages === 0) { @@ -216,7 +216,7 @@ export function generateFixture( const pkgDir = join( spec.rootDir, monorepoRoot, - `pkg-${i.toString().padStart(3, "0")}` + `pkg-${i.toString().padStart(3, "0")}`, ); mkdirSync(pkgDir, { recursive: true }); if (spec.gitignoreDepth === "nested") { @@ -240,7 +240,7 @@ export function generateFixture( writeFileSync( join(spec.rootDir, ".meta.json"), JSON.stringify(meta, null, 2), - "utf8" + "utf8", ); return meta; } @@ -250,12 +250,12 @@ function populatePackage( baseDir: string, spec: FixtureSpec, rng: XorShift32, - _opts: { includeNested: boolean } + _opts: { includeNested: boolean }, ): { fileCount: number; dsnCount: number } { const targetFiles = spec.filesPerPackage; const textCount = Math.max( 1, - Math.round(targetFiles * (1 - spec.binaryRatio)) + Math.round(targetFiles * (1 - spec.binaryRatio)), ); const binaryCount = Math.max(0, targetFiles - textCount); @@ -320,7 +320,7 @@ function pickSubdir(base: string, depth: number, rng: XorShift32): string { function buildTextFileContent( avgKB: number, includeDsn: boolean, - rng: XorShift32 + rng: XorShift32, ): string { // Target size varies between 0.5x–2x avgKB to simulate a lognormal-ish spread. const sizeKB = avgKB * (0.5 + rng.next() * 1.5); diff --git a/packages/cli/test/fixtures/bench/helpers.ts b/packages/cli/test/fixtures/bench/helpers.ts index 95dc1ae4b..cd9f486de 100644 --- a/packages/cli/test/fixtures/bench/helpers.ts +++ b/packages/cli/test/fixtures/bench/helpers.ts @@ -82,7 +82,7 @@ export type MeasureOptions = { */ export async function measure( fn: () => Promise<void> | void, - options: MeasureOptions = {} + options: MeasureOptions = {}, ): Promise<number[]> { const runs = options.runs ?? 10; const warmup = options.warmup ?? 3; @@ -132,7 +132,7 @@ export function summarize(samples: readonly number[]): BenchStats { function percentile(sorted: readonly number[], p: number): number { const idx = Math.min( sorted.length - 1, - Math.max(0, Math.ceil(p * sorted.length) - 1) + Math.max(0, Math.ceil(p * sorted.length) - 1), ); return sorted[idx] as number; } @@ -146,7 +146,7 @@ function percentile(sorted: readonly number[], p: number): number { * user cache even if the harness crashes (finally-block cleanup). */ export async function withBenchDb<T>( - fn: (configDir: string) => Promise<T> + fn: (configDir: string) => Promise<T>, ): Promise<T> { // Import lazily — importing `src/lib/db/index.js` has non-trivial side // effects (loads bun:sqlite, @sentry/node-core via createTracedDatabase). @@ -189,12 +189,11 @@ export async function withBenchDb<T>( * the bench DB (though in practice the bench DB only contains bench rows). */ export async function clearDsnDetectionCache( - projectRoot: string + projectRoot: string, ): Promise<void> { const { clearDsnCache } = await import("../../../src/lib/db/dsn-cache.js"); - const { clearProjectRootCacheFor } = await import( - "../../../src/lib/db/project-root-cache.js" - ); + const { clearProjectRootCacheFor } = + await import("../../../src/lib/db/project-root-cache.js"); clearDsnCache(projectRoot); await clearProjectRootCacheFor(projectRoot); } @@ -211,7 +210,7 @@ export function printReport(report: BenchReport): void { console.log(""); console.log( - `Bench report (${report.runtime.platform}/${report.runtime.arch}, bun ${report.runtime.bun}, ${report.runtime.cpus} cpus)` + `Bench report (${report.runtime.platform}/${report.runtime.arch}, bun ${report.runtime.bun}, ${report.runtime.cpus} cpus)`, ); console.log("─".repeat(72)); @@ -219,13 +218,13 @@ export function printReport(report: BenchReport): void { console.log(`\n${fixture}`); const longest = entries.reduce( (acc, e) => Math.max(acc, e.operation.length), - 0 + 0, ); for (const entry of entries) { const pad = entry.operation.padEnd(longest); const { p50, p95, runs } = entry.stats; console.log( - ` ${pad} p50 ${fmtMs(p50)} p95 ${fmtMs(p95)} (${runs} runs)` + ` ${pad} p50 ${fmtMs(p50)} p95 ${fmtMs(p95)} (${runs} runs)`, ); } } @@ -242,7 +241,7 @@ function fmtMs(ms: number): string { /** Write the report to `path` as indented JSON. */ export async function writeJsonReport( report: BenchReport, - path: string + path: string, ): Promise<void> { await writeFile(path, `${JSON.stringify(report, null, 2)}\n`); } @@ -272,7 +271,7 @@ export type ComparisonRow = { export function compareReports( baseline: BenchReport, current: BenchReport, - thresholdPct: number + thresholdPct: number, ): ComparisonRow[] { const keys = new Set<string>(); const index = new Map<string, BenchEntry>(); @@ -345,7 +344,7 @@ const VERDICT_ICONS: Record<ComparisonRow["verdict"], string> = { function formatComparisonRow( r: ComparisonRow, widthFixture: number, - widthOp: number + widthOp: number, ): string { const base = r.baseline ? fmtMs(r.baseline.p50) : "—".padStart(7); const cur = r.current ? fmtMs(r.current.p50) : "—".padStart(7); @@ -359,20 +358,20 @@ function formatComparisonRow( /** Render comparison rows to stdout. Returns true when no regressions. */ export function printComparison( rows: readonly ComparisonRow[], - thresholdPct: number + thresholdPct: number, ): boolean { const widthFixture = rows.reduce( (acc, r) => Math.max(acc, r.fixture.length), - 8 + 8, ); const widthOp = rows.reduce((acc, r) => Math.max(acc, r.operation.length), 9); console.log(""); console.log( - `Comparison vs baseline (threshold ±${(thresholdPct * 100).toFixed(0)}%)` + `Comparison vs baseline (threshold ±${(thresholdPct * 100).toFixed(0)}%)`, ); console.log("─".repeat(72)); console.log( - `${"fixture".padEnd(widthFixture)} ${"operation".padEnd(widthOp)} ${"base p50".padStart(10)} ${"cur p50".padStart(10)} ${"Δms".padStart(9)} ${"Δ%".padStart(7)} verdict` + `${"fixture".padEnd(widthFixture)} ${"operation".padEnd(widthOp)} ${"base p50".padStart(10)} ${"cur p50".padStart(10)} ${"Δms".padStart(9)} ${"Δ%".padStart(7)} verdict`, ); let ok = true; for (const r of rows) { diff --git a/packages/cli/test/fixtures/local-agent-server.ts b/packages/cli/test/fixtures/local-agent-server.ts index 467ce7af8..9825ae34b 100644 --- a/packages/cli/test/fixtures/local-agent-server.ts +++ b/packages/cli/test/fixtures/local-agent-server.ts @@ -35,8 +35,8 @@ export function createLocalAgentServer() { const id = c.req.param("id"); logger.info("Fixture user loaded", { attributes: { id } }); return c.json({ id, name: "Ada Lovelace", source: "fixture-db" }); - } - ) + }, + ), ); app.post("/api/agent/run", async (c) => { @@ -68,14 +68,14 @@ export function createLocalAgentServer() { "gen_ai.tool.name": "search_files", }, }, - async () => "search_files" + async () => "search_files", ); return c.json({ answer: "The rate limit is configured in src/lib/rate-limit.ts.", tool, }); - } + }, ); }); @@ -94,9 +94,9 @@ export function createLocalAgentServer() { message: "The fixture intentionally failed. Check sentry local for details.", }, - 500 + 500, ); - }) + }), ); return app; @@ -116,7 +116,7 @@ function startServer(): void { serve({ fetch: createLocalAgentServer().fetch, port, hostname: "127.0.0.1" }); process.stderr.write( - `Local agent fixture listening at http://127.0.0.1:${port}\n` + `Local agent fixture listening at http://127.0.0.1:${port}\n`, ); } diff --git a/packages/cli/test/helpers.ts b/packages/cli/test/helpers.ts index cb9f6fb9e..d8a19c112 100644 --- a/packages/cli/test/helpers.ts +++ b/packages/cli/test/helpers.ts @@ -16,7 +16,7 @@ import { } from "../src/lib/db/auth.js"; import { CONFIG_DIR_ENV_VAR, closeDatabase } from "../src/lib/db/index.js"; -// biome-ignore lint/performance/noBarrelFile: re-exporting a single constant, not a barrel +// re-exporting a single constant, not a barrel export { TEST_TMP_DIR } from "./constants.js"; import { TEST_TMP_DIR } from "./constants.js"; @@ -42,7 +42,7 @@ type TestConfigDirOptions = { */ export async function createTestConfigDir( prefix = "sentry-test-", - options?: TestConfigDirOptions + options?: TestConfigDirOptions, ): Promise<string> { const dir = await mkdtemp(join(TEST_TMP_DIR, prefix)); @@ -103,7 +103,7 @@ export function mockFetch(fn: FetchMockFn): typeof fetch { */ export function useTestConfigDir( prefix = "sentry-test-", - options?: TestConfigDirOptions + options?: TestConfigDirOptions, ): () => string { let dir: string; let savedConfigDir: string | undefined; @@ -274,7 +274,7 @@ export type FetchCall = { */ export function installFetchMock( routes: FetchRoute[], - fallback?: { status?: number; body?: unknown } + fallback?: { status?: number; body?: unknown }, ): { calls: FetchCall[]; restore: () => void } { const calls: FetchCall[] = []; const originalFetch = globalThis.fetch; @@ -283,8 +283,8 @@ export function installFetchMock( globalThis.fetch = (async ( input: RequestInfo | URL, - init?: RequestInit - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: test mock router requires branching for method/URL/body matching + init?: RequestInit, + // test mock router requires branching for method/URL/body matching ): Promise<Response> => { const url = extractFetchUrl(input); const method = init?.method ?? "GET"; @@ -317,12 +317,13 @@ export function installFetchMock( const headers: Record<string, string> = { "Content-Type": "application/json", + // oxlint-disable-next-line unicorn/no-useless-fallback-in-spread -- Retain the existing fallback while migrating lint tools. ...(route.headers ?? {}), }; return new Response( responseBody !== undefined ? JSON.stringify(responseBody) : undefined, - { status, headers } + { status, headers }, ); } diff --git a/packages/cli/test/init-eval/helpers/assertions.ts b/packages/cli/test/init-eval/helpers/assertions.ts index 52633b95f..f5011f4c5 100644 --- a/packages/cli/test/init-eval/helpers/assertions.ts +++ b/packages/cli/test/init-eval/helpers/assertions.ts @@ -16,7 +16,7 @@ export type AssertionFailure = { export function runAssertions( projectDir: string, platform: Platform, - result: WizardResult + result: WizardResult, ): AssertionFailure[] { const failures: AssertionFailure[] = []; @@ -32,7 +32,7 @@ export function runAssertions( try { const depContent = readFileSync( join(projectDir, platform.depFile), - "utf-8" + "utf-8", ); if (!depContent.includes(platform.sdkPackage)) { failures.push({ diff --git a/packages/cli/test/init-eval/helpers/create-eval-suite.ts b/packages/cli/test/init-eval/helpers/create-eval-suite.ts index 958987285..1cd4f703c 100644 --- a/packages/cli/test/init-eval/helpers/create-eval-suite.ts +++ b/packages/cli/test/init-eval/helpers/create-eval-suite.ts @@ -32,7 +32,7 @@ export function createEvalSuite(platformId: string) { result = await runWizard(env.projectDir, p, wizardFeatures); expect(result.exitCode).toBe(0); }, - p.timeout + p.timeout, ); test("hard assertions pass", async () => { diff --git a/packages/cli/test/init-eval/helpers/docs-fetcher.ts b/packages/cli/test/init-eval/helpers/docs-fetcher.ts index 6964320a6..6d2e4a5ae 100644 --- a/packages/cli/test/init-eval/helpers/docs-fetcher.ts +++ b/packages/cli/test/init-eval/helpers/docs-fetcher.ts @@ -25,7 +25,7 @@ export async function fetchDocsContent(urls: string[]): Promise<string> { const charBudgetPerUrl = Math.floor(6000 / urls.length); const results = await Promise.all( - urls.map((url) => fetchOne(url, charBudgetPerUrl)) + urls.map((url) => fetchOne(url, charBudgetPerUrl)), ); return results.join("\n\n---\n\n"); } diff --git a/packages/cli/test/init-eval/helpers/judge.ts b/packages/cli/test/init-eval/helpers/judge.ts index 6c3311646..e8882b51f 100644 --- a/packages/cli/test/init-eval/helpers/judge.ts +++ b/packages/cli/test/init-eval/helpers/judge.ts @@ -31,12 +31,12 @@ export async function judgeFeature( result: WizardResult, platform: Platform, feature: FeatureDoc, - docsContent: string + docsContent: string, ): Promise<JudgeVerdict | null> { const provider = resolveEvalProvider(); if (!provider) { console.log( - ` [judge:${feature.feature}] Skipping LLM judge (no OPENROUTER_API_KEY or ANTHROPIC_API_KEY set)` + ` [judge:${feature.feature}] Skipping LLM judge (no OPENROUTER_API_KEY or ANTHROPIC_API_KEY set)`, ); return null; } @@ -90,7 +90,7 @@ Return ONLY valid JSON with this structure: const text = await provider.chat( process.env.EVAL_JUDGE_MODEL ?? INIT_EVAL_JUDGE_MODEL, [{ role: "user", content: prompt }], - 1024 + 1024, ); // Extract JSON from response (handle markdown code blocks) @@ -98,7 +98,7 @@ Return ONLY valid JSON with this structure: if (!jsonMatch) { console.log( ` [judge:${feature.feature}] Failed to parse judge response:`, - text.slice(0, 200) + text.slice(0, 200), ); return null; } @@ -109,7 +109,7 @@ Return ONLY valid JSON with this structure: } catch { console.log( ` [judge:${feature.feature}] Invalid JSON in response:`, - jsonMatch[0].slice(0, 200) + jsonMatch[0].slice(0, 200), ); return null; } @@ -128,7 +128,7 @@ Return ONLY valid JSON with this structure: // Log for visibility in test output console.log( - ` [judge:${feature.feature}] Score: ${passing}/${total} (${(score * 100).toFixed(0)}%)` + ` [judge:${feature.feature}] Score: ${passing}/${total} (${(score * 100).toFixed(0)}%)`, ); for (const c of parsed.criteria) { let icon = "FAIL"; diff --git a/packages/cli/test/init-eval/helpers/platforms.ts b/packages/cli/test/init-eval/helpers/platforms.ts index 86dba6691..c4451e7d4 100644 --- a/packages/cli/test/init-eval/helpers/platforms.ts +++ b/packages/cli/test/init-eval/helpers/platforms.ts @@ -43,14 +43,14 @@ const TEMPLATES_DIR = join(import.meta.dirname, "../templates"); /** Load feature docs from the external JSON config. */ const featureDocsRaw: Record<string, Record<string, string[]>> = JSON.parse( - readFileSync(join(import.meta.dirname, "../feature-docs.json"), "utf-8") + readFileSync(join(import.meta.dirname, "../feature-docs.json"), "utf-8"), ); function getDocs(platformId: string): FeatureDoc[] { const entry = featureDocsRaw[platformId]; if (!entry) { throw new Error( - `No feature docs found for platform "${platformId}" in feature-docs.json` + `No feature docs found for platform "${platformId}" in feature-docs.json`, ); } return Object.entries(entry).map(([feature, urls]) => ({ diff --git a/packages/cli/test/init-eval/helpers/run-wizard.ts b/packages/cli/test/init-eval/helpers/run-wizard.ts index 95f056f78..bddb4f30d 100644 --- a/packages/cli/test/init-eval/helpers/run-wizard.ts +++ b/packages/cli/test/init-eval/helpers/run-wizard.ts @@ -26,12 +26,12 @@ export type WizardResult = { export async function runWizard( projectDir: string, platform: Platform, - features?: string[] + features?: string[], ): Promise<WizardResult> { // Resolve relative paths (e.g. "src/bin.ts") against the CLI repo root, // since the wizard spawns with cwd set to the temp project directory. const cmd = getCliCommand().map((part) => - part.includes("/") ? resolvePath(CLI_ROOT, part) : part + part.includes("/") ? resolvePath(CLI_ROOT, part) : part, ); const mastraUrl = process.env.MASTRA_API_URL; if (!mastraUrl) { @@ -90,7 +90,7 @@ export async function runWizard( }); const exitCode = await new Promise<number>((resolve) => - proc.on("close", (code) => resolve(code ?? 1)) + proc.on("close", (code) => resolve(code ?? 1)), ); // Capture git diff (staged + unstaged changes since last commit) diff --git a/packages/cli/test/init-eval/templates/react-vite-app/index.html b/packages/cli/test/init-eval/templates/react-vite-app/index.html index 71e5e7848..5da656cb1 100644 --- a/packages/cli/test/init-eval/templates/react-vite-app/index.html +++ b/packages/cli/test/init-eval/templates/react-vite-app/index.html @@ -1,8 +1,8 @@ <!doctype html> <html lang="en"> <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React App diff --git a/packages/cli/test/init-eval/templates/sveltekit-app/src/app.d.ts b/packages/cli/test/init-eval/templates/sveltekit-app/src/app.d.ts index da08e6da5..520c4217a 100644 --- a/packages/cli/test/init-eval/templates/sveltekit-app/src/app.d.ts +++ b/packages/cli/test/init-eval/templates/sveltekit-app/src/app.d.ts @@ -1,13 +1,13 @@ // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } export {}; diff --git a/packages/cli/test/init-eval/templates/sveltekit-app/src/app.html b/packages/cli/test/init-eval/templates/sveltekit-app/src/app.html index f273cc58f..adf8bd873 100644 --- a/packages/cli/test/init-eval/templates/sveltekit-app/src/app.html +++ b/packages/cli/test/init-eval/templates/sveltekit-app/src/app.html @@ -1,11 +1,11 @@ - - - - %sveltekit.head% - - -
%sveltekit.body%
- + + + + %sveltekit.head% + + +
%sveltekit.body%
+ diff --git a/packages/cli/test/init-eval/templates/sveltekit-app/svelte.config.js b/packages/cli/test/init-eval/templates/sveltekit-app/svelte.config.js index 6bfb3c408..d341d7e03 100644 --- a/packages/cli/test/init-eval/templates/sveltekit-app/svelte.config.js +++ b/packages/cli/test/init-eval/templates/sveltekit-app/svelte.config.js @@ -1,10 +1,10 @@ -import adapter from '@sveltejs/adapter-node'; +import adapter from "@sveltejs/adapter-node"; /** @type {import('@sveltejs/kit').Config} */ const config = { - kit: { - adapter: adapter() - } + kit: { + adapter: adapter(), + }, }; export default config; diff --git a/packages/cli/test/lib/agent-skills.test.ts b/packages/cli/test/lib/agent-skills.test.ts index 80f621c8b..d799f8a72 100644 --- a/packages/cli/test/lib/agent-skills.test.ts +++ b/packages/cli/test/lib/agent-skills.test.ts @@ -38,7 +38,7 @@ describe("agent-skills", () => { beforeEach(() => { testDir = join( "/tmp", - `agent-skills-detect-${Date.now()}-${Math.random().toString(36).slice(2)}` + `agent-skills-detect-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -75,7 +75,7 @@ describe("agent-skills", () => { beforeEach(() => { testDir = join( "/tmp", - `agent-skills-install-${Date.now()}-${Math.random().toString(36).slice(2)}` + `agent-skills-install-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -113,7 +113,7 @@ describe("agent-skills", () => { expect(result).not.toBeNull(); expect(result!.created).toBe(true); expect(result!.path).toBe( - join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md") + join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md"), ); expect(existsSync(result!.path)).toBe(true); @@ -126,13 +126,15 @@ describe("agent-skills", () => { ".agents", "skills", "sentry-cli", - "references" + "references", ); expect(existsSync(refsDir)).toBe(true); expect(existsSync(join(refsDir, "issue.md"))).toBe(true); expect( - existsSync(join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md")) + existsSync( + join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md"), + ), ).toBe(false); }); @@ -151,7 +153,7 @@ describe("agent-skills", () => { .mock.calls.map((call) => String(call[0])); expect(writeTargets.length).toBeGreaterThan(0); expect(writeTargets.every((target) => target.endsWith(".tmp"))).toBe( - true + true, ); const renameDestinations = vi @@ -183,7 +185,7 @@ describe("agent-skills", () => { // branch in atomicWriteFile (rm of the temp) and the null-returning // failure handler in writeSkillFiles. vi.mocked(rename).mockRejectedValueOnce( - new Error("simulated rename failure") + new Error("simulated rename failure"), ); const result = await installAgentSkills(testDir); @@ -195,7 +197,7 @@ describe("agent-skills", () => { const skillDir = join(testDir, ".agents", "skills", "sentry-cli"); const leftovers = readdirSync(skillDir).filter((name) => - name.endsWith(".tmp") + name.endsWith(".tmp"), ); expect(leftovers).toEqual([]); expect(existsSync(join(skillDir, "SKILL.md"))).toBe(false); @@ -225,13 +227,17 @@ describe("agent-skills", () => { expect(result).not.toBeNull(); expect(result!.path).toBe( - join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md") + join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md"), ); expect( - existsSync(join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md")) + existsSync( + join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md"), + ), ).toBe(true); expect( - existsSync(join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md")) + existsSync( + join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md"), + ), ).toBe(true); expect( existsSync( @@ -241,9 +247,9 @@ describe("agent-skills", () => { "skills", "sentry-cli", "references", - "issue.md" - ) - ) + "issue.md", + ), + ), ).toBe(true); expect( existsSync( @@ -253,9 +259,9 @@ describe("agent-skills", () => { "skills", "sentry-cli", "references", - "issue.md" - ) - ) + "issue.md", + ), + ), ).toBe(true); }); @@ -276,7 +282,7 @@ describe("agent-skills", () => { const first = await installAgentSkills(testDir); expect(first!.created).toBe(true); expect(first!.path).toBe( - join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md") + join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md"), ); mkdirSync(join(testDir, ".claude"), { recursive: true }); @@ -285,7 +291,7 @@ describe("agent-skills", () => { expect(second).not.toBeNull(); expect(second!.created).toBe(true); expect(second!.path).toBe( - join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md") + join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md"), ); }); @@ -297,11 +303,13 @@ describe("agent-skills", () => { const result = await installAgentSkills(testDir); expect(result).not.toBeNull(); expect(result!.path).toBe( - join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md") + join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md"), ); expect(existsSync(result!.path)).toBe(true); expect( - existsSync(join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md")) + existsSync( + join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md"), + ), ).toBe(false); }); @@ -313,11 +321,13 @@ describe("agent-skills", () => { const result = await installAgentSkills(testDir); expect(result).not.toBeNull(); expect(result!.path).toBe( - join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md") + join(testDir, ".agents", "skills", "sentry-cli", "SKILL.md"), ); expect(existsSync(result!.path)).toBe(true); expect( - existsSync(join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md")) + existsSync( + join(testDir, ".claude", "skills", "sentry-cli", "SKILL.md"), + ), ).toBe(false); }); diff --git a/packages/cli/test/lib/alias.property.test.ts b/packages/cli/test/lib/alias.property.test.ts index fa01328a0..71b008218 100644 --- a/packages/cli/test/lib/alias.property.test.ts +++ b/packages/cli/test/lib/alias.property.test.ts @@ -49,7 +49,7 @@ const projectSlugArb = slugWithHyphensArb; /** Generate org/project pairs */ const orgProjectPairArb = tuple(orgSlugArb, projectSlugArb).map( - ([org, project]): OrgProjectPair => ({ org, project }) + ([org, project]): OrgProjectPair => ({ org, project }), ); /** Generate arrays of unique strings */ @@ -62,7 +62,7 @@ const uniqueStringsArb = uniqueArray(simpleSlugArb, { /** Generate strings with common word prefix (like spotlight-*) */ const commonPrefixStringsArb = tuple( simpleSlugArb, - uniqueArray(simpleSlugArb, { minLength: 2, maxLength: 5 }) + uniqueArray(simpleSlugArb, { minLength: 2, maxLength: 5 }), ).map(([prefix, suffixes]) => suffixes.map((s) => `${prefix}-${s}`)); // Properties for findShortestUniquePrefixes @@ -78,7 +78,7 @@ describe("property: findShortestUniquePrefixes", () => { expect(prefixes.has(str)).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -92,7 +92,7 @@ describe("property: findShortestUniquePrefixes", () => { // All prefixes should be unique expect(uniquePrefixValues.size).toBe(prefixValues.length); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -105,7 +105,7 @@ describe("property: findShortestUniquePrefixes", () => { expect(str.toLowerCase().startsWith(prefix)).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -118,7 +118,7 @@ describe("property: findShortestUniquePrefixes", () => { expect(prefix).toBe(prefix.toLowerCase()); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -136,13 +136,13 @@ describe("property: findShortestUniquePrefixes", () => { const shorterPrefix = prefix.slice(0, -1); const wouldCollide = strings.some( (other) => - other !== str && other.toLowerCase().startsWith(shorterPrefix) + other !== str && other.toLowerCase().startsWith(shorterPrefix), ); expect(wouldCollide).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -152,7 +152,7 @@ describe("property: findShortestUniquePrefixes", () => { const prefixes = findShortestUniquePrefixes([str]); expect(prefixes.get(str)).toBe(str.charAt(0).toLowerCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -172,9 +172,9 @@ describe("property: findShortestUniquePrefixes", () => { expect(prefix.endsWith("-")).toBe(false); expect(prefix.endsWith("_")).toBe(false); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -200,7 +200,7 @@ describe("property: findCommonWordPrefix", () => { expect(str.toLowerCase().startsWith(prefix)).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -212,9 +212,9 @@ describe("property: findCommonWordPrefix", () => { // Simple slugs have no hyphens, so no common word prefix const prefix = findCommonWordPrefix(strings); expect(prefix).toBe(""); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -224,7 +224,7 @@ describe("property: findCommonWordPrefix", () => { const prefix = findCommonWordPrefix([str]); expect(prefix).toBe(""); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -244,7 +244,7 @@ describe("property: findCommonWordPrefix", () => { expect(lastChar === "-" || lastChar === "_").toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -254,7 +254,7 @@ describe("property: findCommonWordPrefix", () => { const prefix = findCommonWordPrefix(strings); expect(prefix).toBe(prefix.toLowerCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -272,9 +272,9 @@ describe("property: buildOrgAwareAliases", () => { // Each unique org/project pair should have an alias const uniqueKeys = new Set(pairs.map((p) => `${p.org}/${p.project}`)); expect(aliasMap.size).toBe(uniqueKeys.size); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -288,9 +288,9 @@ describe("property: buildOrgAwareAliases", () => { const uniqueAliases = new Set(aliases); expect(uniqueAliases.size).toBe(aliases.length); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -304,9 +304,9 @@ describe("property: buildOrgAwareAliases", () => { for (const alias of aliasMap.values()) { expect(alias).toBe(alias.toLowerCase()); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -320,9 +320,9 @@ describe("property: buildOrgAwareAliases", () => { for (const alias of aliasMap.values()) { expect(alias.length).toBeGreaterThan(0); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -337,9 +337,9 @@ describe("property: buildOrgAwareAliases", () => { expect(alias.endsWith("-")).toBe(false); expect(alias.endsWith("_")).toBe(false); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -375,9 +375,9 @@ describe("property: buildOrgAwareAliases", () => { // Colliding aliases should contain "/" (org prefix) expect(alias1?.includes("/")).toBe(true); expect(alias2?.includes("/")).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -386,7 +386,7 @@ describe("property: buildOrgAwareAliases", () => { property( tuple( simpleSlugArb, - uniqueArray(simpleSlugArb, { minLength: 2, maxLength: 5 }) + uniqueArray(simpleSlugArb, { minLength: 2, maxLength: 5 }), ), ([org, projects]) => { const pairs: OrgProjectPair[] = projects.map((p) => ({ @@ -399,9 +399,9 @@ describe("property: buildOrgAwareAliases", () => { for (const alias of aliasMap.values()) { expect(alias.includes("/")).toBe(false); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -418,9 +418,9 @@ describe("property: buildOrgAwareAliases", () => { for (const [key, alias1] of result1.aliasMap) { expect(result2.aliasMap.get(key)).toBe(alias1); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -445,7 +445,7 @@ describe("property: cross-function invariants", () => { expect(alias.length).toBeLessThanOrEqual(project.length); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -454,7 +454,7 @@ describe("property: cross-function invariants", () => { property( tuple( simpleSlugArb, - uniqueArray(simpleSlugArb, { minLength: 2, maxLength: 5 }) + uniqueArray(simpleSlugArb, { minLength: 2, maxLength: 5 }), ), ([org, projects]) => { // Build aliases through the full function @@ -472,9 +472,9 @@ describe("property: cross-function invariants", () => { const project = key.split("/")[1]!; expect(directPrefixes.get(project)).toBe(alias); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/api-client.coverage.test.ts b/packages/cli/test/lib/api-client.coverage.test.ts index cc050a216..7499df72a 100644 --- a/packages/cli/test/lib/api-client.coverage.test.ts +++ b/packages/cli/test/lib/api-client.coverage.test.ts @@ -6,7 +6,7 @@ * pattern as api-client.seer.test.ts. */ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { number, object, string } from "valibot"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; @@ -231,7 +231,7 @@ describe("issues.ts", () => { new Response(JSON.stringify({}), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); try { @@ -249,7 +249,7 @@ describe("issues.ts", () => { new Response(JSON.stringify({ detail: "Not Found" }), { status: 404, headers: { "Content-Type": "application/json" }, - }) + }), ); try { @@ -299,7 +299,7 @@ describe("issues.ts", () => { "Content-Type": "application/json", Link: linkHeader("cursor-abc", false), }, - }) + }), ); const result = await listIssuesAllPages("test-org", "test-project", { @@ -328,7 +328,7 @@ describe("issues.ts", () => { "Content-Type": "application/json", Link: linkHeader("page2-cursor", true), }, - } + }, ); } // Second page: return items without cursor @@ -367,8 +367,8 @@ describe("issues.ts", () => { "Content-Type": "application/json", Link: linkHeader("next", true), }, - } - ) + }, + ), ); const result = await listIssuesAllPages("test-org", "test-project", { @@ -381,7 +381,7 @@ describe("issues.ts", () => { test("throws when limit < 1", async () => { await expect( - listIssuesAllPages("test-org", "test-project", { limit: 0 }) + listIssuesAllPages("test-org", "test-project", { limit: 0 }), ).rejects.toThrow("limit must be at least 1"); }); @@ -398,8 +398,8 @@ describe("issues.ts", () => { "Content-Type": "application/json", Link: linkHeader("end", false), }, - } - ) + }, + ), ); await listIssuesAllPages("test-org", "test-project", { @@ -425,8 +425,8 @@ describe("issues.ts", () => { "Content-Type": "application/json", Link: linkHeader("has-more", true), }, - } - ) + }, + ), ); const result = await listIssuesAllPages("test-org", "test-project", { @@ -448,7 +448,7 @@ describe("issues.ts", () => { requestedLimits.push(url.searchParams.get("limit") ?? ""); const count = callCount === 3 ? 50 : 100; const issues = Array.from({ length: count }, (_, index) => - mockIssue({ id: `${callCount}-${index}` }) + mockIssue({ id: `${callCount}-${index}` }), ); return new Response(JSON.stringify(issues), { status: 200, @@ -627,7 +627,7 @@ describe("projects.ts", () => { headers: { "Content-Type": "application/json", }, - }) + }), ); const result = await listProjects("test-org"); @@ -672,7 +672,7 @@ describe("projects.ts", () => { "Content-Type": "application/json", Link: linkHeader("next-cursor", true), }, - }) + }), ); const result = await listProjectsPaginated("test-org"); @@ -729,7 +729,7 @@ describe("projects.ts", () => { mockProject({ id: String((callCount - 1) * API_MAX_PER_PAGE + i + 1), slug: `proj-${(callCount - 1) * API_MAX_PER_PAGE + i}`, - }) + }), ); const hasMore = callCount === 1; @@ -836,12 +836,12 @@ describe("projects.ts", () => { async () => new Response( JSON.stringify({ detail: MEMBER_PROJECT_CREATION_DISABLED_DETAIL }), - { status: 403, headers: { "Content-Type": "application/json" } } - ) + { status: 403, headers: { "Content-Type": "application/json" } }, + ), ); await expect( - createProjectWithAutoTeam("test-org", { name: "Blocked" }) + createProjectWithAutoTeam("test-org", { name: "Blocked" }), ).rejects.toMatchObject({ status: 403 }); }); @@ -918,7 +918,7 @@ describe("projects.ts", () => { new Response(JSON.stringify(keys), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); const dsn = await tryGetPrimaryDsn("test-org", "test-project"); @@ -940,7 +940,7 @@ describe("projects.ts", () => { new Response(JSON.stringify(keys), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); const dsn = await tryGetPrimaryDsn("test-org", "test-project"); @@ -953,7 +953,7 @@ describe("projects.ts", () => { new Response(JSON.stringify([]), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); const dsn = await tryGetPrimaryDsn("test-org", "test-project"); @@ -966,7 +966,7 @@ describe("projects.ts", () => { new Response(JSON.stringify({ detail: "Not found" }), { status: 404, headers: { "Content-Type": "application/json" }, - }) + }), ); const captureSpy = vi.spyOn(Sentry, "captureException"); @@ -986,7 +986,7 @@ describe("projects.ts", () => { new Response(JSON.stringify({ detail: "Internal error" }), { status: 500, headers: { "Content-Type": "application/json" }, - }) + }), ); const captureSpy = vi.spyOn(Sentry, "captureException"); @@ -1052,7 +1052,7 @@ describe("users.ts", () => { new Response(JSON.stringify({ name: "no-id" }), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect(getCurrentUser()).rejects.toThrow(ApiError); @@ -1091,7 +1091,7 @@ describe("events.ts", () => { globalThis.fetch = mockFetch(async (input, init) => { const req = new Request(input!, init); expect(req.url).toContain( - "/projects/test-org/test-project/events/evt-abc/" + "/projects/test-org/test-project/events/evt-abc/", ); return new Response(JSON.stringify(event), { status: 200, @@ -1111,7 +1111,7 @@ describe("events.ts", () => { const req = new Request(input!, init); const url = new URL(req.url); expect(url.pathname).toContain( - "/projects/test-org/test-project/events/evt-abc/attachments/" + "/projects/test-org/test-project/events/evt-abc/attachments/", ); expect(url.searchParams.get("per_page")).toBe("100"); @@ -1126,7 +1126,7 @@ describe("events.ts", () => { "Content-Type": "application/json", Link: linkHeader("attachments-next", true), }, - } + }, ); } @@ -1136,14 +1136,14 @@ describe("events.ts", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }); const result = await listEventAttachments( "test-org", "test-project", - "evt-abc" + "evt-abc", ); expect(result.map((attachment) => attachment.id)).toEqual([ @@ -1167,7 +1167,7 @@ describe("events.ts", () => { new Response(JSON.stringify(resolved), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); const result = await resolveEventInOrg("test-org", "evt-abc"); @@ -1182,7 +1182,7 @@ describe("events.ts", () => { new Response(JSON.stringify({ detail: "Not found" }), { status: 404, headers: { "Content-Type": "application/json" }, - }) + }), ); const result = await resolveEventInOrg("test-org", "evt-abc"); @@ -1195,7 +1195,7 @@ describe("events.ts", () => { new Response(JSON.stringify({ detail: "Internal Server Error" }), { status: 500, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect(resolveEventInOrg("test-org", "evt-abc")).rejects.toThrow(); @@ -1221,7 +1221,7 @@ describe("traces.ts", () => { globalThis.fetch = mockFetch(async (input, init) => { const req = new Request(input!, init); expect(req.url).toContain( - "/organizations/test-org/trace/abc123def456/" + "/organizations/test-org/trace/abc123def456/", ); const url = new URL(req.url); expect(url.searchParams.get("timestamp")).toBe("1700000000"); @@ -1307,9 +1307,8 @@ describe("repositories.ts", () => { }); // Dynamic import to avoid circular issue with already-imported listRepositories - const { listAllRepositories } = await import( - "../../src/lib/api/repositories.js" - ); + const { listAllRepositories } = + await import("../../src/lib/api/repositories.js"); const result = await listAllRepositories("test-org"); expect(result).toHaveLength(3); expect(result.map((r) => r.name)).toEqual([ @@ -1456,8 +1455,8 @@ describe("infrastructure.ts", () => { data: undefined; error: unknown; }, - "context" - ) + "context", + ), ).toThrow(apiError); }); @@ -1469,8 +1468,8 @@ describe("infrastructure.ts", () => { data: undefined; error: unknown; }, - "context" - ) + "context", + ), ).toThrow(authError); }); @@ -1480,7 +1479,7 @@ describe("infrastructure.ts", () => { data: { foo: string }; error: undefined; }, - "context" + "context", ); expect(result).toEqual({ foo: "bar" }); }); @@ -1499,7 +1498,7 @@ describe("infrastructure.ts", () => { try { unwrapResult( result as { data: undefined; error: unknown }, - "Failed operation" + "Failed operation", ); expect(true).toBe(false); // Should not reach } catch (error) { @@ -1519,7 +1518,7 @@ describe("infrastructure.ts", () => { status: 429, statusText: "Too Many Requests", headers: { "Content-Type": "application/json" }, - }) + }), ); try { @@ -1540,7 +1539,7 @@ describe("infrastructure.ts", () => { status: 400, statusText: "Bad Request", headers: { "Content-Type": "application/json" }, - }) + }), ); try { @@ -1562,7 +1561,7 @@ describe("infrastructure.ts", () => { new Response("Internal Server Error", { status: 500, statusText: "Internal Server Error", - }) + }), ); try { @@ -1586,7 +1585,7 @@ describe("infrastructure.ts", () => { new Response(JSON.stringify({ wrong_field: "value" }), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); try { @@ -1611,7 +1610,7 @@ describe("infrastructure.ts", () => { new Response(JSON.stringify({ name: "test", count: 42 }), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); const { data } = await apiRequestToRegion("https://sentry.io", "/test/", { @@ -1729,7 +1728,7 @@ describe("teams.ts (paginated)", () => { "Content-Type": "application/json", Link: linkHeader("teams-next", true), }, - }) + }), ); const result = await listTeamsPaginated("test-org"); @@ -1781,7 +1780,7 @@ describe("repositories.ts (paginated)", () => { "Content-Type": "application/json", Link: linkHeader("repos-next", true), }, - }) + }), ); const result = await listRepositoriesPaginated("test-org"); @@ -1978,7 +1977,7 @@ describe("logs.ts (detailed)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({}), { status: 200 }); @@ -2092,7 +2091,7 @@ describe("events.ts (findEventAcrossOrgs)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } // resolveOrganizationEventId @@ -2144,7 +2143,7 @@ describe("infrastructure.ts (rawApiRequest)", () => { new Response("plain text response", { status: 200, headers: { "Content-Type": "text/plain" }, - }) + }), ); const result = await rawApiRequest("/test/"); @@ -2157,7 +2156,7 @@ describe("infrastructure.ts (rawApiRequest)", () => { new Response(JSON.stringify({ detail: "Not found" }), { status: 404, headers: { "Content-Type": "application/json" }, - }) + }), ); const result = await rawApiRequest("/test/"); @@ -2227,7 +2226,7 @@ describe("infrastructure.ts (rawApiRequest)", () => { test("merges query params with an existing endpoint query", async () => { globalThis.fetch = mockFetch(async (input) => { const url = new URL( - String(input instanceof Request ? input.url : input) + String(input instanceof Request ? input.url : input), ); expect(url.searchParams.get("download")).toBe("1"); expect(url.searchParams.get("per_page")).toBe("10"); @@ -2252,23 +2251,20 @@ describe("infrastructure.ts (helpers)", () => { describe("buildSearchParams", () => { // Import from infrastructure for direct testing test("returns undefined for undefined input", async () => { - const { buildSearchParams } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { buildSearchParams } = + await import("../../src/lib/api/infrastructure.js"); expect(buildSearchParams(undefined)).toBeUndefined(); }); test("returns undefined for all-undefined values", async () => { - const { buildSearchParams } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { buildSearchParams } = + await import("../../src/lib/api/infrastructure.js"); expect(buildSearchParams({ a: undefined, b: undefined })).toBeUndefined(); }); test("handles string, number, and boolean values", async () => { - const { buildSearchParams } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { buildSearchParams } = + await import("../../src/lib/api/infrastructure.js"); const result = buildSearchParams({ str: "hello", num: 42, @@ -2281,18 +2277,16 @@ describe("infrastructure.ts (helpers)", () => { }); test("handles string arrays (repeated keys)", async () => { - const { buildSearchParams } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { buildSearchParams } = + await import("../../src/lib/api/infrastructure.js"); const result = buildSearchParams({ tags: ["a", "b", "c"] }); expect(result).toBeDefined(); expect(result!.getAll("tags")).toEqual(["a", "b", "c"]); }); test("skips undefined values", async () => { - const { buildSearchParams } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { buildSearchParams } = + await import("../../src/lib/api/infrastructure.js"); const result = buildSearchParams({ present: "yes", missing: undefined, @@ -2305,23 +2299,20 @@ describe("infrastructure.ts (helpers)", () => { describe("parseLinkHeader", () => { test("returns empty for null header", async () => { - const { parseLinkHeader } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { parseLinkHeader } = + await import("../../src/lib/api/infrastructure.js"); expect(parseLinkHeader(null)).toEqual({}); }); test("returns empty for empty string", async () => { - const { parseLinkHeader } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { parseLinkHeader } = + await import("../../src/lib/api/infrastructure.js"); expect(parseLinkHeader("")).toEqual({}); }); test("extracts next cursor when results=true", async () => { - const { parseLinkHeader } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { parseLinkHeader } = + await import("../../src/lib/api/infrastructure.js"); const header = '; rel="next"; results="true"; cursor="1735689600000:0:0"'; expect(parseLinkHeader(header)).toEqual({ @@ -2330,18 +2321,16 @@ describe("infrastructure.ts (helpers)", () => { }); test("returns empty when results=false", async () => { - const { parseLinkHeader } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { parseLinkHeader } = + await import("../../src/lib/api/infrastructure.js"); const header = '; rel="next"; results="false"; cursor="abc"'; expect(parseLinkHeader(header)).toEqual({}); }); test("handles multiple link entries", async () => { - const { parseLinkHeader } = await import( - "../../src/lib/api/infrastructure.js" - ); + const { parseLinkHeader } = + await import("../../src/lib/api/infrastructure.js"); const header = '; rel="previous"; results="false"; cursor="prev",' + '; rel="next"; results="true"; cursor="next-val"'; diff --git a/packages/cli/test/lib/api-client.multiregion.test.ts b/packages/cli/test/lib/api-client.multiregion.test.ts index c9fe5785f..f5c44c10d 100644 --- a/packages/cli/test/lib/api-client.multiregion.test.ts +++ b/packages/cli/test/lib/api-client.multiregion.test.ts @@ -5,7 +5,7 @@ * Covers region discovery, fan-out, and region-aware routing. */ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { @@ -116,7 +116,7 @@ describe("getUserRegions", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -143,7 +143,7 @@ describe("getUserRegions", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -168,7 +168,7 @@ describe("getUserRegions", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -200,7 +200,7 @@ describe("listOrganizationsPage", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; @@ -287,7 +287,7 @@ describe("listOrganizations (control silo)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }, }); @@ -296,10 +296,10 @@ describe("listOrganizations (control silo)", () => { // No region discovery call, and only one request to the control silo. expect(requestedUrls.some((u) => u.includes("/users/me/regions/"))).toBe( - false + false, ); expect( - requestedUrls.filter((u) => u.includes("/organizations/")) + requestedUrls.filter((u) => u.includes("/organizations/")), ).toHaveLength(1); // Both orgs (from different regions) come back from that single call. @@ -334,7 +334,7 @@ describe("listOrganizations (control silo)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ), }); @@ -386,7 +386,7 @@ describe("listOrganizations (control silo)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ), }); @@ -395,7 +395,7 @@ describe("listOrganizations (control silo)", () => { const cachedRegions = getAllOrgRegions(); // Org with links should use its own regionUrl expect(cachedRegions.get("org-with-links")).toBe( - "https://custom.sentry.io" + "https://custom.sentry.io", ); // Org without links falls back to the control silo base URL expect(cachedRegions.get("org-without-links")).toBe("https://sentry.io"); @@ -438,7 +438,7 @@ describe("listOrganizations (control silo)", () => { "Content-Type": "application/json", Link: '; rel="next"; results="true"; cursor="page2:0:0"', }, - } + }, ); } return new Response( @@ -449,7 +449,7 @@ describe("listOrganizations (control silo)", () => { "Content-Type": "application/json", Link: '; rel="next"; results="false"; cursor="end:0:0"', }, - } + }, ); }, }); @@ -482,7 +482,7 @@ describe("findProjectByDsnKey (multi-region)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify([]), { status: 200 }); @@ -512,7 +512,7 @@ describe("findProjectByDsnKey (multi-region)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify([]), { status: 200 }); @@ -521,17 +521,17 @@ describe("findProjectByDsnKey (multi-region)", () => { const project = await findProjectByDsnKey("abc123"); - // Should have searched both regions - expect( - requestedUrls.some( - (u) => u.includes("us.sentry.io") && u.includes("/projects/") - ) - ).toBe(true); - expect( - requestedUrls.some( - (u) => u.includes("de.sentry.io") && u.includes("/projects/") - ) - ).toBe(true); + // Match the origin, not a hostname substring that could occur in another URL. + const searchedRegion = (region: string): boolean => + requestedUrls.some((requestedUrl) => { + const url = new URL(requestedUrl); + return ( + url.origin === `https://${region}.sentry.io` && + url.pathname.includes("/projects/") + ); + }); + expect(searchedRegion("us")).toBe(true); + expect(searchedRegion("de")).toBe(true); // Should find the project from EU region expect(project).not.toBeNull(); @@ -549,7 +549,7 @@ describe("findProjectByDsnKey (multi-region)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify([]), { status: 200 }); @@ -580,7 +580,7 @@ describe("findProjectByDsnKey (multi-region)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } if (req.url.includes("/projects/")) { @@ -595,7 +595,7 @@ describe("findProjectByDsnKey (multi-region)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify([]), { status: 200 }); @@ -625,7 +625,7 @@ describe("findProjectByDsnKey (multi-region)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify([]), { status: 200 }); @@ -645,7 +645,7 @@ describe("findProjectByDsnKey (multi-region)", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ), }); @@ -700,7 +700,7 @@ describe("org-scoped requests use region cache", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; diff --git a/packages/cli/test/lib/api-client.property.test.ts b/packages/cli/test/lib/api-client.property.test.ts index abed95f44..824ca2472 100644 --- a/packages/cli/test/lib/api-client.property.test.ts +++ b/packages/cli/test/lib/api-client.property.test.ts @@ -23,25 +23,25 @@ import { DEFAULT_NUM_RUNS } from "../model-based/helpers.js"; const cursorArb = tuple( nat(2_000_000_000_000), nat(100), - constantFrom(0, 1) + constantFrom(0, 1), ).map(([ts, offset, isPrev]) => `${ts}:${offset}:${isPrev}`); /** Generate a valid "next" link part with results="true" and a cursor */ const nextLinkWithResultsArb = cursorArb.map( (cursor) => - `; rel="next"; results="true"; cursor="${cursor}"` + `; rel="next"; results="true"; cursor="${cursor}"`, ); /** Generate a "next" link part with results="false" */ const nextLinkNoResultsArb = cursorArb.map( (cursor) => - `; rel="next"; results="false"; cursor="${cursor}"` + `; rel="next"; results="false"; cursor="${cursor}"`, ); /** Generate a "previous" link part (should be ignored by parseLinkHeader) */ const prevLinkArb = cursorArb.map( (cursor) => - `; rel="previous"; results="true"; cursor="${cursor}"` + `; rel="previous"; results="true"; cursor="${cursor}"`, ); /** Generate a rel value that is not "next" */ @@ -63,7 +63,7 @@ describe("property: parseLinkHeader", () => { expect(result.nextCursor).toBeDefined(); expect(result.nextCursor).toMatch(/^\d+:\d+:\d+$/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -73,7 +73,7 @@ describe("property: parseLinkHeader", () => { const result = parseLinkHeader(header); expect(result.nextCursor).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -83,7 +83,7 @@ describe("property: parseLinkHeader", () => { const result = parseLinkHeader(header); expect(result.nextCursor).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -96,7 +96,7 @@ describe("property: parseLinkHeader", () => { expect(result.nextCursor).toBeDefined(); expect(result.nextCursor).toMatch(/^\d+:\d+:\d+$/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -107,7 +107,7 @@ describe("property: parseLinkHeader", () => { const result = parseLinkHeader(header); expect(result.nextCursor).toBe(cursor); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -125,7 +125,7 @@ describe("property: parseLinkHeader", () => { const result = parseLinkHeader(header); expect(result.nextCursor).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -136,7 +136,7 @@ describe("property: parseLinkHeader", () => { const result = parseLinkHeader(header); expect(result.nextCursor).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -150,7 +150,7 @@ describe("property: parseLinkHeader", () => { const result = parseLinkHeader(header); expect(result.nextCursor).toBe(nextCursor); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -163,7 +163,7 @@ describe("property: parseLinkHeader", () => { expect(typeof result.nextCursor).toBe("string"); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/lib/api-client.seer-trial.test.ts b/packages/cli/test/lib/api-client.seer-trial.test.ts index 0240f3996..45e94cf57 100644 --- a/packages/cli/test/lib/api-client.seer-trial.test.ts +++ b/packages/cli/test/lib/api-client.seer-trial.test.ts @@ -56,8 +56,8 @@ describe("getProductTrials", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } - ) + }, + ), ); const trials = await getProductTrials("test-org"); @@ -75,7 +75,7 @@ describe("getProductTrials", () => { new Response(JSON.stringify({}), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); const trials = await getProductTrials("test-org"); @@ -89,7 +89,7 @@ describe("getProductTrials", () => { new Response(JSON.stringify({ productTrials: [] }), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); const trials = await getProductTrials("test-org"); @@ -107,7 +107,7 @@ describe("getProductTrials", () => { status: 200, headers: { "Content-Type": "application/json" }, }); - } + }, ); await getProductTrials("test-org"); @@ -122,7 +122,7 @@ describe("getProductTrials", () => { new Response(JSON.stringify({ detail: "Not found" }), { status: 404, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect(getProductTrials("test-org")).rejects.toThrow(); @@ -140,7 +140,7 @@ describe("startProductTrial", () => { status: 200, headers: { "Content-Type": "application/json" }, }); - } + }, ); await startProductTrial("test-org", "seerUsers"); @@ -161,7 +161,7 @@ describe("startProductTrial", () => { status: 200, headers: { "Content-Type": "application/json" }, }); - } + }, ); await startProductTrial("test-org", "seerAutofix"); @@ -182,7 +182,7 @@ describe("startProductTrial", () => { status: 200, headers: { "Content-Type": "application/json" }, }); - } + }, ); await startProductTrial("test-org", "replays"); @@ -205,7 +205,7 @@ describe("startProductTrial", () => { status: 200, headers: { "Content-Type": "application/json" }, }); - } + }, ); await startProductTrial("test-org", "seerUsers"); @@ -220,7 +220,7 @@ describe("startProductTrial", () => { new Response(JSON.stringify({ detail: "Forbidden" }), { status: 403, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect(startProductTrial("test-org", "seerUsers")).rejects.toThrow(); diff --git a/packages/cli/test/lib/api-client.seer.test.ts b/packages/cli/test/lib/api-client.seer.test.ts index abf9a263c..8512e4a40 100644 --- a/packages/cli/test/lib/api-client.seer.test.ts +++ b/packages/cli/test/lib/api-client.seer.test.ts @@ -51,7 +51,7 @@ describe("triggerRootCauseAnalysis", () => { expect(capturedRequest?.method).toBe("POST"); expect(capturedRequest?.url).toContain( - "/organizations/test-org/issues/123456789/autofix/" + "/organizations/test-org/issues/123456789/autofix/", ); expect(capturedRequest?.url).toContain("mode=explorer"); }); @@ -85,7 +85,7 @@ describe("triggerRootCauseAnalysis", () => { }); await expect( - triggerRootCauseAnalysis("test-org", "123456789") + triggerRootCauseAnalysis("test-org", "123456789"), ).rejects.toThrow(); }); @@ -97,7 +97,7 @@ describe("triggerRootCauseAnalysis", () => { }); await expect( - triggerRootCauseAnalysis("test-org", "123456789") + triggerRootCauseAnalysis("test-org", "123456789"), ).rejects.toThrow(); }); }); @@ -121,7 +121,7 @@ describe("getAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }; @@ -131,7 +131,7 @@ describe("getAutofixState", () => { expect(result?.status).toBe("PROCESSING"); expect(capturedRequest?.method).toBe("GET"); expect(capturedRequest?.url).toContain( - "/organizations/test-org/issues/123456789/autofix/" + "/organizations/test-org/issues/123456789/autofix/", ); expect(capturedRequest?.url).toContain("mode=explorer"); }); @@ -150,7 +150,7 @@ describe("getAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); const result = await getAutofixState("test-org", "123456789"); @@ -173,7 +173,7 @@ describe("getAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); const result = await getAutofixState("test-org", "123456789"); @@ -194,7 +194,7 @@ describe("getAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); const result = await getAutofixState("test-org", "123456789"); @@ -215,7 +215,7 @@ describe("getAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); const result = await getAutofixState("test-org", "123456789"); @@ -290,7 +290,7 @@ describe("getAutofixState", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); const result = await getAutofixState("test-org", "123456789"); @@ -318,7 +318,7 @@ describe("triggerSolutionPlanning", () => { expect(capturedRequest?.method).toBe("POST"); expect(capturedRequest?.url).toContain( - "/organizations/test-org/issues/123456789/autofix/" + "/organizations/test-org/issues/123456789/autofix/", ); expect(capturedRequest?.url).toContain("mode=explorer"); expect(capturedBody).toEqual({ @@ -342,14 +342,14 @@ describe("triggerSolutionPlanning", () => { { status: 202, headers: { "Content-Type": "application/json" }, - } + }, ); }; await triggerSolutionPlanning( "test-org", "123456789", - "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + "a1b2c3d4-e5f6-7890-abcd-ef1234567890", ); // sentry_run_id is a UUIDField server-side; run_id is an IntegerField. @@ -375,13 +375,12 @@ describe("normalizeAgentStatus", () => { expect(normalizeAgentStatus(input)).toBe(expected); }); - test.each([ - [null], - [undefined], - [""], - ])("defaults %s status to PROCESSING", (input) => { - expect(normalizeAgentStatus(input)).toBe("PROCESSING"); - }); + test.each([[null], [undefined], [""]])( + "defaults %s status to PROCESSING", + (input) => { + expect(normalizeAgentStatus(input)).toBe("PROCESSING"); + }, + ); test("uppercases unknown statuses", () => { expect(normalizeAgentStatus("some_new_status")).toBe("SOME_NEW_STATUS"); diff --git a/packages/cli/test/lib/api-client.test.ts b/packages/cli/test/lib/api-client.test.ts index a79e553b3..f792c96da 100644 --- a/packages/cli/test/lib/api-client.test.ts +++ b/packages/cli/test/lib/api-client.test.ts @@ -62,11 +62,11 @@ function createMockFetch( requests: RequestLog[], apiRequestHandler: ( req: Request, - requestCount: number + requestCount: number, ) => Response | Promise, options: { oauthHandler?: (req: Request) => Response | Promise; - } = {} + } = {}, ): typeof globalThis.fetch { let apiRequestCount = 0; @@ -94,7 +94,7 @@ function createMockFetch( { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } @@ -148,10 +148,10 @@ describe("401 retry behavior", () => { // 2. OAuth refresh request // 3. Retry API request with refreshed-token -> 200 const apiRequests = requests.filter((r) => - r.url.includes("/test-endpoint") + r.url.includes("/test-endpoint"), ); const oauthRequests = requests.filter((r) => - r.url.includes("/oauth/token/") + r.url.includes("/oauth/token/"), ); expect(apiRequests).toHaveLength(2); @@ -183,14 +183,14 @@ describe("401 retry behavior", () => { // Should only have initial API request, no retry const apiRequests = requests.filter((r) => - r.url.includes("/test-endpoint") + r.url.includes("/test-endpoint"), ); expect(apiRequests).toHaveLength(1); expect(apiRequests[0].isRetry).toBe(false); // No OAuth refresh should have been attempted const oauthRequests = requests.filter((r) => - r.url.includes("/oauth/token/") + r.url.includes("/oauth/token/"), ); expect(oauthRequests).toHaveLength(0); }); @@ -212,7 +212,7 @@ describe("401 retry behavior", () => { // Should have exactly 2 API requests (initial + one retry, no infinite loop) const apiRequests = requests.filter((r) => - r.url.includes("/test-endpoint") + r.url.includes("/test-endpoint"), ); expect(apiRequests).toHaveLength(2); expect(apiRequests[0].isRetry).toBe(false); @@ -220,7 +220,7 @@ describe("401 retry behavior", () => { // OAuth refresh should have been called once (after first 401) const oauthRequests = requests.filter((r) => - r.url.includes("/oauth/token/") + r.url.includes("/oauth/token/"), ); expect(oauthRequests).toHaveLength(1); }); @@ -229,7 +229,7 @@ describe("401 retry behavior", () => { const requests: RequestLog[] = []; const body = { detail: "Unauthorized" }; globalThis.fetch = createMockFetch(requests, (_req, requestCount) => - Response.json(body, { status: requestCount < 3 ? 503 : 401 }) + Response.json(body, { status: requestCount < 3 ? 503 : 401 }), ); const result = await rawApiRequest("/test-endpoint/"); @@ -260,14 +260,14 @@ describe("401 retry behavior", () => { // Should have exactly 1 API request - no retry since token can't be refreshed const apiRequests = requests.filter((r) => - r.url.includes("/test-endpoint") + r.url.includes("/test-endpoint"), ); expect(apiRequests).toHaveLength(1); expect(apiRequests[0].isRetry).toBe(false); // No OAuth refresh should have been attempted (no refresh token available) const oauthRequests = requests.filter((r) => - r.url.includes("/oauth/token/") + r.url.includes("/oauth/token/"), ); expect(oauthRequests).toHaveLength(0); }); @@ -487,7 +487,7 @@ describe("rawApiRequest", () => { // Mixed case Content-TYPE should be detected and preserved expect(requests[0].headers.get("Content-Type")).toBe( - "application/octet-stream" + "application/octet-stream", ); }); @@ -620,7 +620,7 @@ describe("findProjectsBySlug", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -631,7 +631,7 @@ describe("findProjectsBySlug", () => { { id: "1", slug: "acme", name: "Acme Corp" }, { id: "2", slug: "beta", name: "Beta Inc" }, ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -639,7 +639,7 @@ describe("findProjectsBySlug", () => { if (url.includes("/projects/acme/frontend/")) { return new Response( JSON.stringify({ id: "101", slug: "frontend", name: "Frontend" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -651,7 +651,7 @@ describe("findProjectsBySlug", () => { slug: "frontend", name: "Beta Frontend", }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -685,7 +685,7 @@ describe("findProjectsBySlug", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -693,7 +693,7 @@ describe("findProjectsBySlug", () => { if (url.includes("/organizations/") && !url.includes("/projects/")) { return new Response( JSON.stringify([{ id: "1", slug: "acme", name: "Acme Corp" }]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -722,7 +722,7 @@ describe("findProjectsBySlug", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -733,7 +733,7 @@ describe("findProjectsBySlug", () => { { id: "1", slug: "acme", name: "Acme Corp" }, { id: "2", slug: "restricted", name: "Restricted Org" }, ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -741,7 +741,7 @@ describe("findProjectsBySlug", () => { if (url.includes("/projects/acme/frontend/")) { return new Response( JSON.stringify({ id: "101", slug: "frontend", name: "Frontend" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -779,7 +779,7 @@ describe("findProjectsBySlug", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -787,7 +787,7 @@ describe("findProjectsBySlug", () => { if (url.includes("/organizations/") && !url.includes("/projects/")) { return new Response( JSON.stringify([{ id: "1", slug: "acme", name: "Acme Corp" }]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -800,7 +800,7 @@ describe("findProjectsBySlug", () => { slug: "frontend", name: "Frontend", }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -829,14 +829,14 @@ describe("findProjectsBySlug", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } if (url.includes("/organizations/") && !url.includes("/projects/")) { return new Response( JSON.stringify([{ id: "1", slug: "acme", name: "Acme Corp" }]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -844,7 +844,7 @@ describe("findProjectsBySlug", () => { if (url.includes("/projects/acme/wrong-slug/")) { return new Response( JSON.stringify({ id: "999", slug: "actual-slug", name: "Actual" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -885,7 +885,7 @@ describe("findProjectsBySlug", () => { if (url.includes("/projects/acme/frontend/")) { return new Response( JSON.stringify({ id: "101", slug: "frontend", name: "Frontend" }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -906,8 +906,8 @@ describe("findProjectsBySlug", () => { expect(requests.some((r) => r.includes("/users/me/regions/"))).toBe(false); expect( requests.some( - (r) => r.includes("/organizations/") && !r.includes("/projects/") - ) + (r) => r.includes("/organizations/") && !r.includes("/projects/"), + ), ).toBe(false); }); }); @@ -951,7 +951,7 @@ describe("resolveEventInOrg", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -964,7 +964,7 @@ describe("resolveEventInOrg", () => { eventId: "abc123def456", event: sampleEvent, }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -993,7 +993,7 @@ describe("resolveEventInOrg", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1047,7 +1047,7 @@ describe("findEventAcrossOrgs", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1057,7 +1057,7 @@ describe("findEventAcrossOrgs", () => { { id: "1", slug: "no-event-org", name: "No Event Org" }, { id: "2", slug: "has-event-org", name: "Has Event Org" }, ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1070,7 +1070,7 @@ describe("findEventAcrossOrgs", () => { eventId: "abc123def456", event: sampleEvent, }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1098,14 +1098,14 @@ describe("findEventAcrossOrgs", () => { JSON.stringify({ regions: [{ name: "us", url: "https://us.sentry.io" }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } if (url.includes("/organizations/") && !url.includes("/eventids/")) { return new Response( JSON.stringify([{ id: "1", slug: "acme", name: "Acme" }]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1604,7 +1604,7 @@ describe("listTraceLogs", () => { const result = await listTraceLogs( "my-org", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(Array.isArray(result)).toBe(true); expect(result).toHaveLength(1); @@ -1634,7 +1634,7 @@ describe("listTraceLogs", () => { const url = new URL(capturedUrl); expect(url.searchParams.get("traceId")).toBe( - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(url.searchParams.get("statsPeriod")).toBe("7d"); expect(url.searchParams.get("per_page")).toBe("100"); @@ -1692,7 +1692,7 @@ describe("listTraceLogs", () => { const result = await listTraceLogs( "my-org", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(result).toHaveLength(1); expect(typeof result[0]["project.id"]).toBe("number"); @@ -1729,7 +1729,7 @@ describe("listTraceLogs", () => { const result = await listTraceLogs( "my-org", - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); expect(result).toHaveLength(1); expect(result[0].severity_number).toBeUndefined(); @@ -1800,7 +1800,7 @@ describe("getLogs", () => { capturedUrl = req.url; return new Response( JSON.stringify({ data: [], meta: { fields: {} } }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify({}), { status: 200 }); @@ -1819,7 +1819,7 @@ describe("getLogs", () => { if (req.url.includes("/events/")) { return new Response( JSON.stringify({ data: [], meta: { fields: {} } }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify({}), { status: 200 }); @@ -1834,7 +1834,7 @@ describe("getLogs", () => { test("batches requests when IDs exceed API_MAX_PER_PAGE", async () => { // Create 150 IDs (should split into 2 batches: 100 + 50) const ids = Array.from({ length: 150 }, (_, i) => - i.toString(16).padStart(32, "0") + i.toString(16).padStart(32, "0"), ); const capturedUrls: string[] = []; @@ -1852,7 +1852,7 @@ describe("getLogs", () => { data: [makeLogEntry(batchIds[0])], meta: { fields: {} }, }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify({}), { status: 200 }); @@ -1905,7 +1905,7 @@ describe("getProject", () => { // Collapsed response: no `name` on organization organization: { id: "42", slug: "acme" }, }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1922,7 +1922,7 @@ describe("getProject", () => { // Verify the outgoing URL carries ?collapse=organization const projectRequest = capturedUrls.find((u) => - u.includes("/projects/acme/frontend/") + u.includes("/projects/acme/frontend/"), ); expect(projectRequest).toBeDefined(); const parsed = new URL(projectRequest as string); @@ -1952,7 +1952,7 @@ describe("getProject", () => { name: "Frontend", organization: { id: "42", slug: "acme" }, }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -1972,17 +1972,15 @@ describe("getProject", () => { describe("resolveOrgDisplayName", () => { test("prefers explicit name when provided", async () => { - const { resolveOrgDisplayName } = await import( - "../../src/lib/api-client.js" - ); + const { resolveOrgDisplayName } = + await import("../../src/lib/api-client.js"); expect(resolveOrgDisplayName("acme", "Acme Corp")).toBe("Acme Corp"); }); test("falls back to cached org name when explicit name is absent", async () => { - const { resolveOrgDisplayName } = await import( - "../../src/lib/api-client.js" - ); + const { resolveOrgDisplayName } = + await import("../../src/lib/api-client.js"); setOrgRegions([ { @@ -1997,18 +1995,16 @@ describe("resolveOrgDisplayName", () => { }); test("falls back to slug when no cache entry exists", async () => { - const { resolveOrgDisplayName } = await import( - "../../src/lib/api-client.js" - ); + const { resolveOrgDisplayName } = + await import("../../src/lib/api-client.js"); // Empty cache — no entry for this slug expect(resolveOrgDisplayName("unknown-org")).toBe("unknown-org"); }); test("falls back to slug when explicit name is empty string", async () => { - const { resolveOrgDisplayName } = await import( - "../../src/lib/api-client.js" - ); + const { resolveOrgDisplayName } = + await import("../../src/lib/api-client.js"); // Empty string is falsy, so the cache/slug fallback kicks in expect(resolveOrgDisplayName("unknown-org", "")).toBe("unknown-org"); diff --git a/packages/cli/test/lib/api-schema.test.ts b/packages/cli/test/lib/api-schema.test.ts index 72cb82f5d..214be3d85 100644 --- a/packages/cli/test/lib/api-schema.test.ts +++ b/packages/cli/test/lib/api-schema.test.ts @@ -108,27 +108,27 @@ describe("getEndpoint", () => { describe("parseEndpointQuery", () => { test("parses METHOD + OpenAPI path", () => { const parsed = parseEndpointQuery( - "GET /api/0/organizations/{organization_id_or_slug}/issues/" + "GET /api/0/organizations/{organization_id_or_slug}/issues/", ); expect(parsed.method).toBe("GET"); expect(parsed.path).toBe( - "/api/0/organizations/{organization_id_or_slug}/issues/" + "/api/0/organizations/{organization_id_or_slug}/issues/", ); }); test("parses a bare /api/ path and adds a trailing slash", () => { const parsed = parseEndpointQuery( - "/api/0/organizations/{organization_id_or_slug}/issues" + "/api/0/organizations/{organization_id_or_slug}/issues", ); expect(parsed.method).toBeUndefined(); expect(parsed.path).toBe( - "/api/0/organizations/{organization_id_or_slug}/issues/" + "/api/0/organizations/{organization_id_or_slug}/issues/", ); }); test("parses a full Sentry URL and strips the query string", () => { const parsed = parseEndpointQuery( - "https://sentry.io/api/0/organizations/acme/issues/?query=is:unresolved" + "https://sentry.io/api/0/organizations/acme/issues/?query=is:unresolved", ); expect(parsed.path).toBe("/api/0/organizations/acme/issues/"); }); @@ -145,7 +145,7 @@ describe("findEndpointsByPath", () => { test("matches a concrete org slug against {organization_id_or_slug}", () => { const matches = findEndpointsByPath( "/api/0/organizations/acme/issues/", - "GET" + "GET", ); expect(matches).toHaveLength(1); expect(matches[0]?.fn).toBe("listOrganizationIssues"); @@ -154,7 +154,7 @@ describe("findEndpointsByPath", () => { test("does not treat a query {param} as a wildcard for static siblings", () => { const matches = findEndpointsByPath( "/api/0/organizations/{organization_id_or_slug}/preprodartifacts/snapshots/{snapshot_id}/", - "GET" + "GET", ); expect(matches.map((e) => e.fn)).toEqual([ "getOrganizationPreprodArtifactSnapshot", @@ -164,7 +164,7 @@ describe("findEndpointsByPath", () => { test("prefers a static segment over a neighboring {param}", () => { const matches = findEndpointsByPath( "/api/0/organizations/acme/preprodartifacts/snapshots/latest-base/", - "GET" + "GET", ); expect(matches.map((e) => e.fn)).toEqual([ "getOrganizationPreprodArtifactSnapshotLatestBase", @@ -174,7 +174,7 @@ describe("findEndpointsByPath", () => { test("does not match getProject when the query is an OpenAPI /issues/ path", () => { const matches = findEndpointsByPath( "/api/0/projects/{organization_id_or_slug}/issues/", - "GET" + "GET", ); expect(matches).toEqual([]); }); @@ -192,7 +192,7 @@ describe("findEndpointsByIdentifier", () => { (candidate) => candidate.fn && candidate.operationId && - candidate.fn !== candidate.operationId + candidate.fn !== candidate.operationId, ); expect(endpoint).toBeDefined(); if (!endpoint) { @@ -204,7 +204,7 @@ describe("findEndpointsByIdentifier", () => { test("is case-insensitive but does not accept partial identifiers", () => { expect(findEndpointsByIdentifier("LISTORGANIZATIONEVENTS")[0]?.fn).toBe( - "listOrganizationEvents" + "listOrganizationEvents", ); expect(findEndpointsByIdentifier("OrganizationEvents")).toEqual([]); }); diff --git a/packages/cli/test/lib/api-scope.test.ts b/packages/cli/test/lib/api-scope.test.ts index e983e843e..99e073cbd 100644 --- a/packages/cli/test/lib/api-scope.test.ts +++ b/packages/cli/test/lib/api-scope.test.ts @@ -16,8 +16,8 @@ describe("extractRequiredScopes", () => { test("returns [] when the detail contains no recognizable scopes", () => { expect( extractRequiredScopes( - "You do not have permission to perform this action." - ) + "You do not have permission to perform this action.", + ), ).toEqual([]); }); @@ -26,32 +26,32 @@ describe("extractRequiredScopes", () => { extractRequiredScopes( "Your organization has disabled this feature for members. " + "This is an org-level policy setting, not an auth issue. " + - "You need org:admin/manager/owner role, or team:admin role on the team." - ) + "You need org:admin/manager/owner role, or team:admin role on the team.", + ), ).toEqual([]); }); test("extracts a single scope from a detail string", () => { expect( extractRequiredScopes( - "You do not have the required scope to perform this action. Required scopes: event:read" - ) + "You do not have the required scope to perform this action. Required scopes: event:read", + ), ).toEqual(["event:read"]); }); test("extracts multiple scopes from a detail string preserving order", () => { expect( extractRequiredScopes( - "Required scopes: event:read, project:write. Got: none." - ) + "Required scopes: event:read, project:write. Got: none.", + ), ).toEqual(["event:read", "project:write"]); }); test("deduplicates repeated scopes", () => { expect( extractRequiredScopes( - "event:read is required. Try obtaining event:read scope." - ) + "event:read is required. Try obtaining event:read scope.", + ), ).toEqual(["event:read"]); }); @@ -60,7 +60,7 @@ describe("extractRequiredScopes", () => { // response text mentioning things like `http:localhost` or // `timestamp:now` doesn't accidentally match. expect( - extractRequiredScopes("http:localhost timestamp:now category:billing") + extractRequiredScopes("http:localhost timestamp:now category:billing"), ).toEqual([]); }); @@ -75,7 +75,7 @@ describe("extractRequiredScopes", () => { extractRequiredScopes({ detail: "You do not have permission to perform this action.", required: ["event:read"], - }) + }), ).toEqual(["event:read"]); }); @@ -84,7 +84,7 @@ describe("extractRequiredScopes", () => { extractRequiredScopes({ detail: "Missing scopes.", requiredScopes: ["project:admin", "team:write"], - }) + }), ).toEqual(["project:admin", "team:write"]); }); @@ -93,7 +93,7 @@ describe("extractRequiredScopes", () => { extractRequiredScopes({ detail: "Missing scopes.", scopes: [{ scope: "org:read" }, { scope: "org:write" }], - }) + }), ).toEqual(["org:read", "org:write"]); }); @@ -104,7 +104,7 @@ describe("extractRequiredScopes", () => { expect( extractRequiredScopes({ message: "Your token lacks project:read.", - }) + }), ).toEqual(["project:read"]); }); @@ -112,7 +112,7 @@ describe("extractRequiredScopes", () => { expect( extractRequiredScopes({ required: [42, null, "event:read", { unrelated: true }], - }) + }), ).toEqual(["event:read"]); }); @@ -145,7 +145,7 @@ describe("extractRequiredScopes", () => { "alerts:write", ]; expect( - extractRequiredScopes(`Required scopes: ${allScopes.join(", ")}`) + extractRequiredScopes(`Required scopes: ${allScopes.join(", ")}`), ).toEqual(allScopes); }); @@ -156,8 +156,8 @@ describe("extractRequiredScopes", () => { // `project:releases`) or `alerts:admin` (no admin tier). expect( extractRequiredScopes( - "Not real: release:read release:write alerts:admin team:superuser" - ) + "Not real: release:read release:write alerts:admin team:superuser", + ), ).toEqual([]); }); }); diff --git a/packages/cli/test/lib/api/alerts.test.ts b/packages/cli/test/lib/api/alerts.test.ts index 5e42d82ca..11397a5a9 100644 --- a/packages/cli/test/lib/api/alerts.test.ts +++ b/packages/cli/test/lib/api/alerts.test.ts @@ -42,14 +42,14 @@ describe("deleteIssueAlertRule", () => { const req = new Request(input!, init); expect(req.method).toBe("DELETE"); expect(req.url).toBe( - `${DEFAULT_SENTRY_URL}/api/0/organizations/test-org/workflows/42/` + `${DEFAULT_SENTRY_URL}/api/0/organizations/test-org/workflows/42/`, ); expect(req.headers.get("Authorization")).toBe("Bearer test-token"); return new Response(null, { status: 204 }); }); await expect( - deleteIssueAlertRule("test-org", "42") + deleteIssueAlertRule("test-org", "42"), ).resolves.toBeUndefined(); }); }); @@ -109,11 +109,11 @@ describe("getIssueAlertRule", () => { test("throws 404 ApiError when no attached rule matches", async () => { globalThis.fetch = mockFetch(async () => // Only an unattached workflow comes back → filtered out → not found. - Response.json([workflowRule({ id: "42", detectorIds: [] })]) + Response.json([workflowRule({ id: "42", detectorIds: [] })]), ); await expect( - getIssueAlertRule("test-org", "test-project", "42") + getIssueAlertRule("test-org", "test-project", "42"), ).rejects.toMatchObject({ name: "ApiError", status: 404 }); }); }); @@ -176,7 +176,7 @@ describe("getMetricAlertRule", () => { const url = new URL(new Request(input!, init).url); expect(url.pathname).toBe("/api/0/organizations/test-org/detectors/9/"); return Response.json( - metricDetector({ id: "9", name: "Disabled rule", enabled: false }) + metricDetector({ id: "9", name: "Disabled rule", enabled: false }), ); }); @@ -201,8 +201,8 @@ describe("getMetricAlertRule", () => { }, }, ], - }) - ) + }), + ), ); const rule = await getMetricAlertRule("test-org", "9"); @@ -228,8 +228,8 @@ describe("getMetricAlertRule", () => { }, }, ], - }) - ) + }), + ), ); const rule = await getMetricAlertRule("test-org", "9"); @@ -242,8 +242,8 @@ describe("getMetricAlertRule", () => { test("prefers projectSlug and falls back to a projects array for projects", async () => { globalThis.fetch = mockFetch(async () => Response.json( - metricDetector({ projectSlug: undefined, projects: ["frontend"] }) - ) + metricDetector({ projectSlug: undefined, projects: ["frontend"] }), + ), ); const rule = await getMetricAlertRule("test-org", "9"); @@ -255,8 +255,8 @@ describe("getMetricAlertRule", () => { Response.json( metricDetector({ dataSources: [{ snubaQuery: { timeWindow: "not-a-number" } }], - }) - ) + }), + ), ); const rule = await getMetricAlertRule("test-org", "9"); @@ -265,7 +265,7 @@ describe("getMetricAlertRule", () => { test("rejects a non-metric detector with a 404 instead of mapping it", async () => { globalThis.fetch = mockFetch(async () => - Response.json(metricDetector({ type: "uptime_domain_failure" })) + Response.json(metricDetector({ type: "uptime_domain_failure" })), ); await expect(getMetricAlertRule("test-org", "9")).rejects.toMatchObject({ @@ -277,8 +277,8 @@ describe("getMetricAlertRule", () => { test("reconstructs the legacy actor identifier from an object owner", async () => { globalThis.fetch = mockFetch(async () => Response.json( - metricDetector({ owner: { type: "team", id: "42", name: "backend" } }) - ) + metricDetector({ owner: { type: "team", id: "42", name: "backend" } }), + ), ); const rule = await getMetricAlertRule("test-org", "9"); @@ -292,7 +292,7 @@ describe("createIssueAlertRule", () => { const req = new Request(input!, init); expect(req.method).toBe("POST"); expect(new URL(req.url).pathname).toBe( - "/api/0/organizations/test-org/workflows/" + "/api/0/organizations/test-org/workflows/", ); expect(await req.json()).toEqual({ name: "New", detectorIds: [7] }); return Response.json(workflowRule({ id: "5", name: "New" })); @@ -312,7 +312,7 @@ describe("updateIssueAlertRule", () => { const req = new Request(input!, init); expect(req.method).toBe("PUT"); expect(new URL(req.url).pathname).toBe( - "/api/0/organizations/test-org/workflows/42/" + "/api/0/organizations/test-org/workflows/42/", ); expect(await req.json()).toEqual({ name: "Renamed" }); return Response.json(workflowRule({ id: "42", name: "Renamed" })); @@ -349,7 +349,7 @@ describe("resolveErrorDetectorId", () => { }); await expect( - resolveErrorDetectorId("test-org", "test-project") + resolveErrorDetectorId("test-org", "test-project"), ).resolves.toBe(7); }); @@ -357,7 +357,7 @@ describe("resolveErrorDetectorId", () => { globalThis.fetch = mockFetch(async () => Response.json([])); await expect( - resolveErrorDetectorId("test-org", "test-project") + resolveErrorDetectorId("test-org", "test-project"), ).rejects.toMatchObject({ name: "ApiError", status: 404 }); }); }); @@ -368,14 +368,14 @@ describe("deleteMetricAlertRule", () => { const req = new Request(input!, init); expect(req.method).toBe("DELETE"); expect(new URL(req.url).pathname).toBe( - "/api/0/organizations/test-org/detectors/9/" + "/api/0/organizations/test-org/detectors/9/", ); expect(req.headers.get("Authorization")).toBe("Bearer test-token"); return new Response(null, { status: 204 }); }); await expect( - deleteMetricAlertRule("test-org", "9") + deleteMetricAlertRule("test-org", "9"), ).resolves.toBeUndefined(); }); }); @@ -386,7 +386,7 @@ describe("createMetricAlertRule", () => { const req = new Request(input!, init); expect(req.method).toBe("POST"); expect(new URL(req.url).pathname).toBe( - "/api/0/organizations/test-org/projects/backend/detectors/" + "/api/0/organizations/test-org/projects/backend/detectors/", ); expect(await req.json()).toEqual({ name: "P95 latency", @@ -411,7 +411,7 @@ describe("createMetricAlertRule", () => { }); return Response.json( { ...metricDetector({ id: "77", name: "P95 latency" }) }, - { status: 201 } + { status: 201 }, ); }); @@ -463,7 +463,7 @@ describe("createMetricAlertRule", () => { dataset: "errors", timeWindow: 5, triggers: [{ alertThreshold: 1, actions: [{ id: "notify" }] }], - }) + }), ).rejects.toMatchObject({ name: "ValidationError" }); }); }); @@ -493,7 +493,7 @@ describe("putMetricAlertRule", () => { const req = new Request(input!, init); expect(req.method).toBe("PUT"); expect(new URL(req.url).pathname).toBe( - "/api/0/organizations/test-org/detectors/9/" + "/api/0/organizations/test-org/detectors/9/", ); const body = (await req.json()) as Record; expect(body.name).toBe("Renamed"); diff --git a/packages/cli/test/lib/api/auth.test.ts b/packages/cli/test/lib/api/auth.test.ts index 2a134e9ea..af369f76d 100644 --- a/packages/cli/test/lib/api/auth.test.ts +++ b/packages/cli/test/lib/api/auth.test.ts @@ -23,7 +23,7 @@ describe("getCurrentAuthScopes", () => { expect(apiRequestToRegion).toHaveBeenCalledWith( expect.any(String), "", - expect.objectContaining({ schema: expect.any(Object) }) + expect.objectContaining({ schema: expect.any(Object) }), ); }); diff --git a/packages/cli/test/lib/api/conversations.test.ts b/packages/cli/test/lib/api/conversations.test.ts index 790ebfb73..b46e3bc7f 100644 --- a/packages/cli/test/lib/api/conversations.test.ts +++ b/packages/cli/test/lib/api/conversations.test.ts @@ -30,7 +30,7 @@ const ORG = "test-org"; /** Helper to mock fetch with a single OK response */ function mockOk( body: unknown, - headers: Record = {} + headers: Record = {}, ): { getCapturedUrl: () => string; getCapturedMethod: () => string } { let capturedUrl = ""; let capturedMethod = ""; @@ -56,7 +56,7 @@ function mockOk( * Each call to fetch returns the next response in the queue. */ function mockSequential( - responses: Array<{ body: unknown; headers?: Record }> + responses: Array<{ body: unknown; headers?: Record }>, ): { getCapturedUrls: () => string[] } { const capturedUrls: string[] = []; let callIndex = 0; @@ -112,7 +112,7 @@ describe("listConversations", () => { expect(getCapturedMethod()).toBe("GET"); expect(getCapturedUrl()).toContain( - `/api/0/organizations/${ORG}/agents/conversations/` + `/api/0/organizations/${ORG}/agents/conversations/`, ); }); @@ -167,7 +167,7 @@ describe("listConversations", () => { await listConversations(ORG, { cursor: "1735689600000:0:0" }); expect(getCapturedUrl()).toContain( - `cursor=${encodeURIComponent("1735689600000:0:0")}` + `cursor=${encodeURIComponent("1735689600000:0:0")}`, ); }); @@ -310,7 +310,7 @@ describe("getConversationSpans", () => { /** Details envelope returned by the conversation details endpoint. */ function makeEnvelope( spans: ReturnType[], - title: string | null = "Test conversation" + title: string | null = "Test conversation", ) { return { conversationId: CONV_ID, @@ -326,7 +326,7 @@ describe("getConversationSpans", () => { expect(getCapturedMethod()).toBe("GET"); expect(getCapturedUrl()).toContain( - `/api/0/organizations/${ORG}/agents/conversations/${CONV_ID}/` + `/api/0/organizations/${ORG}/agents/conversations/${CONV_ID}/`, ); }); @@ -356,7 +356,7 @@ describe("getConversationSpans", () => { mockOk([makeSpan("span-1-aabb1122")]); await expect(getConversationSpans(ORG, CONV_ID)).rejects.toThrow( - /Unexpected response format/ + /Unexpected response format/, ); }); @@ -435,7 +435,7 @@ describe("getConversationSpans", () => { await getConversationSpans(ORG, specialId); expect(getCapturedUrl()).toContain( - `/agents/conversations/${encodeURIComponent(specialId)}/` + `/agents/conversations/${encodeURIComponent(specialId)}/`, ); }); }); diff --git a/packages/cli/test/lib/api/dashboards.test.ts b/packages/cli/test/lib/api/dashboards.test.ts index a191f4b70..477370c47 100644 --- a/packages/cli/test/lib/api/dashboards.test.ts +++ b/packages/cli/test/lib/api/dashboards.test.ts @@ -51,13 +51,13 @@ describe("restoreDashboardRevision", () => { apiRequestToRegionMock.mockResolvedValue({ data: dashboard }); await expect( - restoreDashboardRevision("test-org", "123", "42") + restoreDashboardRevision("test-org", "123", "42"), ).resolves.toBe(dashboard); expect(apiRequestToRegionMock).toHaveBeenCalledWith( "https://us.sentry.io", "/organizations/test-org/dashboards/123/revisions/42/restore/", - expect.objectContaining({ method: "POST" }) + expect.objectContaining({ method: "POST" }), ); }); }); diff --git a/packages/cli/test/lib/api/debug-files.test.ts b/packages/cli/test/lib/api/debug-files.test.ts index c11509f34..70fa672f4 100644 --- a/packages/cli/test/lib/api/debug-files.test.ts +++ b/packages/cli/test/lib/api/debug-files.test.ts @@ -19,7 +19,7 @@ const { apiRequestToRegionMock, uploadMissingBufferChunksMock } = vi.hoisted( () => ({ apiRequestToRegionMock: vi.fn(), uploadMissingBufferChunksMock: vi.fn(() => Promise.resolve()), - }) + }), ); vi.mock("../../../src/lib/region.js", () => ({ @@ -54,7 +54,7 @@ vi.mock("../../../src/lib/api/chunk-upload.js", async (importOriginal) => { function makeDif( name: string, content: string, - debugId?: string + debugId?: string, ): DebugFileUpload { return { name, debugId, content: Buffer.from(content) }; } @@ -97,7 +97,7 @@ beforeEach(() => { apiRequestToRegionMock.mockReset(); uploadMissingBufferChunksMock.mockClear(); vi.mocked(getChunkUploadOptions).mockResolvedValue( - BASE_SERVER_OPTIONS as Awaited> + BASE_SERVER_OPTIONS as Awaited>, ); }); @@ -130,7 +130,7 @@ describe("uploadDebugFiles", () => { data[key] = { state: "ok" }; } return { data }; - } + }, ); const results = await uploadDebugFiles({ @@ -164,7 +164,7 @@ describe("uploadDebugFiles", () => { data[key] = { state: "ok" }; } return { data }; - } + }, ); await uploadDebugFiles({ @@ -193,13 +193,13 @@ describe("uploadDebugFiles", () => { [key]: { state: "not_found", missingChunks: body[key]?.chunks }, }, }; - } + }, ); apiRequestToRegionMock.mockImplementationOnce( async (_url: string, _endpoint: string, init: { body: object }) => { const key = Object.keys(init.body as object)[0] as string; return { data: { [key]: { state: "created" } } }; - } + }, ); const promise = uploadDebugFiles({ @@ -224,13 +224,13 @@ describe("uploadDebugFiles", () => { async (_url: string, _endpoint: string, init: { body: object }) => { const key = Object.keys(init.body as object)[0] as string; return { data: { [key]: { state: "assembling" } } }; - } + }, ); apiRequestToRegionMock.mockImplementationOnce( async (_url: string, _endpoint: string, init: { body: object }) => { const key = Object.keys(init.body as object)[0] as string; return { data: { [key]: { state: "ok" } } }; - } + }, ); const promise = uploadDebugFiles({ @@ -254,7 +254,7 @@ describe("uploadDebugFiles", () => { return { data: { [key]: { state: "error", detail: "corrupt object" } }, }; - } + }, ); const results = await uploadDebugFiles({ @@ -275,7 +275,7 @@ describe("uploadDebugFiles", () => { async (_url: string, _endpoint: string, init: { body: object }) => { const key = Object.keys(init.body as object)[0] as string; return { data: { [key]: { state: "assembling" } } }; - } + }, ); await expect( @@ -284,7 +284,7 @@ describe("uploadDebugFiles", () => { difs: [dif], wait: true, maxWaitMs: 0, - }) + }), ).rejects.toBeInstanceOf(ApiError); }); @@ -297,7 +297,7 @@ describe("uploadDebugFiles", () => { data[key] = { state: "ok" }; } return { data }; - } + }, ); const results = await uploadDebugFiles({ @@ -325,7 +325,7 @@ describe("uploadDebugFiles", () => { data[key] = { state: "ok" }; } return { data }; - } + }, ); await uploadDebugFiles({ @@ -351,13 +351,13 @@ describe("uploadDebugFiles", () => { async (_url: string, _endpoint: string, init: { body: object }) => { const key = Object.keys(init.body as object)[0] as string; return { data: { [key]: { state: "not_found" } } }; - } + }, ); apiRequestToRegionMock.mockImplementationOnce( async (_url: string, _endpoint: string, init: { body: object }) => { const key = Object.keys(init.body as object)[0] as string; return { data: { [key]: { state: "ok" } } }; - } + }, ); await uploadDebugFiles({ @@ -383,7 +383,7 @@ describe("uploadDebugFiles", () => { data[key] = { state: "ok" }; } return { data }; - } + }, ); const results = await uploadDebugFiles({ @@ -421,7 +421,7 @@ describe("uploadDebugFiles", () => { difs: [makeDif("big.so", "way too large", "id-big")], wait: false, maxWaitMs: 1000, - }) + }), ).rejects.toThrow(/exceed the maximum file size/); expect(apiRequestToRegionMock).not.toHaveBeenCalled(); }); @@ -435,7 +435,7 @@ describe("uploadDebugFiles", () => { async (_url: string, _endpoint: string, init: { body: object }) => { const key = Object.keys(init.body as object)[0] as string; return { data: { [key]: { state: "assembling" } } }; - } + }, ); const promise = uploadDebugFiles({ @@ -458,7 +458,7 @@ describe("uploadDebugFiles", () => { async (_url: string, _endpoint: string, init: { body: object }) => { const key = Object.keys(init.body as object)[0] as string; return { data: { [key]: { state: "ok" } } }; - } + }, ); const results = await uploadDebugFiles({ diff --git a/packages/cli/test/lib/api/events-overshoot.test.ts b/packages/cli/test/lib/api/events-overshoot.test.ts index 7256d6128..22ff7d530 100644 --- a/packages/cli/test/lib/api/events-overshoot.test.ts +++ b/packages/cli/test/lib/api/events-overshoot.test.ts @@ -82,7 +82,7 @@ describe("listIssueEvents pagination", () => { "Content-Type": "application/json", Link: nextLinkHeader("page2"), }, - }) + }), ); const result = await listIssueEvents("test-org", "123", { limit: 2 }); @@ -104,7 +104,7 @@ describe("listIssueEvents pagination", () => { "Content-Type": "application/json", Link: nextLinkHeader("page2"), }, - }) + }), ); const result = await listIssueEvents("test-org", "123", { limit: 2 }); @@ -119,7 +119,7 @@ describe("listIssueEvents pagination", () => { new Response(JSON.stringify(makeEvents(3)), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); const result = await listIssueEvents("test-org", "123", { limit: 25 }); diff --git a/packages/cli/test/lib/api/explore.test.ts b/packages/cli/test/lib/api/explore.test.ts index 566dec77d..e9cf7d1a8 100644 --- a/packages/cli/test/lib/api/explore.test.ts +++ b/packages/cli/test/lib/api/explore.test.ts @@ -100,7 +100,7 @@ describe("queryEvents", () => { }); expect(capturedUrl).toContain( - `query=${encodeURIComponent("is:unresolved")}` + `query=${encodeURIComponent("is:unresolved")}`, ); }); @@ -150,10 +150,10 @@ describe("queryEvents", () => { }); expect(capturedUrl).toContain( - `start=${encodeURIComponent("2024-01-15T00:00:00Z")}` + `start=${encodeURIComponent("2024-01-15T00:00:00Z")}`, ); expect(capturedUrl).toContain( - `end=${encodeURIComponent("2024-01-16T00:00:00Z")}` + `end=${encodeURIComponent("2024-01-16T00:00:00Z")}`, ); expect(capturedUrl).not.toContain("statsPeriod="); }); @@ -189,7 +189,7 @@ describe("queryEvents", () => { }, { Link: `; rel="next"; results="true"; cursor="${cursor}"`, - } + }, ); const result = await queryEvents("my-org", { @@ -212,7 +212,7 @@ describe("queryEvents", () => { }, { Link: `; rel="next"; results="false"; cursor=""`, - } + }, ); const result = await queryEvents("my-org", { fields: ["title"] }); @@ -229,7 +229,7 @@ describe("queryEvents", () => { * Each call to fetch returns the next response in the queue. */ function mockSequential( - responses: Array<{ body: unknown; headers?: Record }> + responses: Array<{ body: unknown; headers?: Record }>, ): { getCapturedUrls: () => string[] } { const capturedUrls: string[] = []; let callIndex = 0; diff --git a/packages/cli/test/lib/api/feedback.property.test.ts b/packages/cli/test/lib/api/feedback.property.test.ts index fbb393fb8..ed9aed5f2 100644 --- a/packages/cli/test/lib/api/feedback.property.test.ts +++ b/packages/cli/test/lib/api/feedback.property.test.ts @@ -19,15 +19,15 @@ describe("buildFeedbackQuery", () => { "unresolved", "resolved", "spam", - "all" + "all", ), fc.string(), (status, query) => { expect(buildFeedbackQuery(status, query)).toStartWith( - CATEGORY_FILTER + CATEGORY_FILTER, ); - } - ) + }, + ), ); }); @@ -41,7 +41,7 @@ describe("buildFeedbackQuery", () => { test("omits the generated status filter for all", () => { expect(buildFeedbackQuery("all", "browser:Chrome")).toBe( - `${CATEGORY_FILTER} browser:Chrome` + `${CATEGORY_FILTER} browser:Chrome`, ); }); }); diff --git a/packages/cli/test/lib/api/infrastructure.test.ts b/packages/cli/test/lib/api/infrastructure.test.ts index e1d4187a3..9dac96c12 100644 --- a/packages/cli/test/lib/api/infrastructure.test.ts +++ b/packages/cli/test/lib/api/infrastructure.test.ts @@ -19,20 +19,20 @@ describe("throwApiError", () => { throwApiError( new TypeError("fetch failed"), undefined, - "Failed to resolve short ID" - ) + "Failed to resolve short ID", + ), ).toThrow( expect.objectContaining({ message: "Failed to resolve short ID: Network error", status: 0, - }) + }), ); try { throwApiError( new TypeError("fetch failed"), undefined, - "Failed to resolve short ID" + "Failed to resolve short ID", ); } catch (error) { expect(error).toBeInstanceOf(ApiError); @@ -40,7 +40,7 @@ describe("throwApiError", () => { expect(apiError.detail).toContain("fetch failed"); expect(apiError.detail).toContain("Unable to reach Sentry API"); expect(apiError.detail).toContain( - "Check your internet connection and try again" + "Check your internet connection and try again", ); } }); @@ -67,7 +67,7 @@ describe("throwApiError", () => { throwApiError( { detail: "Invalid query syntax" }, mockResponse, - "Failed to list issues" + "Failed to list issues", ); } catch (error) { expect(error).toBeInstanceOf(ApiError); @@ -90,7 +90,7 @@ describe("throwApiError", () => { expect(error).toBeInstanceOf(ApiError); const apiError = error as ApiError; expect(apiError.message).toBe( - "Failed to fetch: 500 Internal Server Error" + "Failed to fetch: 500 Internal Server Error", ); expect(apiError.status).toBe(500); expect(apiError.detail).toBe("something went wrong"); @@ -120,7 +120,7 @@ describe("throwApiError", () => { throwApiError( { detail: "Resource not found" }, mockResponse, - "Failed to get org" + "Failed to get org", ); } catch (error) { const apiError = error as ApiError; @@ -145,7 +145,7 @@ describe("throwApiError", () => { detail: "Your organization has disabled this feature for members.", }, mockResponse, - "Failed to create project" + "Failed to create project", ); } catch (error) { const apiError = error as ApiError; @@ -166,18 +166,18 @@ describe("throwApiError", () => { throwApiError( { detail: "You do not have permission to perform this action." }, mockResponse, - "Failed to get organization" + "Failed to get organization", ); } catch (error) { const apiError = error as ApiError; expect(apiError.enriched403).toBe(true); expect(apiError.status).toBe(403); expect(apiError.detail).toContain( - "You do not have permission to perform this action." + "You do not have permission to perform this action.", ); expect(apiError.detail).toContain("SENTRY_AUTH_TOKEN"); expect(apiError.detail).toContain( - "https://sentry.io/settings/account/api/auth-tokens/" + "https://sentry.io/settings/account/api/auth-tokens/", ); } }); @@ -195,13 +195,13 @@ describe("throwApiError", () => { "You do not have permission. Required scope: org:read, project:read", }, mockResponse, - "Failed to list issues" + "Failed to list issues", ); } catch (error) { const apiError = error as ApiError; expect(apiError.enriched403).toBe(true); expect(apiError.detail).toContain( - "missing the required scope(s) 'org:read', 'project:read'" + "missing the required scope(s) 'org:read', 'project:read'", ); } }); @@ -216,12 +216,12 @@ describe("throwApiError", () => { throwApiError( { detail: "You do not have permission to perform this action." }, mockResponse, - "Failed to get organization" + "Failed to get organization", ); } catch (error) { const apiError = error as ApiError; expect(apiError.detail).toContain( - "may lack the required scope for this operation" + "may lack the required scope for this operation", ); } }); @@ -236,7 +236,7 @@ describe("throwApiError", () => { throwApiError( { detail: undefined }, mockResponse, - "Failed to get organization" + "Failed to get organization", ); } catch (error) { const apiError = error as ApiError; @@ -259,7 +259,7 @@ describe("throwApiError", () => { throwApiError( { detail: null }, mockResponse, - "Failed to get organization" + "Failed to get organization", ); } catch (error) { const apiError = error as ApiError; @@ -308,7 +308,7 @@ describe("throwApiError", () => { "Your organization has disabled this feature for members.", }, mockResponse, - "Failed to create project" + "Failed to create project", ); } catch (error) { const apiError = error as ApiError; @@ -332,13 +332,13 @@ describe("throwApiError", () => { detail: "You do not have permission to perform this action.", }, mockResponse, - "Failed to get organization" + "Failed to get organization", ); } catch (error) { const apiError = error as ApiError; expect(apiError.enriched403).toBe(true); expect(apiError.detail).toContain( - "You may not have access to this resource." + "You may not have access to this resource.", ); expect(apiError.detail).toContain("sentry auth login"); // Should NOT mention SENTRY_AUTH_TOKEN @@ -359,16 +359,16 @@ describe("throwApiError", () => { "You do not have permission. Required scope: event:read, project:read", }, mockResponse, - "Failed to list issues" + "Failed to list issues", ); } catch (error) { const apiError = error as ApiError; expect(apiError.enriched403).toBe(true); expect(apiError.detail).toContain( - "missing the required scope(s) 'event:read', 'project:read'" + "missing the required scope(s) 'event:read', 'project:read'", ); expect(apiError.detail).toContain( - "sentry auth refresh --scope event:read --scope project:read" + "sentry auth refresh --scope event:read --scope project:read", ); // Should NOT mention env var or web UI expect(apiError.detail).not.toContain("SENTRY_AUTH_TOKEN"); @@ -392,19 +392,19 @@ describe("throwApiError", () => { throwApiError( { detail: "Invalid token" }, mockResponse, - "Failed to list organizations" + "Failed to list organizations", ); } catch (error) { const apiError = error as ApiError; expect(apiError.status).toBe(401); expect(apiError.message).toBe( - "Failed to list organizations: 401 Unauthorized" + "Failed to list organizations: 401 Unauthorized", ); expect(apiError.detail).toContain("Invalid token"); expect(apiError.detail).toContain("SENTRY_AUTH_TOKEN"); expect(apiError.detail).toContain("not recognized or has been revoked"); expect(apiError.detail).toContain( - "https://sentry.io/settings/account/api/auth-tokens/" + "https://sentry.io/settings/account/api/auth-tokens/", ); } }); @@ -419,7 +419,7 @@ describe("throwApiError", () => { throwApiError( { detail: "Token expired" }, mockResponse, - "Failed to list organizations" + "Failed to list organizations", ); } catch (error) { const apiError = error as ApiError; @@ -429,7 +429,7 @@ describe("throwApiError", () => { expect(apiError.detail).toContain("has expired"); expect(apiError.detail).not.toContain("not recognized"); expect(apiError.detail).toContain( - "https://sentry.io/settings/account/api/auth-tokens/" + "https://sentry.io/settings/account/api/auth-tokens/", ); } }); @@ -444,7 +444,7 @@ describe("throwApiError", () => { throwApiError( { detail: undefined }, mockResponse, - "Failed to list organizations" + "Failed to list organizations", ); } catch (error) { const apiError = error as ApiError; @@ -473,7 +473,7 @@ describe("throwApiError", () => { }, }, mockResponse, - "Failed to list teams" + "Failed to list teams", ); } catch (error) { captured = error as ApiError; @@ -523,7 +523,7 @@ describe("throwApiError", () => { throwApiError( { detail: "Authentication credentials were not provided." }, mockResponse, - "Failed to list organizations" + "Failed to list organizations", ); } catch (error) { const apiError = error as ApiError; @@ -601,7 +601,7 @@ describe("rawApiRequest binary handling", () => { await expect( rawApiRequest("organizations/", { baseUrl: "https://example.invalid", - }) + }), ).rejects.toBeInstanceOf(HostScopeError); expect(fetchSpy).not.toHaveBeenCalled(); }); @@ -619,11 +619,11 @@ describe("rawApiRequest binary handling", () => { new Response(pngBytes, { status: 200, headers: { "content-type": "image/png" }, - }) + }), ); const result = await rawApiRequest( - "projects/org/proj/events/abc/attachments/1/?download=1" + "projects/org/proj/events/abc/attachments/1/?download=1", ); expect(result.status).toBe(200); @@ -644,7 +644,7 @@ describe("rawApiRequest binary handling", () => { new Response(bytes, { status: 200, headers: { "content-type": "application/octet-stream" }, - }) + }), ); const result = await rawApiRequest("debug-files/1/download/"); @@ -658,7 +658,7 @@ describe("rawApiRequest binary handling", () => { new Response(JSON.stringify({ ok: true }), { status: 200, headers: { "content-type": "application/json" }, - }) + }), ); const result = await rawApiRequest("organizations/"); @@ -670,7 +670,7 @@ describe("rawApiRequest binary handling", () => { async () => new Response(JSON.stringify({ slug: "acme" }), { status: 200, - }) + }), ); const result = await rawApiRequest("organizations/acme/"); @@ -683,7 +683,7 @@ describe("rawApiRequest binary handling", () => { new Response("not json", { status: 200, headers: { "content-type": "text/plain" }, - }) + }), ); const result = await rawApiRequest("some/text/"); @@ -696,7 +696,7 @@ describe("rawApiRequest binary handling", () => { new Response("", { status: 404, statusText: "Not Found", - }) + }), ); const result = await rawApiRequest("missing/"); @@ -786,8 +786,8 @@ describe("fetchAllPages", () => { test("follows cursors and returns every validated item", async () => { const fetchPage = vi.fn((cursor: string | undefined) => Promise.resolve( - cursor ? { data: [3] } : { data: [1, 2], nextCursor: "page-2" } - ) + cursor ? { data: [3] } : { data: [1, 2], nextCursor: "page-2" }, + ), ); expect(await fetchAllPages(fetchPage, numbers, "listing numbers")).toEqual([ 1, 2, 3, @@ -820,16 +820,16 @@ describe("fetchAllPages", () => { })(), message: `Pagination exceeded ${MAX_PAGINATION_PAGES} pages when listing numbers`, }, - ])("fails instead of returning a partial list on $name", async ({ - page, - message, - }) => { - const result = fetchAllPages( - () => Promise.resolve(page()), - numbers, - "listing numbers" - ); - await expect(result).rejects.toBeInstanceOf(ApiError); - await expect(result).rejects.toThrow(message); - }); + ])( + "fails instead of returning a partial list on $name", + async ({ page, message }) => { + const result = fetchAllPages( + () => Promise.resolve(page()), + numbers, + "listing numbers", + ); + await expect(result).rejects.toBeInstanceOf(ApiError); + await expect(result).rejects.toThrow(message); + }, + ); }); diff --git a/packages/cli/test/lib/api/issue-app-links.test.ts b/packages/cli/test/lib/api/issue-app-links.test.ts index d37ca3674..42c06792e 100644 --- a/packages/cli/test/lib/api/issue-app-links.test.ts +++ b/packages/cli/test/lib/api/issue-app-links.test.ts @@ -97,7 +97,7 @@ beforeEach(async () => { if (path.endsWith("/external-issue-actions/")) { return json( actionStatus < 300 ? actionLink : { detail: "Provider failed" }, - actionStatus + actionStatus, ); } if (request.method === "DELETE") { @@ -117,94 +117,101 @@ function writes(): Request[] { } describe("app issue-link action", () => { - test.each([ - undefined, - { issueId: "" }, - ])("resolves Linear key to UUID with target fields %j, then sends the schema URI and fields top-level", async (fields) => { - const prepared = await resolveAppIssueLink({ ...OPTIONS, fields }); - expect(writes()).toHaveLength(0); - expect(prepared.fields).toEqual({ issueId: "linear-uuid" }); - const search = calls.find((request) => - request.url.includes("external-requests") - ); - expect(search?.url).toContain( - "https://sentry.io/api/0/sentry-app-installations/install-uuid/" - ); - expect(search?.url).toContain("query=ENG-42"); - expect(search?.url).toContain("projectId=77"); - expect(calls[0]?.url).toContain( - "https://de.sentry.io/api/0/organizations/" - ); - expect(await linkAppIssue(prepared)).toEqual({ changed: true, link: LINK }); - expect(writes()).toHaveLength(1); - expect( - new globalThis.URL(writes()[0]!.url).searchParams.get( - "expectedExternalIssueUrl" - ) - ).toBe(URL); - expect(await writes()[0]?.json()).toEqual({ - groupId: ISSUE, - action: "link", - uri: FORM.uri, - issueId: "linear-uuid", - }); - expect( - calls.filter((request) => request.url.includes("/external-issues/")) - ).toHaveLength(1); - }); - - test.each([ - "ENG-420: Other issue", - "ENG-99: Follow up on ENG-42", - ])("rejects a nonmatching Linear label: %s", async (label) => { - choices = [["wrong", label]]; - await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( - "did not return an exact match" - ); - expect(writes()).toHaveLength(0); - }); + test.each([undefined, { issueId: "" }])( + "resolves Linear key to UUID with target fields %j, then sends the schema URI and fields top-level", + async (fields) => { + const prepared = await resolveAppIssueLink({ ...OPTIONS, fields }); + expect(writes()).toHaveLength(0); + expect(prepared.fields).toEqual({ issueId: "linear-uuid" }); + const search = calls.find((request) => + request.url.includes("external-requests"), + ); + expect(search?.url).toContain( + "https://sentry.io/api/0/sentry-app-installations/install-uuid/", + ); + expect(search?.url).toContain("query=ENG-42"); + expect(search?.url).toContain("projectId=77"); + expect(calls[0]?.url).toContain( + "https://de.sentry.io/api/0/organizations/", + ); + expect(await linkAppIssue(prepared)).toEqual({ + changed: true, + link: LINK, + }); + expect(writes()).toHaveLength(1); + expect( + new globalThis.URL(writes()[0]!.url).searchParams.get( + "expectedExternalIssueUrl", + ), + ).toBe(URL); + expect(await writes()[0]?.json()).toEqual({ + groupId: ISSUE, + action: "link", + uri: FORM.uri, + issueId: "linear-uuid", + }); + expect( + calls.filter((request) => request.url.includes("/external-issues/")), + ).toHaveLength(1); + }, + ); + + test.each(["ENG-420: Other issue", "ENG-99: Follow up on ENG-42"])( + "rejects a nonmatching Linear label: %s", + async (label) => { + choices = [["wrong", label]]; + await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( + "did not return an exact match", + ); + expect(writes()).toHaveLength(0); + }, + ); test("rejects multiple exact matches rather than selecting the first", async () => { choices.push(["another-uuid", "ENG-42: Another issue"]); await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( - "multiple exact issue matches" + "multiple exact issue matches", ); expect(writes()).toHaveLength(0); }); - test.each([ - 200, 201, - ])("guards an existing Linear link with its canonical URL (HTTP %s)", async (status) => { - links = [ - { ...LINK, webUrl: "https://linear.app/example/issue/eng-42/new-title" }, - ]; - const prepared = await resolveAppIssueLink(OPTIONS); - expect(prepared.existing?.id).toBe(LINK.id); - expect(prepared.fields).toEqual({ issueId: "linear-uuid" }); - actionStatus = status; - actionLink = links[0]!; - expect(await linkAppIssue(prepared)).toEqual({ - changed: status === 201, - link: links[0], - }); - expect( - new globalThis.URL(writes()[0]!.url).searchParams.get( - "expectedExternalIssueUrl" - ) - ).toBe(links[0]!.webUrl); - expect(await writes()[0]?.json()).toMatchObject({ - uri: FORM.uri, - issueId: "linear-uuid", - }); - expect(writes()).toHaveLength(1); - }); + test.each([200, 201])( + "guards an existing Linear link with its canonical URL (HTTP %s)", + async (status) => { + links = [ + { + ...LINK, + webUrl: "https://linear.app/example/issue/eng-42/new-title", + }, + ]; + const prepared = await resolveAppIssueLink(OPTIONS); + expect(prepared.existing?.id).toBe(LINK.id); + expect(prepared.fields).toEqual({ issueId: "linear-uuid" }); + actionStatus = status; + actionLink = links[0]!; + expect(await linkAppIssue(prepared)).toEqual({ + changed: status === 201, + link: links[0], + }); + expect( + new globalThis.URL(writes()[0]!.url).searchParams.get( + "expectedExternalIssueUrl", + ), + ).toBe(links[0]!.webUrl); + expect(await writes()[0]?.json()).toMatchObject({ + uri: FORM.uri, + issueId: "linear-uuid", + }); + expect(writes()).toHaveLength(1); + }, + ); test("refuses to replace another issue linked to the same app", async () => { links = [ { ...LINK, webUrl: "https://linear.app/example/issue/ENG-99/other" }, ]; await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( - "Unlink it before" + "Unlink it before", ); expect(writes()).toHaveLength(0); }); @@ -262,8 +269,8 @@ describe("app issue-link action", () => { expect(await writes()[0]?.json()).toMatchObject({ task_id: "123" }); expect( new globalThis.URL(writes()[0]!.url).searchParams.get( - "expectedExternalIssueUrl" - ) + "expectedExternalIssueUrl", + ), ).toBe(url); expect(writes()).toHaveLength(1); }); @@ -283,28 +290,25 @@ describe("app issue-link action", () => { value: "https://tracker.example/tasks/456", appSlug: "custom", }, - ])("rejects a conflicting $type target", async ({ - type, - name, - url, - value, - appSlug, - }) => { - installation = { - ...INSTALLATION, - app: { ...INSTALLATION.app, slug: appSlug }, - }; - form = { uri: "/link", required_fields: [{ name, type }] }; - await expect( - resolveAppIssueLink({ - ...OPTIONS, - url, - appSlug, - fields: { [name]: value }, - }) - ).rejects.toThrow("conflicts"); - expect(writes()).toHaveLength(0); - }); + ])( + "rejects a conflicting $type target", + async ({ type, name, url, value, appSlug }) => { + installation = { + ...INSTALLATION, + app: { ...INSTALLATION.app, slug: appSlug }, + }; + form = { uri: "/link", required_fields: [{ name, type }] }; + await expect( + resolveAppIssueLink({ + ...OPTIONS, + url, + appSlug, + fields: { [name]: value }, + }), + ).rejects.toThrow("conflicts"); + expect(writes()).toHaveLength(0); + }, + ); test.each([ { url: "https://tracker.example/view?id=42#issue" }, @@ -313,35 +317,35 @@ describe("app issue-link action", () => { { url: "https://tracker.example" }, { url: "https://tracker.example/tasks/42#some text" }, { url: "https://tracker.example/tasks/42", fields: { url: "" } }, - ])("preserves the requested URL in the form and backend guard: %j", async ({ - url, - fields, - }) => { - installation = { - ...INSTALLATION, - app: { ...INSTALLATION.app, slug: "custom" }, - }; - form = { uri: "/link", required_fields: [{ name: "url", type: "text" }] }; - actionLink = { ...LINK, serviceType: "custom", webUrl: url }; - const prepared = await resolveAppIssueLink({ - ...OPTIONS, - appSlug: "custom", - url, - fields, - }); - await linkAppIssue(prepared); - expect(await writes()[0]!.json()).toMatchObject({ url }); - expect( - new globalThis.URL(writes()[0]!.url).searchParams.get( - "expectedExternalIssueUrl" - ) - ).toBe(url); - }); + ])( + "preserves the requested URL in the form and backend guard: %j", + async ({ url, fields }) => { + installation = { + ...INSTALLATION, + app: { ...INSTALLATION.app, slug: "custom" }, + }; + form = { uri: "/link", required_fields: [{ name: "url", type: "text" }] }; + actionLink = { ...LINK, serviceType: "custom", webUrl: url }; + const prepared = await resolveAppIssueLink({ + ...OPTIONS, + appSlug: "custom", + url, + fields, + }); + await linkAppIssue(prepared); + expect(await writes()[0]!.json()).toMatchObject({ url }); + expect( + new globalThis.URL(writes()[0]!.url).searchParams.get( + "expectedExternalIssueUrl", + ), + ).toBe(url); + }, + ); test("requires an installation in the requested organization", async () => { installation = { ...INSTALLATION, organization: { slug: "other-org" } }; await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( - "not installed in this organization" + "not installed in this organization", ); expect(writes()).toHaveLength(0); }); @@ -365,11 +369,11 @@ describe("app issue-link action", () => { const prepared = await resolveAppIssueLink(OPTIONS); expect(prepared.installationUuid).toBe(INSTALLATION.uuid); const pages = calls.filter((request) => - request.url.includes("/sentry-app-installations/?") + request.url.includes("/sentry-app-installations/?"), ); expect(pages).toHaveLength(1); expect(pages[0]?.url).toBe( - "https://sentry.io/api/0/organizations/example-org/sentry-app-installations/?cursor=install-page-2" + "https://sentry.io/api/0/organizations/example-org/sentry-app-installations/?cursor=install-page-2", ); expect(writes()).toHaveLength(0); }); @@ -391,17 +395,20 @@ describe("app issue-link action", () => { test("rejects unsupported app link forms instead of using direct registration", async () => { form = undefined; await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( - "does not expose" + "does not expose", ); expect(writes()).toHaveLength(0); }); test("does not allow user fields to override the action URI or target", async () => { await expect( - resolveAppIssueLink({ ...OPTIONS, fields: { uri: "/create" } }) + resolveAppIssueLink({ ...OPTIONS, fields: { uri: "/create" } }), ).rejects.toThrow("reserved app link field"); await expect( - resolveAppIssueLink({ ...OPTIONS, fields: { issueId: "different-uuid" } }) + resolveAppIssueLink({ + ...OPTIONS, + fields: { issueId: "different-uuid" }, + }), ).rejects.toThrow("conflicts"); expect(writes()).toHaveLength(0); }); @@ -421,31 +428,34 @@ describe("app issue-link action", () => { resolveAppIssueLink({ ...OPTIONS, fields: { issueId: "wrong" }, - }) + }), ).rejects.toThrow("conflicts"); expect(writes()).toHaveLength(0); }); - test.each([ - "ENG-42", - "ENG-99", - ])("uses Linear choice ID %s before the label's key", async (id) => { - choices = [ - [ - id, - id === "ENG-42" - ? "ENG-99 mentioned in title" - : "ENG-42 misleading label", - ], - ]; - const result = resolveAppIssueLink({ ...OPTIONS, fields: { issueId: id } }); - if (id === "ENG-42") { - expect((await result).fields).toEqual({ issueId: id }); - } else { - await expect(result).rejects.toThrow("conflicts"); - } - expect(writes()).toHaveLength(0); - }); + test.each(["ENG-42", "ENG-99"])( + "uses Linear choice ID %s before the label's key", + async (id) => { + choices = [ + [ + id, + id === "ENG-42" + ? "ENG-99 mentioned in title" + : "ENG-42 misleading label", + ], + ]; + const result = resolveAppIssueLink({ + ...OPTIONS, + fields: { issueId: id }, + }); + if (id === "ENG-42") { + expect((await result).fields).toEqual({ issueId: id }); + } else { + await expect(result).rejects.toThrow("conflicts"); + } + expect(writes()).toHaveLength(0); + }, + ); test("keeps the query guard separate from an app field with the same name", async () => { form = { @@ -459,8 +469,8 @@ describe("app issue-link action", () => { await linkAppIssue(prepared); expect( new globalThis.URL(writes()[0]!.url).searchParams.get( - "expectedExternalIssueUrl" - ) + "expectedExternalIssueUrl", + ), ).toBe(URL); expect(await writes()[0]?.json()).toMatchObject({ expectedExternalIssueUrl: "provider-field", @@ -488,10 +498,10 @@ describe("app issue-link action", () => { issueId: "linear-uuid", }); const search = calls.find((request) => - request.url.includes("external-requests") + request.url.includes("external-requests"), ); expect( - new globalThis.URL(search?.url ?? "").searchParams.get("dependentData") + new globalThis.URL(search?.url ?? "").searchParams.get("dependentData"), ).toBe('{"team":"team-uuid"}'); }); @@ -512,7 +522,7 @@ describe("app issue-link action", () => { json({ choices: [["team-uuid", "Engineering"]], defaultValue: "team-uuid", - }) + }), ); } return defaultFetch(input, init); @@ -523,28 +533,30 @@ describe("app issue-link action", () => { issueId: "linear-uuid", }); const search = calls.find((request) => - new globalThis.URL(request.url).searchParams.has("dependentData") + new globalThis.URL(request.url).searchParams.has("dependentData"), ); expect( - new globalThis.URL(search!.url).searchParams.get("dependentData") + new globalThis.URL(search!.url).searchParams.get("dependentData"), ).toBe('{"team":"team-uuid"}'); }); test.each([ { defaultValue: "", fields: undefined }, { defaultValue: "preset", fields: { note: "" } }, - ])("omits empty optional fields ($defaultValue)", async ({ - defaultValue, - fields, - }) => { - form = { - ...FORM, - optional_fields: [{ name: "note", type: "text", defaultValue }], - }; - expect((await resolveAppIssueLink({ ...OPTIONS, fields })).fields).toEqual({ - issueId: "linear-uuid", - }); - }); + ])( + "omits empty optional fields ($defaultValue)", + async ({ defaultValue, fields }) => { + form = { + ...FORM, + optional_fields: [{ name: "note", type: "text", defaultValue }], + }; + expect( + (await resolveAppIssueLink({ ...OPTIONS, fields })).fields, + ).toEqual({ + issueId: "linear-uuid", + }); + }, + ); test("reports required fields rather than sending a partial form", async () => { form = { @@ -555,30 +567,30 @@ describe("app issue-link action", () => { ], }; await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( - "--field team=VALUE" + "--field team=VALUE", ); expect(writes()).toHaveLength(0); }); - test.each([ - undefined, - { team: "" }, - ])("requires missing or empty dependencies (%j)", async (fields) => { - form = { - ...FORM, - required_fields: [ - { ...FORM.required_fields[0], depends_on: ["team"] }, - { name: "team", type: "text" }, - ], - }; - await expect(resolveAppIssueLink({ ...OPTIONS, fields })).rejects.toThrow( - "Missing app link fields: --field team=VALUE" - ); - expect( - calls.some((request) => request.url.includes("external-requests")) - ).toBe(false); - expect(writes()).toHaveLength(0); - }); + test.each([undefined, { team: "" }])( + "requires missing or empty dependencies (%j)", + async (fields) => { + form = { + ...FORM, + required_fields: [ + { ...FORM.required_fields[0], depends_on: ["team"] }, + { name: "team", type: "text" }, + ], + }; + await expect(resolveAppIssueLink({ ...OPTIONS, fields })).rejects.toThrow( + "Missing app link fields: --field team=VALUE", + ); + expect( + calls.some((request) => request.url.includes("external-requests")), + ).toBe(false); + expect(writes()).toHaveLength(0); + }, + ); test("distinguishes actual dependency cycles from missing values", async () => { form = { @@ -592,7 +604,7 @@ describe("app issue-link action", () => { resolveAppIssueLink({ ...OPTIONS, fields: { team: "Engineering", issueId: "ENG-42" }, - }) + }), ).rejects.toThrow("App link fields have circular dependencies"); expect(writes()).toHaveLength(0); }); @@ -631,7 +643,7 @@ describe("list and unlink app associations", () => { }); expect(await listAppIssueLinks(ORG, ISSUE)).toHaveLength(2); expect(calls[1]?.url).toContain( - "https://de.sentry.io/api/0/organizations/example-org/issues/123/external-issues/?cursor=next-page" + "https://de.sentry.io/api/0/organizations/example-org/issues/123/external-issues/?cursor=next-page", ); }); @@ -639,10 +651,10 @@ describe("list and unlink app associations", () => { globalThis.fetch = mockFetch(async () => json([LINK], 200, { Link: '; rel="next"; results="true"; cursor="same"', - }) + }), ); await expect(listAppIssueLinks(ORG, ISSUE)).rejects.toThrow( - "repeated a cursor" + "repeated a cursor", ); }); @@ -650,17 +662,17 @@ describe("list and unlink app associations", () => { await unlinkAppIssueLink(ORG, ISSUE, LINK.id); expect(calls).toHaveLength(1); expect(calls[0]?.url).toBe( - "https://de.sentry.io/api/0/organizations/example-org/issues/123/external-issues/99/" + "https://de.sentry.io/api/0/organizations/example-org/issues/123/external-issues/99/", ); expect(calls[0]?.method).toBe("DELETE"); }); test("rejects relative path segments before issuing an unlink", async () => { await expect(unlinkAppIssueLink(ORG, ISSUE, "..")).rejects.toThrow( - ValidationError + ValidationError, ); await expect(unlinkAppIssueLink(ORG, "..", LINK.id)).rejects.toThrow( - ValidationError + ValidationError, ); expect(calls).toHaveLength(0); }); @@ -671,7 +683,7 @@ describe("list and unlink app associations", () => { return json({ detail: "Requires event:admin" }, 403); }); await expect( - unlinkAppIssueLink(ORG, ISSUE, LINK.id) + unlinkAppIssueLink(ORG, ISSUE, LINK.id), ).rejects.toBeInstanceOf(ApiError); expect(calls).toHaveLength(1); }); @@ -683,16 +695,16 @@ describe("list and unlink app associations", () => { serviceType: "custom", }; expect(findAppIssueLink([link], "https://tracker.example/issues/42")).toBe( - link + link, ); expect(() => findAppIssueLink( [link, { ...link, serviceType: "other" }], - "https://tracker.example/issues/42" - ) + "https://tracker.example/issues/42", + ), ).toThrow(ValidationError); expect( - findAppIssueLink([link], "https://tracker.example/issues/42", "other") + findAppIssueLink([link], "https://tracker.example/issues/42", "other"), ).toBeUndefined(); }); @@ -700,16 +712,19 @@ describe("list and unlink app associations", () => { "invalid", "https://linear.app/example/settings", "javascript:alert(1)", - ])("ignores malformed stored sibling %s when matching a valid target", (webUrl) => { - const sibling = { ...LINK, id: "100", webUrl }; - expect(findAppIssueLink([sibling, LINK], URL)).toBe(LINK); - expect(() => findAppIssueLink([LINK], webUrl)).toThrow(ValidationError); - }); + ])( + "ignores malformed stored sibling %s when matching a valid target", + (webUrl) => { + const sibling = { ...LINK, id: "100", webUrl }; + expect(findAppIssueLink([sibling, LINK], URL)).toBe(LINK); + expect(() => findAppIssueLink([LINK], webUrl)).toThrow(ValidationError); + }, + ); test("still refuses replacement of a corrupt link belonging to the selected app", async () => { links = [{ ...LINK, webUrl: "invalid" }]; await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( - "Unlink it before" + "Unlink it before", ); expect(writes()).toHaveLength(0); }); @@ -725,15 +740,15 @@ describe("list and unlink app associations", () => { findAppIssueLink( [link], "https://tracker.example/view?id=2#issue/42", - "custom" - ) + "custom", + ), ).toBeUndefined(); expect( findAppIssueLink( [link], "https://tracker.example/view?id=1#issue/43", - "custom" - ) + "custom", + ), ).toBeUndefined(); }); }); diff --git a/packages/cli/test/lib/api/issue-integrations.test.ts b/packages/cli/test/lib/api/issue-integrations.test.ts index dd7cd7536..dd0b5df64 100644 --- a/packages/cli/test/lib/api/issue-integrations.test.ts +++ b/packages/cli/test/lib/api/issue-integrations.test.ts @@ -79,7 +79,7 @@ describe("selectNativeIntegration", () => { function select( fixture: IntegrationFixture, url: string, - integrationId?: string + integrationId?: string, ): string { return selectNativeIntegration([integration(fixture)], url, integrationId) .id; @@ -111,7 +111,7 @@ describe("selectNativeIntegration", () => { "selects the $provider installation at $domainName for $url", ({ provider, domainName, url }) => { expect(select({ provider, domainName }, url)).toBe("10"); - } + }, ); test.each` @@ -123,16 +123,16 @@ describe("selectNativeIntegration", () => { "rejects a URL outside the $provider installation at $domainName", ({ provider, domainName, url }) => { expect(() => select({ provider, domainName }, url)).toThrow( - "No installed native" + "No installed native", ); - } + }, ); test("selects a GitHub installation without domain metadata by owner", () => { const github = { provider: "github", domainName: null, name: "Owner" }; expect(select(github, "https://github.com/OWNER/repo/issues/7")).toBe("10"); expect(() => - select(github, "https://github.com/another/repo/issues/7") + select(github, "https://github.com/another/repo/issues/7"), ).toThrow("No installed native"); }); @@ -168,15 +168,15 @@ describe("selectNativeIntegration", () => { ], url: "https://gitlab.example.com/deployment/group/repo/-/issues/7", }, - ])("requires --integration for $name installations sharing a host", ({ - integrations, - url, - }) => { - expect(() => selectNativeIntegration(integrations, url)).toThrow( - "Multiple integrations" - ); - expect(selectNativeIntegration(integrations, url, "20").id).toBe("20"); - }); + ])( + "requires --integration for $name installations sharing a host", + ({ integrations, url }) => { + expect(() => selectNativeIntegration(integrations, url)).toThrow( + "Multiple integrations", + ); + expect(selectNativeIntegration(integrations, url, "20").id).toBe("20"); + }, + ); test("ignores installations with malformed domain metadata", () => { const integrations = [ @@ -211,7 +211,7 @@ describe("findNativeIssueLink", () => { ({ provider, existing, target }) => { const link = { ...LINK, provider, url: existing }; expect(findNativeIssueLink([link], target)).toBe(link); - } + }, ); test.each([ @@ -229,8 +229,8 @@ describe("findNativeIssueLink", () => { url: "https://tracker.example.com/jira/browse/PROJ-7", }, ], - target - ) + target, + ), ).toBeUndefined(); }); @@ -244,14 +244,14 @@ describe("findNativeIssueLink", () => { const malformed = { ...LINK, id: "999", url: "not a URL" }; expect(findNativeIssueLink([malformed, LINK], JIRA_URL)).toBe(LINK); expect(findNativeIssueLink([LINK, enterprise], enterprise.url)).toBe( - enterprise + enterprise, ); }); test("rejects ambiguous links and accepts an integration selector", () => { const second = { ...LINK, id: "5678", integrationId: "20" }; expect(() => findNativeIssueLink([LINK, second], JIRA_URL)).toThrow( - "--integration" + "--integration", ); expect(findNativeIssueLink([LINK, second], JIRA_URL, "20")).toBe(second); }); @@ -260,14 +260,17 @@ describe("findNativeIssueLink", () => { "https://github.com/owner/repo/pull/8", "https://github.com/owner/other/pull/7", "https://other.example.com/owner/repo/pull/7", - ])("distinguishes GitHub PR numbers, repositories and hosts: %s", (target) => { - const link = { - ...LINK, - provider: "github", - url: "https://github.com/owner/repo/pull/7", - }; - expect(findNativeIssueLink([link], target)).toBeUndefined(); - }); + ])( + "distinguishes GitHub PR numbers, repositories and hosts: %s", + (target) => { + const link = { + ...LINK, + provider: "github", + url: "https://github.com/owner/repo/pull/7", + }; + expect(findNativeIssueLink([link], target)).toBeUndefined(); + }, + ); test("never equates URLs just because neither identifies an issue", () => { const link = { @@ -276,10 +279,10 @@ describe("findNativeIssueLink", () => { url: "https://github.com/owner/repo/commit/abc", }; expect( - findNativeIssueLink([link], "https://github.com/owner/repo/commit/def") + findNativeIssueLink([link], "https://github.com/owner/repo/commit/def"), ).toBeUndefined(); expect( - findNativeIssueLink([LINK], "https://other.example.com/browse/PROJ-7") + findNativeIssueLink([LINK], "https://other.example.com/browse/PROJ-7"), ).toBeUndefined(); }); }); @@ -299,7 +302,7 @@ describe("native link API", () => { }); function mockApi( - respond: (request: Request) => Response | Promise + respond: (request: Request) => Response | Promise, ): Request[] { const requests: Request[] = []; globalThis.fetch = mockFetch(async (input, init) => { @@ -320,7 +323,7 @@ describe("native link API", () => { expect(await request.json()).toEqual({ externalIssue: submitted }); return Response.json( { ...LINK, id: 1234, integrationId: 10 }, - { status: 201 } + { status: 201 }, ); } expect(url.pathname).toBe(INTEGRATIONS); @@ -370,40 +373,48 @@ describe("native link API", () => { expect(requests).toHaveLength(2); }); - test.each([ - 200, 201, - ])("uses backend HTTP %i even when preflight found a link", async (status) => { - mockApi((request) => - request.method === "GET" - ? json([integration({ externalIssues: [LINK] })]) - : Response.json({ ...LINK, id: 1234, integrationId: 10 }, { status }) - ); - const prepared = await resolveNativeIssueLink({ ...SOURCE, url: JIRA_URL }); - expect(prepared.existing).toEqual(LINK); - // A concurrent unlink can remove the association after preflight. - expect(await linkNativeIssue(prepared)).toEqual({ - link: LINK, - changed: status === 201, - }); - }); + test.each([200, 201])( + "uses backend HTTP %i even when preflight found a link", + async (status) => { + mockApi((request) => + request.method === "GET" + ? json([integration({ externalIssues: [LINK] })]) + : Response.json({ ...LINK, id: 1234, integrationId: 10 }, { status }), + ); + const prepared = await resolveNativeIssueLink({ + ...SOURCE, + url: JIRA_URL, + }); + expect(prepared.existing).toEqual(LINK); + // A concurrent unlink can remove the association after preflight. + expect(await linkNativeIssue(prepared)).toEqual({ + link: LINK, + changed: status === 201, + }); + }, + ); test.each([ { name: "empty 204", response: () => new Response(null, { status: 204 }) }, { name: "empty object", response: () => json({}) }, { name: "invalid numeric IDs", response: () => json(LINK) }, - ])("does not report success for an invalid mutation response: $name", async ({ - response, - }) => { - mockApi((request) => - request.method === "GET" ? json([integration()]) : response() - ); - const prepared = await resolveNativeIssueLink({ ...SOURCE, url: JIRA_URL }); - const mutation = linkNativeIssue(prepared); - await expect(mutation).rejects.toBeInstanceOf(ApiError); - await expect(mutation).rejects.toThrow( - "inspect the current links before retrying" - ); - }); + ])( + "does not report success for an invalid mutation response: $name", + async ({ response }) => { + mockApi((request) => + request.method === "GET" ? json([integration()]) : response(), + ); + const prepared = await resolveNativeIssueLink({ + ...SOURCE, + url: JIRA_URL, + }); + const mutation = linkNativeIssue(prepared); + await expect(mutation).rejects.toBeInstanceOf(ApiError); + await expect(mutation).rejects.toThrow( + "inspect the current links before retrying", + ); + }, + ); test("propagates the backend's rejection of an issue URL", async () => { const requests = mockApi((request) => @@ -413,24 +424,24 @@ describe("native link API", () => { ]) : Response.json( { detail: "Invalid provider reference" }, - { status: 400 } - ) + { status: 400 }, + ), ); await expect( resolveNativeIssueLink({ ...SOURCE, url: "https://github.com/owner/repo/commit/abcdef", - }).then(linkNativeIssue) + }).then(linkNativeIssue), ).rejects.toBeInstanceOf(ApiError); expect(requests.map((request) => request.method)).toEqual(["GET", "PUT"]); }); test("rejects an invalid integration page without linking", async () => { const requests = mockApi(() => - json([{ ...integration(), externalIssues: [{}] }]) + json([{ ...integration(), externalIssues: [{}] }]), ); await expect( - resolveNativeIssueLink({ ...SOURCE, url: JIRA_URL }) + resolveNativeIssueLink({ ...SOURCE, url: JIRA_URL }), ).rejects.toBeInstanceOf(ApiError); expect(requests.map((request) => request.method)).toEqual(["GET"]); }); @@ -441,7 +452,7 @@ describe("native link API", () => { integration({ externalIssues: [{ ...LINK, id: "999", url: "not a URL" }, LINK], }), - ]) + ]), ); const prepared = await resolveNativeIssueLink({ ...SOURCE, url: JIRA_URL }); expect(prepared.existing).toEqual(LINK); @@ -462,7 +473,7 @@ describe("native link API", () => { unlinkNativeIssueLink(SOURCE.orgSlug, SOURCE.issueId, { ...LINK, id: "9007199254740993", - }) + }), ).rejects.toThrow("safe positive integer"); expect(requests).toHaveLength(0); }); @@ -493,7 +504,7 @@ describe("native link API", () => { // The mutation returns GitHub's html_url; listing reconstructs /issues/N. return Response.json( { ...storedLink, id: 1234, integrationId: 10, url: pullUrl }, - { status: 201 } + { status: 201 }, ); } if (request.method === "DELETE") { @@ -527,7 +538,7 @@ describe("native link API", () => { expect( requests .filter((request) => request.method !== "GET") - .map((request) => request.method) + .map((request) => request.method), ).toEqual(["PUT", "DELETE"]); }); }); diff --git a/packages/cli/test/lib/api/issues.test.ts b/packages/cli/test/lib/api/issues.test.ts index 5bf192de8..4ceab3d75 100644 --- a/packages/cli/test/lib/api/issues.test.ts +++ b/packages/cli/test/lib/api/issues.test.ts @@ -50,7 +50,7 @@ describe("parseResolveSpec", () => { test("parses explicit @commit:@ as commit/explicit", () => { expect( - parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}getsentry/cli@abc123`) + parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}getsentry/cli@abc123`), ).toEqual({ kind: "commit", spec: { kind: "explicit", repository: "getsentry/cli", commit: "abc123" }, @@ -59,7 +59,7 @@ describe("parseResolveSpec", () => { test("explicit @commit splits on the LAST '@' (scoped repo names like @acme/web)", () => { expect( - parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}@acme/web@abc123`) + parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}@acme/web@abc123`), ).toEqual({ kind: "commit", spec: { kind: "explicit", repository: "@acme/web", commit: "abc123" }, @@ -68,19 +68,19 @@ describe("parseResolveSpec", () => { test("@commit:@ rejects missing SHA", () => { expect(() => - parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}getsentry/cli@`) + parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}getsentry/cli@`), ).toThrow(ValidationError); }); test("@commit:@ rejects missing repo", () => { expect(() => - parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}@abc123`) + parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}@abc123`), ).toThrow(ValidationError); }); test("@commit:@ rejects payload with no '@' separator", () => { expect(() => - parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}getsentry/cli`) + parseResolveSpec(`${RESOLVE_COMMIT_EXPLICIT_PREFIX}getsentry/cli`), ).toThrow(ValidationError); }); @@ -169,7 +169,7 @@ describe("mergeIssues", () => { test("rejects fewer than 2 group IDs", async () => { await expect(mergeIssues("test-org", ["1"])).rejects.toThrow( - ValidationError + ValidationError, ); await expect(mergeIssues("test-org", [])).rejects.toThrow(ValidationError); }); @@ -191,7 +191,7 @@ describe("mergeIssues", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }); @@ -213,11 +213,11 @@ describe("mergeIssues", () => { new Response(JSON.stringify(["Only error issues can be merged."]), { status: 400, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect( - mergeIssues("test-org", ["100", "200"]) + mergeIssues("test-org", ["100", "200"]), ).rejects.toBeInstanceOf(ApiError); }); @@ -229,14 +229,14 @@ describe("mergeIssues", () => { async () => new Response(null, { status: 204, - }) + }), ); await expect( - mergeIssues("test-org", ["100", "200"]) + mergeIssues("test-org", ["100", "200"]), ).rejects.toBeInstanceOf(ApiError); await expect(mergeIssues("test-org", ["100", "200"])).rejects.toThrow( - /no matching issues|out of scope/i + /no matching issues|out of scope/i, ); }); }); @@ -272,7 +272,7 @@ describe("mergeIssues: cross-origin legacy cache", () => { new Response(JSON.stringify({ id }), { status: 200, headers: { "content-type": "application/json" }, - }) + }), ); expect(await getCachedResponse("GET", legacyUrl(id), {})).toBeDefined(); } @@ -283,8 +283,8 @@ describe("mergeIssues: cross-origin legacy cache", () => { JSON.stringify({ merge: { parent: "100", children: ["200", "300"] }, }), - { status: 200, headers: { "Content-Type": "application/json" } } - ) + { status: 200, headers: { "Content-Type": "application/json" } }, + ), ); await mergeIssues("test-org", ["100", "200", "300"]); diff --git a/packages/cli/test/lib/api/logs.test.ts b/packages/cli/test/lib/api/logs.test.ts index c09351a3b..e029ec21e 100644 --- a/packages/cli/test/lib/api/logs.test.ts +++ b/packages/cli/test/lib/api/logs.test.ts @@ -37,7 +37,7 @@ function mockOk(body: unknown) { new Response(JSON.stringify(body), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); } @@ -90,7 +90,7 @@ describe("listLogs", () => { mockOk("Proxy error: upstream not found"); await expect(listLogs("test-org", "test-project")).rejects.toThrow( - ApiError + ApiError, ); try { @@ -107,7 +107,7 @@ describe("listLogs", () => { mockOk(null); await expect(listLogs("test-org", "test-project")).rejects.toThrow( - ApiError + ApiError, ); try { @@ -124,7 +124,7 @@ describe("listLogs", () => { mockOk({ wrong: "shape" }); await expect(listLogs("test-org", "test-project")).rejects.toThrow( - ApiError + ApiError, ); try { @@ -132,7 +132,7 @@ describe("listLogs", () => { } catch (error) { expect(error).toBeInstanceOf(ApiError); expect((error as ApiError).message).toContain( - "unexpected response format" + "unexpected response format", ); } }); @@ -270,7 +270,7 @@ describe("getLogs", () => { mockOk("502 Bad Gateway"); await expect( - getLogs("test-org", "test-project", ["log-001"]) + getLogs("test-org", "test-project", ["log-001"]), ).rejects.toThrow(ApiError); try { diff --git a/packages/cli/test/lib/api/monitors.test.ts b/packages/cli/test/lib/api/monitors.test.ts index 97a3098fb..80e3c822b 100644 --- a/packages/cli/test/lib/api/monitors.test.ts +++ b/packages/cli/test/lib/api/monitors.test.ts @@ -59,7 +59,7 @@ describe("SentryMonitorSchema", () => { expect(result.output.config?.schedule).toBe("0 0 * * *"); // unknown fields are preserved via passthrough expect((result.output as Record).isUpserting).toBe( - false + false, ); } }); diff --git a/packages/cli/test/lib/api/organizations-context.focused.test.ts b/packages/cli/test/lib/api/organizations-context.focused.test.ts index 5eddda392..ca0b66d63 100644 --- a/packages/cli/test/lib/api/organizations-context.focused.test.ts +++ b/packages/cli/test/lib/api/organizations-context.focused.test.ts @@ -89,7 +89,7 @@ describe("organization discovery credential context", () => { headers: { Link: '; rel="next"; results="true"; cursor="next"', }, - } + }, ); } return Response.json([ @@ -100,7 +100,7 @@ describe("organization discovery credential context", () => { links: { regionUrl: "https://de.sentry.io" }, }, ]); - } + }, ); const organizations = await listOrganizationsUncached(); @@ -116,15 +116,15 @@ describe("organization discovery credential context", () => { getOrgRegion( "page-two", "https://sentry.io", - identity("first-page-token") - ) + identity("first-page-token"), + ), ).toBe("https://de.sentry.io"); expect( getOrgRegion( "page-two", "https://sentry.io", - identity("concurrent-login-token") - ) + identity("concurrent-login-token"), + ), ).toBeUndefined(); }); @@ -151,7 +151,7 @@ describe("organization discovery credential context", () => { } requests.pages += 1; expect(request.headers.get("authorization")).toBe( - `Bearer rotated-access-${requests.refreshes}` + `Bearer rotated-access-${requests.refreshes}`, ); return Response.json( [ @@ -167,9 +167,9 @@ describe("organization discovery credential context", () => { Link: '; rel="next"; results="true"; cursor="next"', }, } - : undefined + : undefined, ); - } + }, ); const orgs = await listOrganizationsUncached(); @@ -186,10 +186,10 @@ describe("organization discovery credential context", () => { globalThis.fetch = vi.fn( async (input: RequestInfo | URL, init?: RequestInit) => { authorization.push( - new Request(input, init).headers.get("authorization") + new Request(input, init).headers.get("authorization"), ); return Response.json([]); - } + }, ); const pending = listOrganizations(); setAuthToken("second", undefined, undefined, { host: "https://sentry.io" }); @@ -212,11 +212,11 @@ describe("organization discovery credential context", () => { name: "Malformed Region", links: { regionUrl }, }, - ]) + ]), ); await listOrganizationsUncached(); expect( - getOrgRegion("malformed-region", "https://sentry.io", identity(token)) + getOrgRegion("malformed-region", "https://sentry.io", identity(token)), ).toBeUndefined(); }); @@ -237,7 +237,7 @@ describe("organization discovery credential context", () => { name: "Invalid Region", links: { regionUrl: "ftp://region.example.com" }, }, - ] + ], ); }); @@ -259,19 +259,19 @@ describe("organization discovery credential context", () => { "invalid-org", ]); expect( - getOrgRegion("invalid-org", "https://sentry.io", identity(token)) + getOrgRegion("invalid-org", "https://sentry.io", identity(token)), ).toBeUndefined(); expect( - getOrgRegion("valid-org", "https://sentry.io", identity(token)) + getOrgRegion("valid-org", "https://sentry.io", identity(token)), ).toBe("https://sentry.io"); expect( - getCachedOrganizations("https://sentry.io", identity(token)) + getCachedOrganizations("https://sentry.io", identity(token)), ).toEqual([]); expect( getCachedOrganizations( "https://sentry.io", - identity("another-token") - ).map((org) => org.slug) + identity("another-token"), + ).map((org) => org.slug), ).toEqual(["foreign-org"]); expect((await listOrganizations()).map((org) => org.slug)).toEqual([ "valid-org", @@ -289,13 +289,13 @@ describe("organization discovery credential context", () => { return Response.json( requests.count === 1 ? [{ id: "13", slug: "former-org", name: "Former" }] - : [] + : [], ); }); await listOrganizationsUncached(); expect( - getCachedOrganizations("https://sentry.io", identity(token)) + getCachedOrganizations("https://sentry.io", identity(token)), ).toHaveLength(1); await expect(listOrganizationsUncached()).resolves.toEqual([]); await expect(listOrganizations()).resolves.toEqual([]); @@ -324,9 +324,9 @@ describe("organization discovery credential context", () => { expect( getDatabase() .query( - "SELECT source_origin, response_origin, region_url FROM org_regions WHERE credential_identity = ? AND org_slug = ?" + "SELECT source_origin, response_origin, region_url FROM org_regions WHERE credential_identity = ? AND org_slug = ?", ) - .get(identity("redirect-provenance-token"), "redirected-org") + .get(identity("redirect-provenance-token"), "redirected-org"), ).toEqual({ source_origin: "https://sentry.io", response_origin: "https://de.sentry.io", @@ -346,7 +346,7 @@ describe("organization discovery credential context", () => { name: "Path Region", links: { regionUrl: "https://sentry.example.com/sentry/" }, }, - ]) + ]), ); await listOrganizationsUncached(); @@ -354,8 +354,8 @@ describe("organization discovery credential context", () => { getOrgRegion( "path-region-org", "https://sentry.example.com", - identity("path-region-token") - ) + identity("path-region-token"), + ), ).toBe("https://sentry.example.com/sentry"); }); @@ -365,7 +365,7 @@ describe("organization discovery credential context", () => { host: "https://sentry.example.com", }); globalThis.fetch = vi.fn(async () => - Response.json([{ id: "6", slug: "pathless-org", name: "Pathless" }]) + Response.json([{ id: "6", slug: "pathless-org", name: "Pathless" }]), ); await listOrganizationsUncached(); @@ -373,8 +373,8 @@ describe("organization discovery credential context", () => { getOrgRegion( "pathless-org", "https://sentry.example.com", - identity("pathless-region-token") - ) + identity("pathless-region-token"), + ), ).toBe("https://sentry.example.com/sentry"); }); @@ -392,7 +392,7 @@ describe("organization discovery credential context", () => { headers: { Link: '; rel="next"; results="true"; cursor="next"', }, - } + }, ); } if (requests.count <= 4) { @@ -404,14 +404,14 @@ describe("organization discovery credential context", () => { }); await expect(listOrganizationsUncached()).rejects.toThrow( - "Failed to list organizations" + "Failed to list organizations", ); expect( getOrgRegion( "partial-org", "https://sentry.io", - identity("partial-page-token") - ) + identity("partial-page-token"), + ), ).toBeUndefined(); expect((await listOrganizations()).map((org) => org.slug)).toEqual([ "complete-org", @@ -430,17 +430,17 @@ describe("organization discovery credential context", () => { Response.json([{ id: "9", slug: "cached-org", name: "Cached" }], { headers: { "Cache-Control": "public, max-age=300" }, }), - identity(token) + identity(token), ); globalThis.fetch = vi.fn(async () => - Response.json([{ id: "10", slug: "fresh-org", name: "Fresh" }]) + Response.json([{ id: "10", slug: "fresh-org", name: "Fresh" }]), ); const organizations = await listOrganizationsUncached(); expect(organizations.map((org) => org.slug)).toEqual(["fresh-org"]); expect(globalThis.fetch).toHaveBeenCalledTimes(1); expect( - getOrgRegion("fresh-org", "https://sentry.io", identity(token)) + getOrgRegion("fresh-org", "https://sentry.io", identity(token)), ).toBe("https://sentry.io"); }); }); diff --git a/packages/cli/test/lib/api/organizations.test.ts b/packages/cli/test/lib/api/organizations.test.ts index 98d5b5a2b..182768ee5 100644 --- a/packages/cli/test/lib/api/organizations.test.ts +++ b/packages/cli/test/lib/api/organizations.test.ts @@ -35,12 +35,12 @@ describe("listOrganizationsPage", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } - ) + }, + ), ); const { data: orgs } = await listOrganizationsPage( - "https://sentry.example.com" + "https://sentry.example.com", ); expect(orgs).toHaveLength(1); expect(orgs[0].slug).toBe("test-org"); @@ -52,11 +52,11 @@ describe("listOrganizationsPage", () => { new Response(JSON.stringify({}), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect( - listOrganizationsPage("https://sentry.example.com") + listOrganizationsPage("https://sentry.example.com"), ).rejects.toThrow(ApiError); try { @@ -75,11 +75,11 @@ describe("listOrganizationsPage", () => { new Response("", { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect( - listOrganizationsPage("https://sentry.example.com") + listOrganizationsPage("https://sentry.example.com"), ).rejects.toThrow(ApiError); }); }); diff --git a/packages/cli/test/lib/api/preprod-artifacts.test.ts b/packages/cli/test/lib/api/preprod-artifacts.test.ts index 87ff1bd58..165098c2c 100644 --- a/packages/cli/test/lib/api/preprod-artifacts.test.ts +++ b/packages/cli/test/lib/api/preprod-artifacts.test.ts @@ -78,13 +78,13 @@ import { describe("toBinaryDownloadUrl", () => { test("rewrites a plist manifest URL to fetch the ipa binary", () => { expect( - toBinaryDownloadUrl("https://us.sentry.io/dl/?response_format=plist") + toBinaryDownloadUrl("https://us.sentry.io/dl/?response_format=plist"), ).toBe("https://us.sentry.io/dl/?response_format=ipa"); }); test("leaves non-plist URLs unchanged", () => { expect( - toBinaryDownloadUrl("https://us.sentry.io/dl/?response_format=apk") + toBinaryDownloadUrl("https://us.sentry.io/dl/?response_format=apk"), ).toBe("https://us.sentry.io/dl/?response_format=apk"); }); }); @@ -98,7 +98,7 @@ describe("buildFormatFromUrl", () => { }); test("throws on an unrecognized format", () => { expect(() => buildFormatFromUrl("https://x/?response_format=zip")).toThrow( - ValidationError + ValidationError, ); }); }); @@ -121,7 +121,7 @@ describe("getBuildInstallDetails", () => { expect(lastCall?.[0]).toBe("https://us.sentry.io"); // Build id is URL-encoded into the path. expect(lastCall?.[1]).toBe( - "organizations/my-org/preprodartifacts/build%201/install-details/" + "organizations/my-org/preprodartifacts/build%201/install-details/", ); expect(lastCall?.[2]?.schema).toBeDefined(); }); @@ -142,14 +142,14 @@ describe("downloadBuildArtifact", () => { test("streams the body to disk and attaches auth for a same-origin URL", async () => { customFetchMock.mockResolvedValue( - new Response("FAKE-BINARY", { status: 200 }) + new Response("FAKE-BINARY", { status: 200 }), ); const dest = join(tmpDir, "out.ipa"); await downloadBuildArtifact( "https://us.sentry.io", "https://us.sentry.io/dl/?response_format=ipa", - dest + dest, ); expect(await readFile(dest, "utf8")).toBe("FAKE-BINARY"); @@ -166,7 +166,7 @@ describe("downloadBuildArtifact", () => { downloadBuildArtifact( "https://us.sentry.io", "https://us.sentry.io/dl/?response_format=ipa", - join(tmpDir, "out.ipa") + join(tmpDir, "out.ipa"), ), () => openSnapshotArchive("my-org", "snap-1"), ]) { @@ -186,7 +186,7 @@ describe("downloadBuildArtifact", () => { await downloadBuildArtifact( "https://us.sentry.io", "https://cdn.example.com/blob?response_format=ipa", - join(tmpDir, "o2.ipa") + join(tmpDir, "o2.ipa"), ); const init = customFetchMock.mock.calls.at(-1)?.[1] as { @@ -202,7 +202,7 @@ describe("downloadBuildArtifact", () => { await downloadBuildArtifact( "https://us.sentry.io", "https://us.sentry.io/dl/?response_format=ipa", - join(tmpDir, "o3.ipa") + join(tmpDir, "o3.ipa"), ); const init = customFetchMock.mock.calls.at(-1)?.[1] as { @@ -213,15 +213,15 @@ describe("downloadBuildArtifact", () => { test("throws ApiError on a non-2xx response", async () => { customFetchMock.mockResolvedValue( - new Response("nope", { status: 404, statusText: "Not Found" }) + new Response("nope", { status: 404, statusText: "Not Found" }), ); await expect( downloadBuildArtifact( "https://us.sentry.io", "https://us.sentry.io/dl/?response_format=ipa", - join(tmpDir, "o4.ipa") - ) + join(tmpDir, "o4.ipa"), + ), ).rejects.toThrow(ApiError); }); }); @@ -262,7 +262,7 @@ describe("uploadBuild", () => { expect(url).toBe("https://sentry.io/artifact/1"); const call = apiRequestToRegionMock.mock.calls.at(-1); expect(call?.[1]).toBe( - "projects/my-org/my-project/files/preprodartifacts/assemble/" + "projects/my-org/my-project/files/preprodartifacts/assemble/", ); const body = call?.[2]?.body as Record; expect(body.checksum).toEqual(expect.any(String)); @@ -294,7 +294,7 @@ describe("uploadBuild", () => { project: "my-project", contentPath, metadata: {}, - }) + }), ).resolves.toBe("https://sentry.io/artifact/2"); expect(uploadMissingChunksMock).toHaveBeenCalledTimes(1); }); @@ -355,7 +355,7 @@ describe("uploadBuild", () => { project: "my-project", contentPath, metadata: {}, - }) + }), ).rejects.toThrow(ApiError); }); @@ -371,7 +371,7 @@ describe("uploadBuild", () => { project: "my-project", contentPath, metadata: {}, - }) + }), ).rejects.toThrow(/no artifact URL/i); }); }); @@ -390,13 +390,13 @@ describe("snapshots", () => { safeParse(LatestBaseSnapshotSchema, { head_artifact_id: "art-1", image_count: 5, - }).success + }).success, ).toBe(true); expect( safeParse(LatestBaseSnapshotSchema, { headArtifactId: "art-1", imageCount: 5, - }).success + }).success, ).toBe(false); }); @@ -404,34 +404,34 @@ describe("snapshots", () => { { usecase: "preprod_snapshots", expectedUsecase: "preprod_snapshots" }, { usecase: "preprod", expectedUsecase: "preprod" }, { usecase: undefined, expectedUsecase: "preprod" }, - ])("fetchSnapshotsUploadOptions negotiates auto and parses $usecase as $expectedUsecase", async ({ - usecase, - expectedUsecase, - }) => { - apiRequestToRegionMock.mockImplementation( - async (_region, _path, { schema }) => ({ - data: parse(schema, { - objectstore: { - url: "https://os.example.com", - usecase, - scopes: [["org", "1"]], - authToken: "tok", - expirationPolicy: "ttl:30d", - }, + ])( + "fetchSnapshotsUploadOptions negotiates auto and parses $usecase as $expectedUsecase", + async ({ usecase, expectedUsecase }) => { + apiRequestToRegionMock.mockImplementation( + async (_region, _path, { schema }) => ({ + data: parse(schema, { + objectstore: { + url: "https://os.example.com", + usecase, + scopes: [["org", "1"]], + authToken: "tok", + expirationPolicy: "ttl:30d", + }, + }), }), - }) - ); - const opts = await fetchSnapshotsUploadOptions("my-org", "my-project"); - expect(opts.objectstore.url).toBe("https://os.example.com"); - expect(opts.objectstore.usecase).toBe(expectedUsecase); - const [region, endpoint, options] = - apiRequestToRegionMock.mock.calls.at(-1) ?? []; - expect(region).toBe("https://us.sentry.io"); - expect(endpoint).toBe( - "projects/my-org/my-project/preprodartifacts/snapshots/upload-options/" - ); - expect(options.params).toEqual({ usecase: "auto" }); - }); + ); + const opts = await fetchSnapshotsUploadOptions("my-org", "my-project"); + expect(opts.objectstore.url).toBe("https://os.example.com"); + expect(opts.objectstore.usecase).toBe(expectedUsecase); + const [region, endpoint, options] = + apiRequestToRegionMock.mock.calls.at(-1) ?? []; + expect(region).toBe("https://us.sentry.io"); + expect(endpoint).toBe( + "projects/my-org/my-project/preprodartifacts/snapshots/upload-options/", + ); + expect(options.params).toEqual({ usecase: "auto" }); + }, + ); test("createPreprodSnapshot POSTs the manifest and parses the response", async () => { apiRequestToRegionMock.mockResolvedValue({ @@ -444,7 +444,7 @@ describe("snapshots", () => { const [, endpoint, options] = apiRequestToRegionMock.mock.calls.at(-1) ?? []; expect(endpoint).toBe( - "projects/my-org/my-project/preprodartifacts/snapshots/" + "projects/my-org/my-project/preprodartifacts/snapshots/", ); expect(options.method).toBe("POST"); expect(options.body).toBe(manifest); @@ -461,7 +461,7 @@ describe("snapshots", () => { getLatestBaseSnapshot("my-org", "my-app", { branch: "main", project: "proj-1", - }) + }), ).resolves.toEqual({ headArtifactId: "art-1", imageCount: 5 }); // app_id + optional branch/project go through as query params. const params = apiRequestToRegionMock.mock.calls.at(-1)?.[2]?.params; @@ -474,10 +474,10 @@ describe("snapshots", () => { test("getLatestBaseSnapshot returns null on 404", async () => { apiRequestToRegionMock.mockRejectedValue( - new ApiError("not found", 404, "", "endpoint") + new ApiError("not found", 404, "", "endpoint"), ); await expect( - getLatestBaseSnapshot("my-org", "missing") + getLatestBaseSnapshot("my-org", "missing"), ).resolves.toBeNull(); }); @@ -511,12 +511,12 @@ describe("snapshots", () => { customFetchMock.mockResolvedValue(response); await expect(openSnapshotArchive("my-org", "snap-1")).resolves.toBe( - response + response, ); // Auth token attached; URL targets the region archive endpoint. const [url, init] = customFetchMock.mock.calls.at(-1) ?? []; expect(url).toContain( - "/preprodartifacts/snapshots/snap-1/archive/?download" + "/preprodartifacts/snapshots/snap-1/archive/?download", ); expect(init?.headers?.Authorization).toBe("Bearer secret-token"); }); @@ -528,7 +528,7 @@ describe("snapshots", () => { statusText: "Server Error", }); await expect(openSnapshotArchive("my-org", "snap-1")).rejects.toThrow( - ApiError + ApiError, ); }); }); diff --git a/packages/cli/test/lib/api/proguard.test.ts b/packages/cli/test/lib/api/proguard.test.ts index a945428f8..ed2988e8f 100644 --- a/packages/cli/test/lib/api/proguard.test.ts +++ b/packages/cli/test/lib/api/proguard.test.ts @@ -126,7 +126,7 @@ describe("uploadProguardMappings", () => { response[checksum] = { state: "ok" }; } return { data: response } as never; - } + }, ); await uploadProguardMappings({ @@ -144,7 +144,7 @@ describe("uploadProguardMappings", () => { const [entry] = Object.values(assembleBody); // Leading slash required for the server to recognize the DIF as proguard. expect(entry?.name).toBe( - "/proguard/5db7294d-87fc-5726-a5c0-4a90679657a5.txt" + "/proguard/5db7294d-87fc-5726-a5c0-4a90679657a5.txt", ); }); }); diff --git a/packages/cli/test/lib/api/project-dsns.test.ts b/packages/cli/test/lib/api/project-dsns.test.ts index ac35a1c88..2b76ff4d3 100644 --- a/packages/cli/test/lib/api/project-dsns.test.ts +++ b/packages/cli/test/lib/api/project-dsns.test.ts @@ -115,7 +115,7 @@ describe.each([ projectKey(), { ...projectKey(1), isActive: false, dateCreated: null }, { ...projectKey(2), projectId: 7, dsn: { public: BACKEND_DSN } }, - ]) + ]), ); const result = await list(); @@ -139,7 +139,7 @@ describe.each([ ...(organization ? { project: index === 2 ? "backend" : "frontend" } : {}), - })) + })), ); expect(projectIds.sort()).toEqual(organization ? ["42", "7"] : []); }); @@ -152,11 +152,11 @@ describe.each([ const offset = Number(url.searchParams.get("cursor")?.split(":")[1] ?? 0); const count = Math.min( Number(url.searchParams.get("per_page")), - 200 - offset + 200 - offset, ); return keysResponse( Array.from({ length: count }, (_, index) => projectKey(offset + index)), - offset + count < 200 ? `0:${offset + count}:0` : undefined + offset + count < 200 ? `0:${offset + count}:0` : undefined, ); }); @@ -170,7 +170,7 @@ describe.each([ requests.map(({ url }) => { const query = new URL(url).searchParams; return [query.get("per_page"), query.get("cursor")]; - }) + }), ).toEqual([ ["100", "0:25:0"], ["50", "0:125:0"], @@ -178,7 +178,7 @@ describe.each([ ]); expect(second.nextCursor).toBeUndefined(); expect([...first.data, ...second.data].map((key) => key.name)).toEqual( - Array.from({ length: 175 }, (_, index) => `Key ${index + 25}`) + Array.from({ length: 175 }, (_, index) => `Key ${index + 25}`), ); }); }); @@ -197,6 +197,6 @@ test("org metadata lookup failures surface instead of dropping keys or exposing }); await expect(listOrganizationDsns("test-org")).rejects.toBeInstanceOf( - ApiError + ApiError, ); }); diff --git a/packages/cli/test/lib/api/projects.test.ts b/packages/cli/test/lib/api/projects.test.ts index 9b7f12ffa..ea512ce58 100644 --- a/packages/cli/test/lib/api/projects.test.ts +++ b/packages/cli/test/lib/api/projects.test.ts @@ -7,7 +7,7 @@ * re-scan files and hit the API. */ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { @@ -37,17 +37,17 @@ vi.mock("../../../src/lib/db/project-cache.js", async (importOriginal) => { typeof import("../../../src/lib/db/project-cache.js") >(); projectCacheMocks.cacheProjectsForOrg.mockImplementation( - actual.cacheProjectsForOrg + actual.cacheProjectsForOrg, ); projectCacheMocks.setCachedProjectByDsnKey.mockImplementation( - actual.setCachedProjectByDsnKey + actual.setCachedProjectByDsnKey, ); projectCacheMocks.restoreActual = () => { projectCacheMocks.cacheProjectsForOrg.mockImplementation( - actual.cacheProjectsForOrg + actual.cacheProjectsForOrg, ); projectCacheMocks.setCachedProjectByDsnKey.mockImplementation( - actual.setCachedProjectByDsnKey + actual.setCachedProjectByDsnKey, ); }; return { @@ -166,7 +166,7 @@ function mockCreateAndKeysFlow(options?: { } throw new Error( - `Unexpected fetch call #${callIndex}: ${req.method} ${req.url}` + `Unexpected fetch call #${callIndex}: ${req.method} ${req.url}`, ); }); } @@ -265,7 +265,7 @@ describe("createProjectWithDsn", () => { } throw new Error( - `Unexpected fetch call #${callIndex}: ${req.method} ${req.url}` + `Unexpected fetch call #${callIndex}: ${req.method} ${req.url}`, ); }); @@ -329,7 +329,7 @@ describe("listProjects cache seeding", () => { new Response(JSON.stringify([SAMPLE_PROJECT]), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); projectCacheMocks.cacheProjectsForOrg.mockImplementation(() => { throw new Error("disk full"); diff --git a/packages/cli/test/lib/api/releases.test.ts b/packages/cli/test/lib/api/releases.test.ts index 831e644d0..7b2f772c3 100644 --- a/packages/cli/test/lib/api/releases.test.ts +++ b/packages/cli/test/lib/api/releases.test.ts @@ -302,7 +302,7 @@ describe("setCommitsAuto", () => { JSON.stringify({ lastCommit: { id: "prev000000000000000000000000000000000000" }, }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -340,11 +340,11 @@ describe("setCommitsAuto", () => { new Response(JSON.stringify([]), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect(setCommitsAuto("test-org", "1.0.0", "/tmp")).rejects.toThrow( - /No repository integrations/ + /No repository integrations/, ); }); @@ -352,39 +352,39 @@ describe("setCommitsAuto", () => { { body: { ok: true }, status: 200 }, { body: "unexpected response", status: 202 }, { body: null, status: 200 }, - ])("rejects a non-array repository response (%j) before updating the release", async ({ - body, - status, - }) => { - const requests: { method: string; pathname: string }[] = []; - globalThis.fetch = mockFetch(async (input, init) => { - const request = new Request(input!, init); - requests.push({ - method: request.method, - pathname: new URL(request.url).pathname, + ])( + "rejects a non-array repository response (%j) before updating the release", + async ({ body, status }) => { + const requests: { method: string; pathname: string }[] = []; + globalThis.fetch = mockFetch(async (input, init) => { + const request = new Request(input!, init); + requests.push({ + method: request.method, + pathname: new URL(request.url).pathname, + }); + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); }); - return new Response(JSON.stringify(body), { + + const error = await setCommitsAuto("test-org", "1.0.0", "/tmp").catch( + (caught: unknown) => caught, + ); + expect(error).toMatchObject({ + name: "ApiError", + exitCode: EXIT.API, + message: "Failed to list repositories: unexpected response format", status, - headers: { "Content-Type": "application/json" }, + endpoint: "/api/0/organizations/test-org/repos/", }); - }); - - const error = await setCommitsAuto("test-org", "1.0.0", "/tmp").catch( - (caught: unknown) => caught - ); - expect(error).toMatchObject({ - name: "ApiError", - exitCode: EXIT.API, - message: "Failed to list repositories: unexpected response format", - status, - endpoint: "/api/0/organizations/test-org/repos/", - }); - expect(isUserError(error)).toBe(false); - expect(classifySilenced(error)).toBeNull(); - expect(requests).toEqual([ - { method: "GET", pathname: "/api/0/organizations/test-org/repos/" }, - ]); - }); + expect(isUserError(error)).toBe(false); + expect(classifySilenced(error)).toBeNull(); + expect(requests).toEqual([ + { method: "GET", pathname: "/api/0/organizations/test-org/repos/" }, + ]); + }, + ); test("throws ValidationError when no repo matches local remote", async () => { const otherRepo = { ...SAMPLE_REPO, name: "getsentry/sentry" }; @@ -393,11 +393,11 @@ describe("setCommitsAuto", () => { new Response(JSON.stringify([otherRepo]), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect(setCommitsAuto("test-org", "1.0.0", "/tmp")).rejects.toThrow( - /No Sentry repository matching/ + /No Sentry repository matching/, ); }); @@ -457,7 +457,7 @@ describe("setCommitsAuto", () => { // rendered via format() — no stack trace) with actionable guidance, and // keep field "repository" so default mode can fall back to local git. await expect( - setCommitsAuto("test-org", "1.0.0", "/tmp") + setCommitsAuto("test-org", "1.0.0", "/tmp"), ).rejects.toMatchObject({ name: "ValidationError", field: "repository", @@ -465,10 +465,10 @@ describe("setCommitsAuto", () => { }); await expect(setCommitsAuto("test-org", "1.0.0", "/tmp")).rejects.toThrow( - /Could not determine the repository from the local git remote/ + /Could not determine the repository from the local git remote/, ); await expect(setCommitsAuto("test-org", "1.0.0", "/tmp")).rejects.toThrow( - /--local/ + /--local/, ); }); }); @@ -542,7 +542,7 @@ describe("listReleasesPaginated", () => { "Content-Type": "application/json", link: linkHeader("abc:0:0", false), }, - }) + }), ); const result = await listReleasesPaginated("test-org"); diff --git a/packages/cli/test/lib/api/replays.test.ts b/packages/cli/test/lib/api/replays.test.ts index 310949272..ab6fa75d8 100644 --- a/packages/cli/test/lib/api/replays.test.ts +++ b/packages/cli/test/lib/api/replays.test.ts @@ -34,7 +34,7 @@ function replayRow(id = REPLAY_ID) { function recordingSegmentsResponse( body: unknown, - nextCursor?: string + nextCursor?: string, ): Response { const headers: Record = { "Content-Type": "application/json", @@ -139,12 +139,12 @@ describe("listReplays", () => { callIndex === 0 ? { data: Array.from({ length: 100 }, (_, index) => - replayRow(index.toString(16).padStart(32, "a").slice(-32)) + replayRow(index.toString(16).padStart(32, "a").slice(-32)), ), } : { data: Array.from({ length: 50 }, (_, index) => - replayRow(index.toString(16).padStart(32, "b").slice(-32)) + replayRow(index.toString(16).padStart(32, "b").slice(-32)), ), }; const headers = @@ -201,7 +201,7 @@ describe("getReplay", () => { const replay = await getReplay("test-org", REPLAY_ID); expect(capturedUrl).toContain( - `/api/0/organizations/test-org/replays/${REPLAY_ID}/` + `/api/0/organizations/test-org/replays/${REPLAY_ID}/`, ); expect(replay.id).toBe(REPLAY_ID); expect(replay.count_errors).toBe(2); @@ -227,8 +227,8 @@ describe("getReplay", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } - ) + }, + ), ); const replay = await getReplay("test-org", REPLAY_ID); @@ -279,7 +279,7 @@ describe("resolveReplay", () => { expect(replay.id).toBe(REPLAY_ID); expect(capturedUrls).toHaveLength(1); expect(capturedUrls[0]).toContain( - `/api/0/organizations/test-org/replays/${REPLAY_ID}/` + `/api/0/organizations/test-org/replays/${REPLAY_ID}/`, ); }); @@ -312,7 +312,7 @@ describe("resolveReplay", () => { const listUrl = new URL(capturedUrls[1]!); expect(listUrl.pathname).toContain( - "/api/0/organizations/test-org/replays/" + "/api/0/organizations/test-org/replays/", ); expect(listUrl.searchParams.get("query")).toBe(`trace:${TRACE_ID}`); expect(listUrl.searchParams.get("statsPeriod")).toBe("90d"); @@ -394,12 +394,12 @@ describe("getReplayRecordingSegments", () => { const segments = await getReplayRecordingSegments( "test-org", "42", - REPLAY_ID + REPLAY_ID, ); const url = new URL(capturedUrl); expect(url.pathname).toContain( - `/api/0/projects/test-org/42/replays/${REPLAY_ID}/recording-segments/` + `/api/0/projects/test-org/42/replays/${REPLAY_ID}/recording-segments/`, ); expect(url.searchParams.get("download")).toBeNull(); expect(url.searchParams.get("per_page")).toBe("100"); @@ -408,11 +408,11 @@ describe("getReplayRecordingSegments", () => { test("rejects non-object recording events at the API boundary", async () => { globalThis.fetch = mockFetch(async () => - recordingSegmentsResponse([[null]]) + recordingSegmentsResponse([[null]]), ); await expect( - getReplayRecordingSegments("test-org", "42", REPLAY_ID) + getReplayRecordingSegments("test-org", "42", REPLAY_ID), ).rejects.toBeInstanceOf(ApiError); }); @@ -438,7 +438,7 @@ describe("getReplayRecordingSegments", () => { "test-org", "42", REPLAY_ID, - { expectedSegments: 101 } + { expectedSegments: 101 }, ); expect(segments).toHaveLength(101); @@ -472,7 +472,7 @@ describe("getReplayRecordingSegments", () => { const segments = await getReplayRecordingSegments( "test-org", "42", - REPLAY_ID + REPLAY_ID, ); expect(segments).toHaveLength(MAX_PAGINATION_PAGES); @@ -480,7 +480,7 @@ describe("getReplayRecordingSegments", () => { const finalUrl = new URL(capturedUrls.at(-1)!); expect(finalUrl.searchParams.get("cursor")).toBe( - `0:${(MAX_PAGINATION_PAGES - 1) * 100}:0` + `0:${(MAX_PAGINATION_PAGES - 1) * 100}:0`, ); const stderr = stderrSpy.mock.calls.map((call) => String(call[0])).join(""); @@ -518,7 +518,7 @@ describe("listReplayIdsForIssue", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }); @@ -526,7 +526,7 @@ describe("listReplayIdsForIssue", () => { const url = new URL(capturedUrl); expect(url.pathname).toContain( - "/api/0/organizations/test-org/replay-count/" + "/api/0/organizations/test-org/replay-count/", ); expect(url.searchParams.get("returnIds")).toBe("true"); expect(url.searchParams.get("query")).toBe("issue.id:[12345]"); diff --git a/packages/cli/test/lib/api/repositories.test.ts b/packages/cli/test/lib/api/repositories.test.ts index 46471389c..7fa6688f0 100644 --- a/packages/cli/test/lib/api/repositories.test.ts +++ b/packages/cli/test/lib/api/repositories.test.ts @@ -10,7 +10,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { listRepositoriesCached } from "../../../src/lib/api/repositories.js"; import { setAuthToken } from "../../../src/lib/db/auth.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as repoCache from "../../../src/lib/db/repo-cache.js"; import type { SentryRepository } from "../../../src/types/sentry.js"; import { mockFetch, useTestConfigDir } from "../../helpers.js"; @@ -74,7 +74,7 @@ describe("listRepositoriesCached", () => { /* succeed silently */ }); globalThis.fetch = mockFetch(async () => - repoApiResponse([{ name: "owner/fresh" }]) + repoApiResponse([{ name: "owner/fresh" }]), ); const result = await listRepositoriesCached("my-org"); @@ -82,7 +82,9 @@ describe("listRepositoriesCached", () => { expect(result[0]?.name).toBe("owner/fresh"); expect(setSpy).toHaveBeenCalledWith( "my-org", - expect.arrayContaining([expect.objectContaining({ name: "owner/fresh" })]) + expect.arrayContaining([ + expect.objectContaining({ name: "owner/fresh" }), + ]), ); }); @@ -93,7 +95,7 @@ describe("listRepositoriesCached", () => { throw new Error("attempt to write a readonly database"); }); globalThis.fetch = mockFetch(async () => - repoApiResponse([{ name: "owner/primary-succeeded" }]) + repoApiResponse([{ name: "owner/primary-succeeded" }]), ); // The API fetch succeeded, so the command should still receive the diff --git a/packages/cli/test/lib/api/shared-issues.test.ts b/packages/cli/test/lib/api/shared-issues.test.ts index f11c8abe7..745db1433 100644 --- a/packages/cli/test/lib/api/shared-issues.test.ts +++ b/packages/cli/test/lib/api/shared-issues.test.ts @@ -43,12 +43,12 @@ describe("getSharedIssue", () => { const result = await getSharedIssue( "https://sentry.io", "example org", - "share/id" + "share/id", ); expect(result.id).toBe("12345"); expect(request?.url).toBe( - "https://sentry.io/api/0/organizations/example%20org/shared/issues/share%2Fid/" + "https://sentry.io/api/0/organizations/example%20org/shared/issues/share%2Fid/", ); expect(request?.method).toBe("GET"); expect(request?.headers.has("Authorization")).toBe(false); @@ -65,7 +65,7 @@ describe("getSharedIssue", () => { globalThis.fetch = mockFetch(async () => Response.json(body)); await expect( - getSharedIssue("https://sentry.io", "example-org", "share-id") + getSharedIssue("https://sentry.io", "example-org", "share-id"), ).rejects.toBeInstanceOf(ApiError); }); @@ -73,7 +73,7 @@ describe("getSharedIssue", () => { globalThis.fetch = mockFetch(async () => new Response("not JSON")); await expect( - getSharedIssue("https://sentry.io", "example-org", "share-id") + getSharedIssue("https://sentry.io", "example-org", "share-id"), ).rejects.toMatchObject({ name: "ApiError", message: expect.stringContaining("invalid JSON"), @@ -89,12 +89,12 @@ describe("getSharedIssue", () => { start(controller) { controller.error(error); }, - }) - ) + }), + ), ); await expect( - getSharedIssue("https://sentry.io", "example-org", "share-id") + getSharedIssue("https://sentry.io", "example-org", "share-id"), ).rejects.toBe(error); }); @@ -105,7 +105,7 @@ describe("getSharedIssue", () => { globalThis.fetch = mockFetch(async () => new Response("", { status })); await expect( - getSharedIssue("https://sentry.io", "example-org", "share-id") + getSharedIssue("https://sentry.io", "example-org", "share-id"), ).rejects.toMatchObject({ name: "ApiError", status, message }); }); }); diff --git a/packages/cli/test/lib/api/sourcemaps.test.ts b/packages/cli/test/lib/api/sourcemaps.test.ts index 3281e9c9b..aba4c2d2b 100644 --- a/packages/cli/test/lib/api/sourcemaps.test.ts +++ b/packages/cli/test/lib/api/sourcemaps.test.ts @@ -35,15 +35,14 @@ describe("resolveUploadWait", () => { }); }); - test.each([ - Number.NaN, - 0, - -5, - ])("rejects a non-positive --wait-for (%s)", (value) => { - expect(() => resolveUploadWait({ "wait-for": value })).toThrow( - /positive number of seconds/ - ); - }); + test.each([Number.NaN, 0, -5])( + "rejects a non-positive --wait-for (%s)", + (value) => { + expect(() => resolveUploadWait({ "wait-for": value })).toThrow( + /positive number of seconds/, + ); + }, + ); }); describe("pickUploadEncoding", () => { @@ -170,7 +169,7 @@ describe("buildArtifactBundle", () => { test("uses in-memory content and never reads disk for inline maps", async () => { const mapBytes = Buffer.from( - JSON.stringify({ version: 3, sources: [], mappings: "AAAA" }) + JSON.stringify({ version: 3, sources: [], mappings: "AAAA" }), ); const out = join(tmpDir, "bundle-inline.zip"); // `path` points at a nonexistent file — must not be read because @@ -186,7 +185,7 @@ describe("buildArtifactBundle", () => { url: "~/app.js.map", }, ], - { org: "o", project: "p", compression: "stored" } + { org: "o", project: "p", compression: "stored" }, ); // Build succeeded (no ENOENT) and the STORED archive contains the bytes. diff --git a/packages/cli/test/lib/api/status-page.test.ts b/packages/cli/test/lib/api/status-page.test.ts index 36c2d5c41..8d6a1454a 100644 --- a/packages/cli/test/lib/api/status-page.test.ts +++ b/packages/cli/test/lib/api/status-page.test.ts @@ -19,7 +19,7 @@ afterEach(() => { function summaryResponse( indicator: string, description: string, - pageUrl: string + pageUrl: string, ): Response { return Response.json({ page: { url: pageUrl }, @@ -34,8 +34,8 @@ test("probes summary.json first and uses it when the target is Statuspage", asyn summaryResponse( "none", "All Systems Operational", - "https://status.sentry.io" - ) + "https://status.sentry.io", + ), ); const status = await fetchSentryStatus(); @@ -52,8 +52,8 @@ test("uses summary.json for an arbitrary host that responds like Statuspage", as summaryResponse( "minor", "Minor Service Outage", - "https://sentry.example.com" - ) + "https://sentry.example.com", + ), ); const status = await fetchSentryStatus("https://sentry.example.com"); @@ -97,7 +97,7 @@ test("falls back to /_health/ when summary.json returns non-Statuspage JSON", as test("falls back to /_health/ when summary.json is not JSON", async () => { customFetchMock .mockResolvedValueOnce( - new Response("not json", { status: 200 }) + new Response("not json", { status: 200 }), ) .mockResolvedValueOnce(new Response("", { status: 200, statusText: "OK" })); @@ -111,7 +111,7 @@ test("reports major when the health fallback returns non-2xx", async () => { customFetchMock .mockResolvedValueOnce(new Response("Not Found", { status: 404 })) .mockResolvedValueOnce( - new Response("", { status: 503, statusText: "Service Unavailable" }) + new Response("", { status: 503, statusText: "Service Unavailable" }), ); const status = await fetchSentryStatus("https://self.sentry.local"); diff --git a/packages/cli/test/lib/api/traces.test.ts b/packages/cli/test/lib/api/traces.test.ts index 6bde9ed08..432ca06eb 100644 --- a/packages/cli/test/lib/api/traces.test.ts +++ b/packages/cli/test/lib/api/traces.test.ts @@ -53,7 +53,7 @@ describe("listTransactions", () => { * Each call to fetch returns the next response in the queue. */ function mockSequential( - responses: Array<{ body: unknown; headers?: Record }> + responses: Array<{ body: unknown; headers?: Record }>, ): { getCapturedUrls: () => string[] } { const capturedUrls: string[] = []; let callIndex = 0; @@ -189,10 +189,10 @@ describe("listTransactions", () => { }); expect(capturedUrl).toContain( - `start=${encodeURIComponent("2024-01-15T00:00:00Z")}` + `start=${encodeURIComponent("2024-01-15T00:00:00Z")}`, ); expect(capturedUrl).toContain( - `end=${encodeURIComponent("2024-01-16T00:00:00Z")}` + `end=${encodeURIComponent("2024-01-16T00:00:00Z")}`, ); expect(capturedUrl).not.toContain("statsPeriod="); }); @@ -271,7 +271,7 @@ describe("listTransactions", () => { await listTransactions("my-org", "my-project"); expect(decodeURIComponent(capturedUrl).replaceAll("+", " ")).toContain( - "query=is_transaction:true project:my-project" + "query=is_transaction:true project:my-project", ); // Should NOT appear as a separate project= param expect(capturedUrl).not.toMatch(/[?&]project=my-project/); @@ -302,7 +302,7 @@ describe("listTransactions", () => { { data: makeTxnRows(10), meta: TX_META }, { Link: `; rel="next"; results="true"; cursor="${cursor}"`, - } + }, ); const result = await listTransactions("my-org", "my-project", { @@ -317,7 +317,7 @@ describe("listTransactions", () => { { data: [], meta: TX_META }, { Link: `; rel="next"; results="false"; cursor=""`, - } + }, ); const result = await listTransactions("my-org", "my-project"); @@ -360,7 +360,7 @@ describe("listSpans", () => { } function mockSequential( - responses: Array<{ body: unknown; headers?: Record }> + responses: Array<{ body: unknown; headers?: Record }>, ): { getCapturedUrls: () => string[] } { const capturedUrls: string[] = []; let callIndex = 0; @@ -473,7 +473,7 @@ describe("listSpans", () => { expect(capturedUrl).toContain("project=-1"); // Should NOT have project:my-project in query expect(decodeURIComponent(capturedUrl)).not.toContain( - "project%3Amy-project" + "project%3Amy-project", ); }); @@ -569,7 +569,7 @@ describe("listSpans", () => { await listSpans("my-org", "my-project"); expect(capturedUrl).toContain( - `query=${encodeURIComponent("project:my-project")}` + `query=${encodeURIComponent("project:my-project")}`, ); // Should NOT appear as a separate project= param with the slug value expect(capturedUrl).not.toMatch(/[?&]project=my-project/); @@ -612,10 +612,10 @@ describe("listSpans", () => { }); expect(capturedUrl).toContain( - `start=${encodeURIComponent("2024-01-15T00:00:00Z")}` + `start=${encodeURIComponent("2024-01-15T00:00:00Z")}`, ); expect(capturedUrl).toContain( - `end=${encodeURIComponent("2024-01-16T00:00:00Z")}` + `end=${encodeURIComponent("2024-01-16T00:00:00Z")}`, ); expect(capturedUrl).not.toContain("statsPeriod="); }); @@ -634,7 +634,7 @@ describe("listSpans", () => { { data: makeSpanRows(10), meta: SPAN_META }, { Link: `; rel="next"; results="true"; cursor="${cursor}"`, - } + }, ); const result = await listSpans("my-org", "my-project", { limit: 10 }); @@ -647,7 +647,7 @@ describe("listSpans", () => { { data: [], meta: SPAN_META }, { Link: `; rel="next"; results="false"; cursor=""`, - } + }, ); const result = await listSpans("my-org", "my-project"); @@ -707,7 +707,7 @@ describe("getSpanDetails", () => { await getSpanDetails("my-org", "my-project", "span-id-abc", "trace-id-xyz"); expect(capturedUrl).toContain( - "/projects/my-org/my-project/trace-items/span-id-abc/" + "/projects/my-org/my-project/trace-items/span-id-abc/", ); expect(capturedParams.item_type).toBe("spans"); expect(capturedParams.trace_id).toBe("trace-id-xyz"); @@ -720,7 +720,7 @@ describe("getSpanDetails", () => { "my-org", "my-project", "span-id-abc", - "trace-id-xyz" + "trace-id-xyz", ); expect(result.itemId).toBe("abc123"); @@ -762,7 +762,7 @@ describe("fetchMultiSpanDetails", () => { timestamp: "2026-01-01T00:00:00Z", attributes: [], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); }); } @@ -774,7 +774,7 @@ describe("fetchMultiSpanDetails", () => { // project_slug — a request here would produce /projects/my-org//trace-items/… const details = await fetchMultiSpanDetails( [{ span_id: "span-no-project" }], - { org: "my-org", fallbackProject: "", traceId: "trace-xyz" } + { org: "my-org", fallbackProject: "", traceId: "trace-xyz" }, ); expect(details.size).toBe(0); @@ -792,7 +792,7 @@ describe("fetchMultiSpanDetails", () => { expect(requestedUrls).toHaveLength(1); expect(requestedUrls[0]).toContain( - "/projects/my-org/fallback-proj/trace-items/span-a/" + "/projects/my-org/fallback-proj/trace-items/span-a/", ); }); @@ -804,12 +804,12 @@ describe("fetchMultiSpanDetails", () => { { span_id: "span-no-project" }, { span_id: "span-b", project_slug: "proj-b" }, ], - { org: "my-org", fallbackProject: "", traceId: "trace-xyz" } + { org: "my-org", fallbackProject: "", traceId: "trace-xyz" }, ); expect(requestedUrls).toHaveLength(1); expect(requestedUrls[0]).toContain( - "/projects/my-org/proj-b/trace-items/span-b/" + "/projects/my-org/proj-b/trace-items/span-b/", ); expect(details.has("span-b")).toBe(true); }); @@ -896,16 +896,16 @@ describe("getDetailedTrace", () => { expect(result[0]!.span_id).toBe("span-1"); expect(getCapturedUrls()).toHaveLength(2); expect(new URL(getCapturedUrls()[0]!).searchParams.get("statsPeriod")).toBe( - "14d" + "14d", ); expect(new URL(getCapturedUrls()[1]!).searchParams.get("statsPeriod")).toBe( - "90d" + "90d", ); expect( - new URL(getCapturedUrls()[0]!).searchParams.get("timestamp") + new URL(getCapturedUrls()[0]!).searchParams.get("timestamp"), ).toBeNull(); expect( - new URL(getCapturedUrls()[1]!).searchParams.get("timestamp") + new URL(getCapturedUrls()[1]!).searchParams.get("timestamp"), ).toBeNull(); }); @@ -919,10 +919,10 @@ describe("getDetailedTrace", () => { expect(result).toHaveLength(0); expect(getCapturedUrls()).toHaveLength(1); expect(new URL(getCapturedUrls()[0]!).searchParams.get("timestamp")).toBe( - "1700000000" + "1700000000", ); expect( - new URL(getCapturedUrls()[0]!).searchParams.get("statsPeriod") + new URL(getCapturedUrls()[0]!).searchParams.get("statsPeriod"), ).toBeNull(); }); diff --git a/packages/cli/test/lib/arg-parsing.property.test.ts b/packages/cli/test/lib/arg-parsing.property.test.ts index a3c56e43e..b16c0e708 100644 --- a/packages/cli/test/lib/arg-parsing.property.test.ts +++ b/packages/cli/test/lib/arg-parsing.property.test.ts @@ -48,7 +48,7 @@ describe("parseIssueArg properties", () => { expect(result.id).toBe(input); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -61,7 +61,7 @@ describe("parseIssueArg properties", () => { expect(result.suffix).toBe(suffix.toUpperCase()); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -79,9 +79,9 @@ describe("parseIssueArg properties", () => { expect(result.project).toBe(project); expect(result.suffix).toBe(suffix.toUpperCase()); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -97,7 +97,7 @@ describe("parseIssueArg properties", () => { expect(result.numericId).toBe(numericId); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -116,7 +116,7 @@ describe("parseIssueArg properties", () => { expect(result.suffix).toBe(suffix.toUpperCase()); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -132,14 +132,14 @@ describe("parseIssueArg properties", () => { expect(result.suffix).toBe(suffix.toUpperCase()); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); test("alphanumeric input without dash/slash that isn't numeric returns 'suffix-only'", async () => { // Generate alphanumeric strings that contain at least one letter (not pure numeric) const alphanumericWithLetterArb = stringMatching( - /^[a-zA-Z][a-zA-Z0-9]{0,9}$/ + /^[a-zA-Z][a-zA-Z0-9]{0,9}$/, ); await fcAssert( @@ -151,7 +151,7 @@ describe("parseIssueArg properties", () => { expect(result.suffix).toBe(input.toUpperCase()); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -170,12 +170,12 @@ describe("parseIssueArg properties", () => { const validInputArb = oneof( numericIdArb, tuple(orgSlugArb, projectSlugArb, suffixArb).map( - ([o, p, s]) => `${o}/${p}-${s}` + ([o, p, s]) => `${o}/${p}-${s}`, ), tuple(orgSlugArb, numericIdArb).map(([o, n]) => `${o}/${n}`), tuple(orgSlugArb, suffixArb).map(([o, s]) => `${o}/${s}`), tuple(projectSlugArb, suffixArb).map(([p, s]) => `${p}-${s}`), - suffixArb + suffixArb, ); await fcAssert( @@ -187,7 +187,7 @@ describe("parseIssueArg properties", () => { // Some generated inputs may throw - that's expected for invalid formats } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -195,9 +195,9 @@ describe("parseIssueArg properties", () => { const inputArb = oneof( numericIdArb, tuple(orgSlugArb, projectSlugArb, suffixArb).map( - ([o, p, s]) => `${o}/${p}-${s}` + ([o, p, s]) => `${o}/${p}-${s}`, ), - suffixArb + suffixArb, ); await fcAssert( @@ -211,7 +211,7 @@ describe("parseIssueArg properties", () => { // String is immutable in JS, but this verifies no weird side effects expect(input).toBe(originalInput); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -229,9 +229,9 @@ describe("parseIssueArg: GitHub-style # separator properties (CLI-1G1)", () => { const hashForm = parseIssueArg(`${org}/${project}#${shortId}`); const slashForm = parseIssueArg(`${org}/${project}/${shortId}`); expect(hashForm).toEqual(slashForm); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -245,7 +245,7 @@ describe("parseIssueArg: GitHub-style # separator properties (CLI-1G1)", () => { suffix: suffix.toUpperCase(), }); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -255,9 +255,9 @@ describe("parseIssueArg: GitHub-style # separator properties (CLI-1G1)", () => { tuple(projectSlugArb, suffixArb, forbiddenCharArb), ([project, suffix, bad]) => { expect(() => parseIssueArg(`${project}#${suffix}${bad}x`)).toThrow(); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -271,7 +271,7 @@ describe("parseOrgProjectArg properties", () => { const result = parseOrgProjectArg(input); expect(result.type).toBe("auto-detect"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -287,7 +287,7 @@ describe("parseOrgProjectArg properties", () => { expect(result.project).toBe(project); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -302,7 +302,7 @@ describe("parseOrgProjectArg properties", () => { expect(result.org).toBe(org); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -316,7 +316,7 @@ describe("parseOrgProjectArg properties", () => { expect(result.projectSlug).toBe(project); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -331,7 +331,7 @@ describe("parseOrgProjectArg properties", () => { expect(result.projectSlug).toBe(project); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -343,7 +343,7 @@ describe("parseOrgProjectArg properties", () => { tuple(orgSlugArb, projectSlugArb).map(([o, p]) => `${o}/${p}`), orgSlugArb.map((o) => `${o}/`), projectSlugArb, - projectSlugArb.map((p) => `/${p}`) + projectSlugArb.map((p) => `/${p}`), ); await fcAssert( @@ -355,7 +355,7 @@ describe("parseOrgProjectArg properties", () => { // Some inputs may throw - that's expected } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -372,9 +372,9 @@ describe("parseOrgProjectArg properties", () => { expect(result.org).toBe(org); expect(result.project).toBe(project); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -405,16 +405,16 @@ describe("parseIssueArg and parseOrgProjectArg consistency", () => { expect(issueResult.org).toBe(orgProjectResult.org); expect(issueResult.project).toBe(orgProjectResult.project); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); /** Generates all-lowercase slug-like strings with at least one dash */ const lowercaseSlugWithDashArb = stringMatching( - /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)+$/ + /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)+$/, ); /** Generates alphanumeric strings without dashes */ @@ -436,7 +436,7 @@ describe("normalizeSlug properties (no-op)", () => { const result = normalizeSlug(input); expect(result.slug).toBe(input); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -446,7 +446,7 @@ describe("normalizeSlug properties (no-op)", () => { const result = normalizeSlug(input); expect(result.normalized).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -458,7 +458,7 @@ describe("normalizeSlug properties (no-op)", () => { expect(second.slug).toBe(first.slug); expect(second.normalized).toBe(first.normalized); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -469,7 +469,7 @@ describe("looksLikeIssueShortId properties", () => { property(lowercaseSlugWithDashArb, (input) => { expect(looksLikeIssueShortId(input)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -478,7 +478,7 @@ describe("looksLikeIssueShortId properties", () => { property(noDashAlphanumArb, (input) => { expect(looksLikeIssueShortId(input)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -487,7 +487,7 @@ describe("looksLikeIssueShortId properties", () => { property(withSlashArb, (input) => { expect(looksLikeIssueShortId(input)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -496,11 +496,11 @@ describe("looksLikeIssueShortId properties", () => { property(lowercaseSlugWithDashArb, (input) => { if (input.split("-").length === 2) { expect(looksLikeIssueShortId(input, { ignoreCase: true })).toBe( - false + false, ); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -513,11 +513,11 @@ describe("looksLikeIssueShortId properties", () => { const hasLetter = /[a-z]/.test(lastPart); if (parts.length >= 3 && !(hasDigit && hasLetter)) { expect(looksLikeIssueShortId(input, { ignoreCase: true })).toBe( - false + false, ); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -534,7 +534,7 @@ describe("looksLikeIssueShortId properties", () => { expect(looksLikeIssueShortId(input, { ignoreCase: true })).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -549,7 +549,7 @@ describe("parseSelector properties", () => { "@Most_Frequent", "@MOST_FREQUENT", "@mostfrequent", - "@most-frequent" + "@most-frequent", ); test("recognized selectors always return a canonical value", async () => { @@ -559,7 +559,7 @@ describe("parseSelector properties", () => { expect(result).toBeDefined(); expect(["@latest", "@most_frequent"]).toContain(result!); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -568,7 +568,7 @@ describe("parseSelector properties", () => { property(orgSlugArb, (input) => { expect(parseSelector(input)).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -582,7 +582,7 @@ describe("parseSelector properties", () => { expect(result.org).toBeUndefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -597,7 +597,7 @@ describe("parseSelector properties", () => { expect(result.org).toBe(org); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -608,7 +608,7 @@ describe("parseSelector properties", () => { const lower = parseSelector(input.toLowerCase()); expect(direct).toBe(lower); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -622,7 +622,7 @@ describe("detectSwappedViewArgs properties", () => { // withSlash first, noSlash second → correct → null expect(detectSwappedViewArgs(withSlash, noSlash)).toBeNull(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -631,7 +631,7 @@ describe("detectSwappedViewArgs properties", () => { property(tuple(noSlashArb, noSlashArb), ([a, b]) => { expect(detectSwappedViewArgs(a, b)).toBeNull(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -640,7 +640,7 @@ describe("detectSwappedViewArgs properties", () => { property(tuple(withSlashArb, withSlashArb), ([a, b]) => { expect(detectSwappedViewArgs(a, b)).toBeNull(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/arg-parsing.test.ts b/packages/cli/test/lib/arg-parsing.test.ts index c23f6680d..86afa9d73 100644 --- a/packages/cli/test/lib/arg-parsing.test.ts +++ b/packages/cli/test/lib/arg-parsing.test.ts @@ -74,7 +74,7 @@ describe("parseOrgProjectArg", () => { // Error case - verify specific message test("just slash throws error", () => { expect(() => parseOrgProjectArg("/")).toThrow( - 'Invalid format: "/" requires a project slug' + 'Invalid format: "/" requires a project slug', ); }); @@ -125,9 +125,8 @@ describe("parseOrgProjectArg", () => { savedSentryHost = process.env.SENTRY_HOST; delete process.env.SENTRY_URL; delete process.env.SENTRY_HOST; - const { resetEnvTokenHostForTesting } = await import( - "../../src/lib/env-token-host.js" - ); + const { resetEnvTokenHostForTesting } = + await import("../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); }); @@ -142,17 +141,16 @@ describe("parseOrgProjectArg", () => { } else { delete process.env.SENTRY_HOST; } - const { resetEnvTokenHostForTesting } = await import( - "../../src/lib/env-token-host.js" - ); + const { resetEnvTokenHostForTesting } = + await import("../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); }); test("issue URL returns org-all", () => { expect( parseOrgProjectArg( - "https://sentry.io/organizations/my-org/issues/12345/" - ) + "https://sentry.io/organizations/my-org/issues/12345/", + ), ).toEqual({ type: "org-all", org: "my-org", @@ -162,8 +160,8 @@ describe("parseOrgProjectArg", () => { test("project settings URL returns explicit", () => { expect( parseOrgProjectArg( - "https://sentry.io/settings/my-org/projects/backend/" - ) + "https://sentry.io/settings/my-org/projects/backend/", + ), ).toEqual({ type: "explicit", org: "my-org", @@ -173,7 +171,7 @@ describe("parseOrgProjectArg", () => { test("org-only URL returns org-all", () => { expect( - parseOrgProjectArg("https://sentry.io/organizations/my-org/") + parseOrgProjectArg("https://sentry.io/organizations/my-org/"), ).toEqual({ type: "org-all", org: "my-org", @@ -185,8 +183,8 @@ describe("parseOrgProjectArg", () => { process.env.SENTRY_HOST = "https://sentry.example.com"; expect( parseOrgProjectArg( - "https://sentry.example.com/organizations/acme-corp/issues/99/" - ) + "https://sentry.example.com/organizations/acme-corp/issues/99/", + ), ).toEqual({ type: "org-all", org: "acme-corp", @@ -197,8 +195,8 @@ describe("parseOrgProjectArg", () => { // No SENTRY_HOST set → env-token defaults to SaaS → mismatch on self-hosted URL. expect(() => parseOrgProjectArg( - "https://sentry.example.com/organizations/acme-corp/issues/99/" - ) + "https://sentry.example.com/organizations/acme-corp/issues/99/", + ), ).toThrow(/does not match|sentry auth login --url/); }); }); @@ -263,31 +261,31 @@ describe("parseOrgProjectArg", () => { describe("@-selector rejection", () => { test("@latest throws with redirect to issue view", () => { expect(() => parseOrgProjectArg("@latest")).toThrow( - "is an issue selector, not a project slug" + "is an issue selector, not a project slug", ); expect(() => parseOrgProjectArg("@latest")).toThrow( - "sentry issue view @latest" + "sentry issue view @latest", ); }); test("@most_frequent throws with redirect to issue view", () => { expect(() => parseOrgProjectArg("@most_frequent")).toThrow( - "is an issue selector, not a project slug" + "is an issue selector, not a project slug", ); expect(() => parseOrgProjectArg("@most_frequent")).toThrow( - "sentry issue view @most_frequent" + "sentry issue view @most_frequent", ); }); test("case-insensitive selector variants are rejected", () => { expect(() => parseOrgProjectArg("@Latest")).toThrow( - "is an issue selector" + "is an issue selector", ); expect(() => parseOrgProjectArg("@LATEST")).toThrow( - "is an issue selector" + "is an issue selector", ); expect(() => parseOrgProjectArg("@mostFrequent")).toThrow( - "is an issue selector" + "is an issue selector", ); }); @@ -297,25 +295,25 @@ describe("parseOrgProjectArg", () => { test("/@latest (leading slash) throws with redirect", () => { expect(() => parseOrgProjectArg("/@latest")).toThrow( - "is an issue selector" + "is an issue selector", ); }); test("sentry/@latest (org/selector) throws with redirect", () => { expect(() => parseOrgProjectArg("sentry/@latest")).toThrow( - "is an issue selector" + "is an issue selector", ); }); test("@latest/project (selector as org) throws", () => { expect(() => parseOrgProjectArg("@latest/cli")).toThrow( - "is an issue selector, not an organization slug" + "is an issue selector, not an organization slug", ); }); test("@unknown/project (unknown @ as org) throws", () => { expect(() => parseOrgProjectArg("@unknown/cli")).toThrow( - "starts with '@'" + "starts with '@'", ); }); }); @@ -366,7 +364,7 @@ describe("parseIssueArg", () => { test("keeps the first line when a note is appended after a newline", () => { expect(parseIssueArg("sentry/cli-G\nthe auth-token error")).toEqual( - expected + expected, ); }); @@ -381,7 +379,7 @@ describe("parseIssueArg", () => { test("throws a clear error when input is only blank lines", () => { expect(() => parseIssueArg("\n \n\t\n")).toThrow(ValidationError); expect(() => parseIssueArg("\n \n\t\n")).toThrow( - /empty after trimming/ + /empty after trimming/, ); }); }); @@ -390,13 +388,13 @@ describe("parseIssueArg", () => { describe("error cases", () => { test("org/-suffix throws error", () => { expect(() => parseIssueArg("sentry/-G")).toThrow( - "Cannot use trailing slash before suffix" + "Cannot use trailing slash before suffix", ); }); test("-suffix (empty left) throws error", () => { expect(() => parseIssueArg("-G")).toThrow( - "Missing project before suffix" + "Missing project before suffix", ); }); @@ -406,20 +404,20 @@ describe("parseIssueArg", () => { test("org/project with trailing dash (empty suffix) throws error", () => { expect(() => parseIssueArg("sentry/cli-")).toThrow( - "Missing suffix after dash" + "Missing suffix after dash", ); }); test("org with trailing slash (empty issue ID) throws error", () => { expect(() => parseIssueArg("sentry/")).toThrow( - "Missing issue ID after slash" + "Missing issue ID after slash", ); }); test("issue-1 throws ValidationError for issue command token prefix", () => { expect(() => parseIssueArg("issue-1")).toThrow(ValidationError); expect(() => parseIssueArg("issue-1")).toThrow( - "looks like a command token plus a suffix" + "looks like a command token plus a suffix", ); }); @@ -531,7 +529,7 @@ describe("parseIssueArg", () => { test("issue URL with numeric ID returns explicit-org-numeric", () => { expect( - parseIssueArg("https://sentry.io/organizations/my-org/issues/32886/") + parseIssueArg("https://sentry.io/organizations/my-org/issues/32886/"), ).toEqual({ type: "explicit-org-numeric", org: "my-org", @@ -541,7 +539,7 @@ describe("parseIssueArg", () => { test("issue URL with short ID returns explicit with lowercase project", () => { expect( - parseIssueArg("https://sentry.io/organizations/my-org/issues/CLI-G/") + parseIssueArg("https://sentry.io/organizations/my-org/issues/CLI-G/"), ).toEqual({ type: "explicit", org: "my-org", @@ -553,8 +551,8 @@ describe("parseIssueArg", () => { test("issue URL with multi-part short ID returns explicit with lowercase project", () => { expect( parseIssueArg( - "https://sentry.io/organizations/my-org/issues/SPOTLIGHT-ELECTRON-4Y/" - ) + "https://sentry.io/organizations/my-org/issues/SPOTLIGHT-ELECTRON-4Y/", + ), ).toEqual({ type: "explicit", org: "my-org", @@ -567,8 +565,8 @@ describe("parseIssueArg", () => { process.env.SENTRY_HOST = "https://sentry.example.com"; expect( parseIssueArg( - "https://sentry.example.com/organizations/acme/issues/32886/?project=2" - ) + "https://sentry.example.com/organizations/acme/issues/32886/?project=2", + ), ).toEqual({ type: "explicit-org-numeric", org: "acme", @@ -578,7 +576,7 @@ describe("parseIssueArg", () => { test("event URL extracts issue ID (ignores event part)", () => { const result = parseIssueArg( - "https://sentry.io/organizations/my-org/issues/32886/events/abc123/" + "https://sentry.io/organizations/my-org/issues/32886/events/abc123/", ); expect(result).toEqual({ type: "explicit-org-numeric", @@ -590,20 +588,20 @@ describe("parseIssueArg", () => { test("trace URL throws ValidationError (no issue ID in URL)", () => { expect(() => parseIssueArg( - "https://sentry.io/organizations/my-org/traces/a4d1aae7216b47ff/" - ) + "https://sentry.io/organizations/my-org/traces/a4d1aae7216b47ff/", + ), ).toThrow(ValidationError); }); test("org-only URL throws ValidationError (no issue ID in URL)", () => { expect(() => - parseIssueArg("https://sentry.io/organizations/my-org/") + parseIssueArg("https://sentry.io/organizations/my-org/"), ).toThrow(ValidationError); }); test("project settings URL throws ValidationError (no issue ID in URL)", () => { expect(() => - parseIssueArg("https://sentry.io/settings/my-org/projects/backend/") + parseIssueArg("https://sentry.io/settings/my-org/projects/backend/"), ).toThrow(ValidationError); }); @@ -614,7 +612,7 @@ describe("parseIssueArg", () => { } catch (error) { expect(error).toBeInstanceOf(ValidationError); expect((error as ValidationError).message).toContain( - "does not contain an issue ID" + "does not contain an issue ID", ); } }); @@ -622,8 +620,8 @@ describe("parseIssueArg", () => { test("SaaS subdomain share URL returns share type with org", () => { expect( parseIssueArg( - "https://gibush-kq.sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/" - ) + "https://gibush-kq.sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/", + ), ).toEqual({ type: "share", shareId: "f1abd515c51346778384ff25dfb341e5", @@ -635,8 +633,8 @@ describe("parseIssueArg", () => { test("bare sentry.io share URL returns share type without org", () => { expect( parseIssueArg( - "https://sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/" - ) + "https://sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/", + ), ).toEqual({ type: "share", shareId: "f1abd515c51346778384ff25dfb341e5", @@ -649,8 +647,8 @@ describe("parseIssueArg", () => { process.env.SENTRY_HOST = "https://sentry.example.com"; expect( parseIssueArg( - "https://sentry.example.com/share/issue/aabbccdd11223344aabbccdd11223344/" - ) + "https://sentry.example.com/share/issue/aabbccdd11223344aabbccdd11223344/", + ), ).toEqual({ type: "share", shareId: "aabbccdd11223344aabbccdd11223344", @@ -744,7 +742,7 @@ describe("parseIssueArg", () => { test("compound project slug with matching full short ID (CLI-KC)", () => { expect( - parseIssueArg("org/spotlight-electron/SPOTLIGHT-ELECTRON-4Y") + parseIssueArg("org/spotlight-electron/SPOTLIGHT-ELECTRON-4Y"), ).toEqual({ type: "explicit", org: "org", @@ -772,13 +770,13 @@ describe("parseIssueArg", () => { test("org/project/ (trailing slash, empty suffix) throws error", () => { expect(() => parseIssueArg("org/project/")).toThrow( - "Missing project or issue ID segment" + "Missing project or issue ID segment", ); }); test("org//suffix (empty project) throws error", () => { expect(() => parseIssueArg("org//suffix")).toThrow( - "Missing project or issue ID segment" + "Missing project or issue ID segment", ); }); }); @@ -1098,7 +1096,7 @@ describe("looksLikeIssueShortId", () => { describe("with ignoreCase option", () => { test("cam-82x with ignoreCase is false (two-part lowercase slug)", () => { expect(looksLikeIssueShortId("cam-82x", { ignoreCase: true })).toBe( - false + false, ); }); @@ -1108,49 +1106,49 @@ describe("looksLikeIssueShortId", () => { test("javascript-react-mr-1b with ignoreCase", () => { expect( - looksLikeIssueShortId("javascript-react-mr-1b", { ignoreCase: true }) + looksLikeIssueShortId("javascript-react-mr-1b", { ignoreCase: true }), ).toBe(true); }); test("my-frontend-app with ignoreCase is false (3-part project slug)", () => { expect( - looksLikeIssueShortId("my-frontend-app", { ignoreCase: true }) + looksLikeIssueShortId("my-frontend-app", { ignoreCase: true }), ).toBe(false); }); test("my-app-2 with ignoreCase is false (versioned project slug)", () => { expect(looksLikeIssueShortId("my-app-2", { ignoreCase: true })).toBe( - false + false, ); }); test("my-app-2b with ignoreCase is true (lowercase multi-segment, alphanumeric final)", () => { expect(looksLikeIssueShortId("my-app-2b", { ignoreCase: true })).toBe( - true + true, ); }); test("my-apps-2b with ignoreCase is true (alphanumeric final, no length special-casing)", () => { expect(looksLikeIssueShortId("my-apps-2b", { ignoreCase: true })).toBe( - true + true, ); }); test("api-gateway-1 with ignoreCase is false (versioned project slug)", () => { expect(looksLikeIssueShortId("api-gateway-1", { ignoreCase: true })).toBe( - false + false, ); }); test("My-App-2 with ignoreCase is false (title-case project slug)", () => { expect(looksLikeIssueShortId("My-App-2", { ignoreCase: true })).toBe( - false + false, ); }); test("My-Frontend-App with ignoreCase is false (title-case project slug)", () => { expect( - looksLikeIssueShortId("My-Frontend-App", { ignoreCase: true }) + looksLikeIssueShortId("My-Frontend-App", { ignoreCase: true }), ).toBe(false); }); @@ -1160,7 +1158,7 @@ describe("looksLikeIssueShortId", () => { test("My-Project with ignoreCase is false (two-part title-case slug)", () => { expect(looksLikeIssueShortId("My-Project", { ignoreCase: true })).toBe( - false + false, ); }); @@ -1186,31 +1184,31 @@ describe("looksLikeIssueShortId", () => { test("spotlight-electron-4y with ignoreCase is true (lowercase multi-segment short ID)", () => { expect( - looksLikeIssueShortId("spotlight-electron-4y", { ignoreCase: true }) + looksLikeIssueShortId("spotlight-electron-4y", { ignoreCase: true }), ).toBe(true); }); test("SPOTLIGHT-ELECTRON-5 with ignoreCase is true (uppercase multi-segment numeric suffix)", () => { expect( - looksLikeIssueShortId("SPOTLIGHT-ELECTRON-5", { ignoreCase: true }) + looksLikeIssueShortId("SPOTLIGHT-ELECTRON-5", { ignoreCase: true }), ).toBe(true); }); test("JAVASCRIPT-NUXT-52 with ignoreCase is true (uppercase multi-segment numeric suffix)", () => { expect( - looksLikeIssueShortId("JAVASCRIPT-NUXT-52", { ignoreCase: true }) + looksLikeIssueShortId("JAVASCRIPT-NUXT-52", { ignoreCase: true }), ).toBe(true); }); test("my-project with ignoreCase is false (project slug)", () => { expect(looksLikeIssueShortId("my-project", { ignoreCase: true })).toBe( - false + false, ); }); test("acme-corp with ignoreCase is false (org/project slug)", () => { expect(looksLikeIssueShortId("acme-corp", { ignoreCase: true })).toBe( - false + false, ); }); }); @@ -1219,25 +1217,25 @@ describe("looksLikeIssueShortId", () => { describe("rejectIssueCommandTokenListTarget", () => { test("issue-1 throws ValidationError", () => { expect(() => rejectIssueCommandTokenListTarget("issue-1")).toThrow( - ValidationError + ValidationError, ); expect(() => rejectIssueCommandTokenListTarget("issue-1")).toThrow( - "looks like a command token plus a suffix" + "looks like a command token plus a suffix", ); }); test("my-org/issue-1 throws ValidationError", () => { expect(() => rejectIssueCommandTokenListTarget("my-org/issue-1")).toThrow( - ValidationError + ValidationError, ); }); test("whitespace-padded issue-1 throws ValidationError", () => { expect(() => rejectIssueCommandTokenListTarget(" issue-1 ")).toThrow( - ValidationError + ValidationError, ); expect(() => - rejectIssueCommandTokenListTarget(" my-org/issue-1\n") + rejectIssueCommandTokenListTarget(" my-org/issue-1\n"), ).toThrow(ValidationError); }); @@ -1264,7 +1262,7 @@ describe("detectSwappedViewArgs", () => { test("returns null when first has slash (correct order)", () => { expect( - detectSwappedViewArgs("mv-software/mvsoftware", "a9b4ad2c") + detectSwappedViewArgs("mv-software/mvsoftware", "a9b4ad2c"), ).toBeNull(); }); @@ -1380,7 +1378,7 @@ describe("parseOrgProjectArg space handling (no normalization)", () => { test("org/project with spaces throws ValidationError (spaces in org)", () => { expect(() => parseOrgProjectArg("My Org/My Project")).toThrow( - ValidationError + ValidationError, ); }); @@ -1421,31 +1419,31 @@ describe("parseOrgProjectArg space handling (no normalization)", () => { describe("parseOrgProjectArg: injection hardening", () => { test("rejects query injection in org slug", () => { expect(() => parseOrgProjectArg("my-org?query=foo/cli")).toThrow( - ValidationError + ValidationError, ); }); test("rejects query injection in project slug", () => { expect(() => parseOrgProjectArg("sentry/cli?extra=1")).toThrow( - ValidationError + ValidationError, ); }); test("rejects fragment injection in org slug", () => { expect(() => parseOrgProjectArg("my-org#anchor/cli")).toThrow( - ValidationError + ValidationError, ); }); test("rejects fragment injection in project slug", () => { expect(() => parseOrgProjectArg("sentry/my-project#anchor")).toThrow( - ValidationError + ValidationError, ); }); test("rejects pre-encoded space in project slug", () => { expect(() => parseOrgProjectArg("sentry/my%20project")).toThrow( - ValidationError + ValidationError, ); }); @@ -1465,7 +1463,7 @@ describe("parseOrgProjectArg: injection hardening", () => { test("rejects null byte in project slug", () => { expect(() => parseOrgProjectArg("sentry/cli\x00extra")).toThrow( - ValidationError + ValidationError, ); }); }); @@ -1700,7 +1698,7 @@ describe("parseSlashSeparatedArg: whitespace trimming", () => { const result = parseSlashSeparatedArg( "a9b4ad2c\n", "Event ID", - "sentry event view " + "sentry event view ", ); expect(result).toEqual({ id: "a9b4ad2c", targetArg: undefined }); }); @@ -1709,7 +1707,7 @@ describe("parseSlashSeparatedArg: whitespace trimming", () => { const result = parseSlashSeparatedArg( "sentry/cli/a9b4ad2c\n", "Event ID", - "sentry event view " + "sentry event view ", ); expect(result).toEqual({ id: "a9b4ad2c", targetArg: "sentry/cli" }); }); @@ -1718,7 +1716,7 @@ describe("parseSlashSeparatedArg: whitespace trimming", () => { const result = parseSlashSeparatedArg( " a9b4ad2c ", "Event ID", - "sentry event view " + "sentry event view ", ); expect(result).toEqual({ id: "a9b4ad2c", targetArg: undefined }); }); @@ -1727,7 +1725,7 @@ describe("parseSlashSeparatedArg: whitespace trimming", () => { const result = parseSlashSeparatedArg( "abc123\ndef456", "Log ID", - "sentry log view " + "sentry log view ", ); // No-slash path must NOT strip newlines — log view splits them downstream expect(result.id).toBe("abc123\ndef456"); diff --git a/packages/cli/test/lib/auth-header.property.test.ts b/packages/cli/test/lib/auth-header.property.test.ts index 3b3fa3f10..26f79afb2 100644 --- a/packages/cli/test/lib/auth-header.property.test.ts +++ b/packages/cli/test/lib/auth-header.property.test.ts @@ -24,10 +24,10 @@ const paddingCharacter = constantFrom( ...Array.from({ length: 33 }, (_, code) => String.fromCharCode(code)), "\x7f", "\u00a0", - "\ufeff" + "\ufeff", ); const padding = array(paddingCharacter, { maxLength: 20 }).map((chars) => - chars.join("") + chars.join(""), ); describe("auth token normalization", () => { @@ -40,7 +40,7 @@ describe("auth token normalization", () => { expect(normalizeAuthToken(normalizeAuthToken(input))).toBe(token); expect(formatAuthHeader(input)).toBe(`Bearer ${token}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -54,11 +54,11 @@ describe("auth token normalization", () => { const input = before + char + after; expect(trimAuthToken(input)).toBe(input); expect(() => normalizeAuthToken(input)).toThrow( - MalformedAuthTokenError + MalformedAuthTokenError, ); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -66,25 +66,22 @@ describe("auth token normalization", () => { fcAssert( property(padding, (input) => { expect(() => normalizeAuthToken(input)).toThrow( - MalformedAuthTokenError + MalformedAuthTokenError, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); - test.each([ - "\x80", - "\x85", - "\u200b", - "é", - "💥", - ])("does not silently remove other Unicode characters %#", (char) => { - expect(() => normalizeAuthToken(`${char}token`)).toThrow( - MalformedAuthTokenError - ); - expect(() => normalizeAuthToken(`token${char}`)).toThrow( - MalformedAuthTokenError - ); - }); + test.each(["\x80", "\x85", "\u200b", "é", "💥"])( + "does not silently remove other Unicode characters %#", + (char) => { + expect(() => normalizeAuthToken(`${char}token`)).toThrow( + MalformedAuthTokenError, + ); + expect(() => normalizeAuthToken(`token${char}`)).toThrow( + MalformedAuthTokenError, + ); + }, + ); }); diff --git a/packages/cli/test/lib/auth-hint.test.ts b/packages/cli/test/lib/auth-hint.test.ts index b2a5a7b03..3c06746ed 100644 --- a/packages/cli/test/lib/auth-hint.test.ts +++ b/packages/cli/test/lib/auth-hint.test.ts @@ -80,7 +80,7 @@ function captureStderr() { /** Number of calls whose first argument contained the env-hint body. */ hintCalls: () => stderrSpy.mock.calls.filter((call) => - String(call[0] ?? "").includes("SENTRY_FORCE_ENV_TOKEN=1") + String(call[0] ?? "").includes("SENTRY_FORCE_ENV_TOKEN=1"), ).length, /** Flattened first-arg text across all calls (for substring assertions). */ text: () => diff --git a/packages/cli/test/lib/auto-auth.test.ts b/packages/cli/test/lib/auto-auth.test.ts index 249373922..4719330f5 100644 --- a/packages/cli/test/lib/auto-auth.test.ts +++ b/packages/cli/test/lib/auto-auth.test.ts @@ -32,7 +32,7 @@ const alwaysInteractive = () => true; describe("shouldAutoAuth", () => { test("true for not_authenticated in an interactive TTY", () => { expect(shouldAutoAuth(new AuthError("not_authenticated"), () => true)).toBe( - true + true, ); }); @@ -116,7 +116,7 @@ describe("recoverWithAutoLogin", () => { recoverWithAutoLogin(boom, retry, { runInteractiveLogin, isInteractive: alwaysInteractive, - }) + }), ).rejects.toBe(boom); expect(runInteractiveLogin).not.toHaveBeenCalled(); expect(retry).not.toHaveBeenCalled(); @@ -129,7 +129,7 @@ describe("recoverWithAutoLogin", () => { const err = new AuthError("not_authenticated"); await expect( - recoverWithAutoLogin(err, vi.fn(), { runInteractiveLogin }) + recoverWithAutoLogin(err, vi.fn(), { runInteractiveLogin }), ).rejects.toBe(err); expect(runInteractiveLogin).not.toHaveBeenCalled(); }); @@ -144,7 +144,7 @@ describe("recoverWithAutoLogin", () => { runInteractiveLogin, isInteractive: alwaysInteractive, write: (m) => writes.push(m), - }) + }), ).rejects.toBeInstanceOf(HostScopeError); // No login attempt, no browser, no status output. @@ -164,7 +164,7 @@ describe("recoverWithAutoLogin", () => { runInteractiveLogin, isInteractive: alwaysInteractive, write: (m) => writes.push(m), - } + }, ); expect(exitCode).toBeUndefined(); @@ -197,7 +197,7 @@ describe("recoverWithAutoLogin", () => { runInteractiveLogin: vi.fn().mockResolvedValue(null), isInteractive: alwaysInteractive, write: (m) => writes.push(m), - } + }, ); expect(exitCode).toBe(1); @@ -218,7 +218,7 @@ describe("recoverWithAutoLogin", () => { runInteractiveLogin, isInteractive: alwaysInteractive, write: noop, - } + }, ); expect(exitCode).toBeUndefined(); diff --git a/packages/cli/test/lib/auto-paginate.property.test.ts b/packages/cli/test/lib/auto-paginate.property.test.ts index 64e54194d..493498e95 100644 --- a/packages/cli/test/lib/auto-paginate.property.test.ts +++ b/packages/cli/test/lib/auto-paginate.property.test.ts @@ -38,7 +38,7 @@ function makePagedFetcher(total: number, pageSize: number) { const offset = cursor ? Number(cursor) : 0; const data = Array.from( { length: Math.min(cappedPageSize, Math.max(0, total - offset)) }, - (_unused, i) => offset + i + (_unused, i) => offset + i, ); const nextOffset = offset + data.length; const nextCursor = nextOffset < total ? String(nextOffset) : undefined; @@ -70,12 +70,12 @@ describe("property: autoPaginate multi-page contract", () => { async (total, limit, pageSize) => { const result = await autoPaginate( makePagedFetcher(total, pageSize), - limit + limit, ); expect(result.data.length).toBeLessThanOrEqual(limit); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -88,7 +88,7 @@ describe("property: autoPaginate multi-page contract", () => { async (total, limit, pageSize) => { const result = await autoPaginate( makePagedFetcher(total, pageSize), - limit + limit, ); // Only meaningful when more rows exist than the limit. @@ -119,9 +119,9 @@ describe("property: autoPaginate multi-page contract", () => { // that cursor. This is the original skip-bug guard. expect(result.nextCursor).toBeUndefined(); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -134,15 +134,15 @@ describe("property: autoPaginate multi-page contract", () => { async (total, limit, pageSize) => { const result = await autoPaginate( makePagedFetcher(total, pageSize), - limit + limit, ); const expectedLen = Math.min(total, limit); expect(result.data).toEqual( - Array.from({ length: expectedLen }, (_unused, i) => i) + Array.from({ length: expectedLen }, (_unused, i) => i), ); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/bash-hook/traceback.test.ts b/packages/cli/test/lib/bash-hook/traceback.test.ts index 1a59bf573..98afc93cd 100644 --- a/packages/cli/test/lib/bash-hook/traceback.test.ts +++ b/packages/cli/test/lib/bash-hook/traceback.test.ts @@ -157,7 +157,7 @@ describe("buildBashErrorEvent", () => { const tracebackPath = join(tempDir, "traceback"); await writeFile( tracebackPath, - "main:/script.sh:10\n@command:curl\n@exit_code:1\n" + "main:/script.sh:10\n@command:curl\n@exit_code:1\n", ); const event = await buildBashErrorEvent({ tracebackPath }); @@ -168,7 +168,7 @@ describe("buildBashErrorEvent", () => { expect(event.exception?.values).toHaveLength(1); expect(event.exception?.values?.[0]?.type).toBe("BashError"); expect(event.exception?.values?.[0]?.value).toBe( - "command curl exited with status 1" + "command curl exited with status 1", ); expect(event.exception?.values?.[0]?.stacktrace?.frames).toHaveLength(1); }); @@ -212,7 +212,7 @@ describe("buildBashErrorEvent", () => { test("throws ValidationError for missing traceback file", async () => { await expect( - buildBashErrorEvent({ tracebackPath: join(tempDir, "nonexistent") }) + buildBashErrorEvent({ tracebackPath: join(tempDir, "nonexistent") }), ).rejects.toThrow("Traceback file not found"); }); @@ -250,7 +250,7 @@ describe("buildBashErrorEvent", () => { const tracebackPath = join(tempDir, "traceback"); await writeFile( tracebackPath, - "inner:/script.sh:10\nmiddle:/script.sh:5\nouter:/script.sh:1\n@command:test\n" + "inner:/script.sh:10\nmiddle:/script.sh:5\nouter:/script.sh:1\n@command:test\n", ); const event = await buildBashErrorEvent({ tracebackPath }); diff --git a/packages/cli/test/lib/bench/generate.test.ts b/packages/cli/test/lib/bench/generate.test.ts index 5a621230e..3d002a2aa 100644 --- a/packages/cli/test/lib/bench/generate.test.ts +++ b/packages/cli/test/lib/bench/generate.test.ts @@ -39,7 +39,7 @@ afterAll(() => { function makeSpec( name: "small" | "medium", dir: string, - seed = 42 + seed = 42, ): FixtureSpec { return { ...PRESETS[name], seed, rootDir: dir }; } @@ -54,7 +54,7 @@ describe("generateFixture", () => { expect(meta.dsnCount).toBeGreaterThan(0); expect(meta.dsnCount).toBeLessThan(meta.fileCount); const onDisk = JSON.parse( - readFileSync(join(dir, ".meta.json"), "utf8") + readFileSync(join(dir, ".meta.json"), "utf8"), ) as FixtureMeta; expect(onDisk.version).toBe(1); expect(onDisk.spec.packages).toBe(0); diff --git a/packages/cli/test/lib/bench/helpers.test.ts b/packages/cli/test/lib/bench/helpers.test.ts index e87b2ae6a..ec1665d8a 100644 --- a/packages/cli/test/lib/bench/helpers.test.ts +++ b/packages/cli/test/lib/bench/helpers.test.ts @@ -70,7 +70,7 @@ describe("measure", () => { () => { calls += 1; }, - { runs: 5, warmup: 2 } + { runs: 5, warmup: 2 }, ); // 2 warmup + 5 measured = 7 total calls; 5 reported samples. expect(calls).toBe(7); @@ -92,7 +92,7 @@ describe("measure", () => { beforeEach: () => { calls.push("setup"); }, - } + }, ); // expected: setup, run, setup, run, setup, run, setup, run expect(calls).toEqual([ @@ -123,7 +123,7 @@ describe("measure", () => { await new Promise((r) => setTimeout(r, 1)); order.push("setup-end"); }, - } + }, ); expect(order).toEqual(["setup-start", "setup-end", "run-start", "run-end"]); }); diff --git a/packages/cli/test/lib/binary.mocked.test.ts b/packages/cli/test/lib/binary.mocked.test.ts index ce8611c04..c83324155 100644 --- a/packages/cli/test/lib/binary.mocked.test.ts +++ b/packages/cli/test/lib/binary.mocked.test.ts @@ -30,7 +30,7 @@ describe("installBinary canonical() fallback when realpath throws", () => { beforeEach(() => { testDir = join( "/tmp", - `binary-mocked-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `binary-mocked-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); installDir = join(testDir, "install"); mkdirSync(installDir, { recursive: true }); @@ -59,7 +59,7 @@ describe("installBinary canonical() fallback when realpath throws", () => { expect(await readFile(result, "utf-8")).toBe("upgraded binary"); expect(debugSpy).toHaveBeenCalledWith( "realpath failed, falling back to resolve()", - expect.any(Error) + expect.any(Error), ); }); }); diff --git a/packages/cli/test/lib/binary.test.ts b/packages/cli/test/lib/binary.test.ts index 29ead3828..3450bb849 100644 --- a/packages/cli/test/lib/binary.test.ts +++ b/packages/cli/test/lib/binary.test.ts @@ -47,7 +47,7 @@ describe("getBinaryDownloadUrl", () => { expect(url).toContain("/cli@1.0.0/"); expect(url).toStartWith( - "https://github.com/getsentry/toolkit/releases/download/" + "https://github.com/getsentry/toolkit/releases/download/", ); expect(url).toContain("sentry-"); @@ -117,7 +117,7 @@ describe("resolveUpgradeSource", () => { requests.length === 1 ? "Not Found" : JSON.stringify({ tag_name: "0.45.0" }), - { status: requests.length === 1 ? 404 : 200 } + { status: requests.length === 1 ? 404 : 200 }, ); }, }); @@ -129,25 +129,26 @@ describe("resolveUpgradeSource", () => { ]); }); - test.each([ - 401, 403, 429, 500, - ])("does not fall back on HTTP %i", async (status) => { - const requests: string[] = []; + test.each([401, 403, 429, 500])( + "does not fall back on HTTP %i", + async (status) => { + const requests: string[] = []; - await expect( - resolveUpgradeSource({ - getProbeUrl: (source) => getGitHubReleaseByTagUrl("0.45.0", source), - fetch: async (url) => { - requests.push(String(url)); - return new Response("failure", { status }); - }, - }) - ).rejects.toThrow(`HTTP ${status}`); + await expect( + resolveUpgradeSource({ + getProbeUrl: (source) => getGitHubReleaseByTagUrl("0.45.0", source), + fetch: async (url) => { + requests.push(String(url)); + return new Response("failure", { status }); + }, + }), + ).rejects.toThrow(`HTTP ${status}`); - expect(requests).toEqual([ - "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.45.0", - ]); - }); + expect(requests).toEqual([ + "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.45.0", + ]); + }, + ); test("does not fall back on a network failure", async () => { const requests: string[] = []; @@ -159,7 +160,7 @@ describe("resolveUpgradeSource", () => { requests.push(String(url)); throw new TypeError("fetch failed"); }, - }) + }), ).rejects.toThrow("Failed to connect to GitHub: fetch failed"); expect(requests).toEqual([ @@ -210,13 +211,13 @@ describe("getBinaryPaths", () => { describe("samePath", () => { test("matches identical paths", () => { expect(samePath("/home/user/.local/bin", "/home/user/.local/bin")).toBe( - true + true, ); }); test("distinguishes genuinely different paths", () => { expect(samePath("/home/user/.local/bin", "/home/user/.sentry/bin")).toBe( - false + false, ); }); @@ -259,7 +260,7 @@ describe("determineInstallDir", () => { beforeEach(() => { testDir = join( "/tmp", - `binary-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `binary-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -433,7 +434,7 @@ describe("fetchWithUpgradeError", () => { const response = await fetchWithUpgradeError( "https://example.com", {}, - "Test" + "Test", ); expect(response.status).toBe(200); }); @@ -487,7 +488,7 @@ describe("fetchWithUpgradeError", () => { test("wraps non-Error thrown values as UpgradeError", async () => { globalThis.fetch = (async () => { - // biome-ignore lint/style/useThrowOnlyError: intentionally testing non-Error throw + // intentionally testing non-Error throw throw { code: "ECONNRESET", reason: "connection reset" }; }) as typeof globalThis.fetch; @@ -512,7 +513,7 @@ describe("parseUpgradeJson", () => { }; await expect( - parseUpgradeJson(response, controller.signal, "invalid metadata") + parseUpgradeJson(response, controller.signal, "invalid metadata"), ).rejects.toBe(reason); }); @@ -523,7 +524,7 @@ describe("parseUpgradeJson", () => { }; await expect( - parseUpgradeJson(response, undefined, "invalid metadata") + parseUpgradeJson(response, undefined, "invalid metadata"), ).rejects.toMatchObject({ name: "UpgradeTransportError", reason: "network_error", @@ -534,7 +535,7 @@ describe("parseUpgradeJson", () => { const response = new Response("not json"); await expect( - parseUpgradeJson(response, undefined, "invalid metadata") + parseUpgradeJson(response, undefined, "invalid metadata"), ).rejects.toMatchObject({ name: "UpgradeError", reason: "network_error", @@ -549,7 +550,7 @@ describe("replaceBinarySync", () => { beforeEach(() => { testDir = join( "/tmp", - `replace-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `replace-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -579,8 +580,8 @@ describe("replaceBinarySync", () => { expect( await access(tempPath).then( () => true, - () => false - ) + () => false, + ), ).toBe(false); }); @@ -608,7 +609,7 @@ describe("installBinary", () => { beforeEach(() => { testDir = join( "/tmp", - `binary-install-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `binary-install-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); sourceDir = join(testDir, "source"); installDir = join(testDir, "install"); @@ -631,8 +632,8 @@ describe("installBinary", () => { expect( await access(result).then( () => true, - () => false - ) + () => false, + ), ).toBe(true); const installed = await readFile(result); @@ -651,8 +652,8 @@ describe("installBinary", () => { expect( await access(result).then( () => true, - () => false - ) + () => false, + ), ).toBe(true); }); @@ -667,8 +668,8 @@ describe("installBinary", () => { expect( await access(lockPath).then( () => true, - () => false - ) + () => false, + ), ).toBe(false); }); @@ -683,8 +684,8 @@ describe("installBinary", () => { expect( await access(tempPath).then( () => true, - () => false - ) + () => false, + ), ).toBe(false); }); @@ -757,7 +758,7 @@ describe("acquireLock", () => { beforeEach(() => { testDir = join( "/tmp", - `lock-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `lock-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -819,7 +820,7 @@ describe("acquireLock", () => { writeFileSync(lockPath, "1"); expect(() => acquireLock(lockPath)).toThrow( - "Another upgrade is already in progress" + "Another upgrade is already in progress", ); }); @@ -867,19 +868,19 @@ describe("compareVersions", () => { test("nightly: later timestamp > earlier timestamp", () => { expect( - compareVersions("0.14.0-dev.1772732047", "0.14.0-dev.1772724107") + compareVersions("0.14.0-dev.1772732047", "0.14.0-dev.1772724107"), ).toBe(1); }); test("nightly: earlier timestamp < later timestamp", () => { expect( - compareVersions("0.14.0-dev.1772724107", "0.14.0-dev.1772732047") + compareVersions("0.14.0-dev.1772724107", "0.14.0-dev.1772732047"), ).toBe(-1); }); test("nightly: equal", () => { expect( - compareVersions("0.14.0-dev.1772724107", "0.14.0-dev.1772724107") + compareVersions("0.14.0-dev.1772724107", "0.14.0-dev.1772724107"), ).toBe(0); }); @@ -903,13 +904,13 @@ describe("isDowngrade", () => { test("returns true when target is older nightly (earlier timestamp)", () => { expect(isDowngrade("0.14.0-dev.1772732047", "0.14.0-dev.1772724107")).toBe( - true + true, ); }); test("returns false when target is newer nightly (later timestamp)", () => { expect(isDowngrade("0.14.0-dev.1772724107", "0.14.0-dev.1772732047")).toBe( - false + false, ); }); }); diff --git a/packages/cli/test/lib/bspatch.property.test.ts b/packages/cli/test/lib/bspatch.property.test.ts index f6f7fd636..9e6bfb3a5 100644 --- a/packages/cli/test/lib/bspatch.property.test.ts +++ b/packages/cli/test/lib/bspatch.property.test.ts @@ -19,7 +19,7 @@ describe("property: offtin", () => { // |value| should be representable expect(Number.isFinite(value)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -47,7 +47,7 @@ describe("property: offtin", () => { expect(positive).toBeGreaterThanOrEqual(0); expect(negative).toBeLessThanOrEqual(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -74,9 +74,9 @@ describe("property: offtin", () => { } else { expect(negResult).toBe(-magnitude); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -87,7 +87,7 @@ describe("property: parsePatchHeader", () => { property(uint8Array({ minLength: 0, maxLength: 31 }), (buf) => { expect(() => parsePatchHeader(buf)).toThrow("Patch too small"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -101,7 +101,7 @@ describe("property: parsePatchHeader", () => { } expect(() => parsePatchHeader(buf)).toThrow("Invalid patch format"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -125,7 +125,7 @@ describe("property: parsePatchHeader", () => { // May throw for other reasons (length overflow) — that's fine } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/bspatch.test.ts b/packages/cli/test/lib/bspatch.test.ts index d474b8655..f78e8cb14 100644 --- a/packages/cli/test/lib/bspatch.test.ts +++ b/packages/cli/test/lib/bspatch.test.ts @@ -52,7 +52,7 @@ function writeI64LE(buf: Uint8Array, offset: number, value: number): void { function buildDiffOnlyPatch( oldBytes: Uint8Array, newBytes: Uint8Array, - chunkSize = newBytes.length + chunkSize = newBytes.length, ): Uint8Array { if (oldBytes.length !== newBytes.length) { throw new Error("diff-only patch requires equal lengths"); @@ -61,7 +61,7 @@ function buildDiffOnlyPatch( // One control tuple per chunk: readDiffBy=chunk, readExtraBy=0, seekBy=0. const chunks: number[] = []; - for (let remaining = len; remaining > 0; ) { + for (let remaining = len; remaining > 0;) { const c = Math.min(chunkSize, remaining); chunks.push(c); remaining -= c; @@ -84,7 +84,7 @@ function buildDiffOnlyPatch( const extraZ = new Uint8Array(zstdCompressSync(new Uint8Array(0))); const patch = new Uint8Array( - 32 + controlZ.length + diffZ.length + extraZ.length + 32 + controlZ.length + diffZ.length + extraZ.length, ); patch.set(new TextEncoder().encode("TRDIFF10"), 0); writeI64LE(patch, 8, controlZ.length); @@ -114,7 +114,7 @@ function makeBytes(seed: number, len: number): Uint8Array { describe("parsePatchHeader: fixtures", () => { test("parses valid small fixture header", async () => { const patchData = new Uint8Array( - await readFile(join(FIXTURES_DIR, "small.trdiff10")) + await readFile(join(FIXTURES_DIR, "small.trdiff10")), ); const header = parsePatchHeader(patchData); expect(header.controlLen).toBeGreaterThan(0); @@ -124,7 +124,7 @@ describe("parsePatchHeader: fixtures", () => { test("parses valid large fixture header", async () => { const patchData = new Uint8Array( - await readFile(join(FIXTURES_DIR, "large.trdiff10")) + await readFile(join(FIXTURES_DIR, "large.trdiff10")), ); const header = parsePatchHeader(patchData); expect(header.controlLen).toBeGreaterThan(0); @@ -185,7 +185,7 @@ describe("applyPatch", () => { test("patches small text files correctly", async () => { const oldPath = join(FIXTURES_DIR, "small-old.bin"); const patchData = new Uint8Array( - await readFile(join(FIXTURES_DIR, "small.trdiff10")) + await readFile(join(FIXTURES_DIR, "small.trdiff10")), ); const destPath = tempFile("small-out.bin"); @@ -210,7 +210,7 @@ describe("applyPatch", () => { test("patches large binary files correctly", async () => { const oldPath = join(FIXTURES_DIR, "large-old.bin"); const patchData = new Uint8Array( - await readFile(join(FIXTURES_DIR, "large.trdiff10")) + await readFile(join(FIXTURES_DIR, "large.trdiff10")), ); const destPath = tempFile("large-out.bin"); @@ -235,7 +235,7 @@ describe("applyPatch", () => { test("returns correct SHA-256 hex digest", async () => { const oldPath = join(FIXTURES_DIR, "small-old.bin"); const patchData = new Uint8Array( - await readFile(join(FIXTURES_DIR, "small.trdiff10")) + await readFile(join(FIXTURES_DIR, "small.trdiff10")), ); const destPath = tempFile("sha256-out.bin"); @@ -260,7 +260,7 @@ describe("applyPatch", () => { try { await expect(applyPatch(oldPath, buf, destPath)).rejects.toThrow( - "Invalid patch format" + "Invalid patch format", ); } finally { try { @@ -276,7 +276,7 @@ describe("applyPatchChainInMemory", () => { function tempFile(name: string): string { return join( tmpdir(), - `bspatch-chain-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}` + `bspatch-chain-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}`, ); } @@ -350,7 +350,7 @@ describe("applyPatchChainInMemory", () => { // control entries include diff, extra, and seek operations. const oldPath = join(FIXTURES_DIR, "large-old.bin"); const patchData = new Uint8Array( - await readFile(join(FIXTURES_DIR, "large.trdiff10")) + await readFile(join(FIXTURES_DIR, "large.trdiff10")), ); const destPath = tempFile("single-chain.bin"); @@ -358,11 +358,11 @@ describe("applyPatchChainInMemory", () => { const sha256 = await applyPatchChainInMemory( oldPath, [patchData], - destPath + destPath, ); const expected = await readFile(join(FIXTURES_DIR, "large-new.bin")); expect(new Uint8Array(await readFile(destPath))).toEqual( - new Uint8Array(expected) + new Uint8Array(expected), ); expect(sha256).toBe(createHash("sha256").update(expected).digest("hex")); } finally { @@ -378,7 +378,7 @@ describe("applyPatchChainInMemory", () => { try { await expect( - applyPatchChainInMemory(oldPath, [], destPath) + applyPatchChainInMemory(oldPath, [], destPath), ).rejects.toThrow("empty patch chain"); } finally { if (existsSync(destPath)) { @@ -392,7 +392,7 @@ describe("applyPatchToMemory", () => { function tempFile(name: string): string { return join( tmpdir(), - `bspatch-mem-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}` + `bspatch-mem-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}`, ); } @@ -402,7 +402,7 @@ describe("applyPatchToMemory", () => { const oldPath = join(FIXTURES_DIR, "large-old.bin"); const oldBytes = new Uint8Array(await readFile(oldPath)); const patchData = new Uint8Array( - await readFile(join(FIXTURES_DIR, "large.trdiff10")) + await readFile(join(FIXTURES_DIR, "large.trdiff10")), ); const destPath = tempFile("disk.bin"); @@ -479,7 +479,7 @@ describe("addDiffChunk SWAR wrapping-add", () => { for (let i = 0; i < n; i++) oldBytes[i] = (i * 17) % 256; const expected = referenceAdd( oldBytes, - Uint8Array.from({ length: n }, (_, i) => (i * 29) % 256) + Uint8Array.from({ length: n }, (_, i) => (i * 29) % 256), ); const patch = buildDiffOnlyPatch(oldBytes, expected); const out = await applyPatchToMemory(oldBytes, patch); @@ -520,7 +520,7 @@ describe("FileOldReader block cache", () => { function tempFile(name: string): string { return join( tmpdir(), - `bspatch-cache-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}` + `bspatch-cache-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}`, ); } @@ -541,7 +541,7 @@ describe("FileOldReader block cache", () => { */ async function runBothReaders( oldBytes: Uint8Array, - patch: Uint8Array + patch: Uint8Array, ): Promise<{ fileSha: string; fileBytesSha: string; memSha: string }> { const oldPath = tempFile("old.bin"); const destPath = tempFile("out.bin"); @@ -571,7 +571,7 @@ describe("FileOldReader block cache", () => { const expected = sha(newBytes); const { fileSha, fileBytesSha, memSha } = await runBothReaders( oldBytes, - patch + patch, ); expect(fileSha).toBe(expected); expect(fileBytesSha).toBe(expected); @@ -588,7 +588,7 @@ describe("FileOldReader block cache", () => { const expected = sha(newBytes); const { fileSha, fileBytesSha, memSha } = await runBothReaders( oldBytes, - patch + patch, ); expect(fileSha).toBe(expected); expect(fileBytesSha).toBe(expected); @@ -607,7 +607,7 @@ describe("FileOldReader block cache", () => { const expected = sha(newBytes); const { fileSha, fileBytesSha, memSha } = await runBothReaders( oldBytes, - patch + patch, ); expect(fileSha).toBe(expected); expect(fileBytesSha).toBe(expected); diff --git a/packages/cli/test/lib/build/index.test.ts b/packages/cli/test/lib/build/index.test.ts index a46264428..c799addaf 100644 --- a/packages/cli/test/lib/build/index.test.ts +++ b/packages/cli/test/lib/build/index.test.ts @@ -61,7 +61,7 @@ function writeTmpFile(name: string, data: Uint8Array): string { * The output path lives in a tracked temp dir. */ async function normalizeToBuffer( - run: (outPath: string) => Promise + run: (outPath: string) => Promise, ): Promise { const dir = makeTmpDir("build-out-"); const outPath = join(dir, "normalized.zip"); @@ -99,7 +99,7 @@ describe("detectBuildFormat", () => { test("returns null for a ZIP without build markers", () => { expect(detectBuildFormat(zipSync({ "readme.txt": strToU8("hi") }))).toBe( - null + null, ); }); @@ -142,7 +142,7 @@ describe("normalizeBuildFile", () => { const apk = fakeApk(); const src = writeTmpFile("app-release.apk", apk); const zip = await normalizeToBuffer((out) => - normalizeBuildFile(src, out, null) + normalizeBuildFile(src, out, null), ); const entries = unzipSync(zip); @@ -152,7 +152,9 @@ describe("normalizeBuildFile", () => { ]); // The build bytes are stored verbatim. expect(entries["app-release.apk"]).toEqual(apk); - const metadata = new TextDecoder().decode(entries[".sentry-cli-metadata.txt"]); + const metadata = new TextDecoder().decode( + entries[".sentry-cli-metadata.txt"], + ); expect(metadata).toContain("sentry-cli-version:"); }); @@ -162,10 +164,10 @@ describe("normalizeBuildFile", () => { normalizeBuildFile(src, out, { name: "sentry-gradle-plugin", version: "4.12.0", - }) + }), ); const metadata = new TextDecoder().decode( - unzipSync(zip)[".sentry-cli-metadata.txt"] + unzipSync(zip)[".sentry-cli-metadata.txt"], ); expect(metadata).toContain("sentry-gradle-plugin: 4.12.0"); }); @@ -173,10 +175,10 @@ describe("normalizeBuildFile", () => { test("is deterministic (identical input → identical bytes)", async () => { const src = writeTmpFile("app.apk", fakeApk()); const a = await normalizeToBuffer((out) => - normalizeBuildFile(src, out, null) + normalizeBuildFile(src, out, null), ); const b = await normalizeToBuffer((out) => - normalizeBuildFile(src, out, null) + normalizeBuildFile(src, out, null), ); expect(a.equals(b)).toBe(true); }); @@ -204,14 +206,14 @@ describe("normalizeBuildDirectory", () => { writeFileSync(join(xc, "Info.plist"), ""); writeFileSync( join(xc, "Products", "Applications", "MyApp.app", "MyApp"), - "binary" + "binary", ); return xc; } test("zips files under the directory basename plus a root metadata file", async () => { const zip = await normalizeToBuffer((out) => - normalizeBuildDirectory(fakeXcarchive(), out, null) + normalizeBuildDirectory(fakeXcarchive(), out, null), ); const entries = unzipSync(zip); expect(Object.keys(entries).sort()).toEqual([ @@ -225,10 +227,10 @@ describe("normalizeBuildDirectory", () => { test("is deterministic (identical tree → identical bytes)", async () => { const xc = fakeXcarchive(); const a = await normalizeToBuffer((out) => - normalizeBuildDirectory(xc, out, null) + normalizeBuildDirectory(xc, out, null), ); const b = await normalizeToBuffer((out) => - normalizeBuildDirectory(xc, out, null) + normalizeBuildDirectory(xc, out, null), ); expect(a.equals(b)).toBe(true); }); @@ -245,17 +247,19 @@ describe("normalizeBuildDirectory", () => { symlinkSync("real.txt", join(xc, "link.txt")); const entries = unzipSync( - await normalizeToBuffer((out) => normalizeBuildDirectory(xc, out, null)) + await normalizeToBuffer((out) => + normalizeBuildDirectory(xc, out, null), + ), ); // The symlink entry stores its target path — proof it was NOT followed // (following would store "REAL", the target's file content). expect(new TextDecoder().decode(entries["App.xcarchive/link.txt"])).toBe( - "real.txt" + "real.txt", ); expect(new TextDecoder().decode(entries["App.xcarchive/real.txt"])).toBe( - "REAL" + "REAL", ); - } + }, ); }); @@ -320,7 +324,7 @@ describe("extractIpaAppName", () => { extractIpaAppName([ "Payload/MyApp.app/Info.plist", "Payload/MyApp.app/MyApp", - ]) + ]), ).toBe("MyApp"); }); @@ -333,7 +337,7 @@ describe("extractIpaAppName", () => { extractIpaAppName([ "Payload/A.app/Info.plist", "Payload/B.app/Info.plist", - ]) + ]), ).toThrow("exactly one"); }); }); @@ -366,12 +370,12 @@ describe("normalizeIpa", () => { "archive.xcarchive/Products/Applications/MyApp.app/MyApp", ]); const plist = new TextDecoder().decode( - entries["archive.xcarchive/Info.plist"] + entries["archive.xcarchive/Info.plist"], ); expect(plist).toContain("Applications/MyApp.app"); // Assets.car is carried through verbatim (not parsed). expect( - entries["archive.xcarchive/Products/Applications/MyApp.app/Assets.car"] + entries["archive.xcarchive/Products/Applications/MyApp.app/Assets.car"], ).toEqual(strToU8("carbytes")); }); @@ -381,13 +385,13 @@ describe("normalizeIpa", () => { zipSync({ "Payload/MyApp.app/Info.plist": strToU8(""), "Payload/MyApp.app/Frameworks/X.framework/X": strToU8("fw"), - }) - ) + }), + ), ); expect( entries[ "archive.xcarchive/Products/Applications/MyApp.app/Frameworks/X.framework/X" - ] + ], ).toEqual(strToU8("fw")); }); @@ -401,15 +405,13 @@ describe("normalizeIpa", () => { // A stray second .app (no Info.plist so extractIpaAppName still // sees one) must not be bundled. "Payload/Stray.app/junk": strToU8("junk"), - }) - ) - ) + }), + ), + ), ); expect(names.some((n) => n.includes("Stray"))).toBe(false); expect( - names.includes( - "archive.xcarchive/Products/Applications/MyApp.app/MyApp" - ) + names.includes("archive.xcarchive/Products/Applications/MyApp.app/MyApp"), ).toBe(true); }); @@ -420,9 +422,9 @@ describe("normalizeIpa", () => { zipSync({ "Payload/MyApp.app/Info.plist": strToU8(""), "Payload/MyApp.app/../../evil": strToU8("x"), - }) - ) - ) + }), + ), + ), ); expect(names.some((n) => n.includes("evil"))).toBe(false); }); @@ -435,9 +437,12 @@ describe("normalizeIpa", () => { }); test("throws when the IPA has no single .app", async () => { - const src = writeTmpFile("bad.ipa", zipSync({ "readme.txt": strToU8("x") })); + const src = writeTmpFile( + "bad.ipa", + zipSync({ "readme.txt": strToU8("x") }), + ); await expect( - normalizeToBuffer((out) => normalizeIpa(src, out, null)) + normalizeToBuffer((out) => normalizeIpa(src, out, null)), ).rejects.toThrow("exactly one"); }); }); diff --git a/packages/cli/test/lib/build/vcs.test.ts b/packages/cli/test/lib/build/vcs.test.ts index 9ab2ca2c3..a2ce813fe 100644 --- a/packages/cli/test/lib/build/vcs.test.ts +++ b/packages/cli/test/lib/build/vcs.test.ts @@ -68,7 +68,7 @@ describe("vcsInfoToBody", () => { vcsProvider: "github", headRepoName: "o/r", prNumber: 5, - }) + }), ).toEqual({ head_sha: "h", provider: "github", @@ -93,7 +93,7 @@ describe("collectVcsMetadata", () => { { "head-sha": sha.toUpperCase(), "head-ref": "main", "pr-number": 7 }, "/repo", {}, - false + false, ); expect(vcs.headSha).toBe(sha); // normalized to lowercase @@ -131,7 +131,7 @@ describe("collectVcsMetadata", () => { {}, "/repo", { GITHUB_EVENT_NAME: "pull_request", GITHUB_BASE_REF: "main" }, - true + true, ); expect(vcs.headSha).toBe("c".repeat(40)); @@ -149,14 +149,14 @@ describe("collectVcsMetadata", () => { eventPath, JSON.stringify({ pull_request: { head: { sha: headSha }, base: { sha: baseSha } }, - }) + }), ); const vcs = collectVcsMetadata( {}, "/repo", { GITHUB_EVENT_NAME: "pull_request", GITHUB_EVENT_PATH: eventPath }, - true + true, ); // Event payload wins over `git rev-parse HEAD` / merge-base. @@ -174,7 +174,7 @@ describe("collectVcsMetadata", () => { test("rejects a malformed --head-sha", () => { expect(() => - collectVcsMetadata({ "head-sha": "not-a-sha" }, "/repo", {}, false) + collectVcsMetadata({ "head-sha": "not-a-sha" }, "/repo", {}, false), ).toThrow(ValidationError); }); @@ -184,7 +184,7 @@ describe("collectVcsMetadata", () => { {}, "/repo", { GITHUB_EVENT_NAME: "push" }, - true + true, ); expect(vcs.baseSha).toBe("d".repeat(40)); }); @@ -195,23 +195,25 @@ describe("collectVcsMetadata", () => { { "base-ref": "release/2.0" }, "/repo", {}, - true + true, ); expect(vcs.baseRef).toBe("release/2.0"); expect(vcs.baseSha).toBe("e".repeat(40)); expect(gitMock.getMergeBase).toHaveBeenCalledWith( "origin/release/2.0", - "/repo" + "/repo", ); }); test("derives provider from an SCP-style remote and GHE hosts", () => { gitMock.getRemoteUrl.mockReturnValueOnce("git@github.com:acme/app.git"); - expect(collectVcsMetadata({}, "/repo", {}, true).vcsProvider).toBe("github"); + expect(collectVcsMetadata({}, "/repo", {}, true).vcsProvider).toBe( + "github", + ); gitMock.getRemoteUrl.mockReturnValueOnce("https://acme.ghe.com/o/r.git"); expect(collectVcsMetadata({}, "/repo", {}, true).vcsProvider).toBe( - "github_enterprise" + "github_enterprise", ); }); }); diff --git a/packages/cli/test/lib/build/zip-writer.mocked.test.ts b/packages/cli/test/lib/build/zip-writer.mocked.test.ts index d2c3e8a1c..5026fea52 100644 --- a/packages/cli/test/lib/build/zip-writer.mocked.test.ts +++ b/packages/cli/test/lib/build/zip-writer.mocked.test.ts @@ -32,10 +32,10 @@ test("writes valid CRCs when zlib.crc32 is unavailable", async () => { const entries = unzipSync(await readFile(outputPath)); expect(Buffer.from(entries["memory.txt"])).toEqual( - Buffer.from("in-memory contents") + Buffer.from("in-memory contents"), ); expect(Buffer.from(entries["streamed.txt"])).toEqual( - Buffer.from("streamed contents") + Buffer.from("streamed contents"), ); } finally { await rm(tmpDir, { recursive: true, force: true }); diff --git a/packages/cli/test/lib/build/zip-writer.test.ts b/packages/cli/test/lib/build/zip-writer.test.ts index e67ebbe72..2a364b16e 100644 --- a/packages/cli/test/lib/build/zip-writer.test.ts +++ b/packages/cli/test/lib/build/zip-writer.test.ts @@ -10,12 +10,7 @@ * multi-byte names, and Unix symlink/permission attributes. */ -import { - mkdtempSync, - readFileSync, - rmSync, - writeFileSync, -} from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { strToU8, unzipSync, zipSync } from "fflate"; @@ -135,13 +130,13 @@ describe("DeterministicZipWriter byte-parity with zipSync", () => { for (const [label, entries] of cases) { test(`addData matches zipSync for ${label}`, async () => { expect((await writeWithData(entries)).equals(referenceZip(entries))).toBe( - true + true, ); }); test(`addFile matches zipSync for ${label}`, async () => { expect( - (await writeWithFiles(entries)).equals(referenceZip(entries)) + (await writeWithFiles(entries)).equals(referenceZip(entries)), ).toBe(true); }); } @@ -156,7 +151,7 @@ describe("DeterministicZipWriter round-trips", () => { const entries = unzipSync(zip); expect(new TextDecoder().decode(entries["one.txt"])).toBe("first"); expect(new TextDecoder().decode(entries["dir/two.bin"])).toBe( - "second value" + "second value", ); }); @@ -170,8 +165,8 @@ describe("DeterministicZipWriter round-trips", () => { const fileZip = await writeWithFiles([{ name: "big.bin", data: big }]); // Both entry paths must agree and extract to the original bytes. expect(fileZip.equals(dataZip)).toBe(true); - expect(Buffer.from(unzipSync(fileZip)["big.bin"]).equals(Buffer.from(big))).toBe( - true - ); + expect( + Buffer.from(unzipSync(fileZip)["big.bin"]).equals(Buffer.from(big)), + ).toBe(true); }); }); diff --git a/packages/cli/test/lib/cache-hint.test.ts b/packages/cli/test/lib/cache-hint.test.ts index 35f2565d9..644579e65 100644 --- a/packages/cli/test/lib/cache-hint.test.ts +++ b/packages/cli/test/lib/cache-hint.test.ts @@ -95,7 +95,7 @@ describe("appendCacheHint", () => { test("returns just the cache hint when no existing hint", () => { setLastCacheHitAgeForTesting(180_000); expect(appendCacheHint(undefined)).toBe( - "cached · 3m ago · use -f to refresh" + "cached · 3m ago · use -f to refresh", ); clearLastCacheHitAge(); }); @@ -103,7 +103,7 @@ describe("appendCacheHint", () => { test("joins existing and cache hints with ' | ' separator", () => { setLastCacheHitAgeForTesting(180_000); expect(appendCacheHint("Showing 5 issues")).toBe( - "Showing 5 issues | cached · 3m ago · use -f to refresh" + "Showing 5 issues | cached · 3m ago · use -f to refresh", ); clearLastCacheHitAge(); }); diff --git a/packages/cli/test/lib/cache-keys.test.ts b/packages/cli/test/lib/cache-keys.test.ts index ed49bd76a..ccb264f25 100644 --- a/packages/cli/test/lib/cache-keys.test.ts +++ b/packages/cli/test/lib/cache-keys.test.ts @@ -16,7 +16,7 @@ function computeInvalidationPrefixes(fullUrl: string): string[] { describe("computeInvalidationPrefixes — hierarchy walk", () => { test("detail URL yields self + ancestors down to owner", () => { const prefixes = computeInvalidationPrefixes( - `${BASE}organizations/acme/issues/12345/` + `${BASE}organizations/acme/issues/12345/`, ); expect(prefixes).toContain(`${BASE}organizations/acme/issues/12345/`); expect(prefixes).toContain(`${BASE}organizations/acme/issues/`); @@ -28,10 +28,10 @@ describe("computeInvalidationPrefixes — hierarchy walk", () => { test("deeply nested path yields every ancestor down to owner", () => { const prefixes = computeInvalidationPrefixes( - `${BASE}organizations/acme/releases/1.0.0/deploys/` + `${BASE}organizations/acme/releases/1.0.0/deploys/`, ); expect(prefixes).toContain( - `${BASE}organizations/acme/releases/1.0.0/deploys/` + `${BASE}organizations/acme/releases/1.0.0/deploys/`, ); expect(prefixes).toContain(`${BASE}organizations/acme/releases/1.0.0/`); expect(prefixes).toContain(`${BASE}organizations/acme/releases/`); @@ -54,17 +54,17 @@ describe("computeInvalidationPrefixes — hierarchy walk", () => { test("query string is stripped from the prefix set", () => { const prefixes = computeInvalidationPrefixes( - `${BASE}organizations/acme/issues/12345/?collapse=stats&collapse=lifetime` + `${BASE}organizations/acme/issues/12345/?collapse=stats&collapse=lifetime`, ); expect(prefixes).toContain(`${BASE}organizations/acme/issues/12345/`); expect( - prefixes.every((p) => !(p.includes("collapse=") || p.includes("?"))) + prefixes.every((p) => !(p.includes("collapse=") || p.includes("?"))), ).toBe(true); }); test("trailing-slashless URL still works", () => { const prefixes = computeInvalidationPrefixes( - `${BASE}organizations/acme/issues` + `${BASE}organizations/acme/issues`, ); expect(prefixes).toContain(`${BASE}organizations/acme/issues/`); expect(prefixes).toContain(`${BASE}organizations/acme/`); @@ -74,7 +74,7 @@ describe("computeInvalidationPrefixes — hierarchy walk", () => { describe("computeInvalidationPrefixes — cross-endpoint rules", () => { test("POST /teams/{org}/{team}/projects/ invalidates org project list", () => { const prefixes = computeInvalidationPrefixes( - `${BASE}teams/acme/backend/projects/` + `${BASE}teams/acme/backend/projects/`, ); expect(prefixes).toContain(`${BASE}organizations/acme/projects/`); expect(prefixes).toContain(`${BASE}teams/acme/backend/projects/`); @@ -85,7 +85,7 @@ describe("computeInvalidationPrefixes — cross-endpoint rules", () => { test("DELETE /projects/{org}/{project}/ invalidates org project list", () => { const prefixes = computeInvalidationPrefixes( - `${BASE}projects/acme/frontend/` + `${BASE}projects/acme/frontend/`, ); expect(prefixes).toContain(`${BASE}organizations/acme/projects/`); expect(prefixes).toContain(`${BASE}projects/acme/frontend/`); @@ -99,7 +99,7 @@ describe("computeInvalidationPrefixes — cross-endpoint rules", () => { // URL at a DIFFERENT origin. Without a cross-origin rule, stale // legacy cache entries survive org-scoped mutations. const prefixes = computeInvalidationPrefixes( - `${BASE}organizations/acme/issues/12345/` + `${BASE}organizations/acme/issues/12345/`, ); expect(prefixes).toContain(`${API_BASE_URL}/api/0/issues/12345/`); // Plus the hierarchy walk under the mutation's own origin: @@ -109,7 +109,7 @@ describe("computeInvalidationPrefixes — cross-endpoint rules", () => { test("unrelated paths get no cross-endpoint sweep", () => { const prefixes = computeInvalidationPrefixes( - `${BASE}organizations/acme/teams/` + `${BASE}organizations/acme/teams/`, ); expect(prefixes).not.toContain(`${BASE}organizations/acme/projects/`); expect(prefixes).toContain(`${BASE}organizations/acme/teams/`); @@ -119,10 +119,10 @@ describe("computeInvalidationPrefixes — cross-endpoint rules", () => { describe("computeInvalidationPrefixes — edge cases", () => { test("returns [] for URLs not under /api/0/", () => { expect( - computeInvalidationPrefixes("https://example.com/some/path/") + computeInvalidationPrefixes("https://example.com/some/path/"), ).toEqual([]); expect( - computeInvalidationPrefixes("https://uploads.sentry.io/x/y") + computeInvalidationPrefixes("https://uploads.sentry.io/x/y"), ).toEqual([]); }); @@ -137,7 +137,7 @@ describe("computeInvalidationPrefixes — edge cases", () => { test("deduplicates prefixes across hierarchy + rule-table", () => { const prefixes = computeInvalidationPrefixes( - `${BASE}teams/acme/backend/projects/` + `${BASE}teams/acme/backend/projects/`, ); expect(prefixes.length).toBe(new Set(prefixes).size); }); @@ -147,24 +147,24 @@ describe("computeInvalidationPrefixes — edge cases", () => { // cacheable state; without this skip, every chunk POST would // sweep the org's cache hierarchy. expect( - computeInvalidationPrefixes(`${BASE}organizations/acme/chunk-upload/`) + computeInvalidationPrefixes(`${BASE}organizations/acme/chunk-upload/`), ).toEqual([]); expect( computeInvalidationPrefixes( - `${BASE}organizations/acme/artifactbundle/assemble/` - ) + `${BASE}organizations/acme/artifactbundle/assemble/`, + ), ).toEqual([]); }); test("self-hosted base URLs are preserved", () => { const prefixes = computeInvalidationPrefixes( - "https://sentry.example.com/api/0/organizations/acme/issues/12345/" + "https://sentry.example.com/api/0/organizations/acme/issues/12345/", ); expect(prefixes).toContain( - "https://sentry.example.com/api/0/organizations/acme/issues/12345/" + "https://sentry.example.com/api/0/organizations/acme/issues/12345/", ); expect(prefixes).toContain( - "https://sentry.example.com/api/0/organizations/acme/" + "https://sentry.example.com/api/0/organizations/acme/", ); }); }); diff --git a/packages/cli/test/lib/checkin-builder.property.test.ts b/packages/cli/test/lib/checkin-builder.property.test.ts index 6716aa7c7..64d36e10d 100644 --- a/packages/cli/test/lib/checkin-builder.property.test.ts +++ b/packages/cli/test/lib/checkin-builder.property.test.ts @@ -28,7 +28,7 @@ const crontabArb = constantFrom( "0 * * * *", "*/5 * * * *", "0 0 * * *", - "30 2 * * 1" + "30 2 * * 1", ); const positiveIntArb = integer({ min: 1, max: 1440 }); @@ -46,7 +46,7 @@ describe("property: buildMonitorConfig", () => { expect(buildMonitorConfig(flags)).toBeUndefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -107,7 +107,7 @@ describe("property: buildMonitorConfig", () => { expect(config?.failure_issue_threshold).toBe(thresholds.failure); expect(config?.recovery_threshold).toBe(thresholds.recovery); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -131,9 +131,9 @@ describe("property: buildCheckIn", () => { expect(checkIn.check_in_id).toBe(checkInId); expect(checkIn.monitor_slug).toBe(monitorSlug); expect(checkIn.status).toBe(status); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/lib/cli-startup.test.ts b/packages/cli/test/lib/cli-startup.test.ts index 96c66f175..59af923e7 100644 --- a/packages/cli/test/lib/cli-startup.test.ts +++ b/packages/cli/test/lib/cli-startup.test.ts @@ -1,6 +1,6 @@ import { expect, test, vi } from "vitest"; import { startCli } from "../../src/cli.js"; -// biome-ignore lint/performance/noNamespaceImport: spy on the startup dependency +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spy on the startup dependency import * as upgrade from "../../src/lib/upgrade.js"; import { useTestConfigDir } from "../helpers.js"; @@ -16,7 +16,7 @@ test("fatal errors preserve their name while redacting credentials", async () => .spyOn(upgrade, "startCleanupOldBinary") .mockImplementation(() => { throw new TypeError( - 'Headers.set: "Bearer SYNTHETIC_PREFIX\nSYNTHETIC_SECRET" is an invalid header value.' + 'Headers.set: "Bearer SYNTHETIC_PREFIX\nSYNTHETIC_SECRET" is an invalid header value.', ); }); @@ -26,7 +26,7 @@ test("fatal errors preserve their name while redacting credentials", async () => expect(cleanup).toHaveBeenCalledOnce(); expect(stderr).toHaveBeenLastCalledWith( - 'Fatal: TypeError: Headers.set: "Bearer [REDACTED]" is an invalid header value.\n' + 'Fatal: TypeError: Headers.set: "Bearer [REDACTED]" is an invalid header value.\n', ); expect(process.exitCode).toBe(1); } finally { diff --git a/packages/cli/test/lib/command-suggestions.test.ts b/packages/cli/test/lib/command-suggestions.test.ts index 8dc8c067c..a6f742f94 100644 --- a/packages/cli/test/lib/command-suggestions.test.ts +++ b/packages/cli/test/lib/command-suggestions.test.ts @@ -29,13 +29,13 @@ describe("getCommandSuggestion", () => { test("suggests issue view for 'issue/details'", () => { expect(getCommandSuggestion("issue", "details")?.command).toContain( - "issue view" + "issue view", ); }); test("suggests issue view for 'issue/info'", () => { expect(getCommandSuggestion("issue", "info")?.command).toContain( - "issue view" + "issue view", ); }); @@ -67,13 +67,13 @@ describe("getCommandSuggestion", () => { // --- Pattern 5: old sentry-cli commands --- test("suggests auth status for 'cli/info'", () => { expect(getCommandSuggestion("cli", "info")?.command).toContain( - "auth status" + "auth status", ); }); test("suggests issue list for 'cli/issues'", () => { expect(getCommandSuggestion("cli", "issues")?.command).toContain( - "issue list" + "issue list", ); }); @@ -83,14 +83,14 @@ describe("getCommandSuggestion", () => { test("suggests send-event for 'cli/send-event'", () => { expect(getCommandSuggestion("cli", "send-event")?.command).toContain( - "sentry event send" + "sentry event send", ); }); // --- Pattern 6: dashboard synonyms --- test("suggests dashboard list for 'dashboard/default-overview'", () => { expect( - getCommandSuggestion("dashboard", "default-overview")?.command + getCommandSuggestion("dashboard", "default-overview")?.command, ).toContain("dashboard list"); }); @@ -123,9 +123,9 @@ describe("routes with defaultCommand", () => { .filter( (e) => isRouteMap(e.target as unknown) && - (e.target as unknown as RouteMap).getDefaultCommand?.() + (e.target as unknown as RouteMap).getDefaultCommand?.(), ) - .map((e) => e.name.original) + .map((e) => e.name.original), ); test("all route groups with a view subcommand have defaultCommand set", () => { diff --git a/packages/cli/test/lib/command.test.ts b/packages/cli/test/lib/command.test.ts index a251151e5..4b269c9e5 100644 --- a/packages/cli/test/lib/command.test.ts +++ b/packages/cli/test/lib/command.test.ts @@ -7,7 +7,7 @@ */ import { setTimeout as sleep } from "node:timers/promises"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { buildApplication, @@ -92,7 +92,7 @@ test("derives prompt availability from parsed flags instead of ambiguous aliases flags: { yes: YES_FLAG, "dry-run": DRY_RUN_FLAG }, aliases: { y: "yes", n: "dry-run" }, }, - // biome-ignore lint/correctness/useYield: test command only observes wrapper state + // oxlint-disable-next-line require-yield -- test command only observes wrapper state async *func() { promptStates.push(interactivePromptsAllowed()); }, @@ -117,7 +117,7 @@ test("derives prompt availability from parsed flags instead of ambiguous aliases }, aliases: { n: "limit", y: "row" }, }, - // biome-ignore lint/correctness/useYield: test command only observes wrapper state + // oxlint-disable-next-line require-yield -- test command only observes wrapper state async *func() { promptStates.push(interactivePromptsAllowed()); }, @@ -197,7 +197,7 @@ describe("buildCommand", () => { }); expect( - (command as unknown as { __primaryUsage?: string }).__primaryUsage + (command as unknown as { __primaryUsage?: string }).__primaryUsage, ).toBe(":..."); }); @@ -242,10 +242,10 @@ describe("buildCommand telemetry integration", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { verbose: boolean; limit: number } + flags: { verbose: boolean; limit: number }, ) { calledWith = flags; }, @@ -284,7 +284,7 @@ describe("buildCommand telemetry integration", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(this: TestContext, flags: { limit: number }) { receivedFlags = flags as unknown as Record; }, @@ -331,7 +331,7 @@ describe("buildCommand telemetry integration", () => { // Should not set tag for default false boolean const flagCalls = setTagSpy.mock.calls.filter( - (call) => typeof call[0] === "string" && call[0].startsWith("flag.") + (call) => typeof call[0] === "string" && call[0].startsWith("flag."), ); expect(flagCalls).toHaveLength(0); }); @@ -348,11 +348,11 @@ describe("buildCommand telemetry integration", () => { parameters: [{ brief: "Issue ID", parse: String }], }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, _flags: Record, - issueId: string + issueId: string, ) { calledArgs = issueId; }, @@ -381,7 +381,7 @@ describe("buildCommand telemetry integration", () => { auth: false, docs: { brief: "Test" }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(this: TestContext) { // Verify 'this' is correctly bound to context capturedStdout = typeof this.process.stdout.write === "function"; @@ -416,7 +416,7 @@ describe("buildCommand telemetry integration", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(_flags: { delay: number }) { await sleep(1); executed = true; @@ -550,7 +550,7 @@ describe("buildCommand", () => { json: { kind: "boolean", brief: "JSON output", default: false }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(this: TestContext, flags: { json: boolean }) { calledFlags = flags as unknown as Record; }, @@ -655,7 +655,7 @@ describe("buildCommand", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(this: TestContext, flags: { limit: number }) { receivedFlags = flags as unknown as Record; }, @@ -671,7 +671,7 @@ describe("buildCommand", () => { await run( app, ["test", "--verbose", "--log-level", "debug", "--limit", "50"], - ctx as TestContext + ctx as TestContext, ); expect(receivedFlags).toBeDefined(); @@ -706,10 +706,10 @@ describe("buildCommand", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { verbose: boolean; silent: boolean } + flags: { verbose: boolean; silent: boolean }, ) { receivedFlags = flags as unknown as Record; }, @@ -725,7 +725,7 @@ describe("buildCommand", () => { await run( app, ["test", "--verbose", "--log-level", "trace"], - ctx as TestContext + ctx as TestContext, ); // Command's own --verbose is passed through (not stripped) @@ -833,10 +833,10 @@ describe("buildCommand output config", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { json: boolean; fields?: string[] } + flags: { json: boolean; fields?: string[] }, ) { receivedFlags = flags as unknown as Record; }, @@ -868,10 +868,10 @@ describe("buildCommand output config", () => { docs: { brief: "Test" }, output: { human: () => "unused" }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { json: boolean; fields?: string[] } + flags: { json: boolean; fields?: string[] }, ) { receivedFlags = flags as unknown as Record; }, @@ -887,7 +887,7 @@ describe("buildCommand output config", () => { await run( app, ["test", "--json", "--fields", "id,title,status"], - ctx as TestContext + ctx as TestContext, ); expect(receivedFlags).toBeDefined(); @@ -908,10 +908,10 @@ describe("buildCommand output config", () => { docs: { brief: "Test" }, output: { human: () => "unused" }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { json: boolean; fields?: string[] } + flags: { json: boolean; fields?: string[] }, ) { receivedFlags = flags as unknown as Record; }, @@ -927,7 +927,7 @@ describe("buildCommand output config", () => { await run( app, ["test", "--fields", " id , title , id "], - ctx as TestContext + ctx as TestContext, ); expect(receivedFlags).toBeDefined(); @@ -947,10 +947,10 @@ describe("buildCommand output config", () => { docs: { brief: "Test" }, output: { human: () => "unused" }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { json: boolean; fields?: string[] } + flags: { json: boolean; fields?: string[] }, ) { receivedFlags = flags as unknown as Record; }, @@ -978,7 +978,7 @@ describe("buildCommand output config", () => { auth: false, docs: { brief: "Test" }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func() { funcCalled = true; }, @@ -996,7 +996,7 @@ describe("buildCommand output config", () => { expect(funcCalled).toBe(false); expect( - ctx.errors.some((s) => s.includes("No flag registered for --json")) + ctx.errors.some((s) => s.includes("No flag registered for --json")), ).toBe(true); }); @@ -1021,10 +1021,10 @@ describe("buildCommand output config", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { json: boolean; fields?: string[] } + flags: { json: boolean; fields?: string[] }, ) { receivedFlags = flags as unknown as Record; }, @@ -1057,10 +1057,10 @@ describe("buildCommand output config", () => { docs: { brief: "Test" }, output: { human: () => "unused" }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { json: boolean; fields?: string[] } + flags: { json: boolean; fields?: string[] }, ) { receivedFlags = flags as unknown as Record; }, @@ -1076,7 +1076,7 @@ describe("buildCommand output config", () => { await run( app, ["test", "--fields", "id,metadata.value,contexts.trace.traceId"], - ctx as TestContext + ctx as TestContext, ); expect(receivedFlags).toBeDefined(); @@ -1108,10 +1108,10 @@ describe("buildCommand output config", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func( this: TestContext, - flags: { json: boolean; fields?: string[]; limit: number } + flags: { json: boolean; fields?: string[]; limit: number }, ) { receivedFlags = flags as unknown as Record; }, @@ -1127,7 +1127,7 @@ describe("buildCommand output config", () => { await run( app, ["test", "--json", "--fields", "id", "--limit", "50", "--verbose"], - ctx as TestContext + ctx as TestContext, ); expect(receivedFlags).toBeDefined(); @@ -1230,7 +1230,7 @@ describe("buildCommand return-based output", () => { await run( app, ["test", "--json", "--fields", "id,name"], - ctx as TestContext + ctx as TestContext, ); const jsonOutput = JSON.parse(ctx.output.join("")); @@ -1295,7 +1295,7 @@ describe("buildCommand return-based output", () => { human: () => "unused", }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(this: TestContext) { executed = true; // Void return — simulates --web early exit @@ -1454,7 +1454,7 @@ describe("buildCommand return-based output", () => { human: (d: { error: string }) => `Error: ${d.error}`, }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(this: TestContext) { throw new OutputError({ error: "not found" }); }, @@ -1503,7 +1503,7 @@ describe("buildCommand return-based output", () => { human: (d: { error: string }) => `Error: ${d.error}`, }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(this: TestContext) { throw new OutputError({ error: "not found" }); }, @@ -1738,7 +1738,7 @@ describe("buildCommand --org/--project compat flags", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command + // oxlint-disable-next-line require-yield -- test command async *func(this: TestContext, flags: { limit: number }) { receivedFlags = flags as unknown as Record; }, @@ -1754,7 +1754,7 @@ describe("buildCommand --org/--project compat flags", () => { await run( app, ["test", "--org", "sentry", "--project", "cli", "--limit", "50"], - ctx as TestContext + ctx as TestContext, ); expect(receivedFlags).toBeDefined(); @@ -1783,7 +1783,7 @@ describe("buildCommand --org/--project compat flags", () => { }, }, }, - // biome-ignore lint/correctness/useYield: test command + // oxlint-disable-next-line require-yield -- test command async *func(this: TestContext, flags: { project?: string }) { receivedFlags = flags as unknown as Record; }, @@ -1863,7 +1863,7 @@ describe("buildCommand --org/--project compat flags", () => { auth: false, docs: { brief: "Test" }, parameters: {}, - // biome-ignore lint/correctness/useYield: test command — no output to yield + // oxlint-disable-next-line require-yield -- test command — no output to yield async *func(this: TestContext) { resolved = await resolveOrgAndProject({ cwd: "/tmp" }); }, @@ -1879,7 +1879,7 @@ describe("buildCommand --org/--project compat flags", () => { await run( app, ["test", "--org", "flag-org", "--project", "flag-proj"], - ctx as TestContext + ctx as TestContext, ); expect(resolved).not.toBeNull(); diff --git a/packages/cli/test/lib/complete.test.ts b/packages/cli/test/lib/complete.test.ts index 545ad3861..b10bfc6ea 100644 --- a/packages/cli/test/lib/complete.test.ts +++ b/packages/cli/test/lib/complete.test.ts @@ -28,7 +28,7 @@ useTestConfigDir("test-complete-"); // -- Helpers -- async function seedOrgs( - orgs: { slug: string; name: string; regionUrl?: string }[] + orgs: { slug: string; name: string; regionUrl?: string }[], ): Promise { const entries: OrgRegionEntry[] = orgs.map((o) => ({ slug: o.slug, @@ -46,7 +46,7 @@ async function seedProjects( orgSlug: string; projectSlug: string; projectName: string; - }[] + }[], ): Promise { for (const p of projects) { setCachedProject(p.orgId, p.projectId, { @@ -112,7 +112,7 @@ describe("getCompletions: context detection", () => { ]); setProjectAliases( { app: { orgSlug: "acme", projectSlug: "existing" } }, - "fingerprint" + "fingerprint", ); expect(getCompletions(["project", "create"], "")).toEqual([ @@ -132,7 +132,7 @@ describe("getCompletions: context detection", () => { // Completion only ever looks at the current partial — a self-contained // colon pair completes the same way no matter what preceded it. expect( - getCompletions(["project", "create", "api:node"], "worker:java") + getCompletions(["project", "create", "api:node"], "worker:java"), ).toContainEqual({ value: "worker:javascript", description: "Platform", @@ -150,7 +150,7 @@ describe("completeProjectCreateSpec", () => { description: "Platform", }); expect(result.every((completion) => completion.value.includes(":"))).toBe( - true + true, ); }); @@ -170,14 +170,12 @@ describe("completeProjectCreateSpec", () => { expect(completeProjectCreateSpec("My App:java")).toEqual([]); }); - test.each([ - ":java", - "acme/:java", - "/app:java", - "acme/app/child:java", - ])("does not complete a malformed project target in %s", (partial) => { - expect(completeProjectCreateSpec(partial)).toEqual([]); - }); + test.each([":java", "acme/:java", "/app:java", "acme/app/child:java"])( + "does not complete a malformed project target in %s", + (partial) => { + expect(completeProjectCreateSpec(partial)).toEqual([]); + }, + ); }); describe("completeOrgSlugs", () => { @@ -343,7 +341,7 @@ describe("completeAliases", () => { a: { orgSlug: "my-org", projectSlug: "frontend" }, b: { orgSlug: "my-org", projectSlug: "backend" }, }, - "fingerprint" + "fingerprint", ); const result = completeAliases(""); @@ -361,7 +359,7 @@ describe("completeAliases", () => { b: { orgSlug: "org", projectSlug: "proj-b" }, abc: { orgSlug: "org", projectSlug: "proj-abc" }, }, - "fingerprint" + "fingerprint", ); const result = completeAliases("a"); diff --git a/packages/cli/test/lib/completions.property.test.ts b/packages/cli/test/lib/completions.property.test.ts index 098b7aa4e..c320b470f 100644 --- a/packages/cli/test/lib/completions.property.test.ts +++ b/packages/cli/test/lib/completions.property.test.ts @@ -43,7 +43,7 @@ const binaryNameArb = constantFrom( "test-tool", "acme", "dev-helper", - "s" + "s", ); // -- Helpers -- @@ -117,7 +117,7 @@ describe("property: cross-shell consistency", () => { expect(fish).toContain(group.name); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -136,7 +136,7 @@ describe("property: cross-shell consistency", () => { } } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -153,7 +153,7 @@ describe("property: cross-shell consistency", () => { expect(fish).toContain(cmd.name); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -171,7 +171,7 @@ describe("property: binary name parametrization", () => { expect(zsh).toContain(`_${name}()`); expect(fish).toContain(`complete -c ${name}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -210,7 +210,7 @@ describe("proposeCompletions: Stricli integration", () => { const completions = await proposeCompletions( app, [group.name, ""], - completionContext + completionContext, ); const actual = completions.map((c) => c.completion).sort(); const expected = group.subcommands.map((s) => s.name).sort(); @@ -226,7 +226,7 @@ describe("proposeCompletions: Stricli integration", () => { const completions = await proposeCompletions( app, [group.name, ""], - completionContext + completionContext, ); const actual = completions.map((c) => c.completion); // With defaultCommand: "view", Stricli proposes flags for the view @@ -250,7 +250,7 @@ describe("proposeCompletions: Stricli integration", () => { const completions = await proposeCompletions( app, [group.name, prefix], - completionContext + completionContext, ); // Every returned completion should start with the prefix @@ -316,7 +316,7 @@ describe("property: dynamic completion callback", () => { expect(zsh).toContain("__complete"); expect(fish).toContain("__complete"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -348,7 +348,7 @@ _sentry_completions echo "\${COMPREPLY[*]}" `, ], - { stdio: ["pipe", "pipe", "pipe"] } + { stdio: ["pipe", "pipe", "pipe"] }, ); const output = result.stdout.toString().trim(); const completions = output.split(/\s+/); @@ -389,7 +389,7 @@ _sentry_completions echo "\${COMPREPLY[*]}" `, ], - { stdio: ["pipe", "pipe", "pipe"] } + { stdio: ["pipe", "pipe", "pipe"] }, ); const output = result.stdout.toString().trim(); const completions = output.split(/\s+/); diff --git a/packages/cli/test/lib/completions.test.ts b/packages/cli/test/lib/completions.test.ts index 4aaeabef2..4ea3e32b6 100644 --- a/packages/cli/test/lib/completions.test.ts +++ b/packages/cli/test/lib/completions.test.ts @@ -72,7 +72,7 @@ describe("completions", () => { const tree = extractCommandTree(); // At least one group should have subcommands with flags const hasFlags = tree.groups.some((g) => - g.subcommands.some((s) => s.flags.length > 0) + g.subcommands.some((s) => s.flags.length > 0), ); expect(hasFlags).toBe(true); }); @@ -90,7 +90,7 @@ describe("completions", () => { test("returns bash completion path", () => { const path = getCompletionPath("bash", homeDir); expect(path).toBe( - "/home/user/.local/share/bash-completion/completions/sentry" + "/home/user/.local/share/bash-completion/completions/sentry", ); }); @@ -121,7 +121,7 @@ describe("completions", () => { beforeEach(() => { testDir = join( "/tmp", - `completions-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `completions-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -186,7 +186,7 @@ describe("completions", () => { const result = await installCompletions( "bash", "/nonexistent", - join(restrictedDir, "subdir") + join(restrictedDir, "subdir"), ); expect(result).toBeNull(); } finally { @@ -206,7 +206,7 @@ describe("completions", () => { const result = await installCompletions( "zsh", "/nonexistent", - join(lockedParent, "deep", "nested") + join(lockedParent, "deep", "nested"), ); expect(result).toBeNull(); } finally { diff --git a/packages/cli/test/lib/config.test.ts b/packages/cli/test/lib/config.test.ts index 31bd445f4..5237bfb34 100644 --- a/packages/cli/test/lib/config.test.ts +++ b/packages/cli/test/lib/config.test.ts @@ -213,7 +213,7 @@ describe("refreshToken error handling", () => { error: "invalid_grant", error_description: "Token revoked", }), - { status: 400, headers: { "Content-Type": "application/json" } } + { status: 400, headers: { "Content-Type": "application/json" } }, ); try { @@ -340,7 +340,7 @@ describe("DSN-fingerprinted project aliases", () => { { e: { orgSlug: "sentry", projectSlug: "spotlight-electron" }, }, - "123:456,123:789" + "123:456,123:789", ); // Same fingerprint @@ -356,7 +356,7 @@ describe("DSN-fingerprinted project aliases", () => { { e: { orgSlug: "sentry", projectSlug: "spotlight-electron" }, }, - "123:456,123:789" + "123:456,123:789", ); // Different fingerprint (different DSN context) @@ -383,7 +383,7 @@ describe("DSN-fingerprinted project aliases", () => { { e: { orgSlug: "sentry", projectSlug: "spotlight-electron" }, }, - "123:456,123:789" + "123:456,123:789", ); // No current fingerprint provided - skip validation @@ -399,7 +399,7 @@ describe("DSN-fingerprinted project aliases", () => { { e: { orgSlug: "sentry", projectSlug: "spotlight-electron" }, }, - "123:456" + "123:456", ); // Uppercase alias with matching fingerprint @@ -416,7 +416,7 @@ describe("DSN-fingerprinted project aliases", () => { { e: { orgSlug: "sentry", projectSlug: "spotlight-electron" }, }, - "123:456" + "123:456", ); // Current context has no SaaS DSNs (empty fingerprint) @@ -431,7 +431,7 @@ describe("DSN-fingerprinted project aliases", () => { { e: { orgSlug: "sentry", projectSlug: "spotlight-electron" }, }, - "" + "", ); // Current context has SaaS DSNs @@ -446,7 +446,7 @@ describe("DSN-fingerprinted project aliases", () => { { e: { orgSlug: "sentry", projectSlug: "spotlight-electron" }, }, - "" + "", ); // Current context also has only self-hosted DSNs @@ -583,8 +583,8 @@ describe("resolveConfigDir", () => { [CONFIG_DIR_ENV_VAR]: override, XDG_CONFIG_HOME: join(home, "xdg"), }, - home - ) + home, + ), ).toBe(override); }); @@ -604,7 +604,7 @@ describe("resolveConfigDir", () => { test("uses XDG_CONFIG_HOME/sentry when set to an absolute path", () => { const xdg = join(home, "xdg-config"); expect(resolveConfigDir({ XDG_CONFIG_HOME: xdg }, home)).toBe( - join(xdg, "sentry") + join(xdg, "sentry"), ); }); @@ -614,7 +614,7 @@ describe("resolveConfigDir", () => { test("ignores a non-absolute XDG_CONFIG_HOME per the XDG spec", () => { expect(resolveConfigDir({ XDG_CONFIG_HOME: "relative/path" }, home)).toBe( - join(home, ".config", "sentry") + join(home, ".config", "sentry"), ); }); }); @@ -644,7 +644,7 @@ describe("JSON to SQLite migration", () => { organization: "migrated-org", project: "migrated-project", }, - }) + }), ); // Access the database (triggers migration) @@ -663,7 +663,7 @@ describe("JSON to SQLite migration", () => { // config.json should be deleted after migration const configExists = await access(configPath).then( () => true, - () => false + () => false, ); expect(configExists).toBe(false); }); diff --git a/packages/cli/test/lib/constants.property.test.ts b/packages/cli/test/lib/constants.property.test.ts index 6bfebd9e4..2fe2f940c 100644 --- a/packages/cli/test/lib/constants.property.test.ts +++ b/packages/cli/test/lib/constants.property.test.ts @@ -27,13 +27,13 @@ const hostnameArb = array( minLength: 2, maxLength: 12, }).map((chars) => chars.join("")), - { minLength: 2, maxLength: 4 } + { minLength: 2, maxLength: 4 }, ).map((labels) => labels.join(".")); /** Arbitrary for strings that already have a protocol */ const withProtocolArb = oneof( hostnameArb.map((h) => `https://${h}`), - hostnameArb.map((h) => `http://${h}`) + hostnameArb.map((h) => `http://${h}`), ); /** Arbitrary for any input (bare hostname, with protocol, empty, whitespace) */ @@ -41,7 +41,7 @@ const anyInputArb = oneof( hostnameArb, withProtocolArb, constantFrom("", " ", " \t\n"), - string({ minLength: 0, maxLength: 5 }) + string({ minLength: 0, maxLength: 5 }), ); describe("property: normalizeUrl", () => { @@ -53,10 +53,10 @@ describe("property: normalizeUrl", () => { return; } expect( - result.startsWith("https://") || result.startsWith("http://") + result.startsWith("https://") || result.startsWith("http://"), ).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -66,7 +66,7 @@ describe("property: normalizeUrl", () => { const result = normalizeUrl(hostname); expect(result).toBe(`https://${hostname}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -76,7 +76,7 @@ describe("property: normalizeUrl", () => { const result = normalizeUrl(url); expect(result).toBe(url); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -87,7 +87,7 @@ describe("property: normalizeUrl", () => { const twice = normalizeUrl(once); expect(twice).toBe(once); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -103,7 +103,7 @@ describe("property: normalizeUrl", () => { // harmless — the HTTP request will simply fail with a DNS error). expect(result).toStartWith("https://"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/constants.test.ts b/packages/cli/test/lib/constants.test.ts index 0e6e020f3..5fce7714b 100644 --- a/packages/cli/test/lib/constants.test.ts +++ b/packages/cli/test/lib/constants.test.ts @@ -29,7 +29,7 @@ describe("normalizeUrl", () => { test("prepends https:// to bare hostname", () => { expect(normalizeUrl("sentry.example.com")).toBe( - "https://sentry.example.com" + "https://sentry.example.com", ); }); @@ -39,46 +39,46 @@ describe("normalizeUrl", () => { test("prepends https:// to hostname with port", () => { expect(normalizeUrl("sentry.example.com:9000")).toBe( - "https://sentry.example.com:9000" + "https://sentry.example.com:9000", ); }); test("preserves https:// URLs", () => { expect(normalizeUrl("https://sentry.example.com")).toBe( - "https://sentry.example.com" + "https://sentry.example.com", ); }); test("preserves http:// URLs", () => { expect(normalizeUrl("http://sentry.example.com")).toBe( - "http://sentry.example.com" + "http://sentry.example.com", ); }); test("handles case-insensitive protocol", () => { expect(normalizeUrl("HTTPS://sentry.example.com")).toBe( - "HTTPS://sentry.example.com" + "HTTPS://sentry.example.com", ); expect(normalizeUrl("HTTP://sentry.example.com")).toBe( - "HTTP://sentry.example.com" + "HTTP://sentry.example.com", ); }); test("trims whitespace", () => { expect(normalizeUrl(" sentry.example.com ")).toBe( - "https://sentry.example.com" + "https://sentry.example.com", ); expect(normalizeUrl(" https://sentry.example.com ")).toBe( - "https://sentry.example.com" + "https://sentry.example.com", ); }); test("preserves path and trailing slash", () => { expect(normalizeUrl("sentry.example.com/")).toBe( - "https://sentry.example.com/" + "https://sentry.example.com/", ); expect(normalizeUrl("https://sentry.example.com/")).toBe( - "https://sentry.example.com/" + "https://sentry.example.com/", ); }); }); diff --git a/packages/cli/test/lib/context.test.ts b/packages/cli/test/lib/context.test.ts index 2baf37202..5470cda43 100644 --- a/packages/cli/test/lib/context.test.ts +++ b/packages/cli/test/lib/context.test.ts @@ -5,9 +5,9 @@ import { buildContext } from "../../src/context.js"; function deletedCwdError(): NodeJS.ErrnoException { return Object.assign( new Error( - "ENOENT: process.cwd failed with error no such file or directory, the current working directory was likely removed without changing the working directory, uv_cwd" + "ENOENT: process.cwd failed with error no such file or directory, the current working directory was likely removed without changing the working directory, uv_cwd", ), - { code: "ENOENT", errno: -2, syscall: "uv_cwd" } + { code: "ENOENT", errno: -2, syscall: "uv_cwd" }, ); } @@ -27,7 +27,7 @@ describe("buildContext cwd", () => { expect(context.cwd).toBe("/tmp/removed-worktree"); expect(context.forCommand({ prefix: ["sentry"] }).cwd).toBe( - "/tmp/removed-worktree" + "/tmp/removed-worktree", ); }); diff --git a/packages/cli/test/lib/crc32.property.test.ts b/packages/cli/test/lib/crc32.property.test.ts index 656ddf33d..0003f130b 100644 --- a/packages/cli/test/lib/crc32.property.test.ts +++ b/packages/cli/test/lib/crc32.property.test.ts @@ -18,7 +18,7 @@ describe("property: crc32Fallback", () => { property(uint8Array({ maxLength: 4096 }), (data) => { expect(crc32Fallback(data)).toBe(nativeCrc32(data)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -29,7 +29,7 @@ describe("property: crc32Fallback", () => { expect(crc32Fallback(b, crc32Fallback(a))).toBe(crc32Fallback(joined)); expect(crc32Fallback(b, nativeCrc32(a))).toBe(nativeCrc32(joined)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/lib/credential-redaction.test.ts b/packages/cli/test/lib/credential-redaction.test.ts index d35fec976..5730646e3 100644 --- a/packages/cli/test/lib/credential-redaction.test.ts +++ b/packages/cli/test/lib/credential-redaction.test.ts @@ -45,7 +45,7 @@ describe("credential redaction", () => { const diagnostic = 'Headers.set: "Bearer SYNTHETIC_SECRET"; '; const expected = 'Headers.set: "Bearer [REDACTED]"; '; expect(redactCredentialText(diagnostic.repeat(10_000))).toBe( - expected.repeat(10_000) + expected.repeat(10_000), ); }); @@ -68,7 +68,7 @@ describe("credential redaction", () => { const input = `Rejected ${prefix}SYNTHETIC_FIRST${separator}SYNTHETIC_TAIL`; expect(redactCredentialText(input)).not.toContain("SYNTHETIC"); expect( - redactCredentialText(JSON.stringify({ error: input })) + redactCredentialText(JSON.stringify({ error: input })), ).not.toContain("SYNTHETIC"); } }); @@ -115,7 +115,7 @@ describe("credential redaction", () => { test("redacts a quoted header truncated before its closing quote", () => { expect( - redactCredentialText('Headers.set: "Bearer legacy_PAYLOAD\n_SECRET...') + redactCredentialText('Headers.set: "Bearer legacy_PAYLOAD\n_SECRET...'), ).toBe('Headers.set: "Bearer [REDACTED]"'); }); @@ -153,7 +153,7 @@ describe("error output boundaries", () => { expect(error.stack).not.toContain("SYNTHETIC"); expect(error.stderr).not.toContain("SYNTHETIC"); expect(JSON.stringify({ message: error.message, ...error })).not.toContain( - "SYNTHETIC" + "SYNTHETIC", ); }); }); diff --git a/packages/cli/test/lib/custom-ca.test.ts b/packages/cli/test/lib/custom-ca.test.ts index fbd1a0f23..5cb5e1255 100644 --- a/packages/cli/test/lib/custom-ca.test.ts +++ b/packages/cli/test/lib/custom-ca.test.ts @@ -28,19 +28,19 @@ import { useTestConfigDir } from "../helpers.js"; describe("isTlsCertError", () => { test("detects 'unable to get local issuer certificate'", () => { expect( - isTlsCertError(new Error("unable to get local issuer certificate")) + isTlsCertError(new Error("unable to get local issuer certificate")), ).toBe(true); }); test("detects 'unable to verify the first certificate'", () => { expect( - isTlsCertError(new Error("unable to verify the first certificate")) + isTlsCertError(new Error("unable to verify the first certificate")), ).toBe(true); }); test("detects UNABLE_TO_VERIFY_LEAF_SIGNATURE", () => { expect(isTlsCertError(new Error("UNABLE_TO_VERIFY_LEAF_SIGNATURE"))).toBe( - true + true, ); }); @@ -50,7 +50,7 @@ describe("isTlsCertError", () => { test("does NOT detect ERR_TLS_CERT_ALTNAME_INVALID (not a CA trust issue)", () => { expect(isTlsCertError(new Error("ERR_TLS_CERT_ALTNAME_INVALID"))).toBe( - false + false, ); }); @@ -66,9 +66,9 @@ describe("isTlsCertError", () => { expect( isTlsCertError( new Error( - "request to https://sentry.io failed, reason: unable to get local issuer certificate" - ) - ) + "request to https://sentry.io failed, reason: unable to get local issuer certificate", + ), + ), ).toBe(true); }); @@ -84,7 +84,7 @@ describe("isTlsCertError", () => { const wrapper = new TypeError("fetch failed"); wrapper.cause = cause; expect(getTlsCertErrorMessage(wrapper)).toBe( - "unable to get local issuer certificate" + "unable to get local issuer certificate", ); }); @@ -117,7 +117,7 @@ describe("isTlsCertError", () => { test("returns false for non-Error values", () => { expect(isTlsCertError("unable to get local issuer certificate")).toBe( - false + false, ); expect(isTlsCertError(null)).toBe(false); expect(isTlsCertError(undefined)).toBe(false); @@ -380,6 +380,7 @@ describe("customFetch", () => { await customFetch("https://example.com", { headers: { "X-Test": "1" } }); expect(capturedInit).toBeDefined(); expect(capturedInit?.tls).toBeDefined(); + // oxlint-disable-next-line no-unsafe-optional-chaining -- Existing test assumes the optional fixture is present. expect((capturedInit?.tls as { ca: string }).ca).toContain(CERT_PEM); }); diff --git a/packages/cli/test/lib/custom-headers.property.test.ts b/packages/cli/test/lib/custom-headers.property.test.ts index 76f900f6b..0df6ddae2 100644 --- a/packages/cli/test/lib/custom-headers.property.test.ts +++ b/packages/cli/test/lib/custom-headers.property.test.ts @@ -75,7 +75,7 @@ describe("property: parseCustomHeaders", () => { const result = parseCustomHeaders(formatted); expect(result).toEqual([[name, value.trim()]]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -88,7 +88,7 @@ describe("property: parseCustomHeaders", () => { const result = parseCustomHeaders(formatted); expect(result.length).toBe(pairs.length); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -103,7 +103,7 @@ describe("property: parseCustomHeaders", () => { expect(result[i]?.[0]).toBe(pairs[i]?.[0]); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -119,9 +119,9 @@ describe("property: parseCustomHeaders", () => { const formatted = `${name}:${padded}`; const result = parseCustomHeaders(formatted); expect(result[0]?.[1]).toBe(value.trim()); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -134,7 +134,7 @@ describe("property: parseCustomHeaders", () => { expect(result.length).toBe(1); expect(result[0]?.[0]).toBe(name); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/custom-headers.test.ts b/packages/cli/test/lib/custom-headers.test.ts index ec5f87ef7..70ea3628f 100644 --- a/packages/cli/test/lib/custom-headers.test.ts +++ b/packages/cli/test/lib/custom-headers.test.ts @@ -33,7 +33,7 @@ describe("parseCustomHeaders", () => { test("parses multiple headers separated by semicolon", () => { const result = parseCustomHeaders( - "X-First: one; X-Second: two; X-Third: three" + "X-First: one; X-Second: two; X-Third: three", ); expect(result).toEqual([ ["X-First", "one"], @@ -52,7 +52,7 @@ describe("parseCustomHeaders", () => { test("parses mixed semicolon and newline separators", () => { const result = parseCustomHeaders( - "X-First: one; X-Second: two\nX-Third: three" + "X-First: one; X-Second: two\nX-Third: three", ); expect(result).toEqual([ ["X-First", "one"], @@ -104,19 +104,19 @@ describe("parseCustomHeaders", () => { test("throws ConfigError on segment without colon", () => { expect(() => parseCustomHeaders("bad-header-no-colon")).toThrow( - /Expected 'Name: Value' format/ + /Expected 'Name: Value' format/, ); }); test("throws ConfigError on empty header name", () => { expect(() => parseCustomHeaders(": value-only")).toThrow( - /empty header name/ + /empty header name/, ); }); test("throws ConfigError on header name with spaces", () => { expect(() => parseCustomHeaders("Bad Name: value")).toThrow( - /Header names must contain only/ + /Header names must contain only/, ); }); @@ -134,17 +134,17 @@ describe("parseCustomHeaders", () => { for (const header of forbiddenHeaders) { test(`throws ConfigError for forbidden header: ${header}`, () => { expect(() => parseCustomHeaders(`${header}: some-value`)).toThrow( - /Cannot override reserved header/ + /Cannot override reserved header/, ); }); } test("forbidden header check is case-insensitive", () => { expect(() => parseCustomHeaders("AUTHORIZATION: token")).toThrow( - /Cannot override reserved header/ + /Cannot override reserved header/, ); expect(() => parseCustomHeaders("content-type: json")).toThrow( - /Cannot override reserved header/ + /Cannot override reserved header/, ); }); }); @@ -348,19 +348,19 @@ describe("setCustomHeadersOverride", () => { test("throws ConfigError on empty header name", () => { expect(() => setCustomHeadersOverride({ " ": "value" })).toThrow( - "empty header name" + "empty header name", ); }); test("throws ConfigError on invalid header name", () => { expect(() => setCustomHeadersOverride({ "X Bad": "value" })).toThrow( - "Invalid header name 'X Bad' in SentryOptions.headers" + "Invalid header name 'X Bad' in SentryOptions.headers", ); }); test("throws ConfigError on reserved header name", () => { expect(() => setCustomHeadersOverride({ Authorization: "x" })).toThrow( - "Cannot override reserved header 'Authorization' in SentryOptions.headers" + "Cannot override reserved header 'Authorization' in SentryOptions.headers", ); }); }); @@ -403,7 +403,7 @@ describe("applyCustomHeaders", () => { const headers = new Headers({ Accept: "application/json" }); applyCustomHeaders( headers, - "https://sentry.example.com/api/0/organizations/" + "https://sentry.example.com/api/0/organizations/", ); expect(headers.get("X-Test")).toBe("hello"); @@ -417,7 +417,7 @@ describe("applyCustomHeaders", () => { const headers = new Headers({ Accept: "application/json" }); applyCustomHeaders( headers, - "https://sentry.example.com/api/0/organizations/" + "https://sentry.example.com/api/0/organizations/", ); expect(headers.get("Accept")).toBe("application/json"); @@ -448,7 +448,7 @@ describe("applyCustomHeaders", () => { const headers = new Headers({ Accept: "application/json" }); applyCustomHeaders( headers, - "https://evil.example.com/api/0/shared/issues/deadbeef/" + "https://evil.example.com/api/0/shared/issues/deadbeef/", ); expect(headers.get("X-IAP-Token")).toBeNull(); diff --git a/packages/cli/test/lib/db/auth-concurrency.focused.test.ts b/packages/cli/test/lib/db/auth-concurrency.focused.test.ts index 8c91d4a6d..7da6e0dd9 100644 --- a/packages/cli/test/lib/db/auth-concurrency.focused.test.ts +++ b/packages/cli/test/lib/db/auth-concurrency.focused.test.ts @@ -70,7 +70,7 @@ describe("OAuth refresh compare-and-swap", () => { await refreshToken({ force: true, expectedCredential: pinned }); await expect( - refreshToken({ force: true, expectedCredential: pinned }) + refreshToken({ force: true, expectedCredential: pinned }), ).resolves.toMatchObject({ token: "rotated-access-2", refreshed: true, @@ -81,7 +81,7 @@ describe("OAuth refresh compare-and-swap", () => { host: "https://control.example.com", }); await expect( - refreshToken({ force: true, expectedCredential: pinned }) + refreshToken({ force: true, expectedCredential: pinned }), ).rejects.toThrow("Active credentials changed"); expect(requests.count).toBe(2); }); @@ -99,7 +99,7 @@ describe("OAuth refresh compare-and-swap", () => { refresh_token: "late-refresh", expires_in: 3600, token_type: "bearer", - }) + }), ); await expect(pending).rejects.toThrow("Active credentials changed"); expect(getAuthConfig()).toMatchObject({ @@ -119,8 +119,8 @@ describe("OAuth refresh compare-and-swap", () => { response.resolve( Response.json( { error: "invalid_grant", error_description: "Refresh rejected" }, - { status: 400 } - ) + { status: 400 }, + ), ); await expect(pending).rejects.toThrow("refresh credential was rejected"); expect(getAuthConfig()).toMatchObject({ @@ -136,14 +136,14 @@ describe("OAuth refresh compare-and-swap", () => { }); globalThis.fetch = async (input, init) => { expect(new Request(input).url).toBe( - "https://control.example.com/oauth/token/" + "https://control.example.com/oauth/token/", ); expect(init?.redirect).toBe("error"); return new Response("temporary server failure", { status: 503 }); }; await expect(refreshToken({ force: true })).rejects.toThrow( - "Token refresh failed" + "Token refresh failed", ); expect(getAuthConfig()).toMatchObject({ token: "current-access", @@ -158,7 +158,7 @@ describe("OAuth refresh compare-and-swap", () => { expect(() => setAuthToken("replacement-access", undefined, undefined, { host: "https://user:password@evil.example.net", - }) + }), ).toThrow(ConfigError); expect(getAuthConfig()?.token).toBe("current-access"); expect(getCredentialContext()?.host).toBe("https://control.example.com"); diff --git a/packages/cli/test/lib/db/auth.host.test.ts b/packages/cli/test/lib/db/auth.host.test.ts index 5f96236ff..17ad6b67c 100644 --- a/packages/cli/test/lib/db/auth.host.test.ts +++ b/packages/cli/test/lib/db/auth.host.test.ts @@ -86,15 +86,14 @@ describe("db/auth host scoping", () => { // Simulate a pre-v16 row: direct INSERT bypassing setAuthToken const db = getDatabase(); db.query( - "INSERT OR REPLACE INTO auth (id, token, host, updated_at) VALUES (1, 'legacy-token', NULL, ?)" + "INSERT OR REPLACE INTO auth (id, token, host, updated_at) VALUES (1, 'legacy-token', NULL, ?)", ).run(Date.now()); // Simulate the boot ordering: SHELL-exports SENTRY_HOST, then // captureEnvTokenHost snapshots it. Migration reads this snapshot // (not the current env, which could be rc-poisoned by the shim). - const { captureEnvTokenHost, resetEnvTokenHostForTesting } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost, resetEnvTokenHostForTesting } = + await import("../../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); const prevHost = process.env.SENTRY_HOST; process.env.SENTRY_HOST = "https://legacy-configured.example.com"; @@ -122,12 +121,11 @@ describe("db/auth host scoping", () => { test("lazy migration: NULL host + no boot-time env falls back to SaaS", async () => { const db = getDatabase(); db.query( - "INSERT OR REPLACE INTO auth (id, token, host, updated_at) VALUES (1, 'legacy-token', NULL, ?)" + "INSERT OR REPLACE INTO auth (id, token, host, updated_at) VALUES (1, 'legacy-token', NULL, ?)", ).run(Date.now()); - const { captureEnvTokenHost, resetEnvTokenHostForTesting } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost, resetEnvTokenHostForTesting } = + await import("../../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); const prevHost = process.env.SENTRY_HOST; const prevUrl = process.env.SENTRY_URL; @@ -156,12 +154,11 @@ describe("db/auth host scoping", () => { // boot snapshot so rc writes don't affect it. const db = getDatabase(); db.query( - "INSERT OR REPLACE INTO auth (id, token, host, updated_at) VALUES (1, 'legacy-token', NULL, ?)" + "INSERT OR REPLACE INTO auth (id, token, host, updated_at) VALUES (1, 'legacy-token', NULL, ?)", ).run(Date.now()); - const { captureEnvTokenHost, resetEnvTokenHostForTesting } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost, resetEnvTokenHostForTesting } = + await import("../../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); const prevHost = process.env.SENTRY_HOST; const prevUrl = process.env.SENTRY_URL; @@ -215,12 +212,10 @@ describe("db/auth host scoping", () => { // and IAP-protected re-authentication would fail. The anchor is // process-local and overwritten by the NEXT applyLoginUrl, so we // don't need to clear it on logout. - const { isLoginTrustAnchorFor, registerLoginTrustAnchor } = await import( - "../../../src/lib/token-host.js" - ); - const { isTrustedRegionOrigin, registerTrustedRegionUrls } = await import( - "../../../src/lib/db/regions.js" - ); + const { isLoginTrustAnchorFor, registerLoginTrustAnchor } = + await import("../../../src/lib/token-host.js"); + const { isTrustedRegionOrigin, registerTrustedRegionUrls } = + await import("../../../src/lib/db/regions.js"); setAuthToken("tok-A", 3600, "refresh", { host: "https://sentry.host-a.com", }); diff --git a/packages/cli/test/lib/db/auth.property.test.ts b/packages/cli/test/lib/db/auth.property.test.ts index db27388ea..b21935e0a 100644 --- a/packages/cli/test/lib/db/auth.property.test.ts +++ b/packages/cli/test/lib/db/auth.property.test.ts @@ -35,7 +35,7 @@ useEnvSandbox(["SENTRY_AUTH_TOKEN", "SENTRY_TOKEN", "SENTRY_FORCE_ENV_TOKEN"]); /** Arbitrary for non-empty, trimmed token strings */ const tokenArb = string({ minLength: 1, maxLength: 100 }).filter( - (s) => s.trim().length > 0 + (s) => s.trim().length > 0, ); /** Stored tokens must satisfy the persistence boundary; malformed inputs have separate coverage. */ @@ -57,10 +57,10 @@ describe("property: env var priority", () => { expect(getAuthToken()).toBe(authToken.trim()); expect(getAuthConfig()?.source).toBe( - "env:SENTRY_AUTH_TOKEN" satisfies AuthSource + "env:SENTRY_AUTH_TOKEN" satisfies AuthSource, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -76,7 +76,7 @@ describe("property: env var priority", () => { expect(getAuthConfig()?.source).toBe("oauth" satisfies AuthSource); expect(isEnvTokenActive()).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -91,14 +91,14 @@ describe("property: env var priority", () => { resetAuthCaches(); expect(getAuthToken()).toBe(envToken.trim()); expect(getAuthConfig()?.source).toBe( - "env:SENTRY_AUTH_TOKEN" satisfies AuthSource + "env:SENTRY_AUTH_TOKEN" satisfies AuthSource, ); } finally { delete process.env.SENTRY_FORCE_ENV_TOKEN; resetAuthCaches(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -111,7 +111,7 @@ describe("property: env var priority", () => { expect(getAuthToken()).toBe(storedToken); expect(getAuthConfig()?.source).toBe("oauth" satisfies AuthSource); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -129,7 +129,7 @@ describe("property: env tokens never trigger refresh", () => { expect(result.expiresAt).toBeUndefined(); expect(result.expiresIn).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -143,7 +143,7 @@ describe("property: env tokens never trigger refresh", () => { expect(result.token).toBe(envToken.trim()); expect(result.refreshed).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -169,7 +169,7 @@ describe("property: isEnvTokenActive consistency", () => { expect(config.source).toBe("oauth"); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -185,7 +185,7 @@ describe("property: source round-trip", () => { // Verify we can extract the env var name expect(config?.source.slice(4)).toBe("SENTRY_AUTH_TOKEN"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -198,7 +198,7 @@ describe("property: source round-trip", () => { expect(config?.source).toBe("env:SENTRY_TOKEN"); expect(config?.source.slice(4)).toBe("SENTRY_TOKEN"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/db/auth.test.ts b/packages/cli/test/lib/db/auth.test.ts index f2cfa6b5f..708e12fc6 100644 --- a/packages/cli/test/lib/db/auth.test.ts +++ b/packages/cli/test/lib/db/auth.test.ts @@ -129,19 +129,19 @@ describe("env var auth: refreshToken edge cases", () => { }); describe("env var auth: getRawEnvToken", () => { - test.each([ - "SENTRY_AUTH_TOKEN", - "SENTRY_TOKEN", - ] as const)("shares normalization and source selection for %s", (source) => { - process.env[source] = "\x01\u00a0synthetic-token\x7f"; - expect(getRawEnvToken()).toBe("synthetic-token"); - expect(getAuthToken()).toBe("synthetic-token"); - expect(getAuthConfig()).toMatchObject({ - token: "synthetic-token", - source: `env:${source}`, - }); - expect(getActiveEnvVarName()).toBe(source); - }); + test.each(["SENTRY_AUTH_TOKEN", "SENTRY_TOKEN"] as const)( + "shares normalization and source selection for %s", + (source) => { + process.env[source] = "\x01\u00a0synthetic-token\x7f"; + expect(getRawEnvToken()).toBe("synthetic-token"); + expect(getAuthToken()).toBe("synthetic-token"); + expect(getAuthConfig()).toMatchObject({ + token: "synthetic-token", + source: `env:${source}`, + }); + expect(getActiveEnvVarName()).toBe(source); + }, + ); test("keeps a control-only primary credential selected over the alias", () => { process.env.SENTRY_AUTH_TOKEN = "\x01\x7f"; @@ -175,20 +175,18 @@ describe("stored credential validation", () => { }); }); - test.each([ - "prefix\0secret-tail", - "prefix\nsecret-tail", - "", - "\x01\x7f", - ])("rejects a malformed replacement without changing stored credentials %#", (token) => { - setAuthToken("previous-token", 3600, "previous-refresh-token"); - const before = getDatabase().query("SELECT * FROM auth").get(); - expect(() => setAuthToken(token, 60, "new-refresh-token")).toThrow( - MalformedAuthTokenError - ); - expect(getDatabase().query("SELECT * FROM auth").get()).toEqual(before); - expect(getAuthToken()).toBe("previous-token"); - }); + test.each(["prefix\0secret-tail", "prefix\nsecret-tail", "", "\x01\x7f"])( + "rejects a malformed replacement without changing stored credentials %#", + (token) => { + setAuthToken("previous-token", 3600, "previous-refresh-token"); + const before = getDatabase().query("SELECT * FROM auth").get(); + expect(() => setAuthToken(token, 60, "new-refresh-token")).toThrow( + MalformedAuthTokenError, + ); + expect(getDatabase().query("SELECT * FROM auth").get()).toEqual(before); + expect(getAuthToken()).toBe("previous-token"); + }, + ); }); describe("OAuth-preferred auth (#646)", () => { @@ -229,9 +227,8 @@ describe("OAuth-preferred auth (#646)", () => { describe("clearAuth: integration with per-account caches", () => { test("clearAuth drops issue_org_cache entries (prevents cross-account leakage)", async () => { - const { setCachedIssueOrg, getCachedIssueOrg } = await import( - "../../../src/lib/db/issue-org-cache.js" - ); + const { setCachedIssueOrg, getCachedIssueOrg } = + await import("../../../src/lib/db/issue-org-cache.js"); // Seed a mapping as if a previous session resolved this issue. await setAuthToken("test-token"); @@ -465,7 +462,7 @@ describe("hasStoredAuthCredentials memoization", () => { // (simulates a code path the cache doesn't know about). const db = getDatabase(); db.query( - "INSERT OR REPLACE INTO auth (id, token) VALUES (1, 'sneaky')" + "INSERT OR REPLACE INTO auth (id, token) VALUES (1, 'sneaky')", ).run(); // Cache still returns stale false diff --git a/packages/cli/test/lib/db/cache-corruption.test.ts b/packages/cli/test/lib/db/cache-corruption.test.ts index d8eea0144..f712842f3 100644 --- a/packages/cli/test/lib/db/cache-corruption.test.ts +++ b/packages/cli/test/lib/db/cache-corruption.test.ts @@ -23,7 +23,7 @@ function insertPaginationRow(cursorStack: string): void { db.query( `INSERT OR REPLACE INTO pagination_cursors (command_key, context, cursor_stack, page_index, expires_at) - VALUES (?, ?, ?, ?, ?)` + VALUES (?, ?, ?, ?, ?)`, ).run(CMD_KEY, CTX_KEY, cursorStack, 0, Date.now() + 60_000); } @@ -31,7 +31,7 @@ function paginationRowCount(): number { const db = getDatabase(); const row = db .query( - "SELECT COUNT(*) AS n FROM pagination_cursors WHERE command_key = ? AND context = ?" + "SELECT COUNT(*) AS n FROM pagination_cursors WHERE command_key = ? AND context = ?", ) .get(CMD_KEY, CTX_KEY) as { n: number }; return row.n; diff --git a/packages/cli/test/lib/db/concurrent-worker.ts b/packages/cli/test/lib/db/concurrent-worker.ts index 8508c3ce4..534315600 100644 --- a/packages/cli/test/lib/db/concurrent-worker.ts +++ b/packages/cli/test/lib/db/concurrent-worker.ts @@ -20,7 +20,7 @@ const operation = process.argv[4]; if (!(configDir && workerId && operation)) { console.error( - "Usage: tsx concurrent-worker.ts " + "Usage: tsx concurrent-worker.ts ", ); process.exit(1); } @@ -32,12 +32,10 @@ mkdirSync(configDir, { recursive: true }); process.env.SENTRY_CONFIG_DIR = configDir; // Now import db modules (they'll use the env var) -const { setCachedDsn, getCachedDsn } = await import( - "../../../src/lib/db/dsn-cache.js" -); -const { setCachedProject, getCachedProject } = await import( - "../../../src/lib/db/project-cache.js" -); +const { setCachedDsn, getCachedDsn } = + await import("../../../src/lib/db/dsn-cache.js"); +const { setCachedProject, getCachedProject } = + await import("../../../src/lib/db/project-cache.js"); const { closeDatabase } = await import("../../../src/lib/db/index.js"); type WorkerResult = { diff --git a/packages/cli/test/lib/db/concurrent.test.ts b/packages/cli/test/lib/db/concurrent.test.ts index 089baf087..f71d22d80 100644 --- a/packages/cli/test/lib/db/concurrent.test.ts +++ b/packages/cli/test/lib/db/concurrent.test.ts @@ -40,11 +40,11 @@ type WorkerResult = { async function spawnWorker( configDir: string, workerId: string, - operation: string + operation: string, ): Promise { const requireShim = join( import.meta.dirname, - "../../../script/require-shim.mjs" + "../../../script/require-shim.mjs", ); const proc = spawn( process.execPath, @@ -58,7 +58,7 @@ async function spawnWorker( workerId, operation, ], - { stdio: ["pipe", "pipe", "pipe"] } + { stdio: ["pipe", "pipe", "pipe"] }, ); proc.on("error", noop); @@ -72,7 +72,7 @@ async function spawnWorker( }); const exitCode = await new Promise((done) => - proc.on("close", (code) => done(code ?? 1)) + proc.on("close", (code) => done(code ?? 1)), ); if (exitCode !== 0) { @@ -102,7 +102,7 @@ async function spawnWorker( async function spawnWorkersConcurrently( configDir: string, workerCount: number, - operation: string + operation: string, ): Promise { const promises: Promise[] = []; @@ -130,7 +130,7 @@ describe("concurrent database access", () => { const results = await spawnWorkersConcurrently( getConfigDir(), workerCount, - "write-dsn" + "write-dsn", ); // All workers should succeed @@ -153,7 +153,7 @@ describe("concurrent database access", () => { const results = await spawnWorkersConcurrently( getConfigDir(), workerCount, - "write-project" + "write-project", ); // All workers should succeed @@ -175,7 +175,7 @@ describe("concurrent database access", () => { const results = await spawnWorkersConcurrently( getConfigDir(), workerCount, - "read-write" + "read-write", ); // All workers should succeed @@ -201,12 +201,12 @@ describe("concurrent database access", () => { const results = await spawnWorkersConcurrently( getConfigDir(), workerCount, - "write-dsn" + "write-dsn", ); // Check for SQLITE_BUSY errors specifically const busyErrors = results.filter( - (r) => !r.success && r.error?.includes("SQLITE_BUSY") + (r) => !r.success && r.error?.includes("SQLITE_BUSY"), ); expect(busyErrors).toHaveLength(0); diff --git a/packages/cli/test/lib/db/dsn-cache.model-based.test.ts b/packages/cli/test/lib/db/dsn-cache.model-based.test.ts index e2b5dc7c3..b9a2d5bbf 100644 --- a/packages/cli/test/lib/db/dsn-cache.model-based.test.ts +++ b/packages/cli/test/lib/db/dsn-cache.model-based.test.ts @@ -5,7 +5,7 @@ * and verify the system behaves correctly against a simplified model. */ -// biome-ignore-all lint/suspicious/noMisplacedAssertion: Model-based testing uses expect() inside command classes +// Model-based tests use expect() inside command classes, as required by fast-check. import { type AsyncCommand, @@ -111,19 +111,19 @@ const directoryArb = constantFrom( "/home/user/project1", "/home/user/project2", "/tmp/test", - "/var/app" + "/var/app", ); const dsnArb = tuple( constantFrom("abc123", "def456", "xyz789"), constantFrom("12345", "67890", "11111"), - constantFrom("sentry.io", "us.sentry.io", "de.sentry.io") + constantFrom("sentry.io", "us.sentry.io", "de.sentry.io"), ).map(([key, projectId, host]) => `https://${key}@${host}/${projectId}`); const sourceArb = constantFrom( "env" as CachedDsnEntry["source"], "env_file" as CachedDsnEntry["source"], - "code" as CachedDsnEntry["source"] + "code" as CachedDsnEntry["source"], ); const slugArb = constantFrom( @@ -132,7 +132,7 @@ const slugArb = constantFrom( "test-org", "my-project", "backend", - "frontend" + "frontend", ); const projectIdArb = constantFrom("12345", "67890", "11111", "22222"); @@ -145,7 +145,7 @@ const resolvedInfoArb = tuple(slugArb, slugArb, slugArb, slugArb).map( orgName, projectSlug, projectName, - }) + }), ); // DSN Cache Commands @@ -215,9 +215,10 @@ class GetCachedDsnCommand implements AsyncCommand { } } -class UpdateCachedResolutionCommand - implements AsyncCommand -{ +class UpdateCachedResolutionCommand implements AsyncCommand< + CacheModel, + RealCache +> { private readonly directory: string; private readonly resolved: ResolvedInfo; @@ -340,9 +341,10 @@ type DsnKeyInput = { info: ResolvedInfo; }; -class SetCachedProjectByDsnKeyCommand - implements AsyncCommand -{ +class SetCachedProjectByDsnKeyCommand implements AsyncCommand< + CacheModel, + RealCache +> { private readonly input: DsnKeyInput; constructor(input: DsnKeyInput) { @@ -364,9 +366,10 @@ class SetCachedProjectByDsnKeyCommand } } -class GetCachedProjectByDsnKeyCommand - implements AsyncCommand -{ +class GetCachedProjectByDsnKeyCommand implements AsyncCommand< + CacheModel, + RealCache +> { private readonly publicKey: string; constructor(publicKey: string) { @@ -415,7 +418,7 @@ const dsnEntryArb = tuple( projectIdArb, option(orgIdArb, { nil: undefined }), sourceArb, - option(constantFrom(".env", "src/index.ts", "config.py"), { nil: undefined }) + option(constantFrom(".env", "src/index.ts", "config.py"), { nil: undefined }), ).map(([directory, dsn, projectId, orgId, source, sourcePath]) => ({ directory, dsn, @@ -426,41 +429,41 @@ const dsnEntryArb = tuple( })); const setCachedDsnCmdArb = dsnEntryArb.map( - (entry) => new SetCachedDsnCommand(entry) + (entry) => new SetCachedDsnCommand(entry), ); const getCachedDsnCmdArb = directoryArb.map( - (dir) => new GetCachedDsnCommand(dir) + (dir) => new GetCachedDsnCommand(dir), ); const updateResolutionCmdArb = tuple(directoryArb, resolvedInfoArb).map( - ([dir, resolved]) => new UpdateCachedResolutionCommand(dir, resolved) + ([dir, resolved]) => new UpdateCachedResolutionCommand(dir, resolved), ); const clearDsnCacheCmdArb = option(directoryArb, { nil: undefined }).map( - (dir) => new ClearDsnCacheCommand(dir) + (dir) => new ClearDsnCacheCommand(dir), ); const setCachedProjectCmdArb = tuple( orgIdArb, projectIdArb, - resolvedInfoArb + resolvedInfoArb, ).map( ([orgId, projectId, info]) => - new SetCachedProjectCommand({ orgId, projectId, info }) + new SetCachedProjectCommand({ orgId, projectId, info }), ); const getCachedProjectCmdArb = tuple(orgIdArb, projectIdArb).map( - ([orgId, projectId]) => new GetCachedProjectCommand({ orgId, projectId }) + ([orgId, projectId]) => new GetCachedProjectCommand({ orgId, projectId }), ); const setCachedProjectByDsnKeyCmdArb = tuple(publicKeyArb, resolvedInfoArb).map( ([publicKey, info]) => - new SetCachedProjectByDsnKeyCommand({ publicKey, info }) + new SetCachedProjectByDsnKeyCommand({ publicKey, info }), ); const getCachedProjectByDsnKeyCmdArb = publicKeyArb.map( - (key) => new GetCachedProjectByDsnKeyCommand(key) + (key) => new GetCachedProjectByDsnKeyCommand(key), ); const clearProjectCacheCmdArb = constant(new ClearProjectCacheCommand()); @@ -502,7 +505,7 @@ describe("model-based: DSN and project cache", () => { { numRuns: DEFAULT_NUM_RUNS, verbose: false, - } + }, ); }); @@ -522,9 +525,9 @@ describe("model-based: DSN and project cache", () => { } finally { cleanup(); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -551,9 +554,9 @@ describe("model-based: DSN and project cache", () => { } finally { cleanup(); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -577,9 +580,9 @@ describe("model-based: DSN and project cache", () => { } finally { cleanup(); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/db/dsn-cache.test.ts b/packages/cli/test/lib/db/dsn-cache.test.ts index e80520cd2..e0932073e 100644 --- a/packages/cli/test/lib/db/dsn-cache.test.ts +++ b/packages/cli/test/lib/db/dsn-cache.test.ts @@ -30,7 +30,7 @@ beforeEach(() => { mkdirSync(join(testProjectDir, "src"), { recursive: true }); writeFileSync( join(testProjectDir, "src/app.ts"), - 'const DSN = "https://abc@o123.ingest.sentry.io/456";' + 'const DSN = "https://abc@o123.ingest.sentry.io/456";', ); }); @@ -232,7 +232,7 @@ describe("disableDsnCache / enableDsnCache", () => { const testDsn = createTestDsn(); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; const { stat } = await import("node:fs/promises"); @@ -291,7 +291,7 @@ describe("getCachedDetection", () => { const testDsn = createTestDsn(); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; @@ -319,7 +319,7 @@ describe("getCachedDetection", () => { const testDsn = createTestDsn(); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; @@ -344,7 +344,7 @@ describe("getCachedDetection", () => { const srcFile = join(testProjectDir, "src/app.ts"); writeFileSync( srcFile, - 'const DSN = "https://changed@o123.ingest.sentry.io/789";' + 'const DSN = "https://changed@o123.ingest.sentry.io/789";', ); const futureTime = new Date(sourceMtimes["src/app.ts"] + 5000); utimesSync(srcFile, futureTime, futureTime); @@ -358,7 +358,7 @@ describe("getCachedDetection", () => { const testDsn = createTestDsn(); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; @@ -387,7 +387,7 @@ describe("getCachedDetection", () => { const testDsn = createTestDsn(); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; @@ -421,7 +421,7 @@ describe("getCachedDetection", () => { const testDsn = createTestDsn(); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; @@ -461,7 +461,7 @@ describe("setCachedDetection", () => { const testDsn = createTestDsn(); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; @@ -488,7 +488,7 @@ describe("setCachedDetection", () => { const dsn2 = createTestDsn({ raw: "https://b@o2.ingest.sentry.io/2" }); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; @@ -531,7 +531,7 @@ describe("setCachedDetection", () => { const dsn2 = createTestDsn({ raw: "https://second@o2.ingest.sentry.io/2" }); const sourceMtimes = { "src/app.ts": Math.floor( - statSync(join(testProjectDir, "src/app.ts")).mtimeMs + statSync(join(testProjectDir, "src/app.ts")).mtimeMs, ), }; diff --git a/packages/cli/test/lib/db/issue-org-cache.test.ts b/packages/cli/test/lib/db/issue-org-cache.test.ts index 6eac53251..dcfde87ee 100644 --- a/packages/cli/test/lib/db/issue-org-cache.test.ts +++ b/packages/cli/test/lib/db/issue-org-cache.test.ts @@ -99,9 +99,8 @@ describe("clearAllIssueOrgCache", () => { // Seed an unrelated metadata entry + an unrelated table row so we can // confirm clearAllIssueOrgCache only drops its own table. const { getDatabase } = await import("../../../src/lib/db/index.js"); - const { getMetadata, setMetadata } = await import( - "../../../src/lib/db/utils.js" - ); + const { getMetadata, setMetadata } = + await import("../../../src/lib/db/utils.js"); setMetadata(getDatabase(), { "unrelated.key": "keep-me" }); setCachedIssueOrg("1", "a"); @@ -109,7 +108,7 @@ describe("clearAllIssueOrgCache", () => { expect(getCachedIssueOrg("1")).toBeUndefined(); expect( - getMetadata(getDatabase(), ["unrelated.key"]).get("unrelated.key") + getMetadata(getDatabase(), ["unrelated.key"]).get("unrelated.key"), ).toBe("keep-me"); }); }); diff --git a/packages/cli/test/lib/db/migration.test.ts b/packages/cli/test/lib/db/migration.test.ts index 745cf5700..322af456f 100644 --- a/packages/cli/test/lib/db/migration.test.ts +++ b/packages/cli/test/lib/db/migration.test.ts @@ -66,42 +66,45 @@ describe("legacy auth migration", () => { ["number", 123], ["null", null], ["object", { value: "synthetic-secret" }], - ])("preserves the original config and migrates other settings for %s", (_, token) => { - const { path, contents } = writeLegacyConfig(token); - - // Opening the DB must remain possible so login/logout can recover. - const db = getDatabase(); - expect(getAuthConfig()).toBeUndefined(); - expect(db.query("SELECT * FROM auth").get()).toBeNull(); - expect(getDefaultOrganization()).toBe("synthetic-org"); - expect(getDefaultProject()).toBe("synthetic-project"); - expect( - db - .query("SELECT org_slug FROM project_cache WHERE cache_key = ?") - .get("synthetic-cache-key") - ).toEqual({ org_slug: "synthetic-org" }); - expect( - getMetadata(db, ["json_migration_completed"]).get( - "json_migration_completed" - ) - ).toBe("true"); - expect(readFileSync(path, "utf8")).toBe(contents); - - const output = stderr.mock.calls.map(([chunk]) => String(chunk)).join(""); - expect(output).toContain( - "Malformed authentication credentials were not migrated" - ); - expect(output).toContain("config.json was kept"); - expect(output).toContain("sentry auth login"); - expect(output).not.toContain("synthetic-secret"); - expect(output).not.toContain("synthetic-legacy-refresh"); - }); + ])( + "preserves the original config and migrates other settings for %s", + (_, token) => { + const { path, contents } = writeLegacyConfig(token); + + // Opening the DB must remain possible so login/logout can recover. + const db = getDatabase(); + expect(getAuthConfig()).toBeUndefined(); + expect(db.query("SELECT * FROM auth").get()).toBeNull(); + expect(getDefaultOrganization()).toBe("synthetic-org"); + expect(getDefaultProject()).toBe("synthetic-project"); + expect( + db + .query("SELECT org_slug FROM project_cache WHERE cache_key = ?") + .get("synthetic-cache-key"), + ).toEqual({ org_slug: "synthetic-org" }); + expect( + getMetadata(db, ["json_migration_completed"]).get( + "json_migration_completed", + ), + ).toBe("true"); + expect(readFileSync(path, "utf8")).toBe(contents); + + const output = stderr.mock.calls.map(([chunk]) => String(chunk)).join(""); + expect(output).toContain( + "Malformed authentication credentials were not migrated", + ); + expect(output).toContain("config.json was kept"); + expect(output).toContain("sentry auth login"); + expect(output).not.toContain("synthetic-secret"); + expect(output).not.toContain("synthetic-legacy-refresh"); + }, + ); test("keeps an existing SQLite session when legacy credentials are invalid", () => { setAuthToken( "synthetic-existing-token", 3600, - "synthetic-existing-refresh" + "synthetic-existing-refresh", ); const before = getAuthConfig(); const db = getDatabase(); diff --git a/packages/cli/test/lib/db/model-based.test.ts b/packages/cli/test/lib/db/model-based.test.ts index a5340a092..955afeaeb 100644 --- a/packages/cli/test/lib/db/model-based.test.ts +++ b/packages/cli/test/lib/db/model-based.test.ts @@ -10,7 +10,7 @@ * - Invariant violations (e.g., clearAuth also clears regions) */ -// biome-ignore-all lint/suspicious/noMisplacedAssertion: Model-based testing uses expect() inside command classes, not directly in test() functions. This is the standard fast-check pattern for stateful testing. +// Model-based tests use expect() inside command classes, as required by fast-check. import { type AsyncCommand, @@ -131,7 +131,7 @@ class SetAuthTokenCommand implements AsyncCommand { constructor( token: string, expiresIn: number | undefined, - refreshToken: string | undefined + refreshToken: string | undefined, ) { this.token = token; this.expiresIn = expiresIn; @@ -214,7 +214,7 @@ class GetAuthConfigCommand implements AsyncCommand { expect(realConfig?.token).toBe(model.auth.token as string); expect(realConfig?.source).toBe("oauth"); expect(realConfig?.refreshToken).toBe( - (model.auth.refreshToken ?? undefined) as string | undefined + (model.auth.refreshToken ?? undefined) as string | undefined, ); if (model.auth.expiresAt !== null) { expect(realConfig?.expiresAt).toBeDefined(); @@ -441,7 +441,7 @@ class SetOrgRegionsCommand implements AsyncCommand { async run(model: DbModel, _real: RealDb): Promise { setOrgRegions( - this.entries.map(([slug, regionUrl]) => ({ slug, regionUrl })) + this.entries.map(([slug, regionUrl]) => ({ slug, regionUrl })), ); for (const [orgSlug, regionUrl] of this.entries) { @@ -490,7 +490,7 @@ class SetProjectAliasesCommand implements AsyncCommand { constructor( aliases: Record, - fingerprint: string | undefined + fingerprint: string | undefined, ) { this.aliases = aliases; this.fingerprint = fingerprint; @@ -513,7 +513,8 @@ class SetProjectAliasesCommand implements AsyncCommand { toString(): string { const aliasStr = Object.entries(this.aliases) .map( - ([a, e]) => `"${a}": {org: "${e.orgSlug}", project: "${e.projectSlug}"}` + ([a, e]) => + `"${a}": {org: "${e.orgSlug}", project: "${e.projectSlug}"}`, ) .join(", "); return `setProjectAliases({${aliasStr}}, ${this.fingerprint ? `"${this.fingerprint}"` : undefined})`; @@ -634,7 +635,7 @@ class GetVersionCheckCommand implements AsyncCommand { expect(realInfo.lastChecked).not.toBeNull(); // Should be within 1 second of expected expect( - Math.abs(realInfo.lastChecked! - model.versionCheck.lastChecked) + Math.abs(realInfo.lastChecked! - model.versionCheck.lastChecked), ).toBeLessThan(1000); } else { expect(realInfo.lastChecked).toBeNull(); @@ -662,7 +663,7 @@ const regionUrlArb = constantFrom( "https://us.sentry.io", "https://de.sentry.io", "https://eu.sentry.io", - "https://sentry.io" + "https://sentry.io", ); /** Generate alias (single letter) */ @@ -671,18 +672,18 @@ const aliasArb = constantFrom(...aliasChars.split("")); /** Generate alias with optional uppercase */ const aliasWithCaseArb = tuple(aliasArb, boolean()).map(([alias, upper]) => - upper ? alias.toUpperCase() : alias + upper ? alias.toUpperCase() : alias, ); /** Generate DSN fingerprint */ const fingerprintArb = option( tuple(nat(1000), nat(1000)).map(([a, b]) => `${a}:${b}`), - { nil: undefined } + { nil: undefined }, ); /** Generate version string */ const versionArb = tuple(nat(10), nat(20), nat(100)).map( - ([major, minor, patch]) => `${major}.${minor}.${patch}` + ([major, minor, patch]) => `${major}.${minor}.${patch}`, ); /** Generate expiresIn (seconds) - can be negative for testing expiry */ @@ -695,10 +696,10 @@ const expiresInArb = option(integer({ min: -10, max: 7200 }), { const setAuthTokenCmdArb = tuple( storedTokenArb, expiresInArb, - option(tokenArb, { nil: undefined }) + option(tokenArb, { nil: undefined }), ).map( ([token, expiresIn, refreshToken]) => - new SetAuthTokenCommand(token, expiresIn, refreshToken) + new SetAuthTokenCommand(token, expiresIn, refreshToken), ); const getAuthTokenCmdArb = constant(new GetAuthTokenCommand()); @@ -710,17 +711,17 @@ const clearAuthCmdArb = constant(new ClearAuthCommand()); const isAuthenticatedCmdArb = constant(new IsAuthenticatedCommand()); const setEnvAuthTokenCmdArb = tokenArb.map( - (t) => new SetEnvAuthTokenCommand(t) + (t) => new SetEnvAuthTokenCommand(t), ); const clearEnvAuthTokenCmdArb = constant(new ClearEnvAuthTokenCommand()); const setEnvSentryTokenCmdArb = tokenArb.map( - (t) => new SetEnvSentryTokenCommand(t) + (t) => new SetEnvSentryTokenCommand(t), ); const clearEnvSentryTokenCmdArb = constant(new ClearEnvSentryTokenCommand()); const isEnvTokenActiveCmdArb = constant(new IsEnvTokenActiveCommand()); const setOrgRegionCmdArb = tuple(slugArb, regionUrlArb).map( - ([org, url]) => new SetOrgRegionCommand(org, url) + ([org, url]) => new SetOrgRegionCommand(org, url), ); const getOrgRegionCmdArb = slugArb.map((org) => new GetOrgRegionCommand(org)); @@ -736,7 +737,7 @@ const clearOrgRegionsCmdArb = constant(new ClearOrgRegionsCommand()); const setProjectAliasesCmdArb = tuple( array(tuple(aliasArb, slugArb, slugArb), { minLength: 0, maxLength: 5 }), - fingerprintArb + fingerprintArb, ).map(([entries, fp]) => { const aliases: Record = {}; for (const [alias, org, project] of entries) { @@ -748,13 +749,13 @@ const setProjectAliasesCmdArb = tuple( const getProjectAliasesCmdArb = constant(new GetProjectAliasesCommand()); const getProjectByAliasCmdArb = tuple(aliasWithCaseArb, fingerprintArb).map( - ([alias, fp]) => new GetProjectByAliasCommand(alias, fp) + ([alias, fp]) => new GetProjectByAliasCommand(alias, fp), ); const clearProjectAliasesCmdArb = constant(new ClearProjectAliasesCommand()); const setVersionCheckCmdArb = versionArb.map( - (v) => new SetVersionCheckCommand(v) + (v) => new SetVersionCheckCommand(v), ); const getVersionCheckCmdArb = constant(new GetVersionCheckCommand()); @@ -830,7 +831,7 @@ describe("model-based: database layer", () => { { numRuns: DEFAULT_NUM_RUNS, verbose: false, // Set to true for debugging - } + }, ); }); @@ -844,7 +845,7 @@ describe("model-based: database layer", () => { // Set up auth and some regions setAuthToken("test-token"); setOrgRegions( - entries.map(([slug, regionUrl]) => ({ slug, regionUrl })) + entries.map(([slug, regionUrl]) => ({ slug, regionUrl })), ); // Verify regions were set (use unique count since setOrgRegions uses upsert) @@ -861,9 +862,9 @@ describe("model-based: database layer", () => { } finally { cleanup(); } - } + }, ), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -878,7 +879,7 @@ describe("model-based: database layer", () => { commandKey, context, "next", - "1735689600000:100:0" + "1735689600000:100:0", ); // Verify state was stored @@ -896,7 +897,7 @@ describe("model-based: database layer", () => { cleanup(); } }), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -927,9 +928,9 @@ describe("model-based: database layer", () => { } finally { cleanup(); } - } + }, ), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -959,7 +960,7 @@ describe("model-based: database layer", () => { cleanup(); } }), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -972,7 +973,7 @@ describe("model-based: database layer", () => { nat(1000), nat(1000), nat(1000), - nat(1000) + nat(1000), ); await fcAssert( @@ -987,7 +988,7 @@ describe("model-based: database layer", () => { // Set alias with fingerprint 1 setProjectAliases( { [alias]: { orgSlug: org, projectSlug: project } }, - fp1 + fp1, ); // Lookup with fingerprint 2 should fail @@ -1001,7 +1002,7 @@ describe("model-based: database layer", () => { cleanup(); } }), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -1041,7 +1042,7 @@ describe("model-based: database layer", () => { const result = getProjectAliases(); expect(result).toBeDefined(); expect(Object.keys(result!).length).toBe( - Object.keys(aliases2).length + Object.keys(aliases2).length, ); // Check second batch aliases are present @@ -1058,9 +1059,9 @@ describe("model-based: database layer", () => { } finally { cleanup(); } - } + }, ), - { numRuns: 50 } + { numRuns: 50 }, ); }); }); diff --git a/packages/cli/test/lib/db/pagination.model-based.test.ts b/packages/cli/test/lib/db/pagination.model-based.test.ts index 9269445fa..bd906c9de 100644 --- a/packages/cli/test/lib/db/pagination.model-based.test.ts +++ b/packages/cli/test/lib/db/pagination.model-based.test.ts @@ -14,7 +14,7 @@ * - Expired state is treated as absent */ -// biome-ignore-all lint/suspicious/noMisplacedAssertion: Model-based testing uses expect() inside command classes, not directly in test() functions. This is the standard fast-check pattern for stateful testing. +// Model-based tests use expect() inside command classes, as required by fast-check. import { type AsyncCommand, @@ -256,26 +256,26 @@ const cursorArb = constantFrom( "1735689600000:100:0", "1735689600000:200:0", "1735689600000:300:0", - "9999999999999:50:1" + "9999999999999:50:1", ); const optionalCursorArb = constantFrom( "1735689600000:0:0", "1735689600000:100:0", "1735689600000:200:0", - undefined + undefined, ); const advanceNextCmdArb = optionalCursorArb.map( - (cur) => new AdvanceNextCommand(cur) + (cur) => new AdvanceNextCommand(cur), ); const advancePrevCmdArb = optionalCursorArb.map( - (cur) => new AdvancePrevCommand(cur) + (cur) => new AdvancePrevCommand(cur), ); const advanceFirstCmdArb = optionalCursorArb.map( - (cur) => new AdvanceFirstCommand(cur) + (cur) => new AdvanceFirstCommand(cur), ); const clearCmdArb = constantFrom(new ClearCommand()); @@ -310,7 +310,7 @@ describe("model-based: pagination cursor stack", () => { cleanup(); } }), - { numRuns: DEFAULT_NUM_RUNS, verbose: false } + { numRuns: DEFAULT_NUM_RUNS, verbose: false }, ); }); @@ -342,7 +342,7 @@ describe("model-based: pagination cursor stack", () => { cleanup(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -371,7 +371,7 @@ describe("model-based: pagination cursor stack", () => { cleanup(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -462,7 +462,7 @@ describe("model-based: pagination cursor stack", () => { advancePaginationState(CMD_KEY, CTX_KEY, "next", undefined); expect(() => resolveCursor("next", CMD_KEY, CTX_KEY)).toThrow( - /No next page/i + /No next page/i, ); } finally { cleanup(); @@ -493,7 +493,7 @@ describe("model-based: pagination cursor stack", () => { // On page 0 expect(() => resolveCursor("prev", CMD_KEY, CTX_KEY)).toThrow( - /first page/i + /first page/i, ); } finally { cleanup(); @@ -551,7 +551,7 @@ describe("model-based: pagination cursor stack", () => { // Expire it by writing directly to DB with past timestamp const db = getDatabase(); db.query( - "UPDATE pagination_cursors SET expires_at = ? WHERE command_key = ? AND context = ?" + "UPDATE pagination_cursors SET expires_at = ? WHERE command_key = ? AND context = ?", ).run(Date.now() - 1000, CMD_KEY, CTX_KEY); // Should return undefined @@ -563,7 +563,7 @@ describe("model-based: pagination cursor stack", () => { cleanup(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/lib/db/pagination.test.ts b/packages/cli/test/lib/db/pagination.test.ts index 8dad7c1ff..22274009c 100644 --- a/packages/cli/test/lib/db/pagination.test.ts +++ b/packages/cli/test/lib/db/pagination.test.ts @@ -59,10 +59,13 @@ describe("buildMultiTargetContextKey", () => { }); expect(existing).not.toContain("|limit:"); expect( - buildMultiTargetContextKey(targets, { query: "is:unresolved", limit: 10 }) + buildMultiTargetContextKey(targets, { + query: "is:unresolved", + limit: 10, + }), ).toBe(`${existing}|limit:10`); expect(buildMultiTargetContextKey(targets, { limit: 10 })).not.toBe( - buildMultiTargetContextKey(targets, { limit: 25 }) + buildMultiTargetContextKey(targets, { limit: 25 }), ); }); }); diff --git a/packages/cli/test/lib/db/project-cache.test.ts b/packages/cli/test/lib/db/project-cache.test.ts index b164b056a..91dbb8010 100644 --- a/packages/cli/test/lib/db/project-cache.test.ts +++ b/packages/cli/test/lib/db/project-cache.test.ts @@ -580,32 +580,31 @@ describe("getCachedProjectBySlug", () => { }); describe("getCachedProjectById", () => { - test.each([ - "ids", - "dsn", - "list", - ])("finds an ID cached through the %s key shape without crossing organizations", (keyShape) => { - const project = { - orgSlug: "my-org", - orgName: "My Org", - projectSlug: "frontend", - projectName: "Frontend", - projectId: "42", - }; - if (keyShape === "ids") { - setCachedProject("1", "42", project); - } else if (keyShape === "dsn") { - setCachedProjectByDsnKey("public-key", project); - } else { - cacheProjectsForOrg("my-org", "My Org", [ - { id: "42", slug: "frontend", name: "Frontend" }, - ]); - } - - expect(getCachedProjectById("my-org", "42")).toMatchObject(project); - expect(getCachedProjectById("other-org", "42")).toBeUndefined(); - expect(getCachedProjectById("my-org", "43")).toBeUndefined(); - }); + test.each(["ids", "dsn", "list"])( + "finds an ID cached through the %s key shape without crossing organizations", + (keyShape) => { + const project = { + orgSlug: "my-org", + orgName: "My Org", + projectSlug: "frontend", + projectName: "Frontend", + projectId: "42", + }; + if (keyShape === "ids") { + setCachedProject("1", "42", project); + } else if (keyShape === "dsn") { + setCachedProjectByDsnKey("public-key", project); + } else { + cacheProjectsForOrg("my-org", "My Org", [ + { id: "42", slug: "frontend", name: "Frontend" }, + ]); + } + + expect(getCachedProjectById("my-org", "42")).toMatchObject(project); + expect(getCachedProjectById("other-org", "42")).toBeUndefined(); + expect(getCachedProjectById("my-org", "43")).toBeUndefined(); + }, + ); test("uses the most recent slug when a project has been renamed", async () => { cacheProjectsForOrg("my-org", "My Org", [ diff --git a/packages/cli/test/lib/db/regions.focused.test.ts b/packages/cli/test/lib/db/regions.focused.test.ts index eb41a562e..c90e392ad 100644 --- a/packages/cli/test/lib/db/regions.focused.test.ts +++ b/packages/cli/test/lib/db/regions.focused.test.ts @@ -34,10 +34,10 @@ describe("credential and origin scoped organization regions", () => { `${REGION_A}/sentry/`, SOURCE_A, SOURCE_A, - IDENTITY_A + IDENTITY_A, ); expect(getOrgRegion("subpath-org", SOURCE_A, IDENTITY_A)).toBe( - `${REGION_A}/sentry` + `${REGION_A}/sentry`, ); expect(isTrustedRegionOrigin(REGION_A, SOURCE_A, IDENTITY_A)).toBe(true); expect(isTrustedRegionOrigin(REGION_A, SOURCE_A, IDENTITY_B)).toBe(false); @@ -59,7 +59,7 @@ describe("credential and origin scoped organization regions", () => { "not a URL", ])("rejects untrusted region URL %s", (regionUrl) => { expect(() => - setOrgRegion("invalid", regionUrl, SOURCE_A, SOURCE_A, IDENTITY_A) + setOrgRegion("invalid", regionUrl, SOURCE_A, SOURCE_A, IDENTITY_A), ).toThrow(); }); }); diff --git a/packages/cli/test/lib/db/release-channel.test.ts b/packages/cli/test/lib/db/release-channel.test.ts index 60c6efe7e..e721512af 100644 --- a/packages/cli/test/lib/db/release-channel.test.ts +++ b/packages/cli/test/lib/db/release-channel.test.ts @@ -72,7 +72,7 @@ describe("parseReleaseChannel", () => { test("throws on unrecognized value", () => { expect(() => parseReleaseChannel("beta")).toThrow( - "Invalid channel: beta. Must be one of: stable, nightly" + "Invalid channel: beta. Must be one of: stable, nightly", ); }); diff --git a/packages/cli/test/lib/db/repo-cache.test.ts b/packages/cli/test/lib/db/repo-cache.test.ts index 6d065a527..89e5dc48b 100644 --- a/packages/cli/test/lib/db/repo-cache.test.ts +++ b/packages/cli/test/lib/db/repo-cache.test.ts @@ -53,7 +53,7 @@ describe("getCachedRepos", () => { const db = getDatabase(); db.query("UPDATE repo_cache SET cached_at = ? WHERE org_slug = ?").run( Date.now() - (REPO_CACHE_TTL_MS + 1000), - "aged-org" + "aged-org", ); expect(getCachedRepos("aged-org")).toBeNull(); }); @@ -61,7 +61,7 @@ describe("getCachedRepos", () => { test("returns null when repos_json is corrupted (treats as miss)", () => { const db = getDatabase(); db.query( - "INSERT INTO repo_cache (org_slug, repos_json, cached_at) VALUES (?, ?, ?)" + "INSERT INTO repo_cache (org_slug, repos_json, cached_at) VALUES (?, ?, ?)", ).run("broken-org", "{not-json", Date.now()); expect(getCachedRepos("broken-org")).toBeNull(); }); @@ -69,7 +69,7 @@ describe("getCachedRepos", () => { test("returns null when repos_json is valid JSON but not an array", () => { const db = getDatabase(); db.query( - "INSERT INTO repo_cache (org_slug, repos_json, cached_at) VALUES (?, ?, ?)" + "INSERT INTO repo_cache (org_slug, repos_json, cached_at) VALUES (?, ?, ?)", ).run("wrong-shape-org", JSON.stringify({ not: "array" }), Date.now()); expect(getCachedRepos("wrong-shape-org")).toBeNull(); }); diff --git a/packages/cli/test/lib/db/schema-v17.focused.test.ts b/packages/cli/test/lib/db/schema-v17.focused.test.ts index b2df386e9..28764bd36 100644 --- a/packages/cli/test/lib/db/schema-v17.focused.test.ts +++ b/packages/cli/test/lib/db/schema-v17.focused.test.ts @@ -33,13 +33,13 @@ describe("schema v17 organization region provenance", () => { org_name TEXT, org_role TEXT, updated_at INTEGER NOT NULL )`); db.query( - "INSERT INTO org_regions (org_slug, region_url, updated_at) VALUES (?, ?, ?)" + "INSERT INTO org_regions (org_slug, region_url, updated_at) VALUES (?, ?, ?)", ).run("legacy", "https://legacy.example.com", Date.now()); db.query("UPDATE schema_version SET version = 16").run(); closeDatabase(); const migrated = getDatabase(); expect( - migrated.query("SELECT COUNT(*) AS count FROM org_regions").get() + migrated.query("SELECT COUNT(*) AS count FROM org_regions").get(), ).toEqual({ count: 0 }); expect(migrated.query("SELECT version FROM schema_version").get()).toEqual({ version: 17, diff --git a/packages/cli/test/lib/db/schema.test.ts b/packages/cli/test/lib/db/schema.test.ts index 29619bb8d..f4463db83 100644 --- a/packages/cli/test/lib/db/schema.test.ts +++ b/packages/cli/test/lib/db/schema.test.ts @@ -26,7 +26,7 @@ import { useTestConfigDir } from "../../helpers.js"; */ function createDatabaseWithMissingTables( db: Database, - missingTables: string[] + missingTables: string[], ): void { const statements: string[] = []; for (const tableName of Object.keys(EXPECTED_TABLES)) { @@ -35,7 +35,7 @@ function createDatabaseWithMissingTables( } db.exec(statements.join(";\n")); db.query("INSERT INTO schema_version (version) VALUES (?)").run( - CURRENT_SCHEMA_VERSION + CURRENT_SCHEMA_VERSION, ); } @@ -45,7 +45,7 @@ function createDatabaseWithMissingTables( */ function createPreMigrationDatabase( db: Database, - preMigrationTables: string[] + preMigrationTables: string[], ): void { const statements: string[] = []; for (const tableName of Object.keys(EXPECTED_TABLES)) { @@ -57,7 +57,7 @@ function createPreMigrationDatabase( } db.exec(statements.join(";\n")); db.query("INSERT INTO schema_version (version) VALUES (?)").run( - CURRENT_SCHEMA_VERSION + CURRENT_SCHEMA_VERSION, ); } @@ -161,7 +161,7 @@ describe("repairSchema", () => { // Should have created the table expect(tableExists(db, "dsn_cache")).toBe(true); expect( - result.fixed.some((f) => f.includes("Created table dsn_cache")) + result.fixed.some((f) => f.includes("Created table dsn_cache")), ).toBe(true); expect(result.failed).toEqual([]); db.close(); @@ -185,7 +185,7 @@ describe("repairSchema", () => { expect(hasColumn(db, "dsn_cache", "fingerprint")).toBe(true); expect(hasColumn(db, "dsn_cache", "dir_mtimes_json")).toBe(true); expect(result.fixed.some((f) => f.includes("dsn_cache.fingerprint"))).toBe( - true + true, ); expect(result.failed).toEqual([]); db.close(); @@ -318,7 +318,7 @@ describe("runMigrations", () => { initSchema(db); db.exec("DROP TABLE pagination_cursors"); db.exec( - "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)" + "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)", ); // Set version to 5 so migration 5→6 fires db.query("UPDATE schema_version SET version = 5").run(); @@ -328,7 +328,7 @@ describe("runMigrations", () => { // Table should now have the correct composite PK const row = db .query( - "SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'" + "SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'", ) .get() as { sql: string }; expect(row.sql).toContain("PRIMARY KEY (command_key, context)"); @@ -353,7 +353,7 @@ describe("runMigrations", () => { const row = db .query( - "SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'" + "SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'", ) .get() as { sql: string }; expect(row.sql).toContain("PRIMARY KEY (command_key, context)"); @@ -387,7 +387,7 @@ describe("runMigrations", () => { updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000) )`); db.query( - "INSERT INTO defaults (id, organization, project) VALUES (1, ?, ?)" + "INSERT INTO defaults (id, organization, project) VALUES (1, ?, ?)", ).run("migrated-org", "migrated-project"); // Set version to 12 so migration 12→13 fires @@ -442,7 +442,7 @@ describe("runMigrations", () => { updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000) )`); db.query( - "INSERT INTO defaults (id, organization, project) VALUES (1, ?, NULL)" + "INSERT INTO defaults (id, organization, project) VALUES (1, ?, NULL)", ).run("only-org"); db.query("UPDATE schema_version SET version = 12").run(); @@ -483,19 +483,19 @@ describe("repairSchema: wrong primary key", () => { // Simulate the bug: drop and recreate with wrong PK db.exec("DROP TABLE pagination_cursors"); db.exec( - "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)" + "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)", ); const result = repairSchema(db); expect(result.fixed.some((f) => f.includes("pagination_cursors"))).toBe( - true + true, ); expect(result.failed).toEqual([]); const row = db .query( - "SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'" + "SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'", ) .get() as { sql: string }; expect(row.sql).toContain("PRIMARY KEY (command_key, context)"); @@ -510,7 +510,7 @@ describe("repairSchema: wrong primary key", () => { // Should not report pagination_cursors as fixed expect(result.fixed.some((f) => f.includes("pagination_cursors"))).toBe( - false + false, ); db.close(); }); @@ -522,7 +522,7 @@ describe("getSchemaIssues: wrong primary key", () => { initSchema(db); db.exec("DROP TABLE pagination_cursors"); db.exec( - "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)" + "CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)", ); const issues = getSchemaIssues(db); diff --git a/packages/cli/test/lib/db/sqlite.test.ts b/packages/cli/test/lib/db/sqlite.test.ts index 56e2973ef..eaa8198f7 100644 --- a/packages/cli/test/lib/db/sqlite.test.ts +++ b/packages/cli/test/lib/db/sqlite.test.ts @@ -103,7 +103,7 @@ describe.each(DRIVERS)("sqlite adapter [%s driver]", (kind) => { db.query("INSERT INTO t (a, b, c) VALUES (?, ?, ?)").run( "first", 99, - "last" + "last", ); const row = db.query("SELECT a, b, c FROM t").get(); diff --git a/packages/cli/test/lib/db/utils.test.ts b/packages/cli/test/lib/db/utils.test.ts index f5d949c22..c08315d61 100644 --- a/packages/cli/test/lib/db/utils.test.ts +++ b/packages/cli/test/lib/db/utils.test.ts @@ -6,7 +6,7 @@ describe("upsert", () => { const result = upsert("auth", { id: 1, token: "abc123" }, ["id"]); expect(result.sql).toBe( - "INSERT INTO auth (id, token) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET token = excluded.token" + "INSERT INTO auth (id, token) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET token = excluded.token", ); expect(result.values).toEqual([1, "abc123"]); }); @@ -15,11 +15,11 @@ describe("upsert", () => { const result = upsert( "users", { id: 1, name: "Bob", age: 30, updated_at: 12_345 }, - ["id"] + ["id"], ); expect(result.sql).toBe( - "INSERT INTO users (id, name, age, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, age = excluded.age, updated_at = excluded.updated_at" + "INSERT INTO users (id, name, age, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, age = excluded.age, updated_at = excluded.updated_at", ); expect(result.values).toEqual([1, "Bob", 30, 12_345]); }); @@ -28,11 +28,11 @@ describe("upsert", () => { const result = upsert( "cache", { org_id: "org1", project_id: "proj1", data: "cached" }, - ["org_id", "project_id"] + ["org_id", "project_id"], ); expect(result.sql).toBe( - "INSERT INTO cache (org_id, project_id, data) VALUES (?, ?, ?) ON CONFLICT(org_id, project_id) DO UPDATE SET data = excluded.data" + "INSERT INTO cache (org_id, project_id, data) VALUES (?, ?, ?) ON CONFLICT(org_id, project_id) DO UPDATE SET data = excluded.data", ); expect(result.values).toEqual(["org1", "proj1", "cached"]); }); @@ -42,11 +42,11 @@ describe("upsert", () => { "users", { id: 1, name: "Bob", created_at: 1000, updated_at: 2000 }, ["id"], - { excludeFromUpdate: ["created_at"] } + { excludeFromUpdate: ["created_at"] }, ); expect(result.sql).toBe( - "INSERT INTO users (id, name, created_at, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, updated_at = excluded.updated_at" + "INSERT INTO users (id, name, created_at, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, updated_at = excluded.updated_at", ); expect(result.values).toEqual([1, "Bob", 1000, 2000]); }); @@ -57,7 +57,7 @@ describe("upsert", () => { }); expect(result.sql).toBe( - "INSERT INTO settings (id, value) VALUES (?, ?) ON CONFLICT(id) DO NOTHING" + "INSERT INTO settings (id, value) VALUES (?, ?) ON CONFLICT(id) DO NOTHING", ); }); @@ -65,11 +65,11 @@ describe("upsert", () => { const result = upsert( "auth", { id: 1, token: "abc", refresh_token: null }, - ["id"] + ["id"], ); expect(result.sql).toBe( - "INSERT INTO auth (id, token, refresh_token) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET token = excluded.token, refresh_token = excluded.refresh_token" + "INSERT INTO auth (id, token, refresh_token) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET token = excluded.token, refresh_token = excluded.refresh_token", ); expect(result.values).toEqual([1, "abc", null]); }); @@ -78,7 +78,7 @@ describe("upsert", () => { const result = upsert( "auth", { id: 1, token: "abc", refresh_token: undefined }, - ["id"] + ["id"], ); expect(result.values).toEqual([1, "abc", undefined]); @@ -86,13 +86,13 @@ describe("upsert", () => { test("throws error for empty data object", () => { expect(() => upsert("auth", {}, ["id"])).toThrow( - "upsert: data object must have at least one column" + "upsert: data object must have at least one column", ); }); test("throws error for empty conflict columns", () => { expect(() => upsert("auth", { id: 1 }, [])).toThrow( - "upsert: must specify at least one conflict column" + "upsert: must specify at least one conflict column", ); }); diff --git a/packages/cli/test/lib/delta-upgrade.mocked.test.ts b/packages/cli/test/lib/delta-upgrade.mocked.test.ts index f6186ab59..9b356e6c7 100644 --- a/packages/cli/test/lib/delta-upgrade.mocked.test.ts +++ b/packages/cli/test/lib/delta-upgrade.mocked.test.ts @@ -61,7 +61,7 @@ afterEach(() => { }); function mockFetch( - fn: (url: string | URL | Request, init?: RequestInit) => Promise + fn: (url: string | URL | Request, init?: RequestInit) => Promise, ): void { globalThis.fetch = fn as typeof globalThis.fetch; } @@ -81,7 +81,7 @@ function versionHex(version: string): string { function tempFile(name: string): string { return join( tmpdir(), - `delta-iso-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}` + `delta-iso-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}`, ); } @@ -163,7 +163,7 @@ describe("resolveStableDelta", () => { // 4. Apply patch (produces empty file) // 5. SHA-256 check fails → throws await expect( - resolveStableDelta("0.14.0", oldBinaryPath, destPath) + resolveStableDelta("0.14.0", oldBinaryPath, destPath), ).rejects.toThrow("SHA-256 mismatch"); } finally { if (existsSync(oldBinaryPath)) unlinkSync(oldBinaryPath); @@ -182,7 +182,7 @@ describe("resolveStableDelta", () => { const result = await resolveStableDelta( "0.14.0", oldBinaryPath, - destPath + destPath, ); expect(result).toBeNull(); } finally { @@ -230,7 +230,7 @@ describe("resolveNightlyDelta", () => { }, ], }), - { status: 200 } + { status: 200 }, ); } // listTags runs in parallel with fetchManifest — must not 404 @@ -244,7 +244,7 @@ describe("resolveNightlyDelta", () => { const result = await resolveNightlyDelta( "0.0.0-dev.200", oldBinaryPath, - destPath + destPath, ); expect(result).toBeNull(); } finally { @@ -286,7 +286,7 @@ describe("resolveNightlyDelta", () => { }, ], }), - { status: 200 } + { status: 200 }, ); } if (urlStr.includes("/tags/list")) { @@ -299,7 +299,7 @@ describe("resolveNightlyDelta", () => { const result = await resolveNightlyDelta( "0.0.0-dev.200", oldBinaryPath, - destPath + destPath, ); expect(result).toBeNull(); } finally { @@ -318,7 +318,7 @@ describe("attemptDeltaUpgrade", () => { const result = await attemptDeltaUpgrade( "0.0.0-dev.100", "/tmp/fake-binary", - "/tmp/fake-dest" + "/tmp/fake-dest", ); expect(result).toBeNull(); }); @@ -335,7 +335,7 @@ describe("attemptDeltaUpgrade", () => { const result = await attemptDeltaUpgrade( "0.14.0", oldBinaryPath, - destPath + destPath, ); expect(result).toBeNull(); } finally { @@ -395,7 +395,7 @@ describe("attemptDeltaUpgrade", () => { new Uint8Array([0, 1, 2, 3]).buffer as ArrayBuffer, { status: 200, - } + }, ); } return new Response("Not Found", { status: 404 }); @@ -406,7 +406,7 @@ describe("attemptDeltaUpgrade", () => { const result = await attemptDeltaUpgrade( "0.14.0", oldBinaryPath, - destPath + destPath, ); expect(result).toBeNull(); } finally { @@ -481,7 +481,7 @@ describe("attemptDeltaUpgrade", () => { const result = await attemptDeltaUpgrade( "0.14.0", oldBinaryPath, - destPath + destPath, ); expect(result).not.toBeNull(); expect(result!.sha256).toBe(expectedSha256); diff --git a/packages/cli/test/lib/delta-upgrade.test.ts b/packages/cli/test/lib/delta-upgrade.test.ts index 68d1cfbdf..3f2300ebc 100644 --- a/packages/cli/test/lib/delta-upgrade.test.ts +++ b/packages/cli/test/lib/delta-upgrade.test.ts @@ -71,7 +71,7 @@ function makeRelease(tag: string, assets: GitHubAsset[] = []): GitHubRelease { function makePatchManifest( fromVersion: string, sha256Map: Record = {}, - layers: OciManifest["layers"] = [] + layers: OciManifest["layers"] = [], ): OciManifest { const annotations: Record = { "from-version": fromVersion, @@ -228,7 +228,7 @@ describe("extractStableChain", () => { versions: string[], binaryName: string, patchSize = 1000, - gzSize = 100_000 + gzSize = 100_000, ): GitHubRelease[] { return versions.map((v) => makeRelease(v, [ @@ -245,12 +245,12 @@ describe("extractStableChain", () => { name: `${binaryName}.gz`, size: gzSize, }), - ]) + ]), ); } function makeOpts( - overrides: Partial = {} + overrides: Partial = {}, ): ExtractStableChainOpts { return { releases: [], @@ -270,7 +270,7 @@ describe("extractStableChain", () => { currentVersion: "0.12.0", targetVersion: "0.13.0", fullGzSize: 100_000, - }) + }), ); expect(result).not.toBeNull(); expect(result?.patchUrls).toHaveLength(1); @@ -284,10 +284,10 @@ describe("extractStableChain", () => { test("resolves multi-hop chain (0.12→0.13→0.14)", () => { const releases = buildReleases( ["0.14.0", "0.13.0", "0.12.0"], - "sentry-linux-x64" + "sentry-linux-x64", ); const result = extractStableChain( - makeOpts({ releases, fullGzSize: 100_000 }) + makeOpts({ releases, fullGzSize: 100_000 }), ); expect(result).not.toBeNull(); expect(result?.patchUrls).toHaveLength(2); @@ -307,7 +307,7 @@ describe("extractStableChain", () => { releases, targetVersion: "0.15.0", fullGzSize: 100_000, - }) + }), ); expect(result).toBeNull(); }); @@ -315,7 +315,7 @@ describe("extractStableChain", () => { test("returns null when target is older than current (downgrade)", () => { const releases = buildReleases( ["0.14.0", "0.13.0", "0.12.0"], - "sentry-linux-x64" + "sentry-linux-x64", ); const result = extractStableChain( makeOpts({ @@ -323,7 +323,7 @@ describe("extractStableChain", () => { currentVersion: "0.14.0", targetVersion: "0.12.0", fullGzSize: 100_000, - }) + }), ); expect(result).toBeNull(); }); @@ -336,7 +336,7 @@ describe("extractStableChain", () => { currentVersion: "0.13.0", targetVersion: "0.13.0", fullGzSize: 100_000, - }) + }), ); expect(result).toBeNull(); }); @@ -345,10 +345,10 @@ describe("extractStableChain", () => { const releases = buildReleases( ["0.14.0", "0.13.0", "0.12.0"], "sentry-linux-x64", - 70_000 + 70_000, ); const result = extractStableChain( - makeOpts({ releases, fullGzSize: 100_000 }) + makeOpts({ releases, fullGzSize: 100_000 }), ); expect(result).toBeNull(); }); @@ -370,7 +370,7 @@ describe("extractStableChain", () => { currentVersion: "0.13.0", targetVersion: "0.14.0", fullGzSize: 100_000, - }) + }), ); expect(result).toBeNull(); }); @@ -394,7 +394,7 @@ describe("extractStableChain", () => { currentVersion: "0.13.0", targetVersion: "0.14.0", fullGzSize: 100_000, - }) + }), ); expect(result).toBeNull(); }); @@ -409,7 +409,7 @@ describe("extractStableChain", () => { currentVersion: "0.1.0", targetVersion: "0.12.0", fullGzSize: 100_000, - }) + }), ); expect(result).toBeNull(); }); @@ -424,7 +424,7 @@ describe("extractStableChain", () => { currentVersion: "0.1.0", targetVersion: "0.11.0", fullGzSize: 100_000, - }) + }), ); expect(result).not.toBeNull(); expect(result?.patchUrls).toHaveLength(10); @@ -433,7 +433,7 @@ describe("extractStableChain", () => { test("patch URLs are returned in apply order (oldest first)", () => { const releases = buildReleases( ["0.15.0", "0.14.0", "0.13.0", "0.12.0"], - "sentry-linux-x64" + "sentry-linux-x64", ); const result = extractStableChain( makeOpts({ @@ -441,7 +441,7 @@ describe("extractStableChain", () => { currentVersion: "0.12.0", targetVersion: "0.15.0", fullGzSize: 100_000, - }) + }), ); expect(result).not.toBeNull(); expect(result?.patchUrls).toEqual([ @@ -479,7 +479,7 @@ describe("extractStableChain", () => { makeRelease("0.12.0", [makeAsset({ name: "sentry-linux-x64" })]), ]; const result = extractStableChain( - makeOpts({ releases, fullGzSize: 100_000 }) + makeOpts({ releases, fullGzSize: 100_000 }), ); expect(result).toBeNull(); }); @@ -531,7 +531,7 @@ describe("getPatchTargetSha256", () => { }); expect(getPatchTargetSha256(manifest, "sentry-linux-x64")).toBe("abc123"); expect(getPatchTargetSha256(manifest, "sentry-darwin-arm64")).toBe( - "def456" + "def456", ); }); @@ -565,7 +565,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.0.0-dev.100", - "0.0.0-dev.105" + "0.0.0-dev.105", ); expect(result).toEqual([]); }); @@ -585,7 +585,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.0.0-dev.100", - "0.0.0-dev.102" + "0.0.0-dev.102", ); // currentVersion (100) excluded, target (102) included expect(result).toEqual(["patch-0.0.0-dev.101", "patch-0.0.0-dev.102"]); @@ -600,7 +600,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.0.0-dev.100", - "0.0.0-dev.103" + "0.0.0-dev.103", ); expect(result).toEqual(["patch-0.0.0-dev.101"]); }); @@ -615,7 +615,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.0.0-dev.100", - "0.0.0-dev.103" + "0.0.0-dev.103", ); expect(result).toEqual([ "patch-0.0.0-dev.101", @@ -629,7 +629,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.0.0-dev.100", - "0.0.0-dev.101" + "0.0.0-dev.101", ); expect(result).toEqual(["patch-0.0.0-dev.101"]); }); @@ -639,7 +639,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.0.0-dev.100", - "0.0.0-dev.101" + "0.0.0-dev.101", ); expect(result).toEqual(["patch-0.0.0-dev.101"]); }); @@ -653,7 +653,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.14.0-dev.1772661724", - "0.14.0-dev.1772800000" + "0.14.0-dev.1772800000", ); expect(result).toEqual([ "patch-0.14.0-dev.1772732047", @@ -666,12 +666,12 @@ describe("filterAndSortChainTags", () => { // chains allow up to MAX_NIGHTLY_CHAIN_DEPTH (30) hops const tags = Array.from( { length: 25 }, - (_, i) => `patch-0.14.0-dev.${1000 + i + 1}` + (_, i) => `patch-0.14.0-dev.${1000 + i + 1}`, ); const result = filterAndSortChainTags( tags, "0.14.0-dev.1000", - "0.14.0-dev.1025" + "0.14.0-dev.1025", ); expect(result).toHaveLength(25); expect(result[0]).toBe("patch-0.14.0-dev.1001"); @@ -689,7 +689,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.16.0-dev.100", - "0.17.0-dev.500" + "0.17.0-dev.500", ); expect(result).toEqual([ "patch-0.16.0-dev.200", @@ -708,7 +708,7 @@ describe("filterAndSortChainTags", () => { const result = filterAndSortChainTags( tags, "0.0.0-dev.101", - "0.0.0-dev.102" + "0.0.0-dev.102", ); expect(result).toEqual(["patch-0.0.0-dev.102"]); }); @@ -721,7 +721,7 @@ describe("validateChainStep", () => { function makeLayer( title: string, - size: number + size: number, ): OciManifest["layers"][number] { return { digest: `sha256:${title.replace(/\W/g, "")}`, @@ -807,7 +807,7 @@ describe("validateChainStep", () => { /** Helper to mock globalThis.fetch */ function mockFetch( - fn: (url: string | URL | Request, init?: RequestInit) => Promise + fn: (url: string | URL | Request, init?: RequestInit) => Promise, ): void { globalThis.fetch = fn as typeof globalThis.fetch; } @@ -835,7 +835,7 @@ describe("fetchRecentReleases", () => { mockFetch(async (url) => { expect(String(url)).toContain( - "api.github.com/repos/getsentry/toolkit/releases" + "api.github.com/repos/getsentry/toolkit/releases", ); expect(String(url)).toContain("per_page="); return new Response(JSON.stringify(releases), { status: 200 }); @@ -849,21 +849,24 @@ describe("fetchRecentReleases", () => { test.each([ ["Toolkit", undefined, "cli@0.14.0-dev.1", "cli@0.14.0"], ["legacy", LEGACY_UPGRADE_SOURCE, "0.14.0-dev.1", "0.14.0"], - ])("excludes semantic prereleases from the %s stable source", async (_name, source, prereleaseTag, stableTag) => { - mockFetch( - async () => - new Response( - JSON.stringify([ - { ...makeRelease(prereleaseTag, []), prerelease: false }, - makeRelease(stableTag, []), - ]), - { status: 200 } - ) - ); + ])( + "excludes semantic prereleases from the %s stable source", + async (_name, source, prereleaseTag, stableTag) => { + mockFetch( + async () => + new Response( + JSON.stringify([ + { ...makeRelease(prereleaseTag, []), prerelease: false }, + makeRelease(stableTag, []), + ]), + { status: 200 }, + ), + ); - const result = await fetchRecentReleases(undefined, source); - expect(result.map((release) => release.tag_name)).toEqual(["0.14.0"]); - }); + const result = await fetchRecentReleases(undefined, source); + expect(result.map((release) => release.tag_name)).toEqual(["0.14.0"]); + }, + ); test("uses the selected legacy GitHub repository", async () => { const urls: string[] = []; @@ -947,7 +950,7 @@ describe("resolveStableChain", () => { /** Build a mock that serves both releases API and patch downloads */ function setupStableMocks( releases: GitHubRelease[], - patches: Map + patches: Map, ): void { mockFetch(async (url) => { const urlStr = String(url); @@ -1006,7 +1009,7 @@ describe("resolveStableChain", () => { }); await expect( - resolveStableChain("0.13.0", "0.14.0", undefined, LEGACY_UPGRADE_SOURCE) + resolveStableChain("0.13.0", "0.14.0", undefined, LEGACY_UPGRADE_SOURCE), ).resolves.toBeNull(); expect(urls).toEqual([ "https://api.github.com/repos/getsentry/cli/releases?per_page=12", @@ -1054,7 +1057,7 @@ describe("resolveStableChain", () => { new Map([ [urlA, patchA], [urlB, patchB], - ]) + ]), ); const chain = await resolveStableChain("0.13.0", "0.15.0"); @@ -1125,7 +1128,7 @@ describe("resolveStableChain", () => { browser_download_url: `https://example.com/${v}.patch`, }), makeAsset({ name: `${binaryName}.gz`, size: 100_000 }), - ]) + ]), ); setupStableMocks(releases, new Map()); @@ -1145,7 +1148,7 @@ describe("resolveNightlyChain", () => { function setupNightlyMocks( tags: string[], manifests: Map, - blobs: Map + blobs: Map, ): void { mockFetch(async (url) => { const urlStr = String(url); @@ -1217,13 +1220,13 @@ describe("resolveNightlyChain", () => { "org.opencontainers.image.title": PATCH_NAME, }, }, - ] + ], ); setupNightlyMocks( ["patch-0.0.0-dev.101"], new Map([["patch-0.0.0-dev.101", patchManifest]]), - new Map([[patchDigest, patchData]]) + new Map([[patchDigest, patchData]]), ); const chain = await resolveNightlyChain({ @@ -1255,7 +1258,7 @@ describe("resolveNightlyChain", () => { targetVersion: "0.0.0-dev.101", fullGzSize: 100_000, source: LEGACY_UPGRADE_SOURCE, - }) + }), ).resolves.toBeNull(); expect(urls).toEqual(["https://ghcr.io/v2/getsentry/cli/tags/list?n=100"]); expect(urls.every((url) => !url.includes("getsentry/toolkit"))).toBe(true); @@ -1278,7 +1281,7 @@ describe("resolveNightlyChain", () => { // 35 tags exceeds the nightly limit of 30 const tags = Array.from( { length: 35 }, - (_, i) => `patch-0.0.0-dev.${101 + i}` + (_, i) => `patch-0.0.0-dev.${101 + i}`, ); setupNightlyMocks(tags, new Map(), new Map()); @@ -1323,7 +1326,7 @@ describe("resolveNightlyChain", () => { "org.opencontainers.image.title": PATCH_NAME, }, }, - ] + ], ); setupNightlyMocks( @@ -1335,7 +1338,7 @@ describe("resolveNightlyChain", () => { new Map([ [digestA, patchA], [digestB, patchB], - ]) + ]), ); const chain = await resolveNightlyChain({ @@ -1370,13 +1373,13 @@ describe("resolveNightlyChain", () => { "org.opencontainers.image.title": PATCH_NAME, }, }, - ] + ], ); setupNightlyMocks( ["patch-0.0.0-dev.101"], new Map([["patch-0.0.0-dev.101", patchManifest]]), - new Map() + new Map(), ); const chain = await resolveNightlyChain({ @@ -1403,13 +1406,13 @@ describe("resolveNightlyChain", () => { "org.opencontainers.image.title": PATCH_NAME, }, }, - ] + ], ); setupNightlyMocks( ["patch-0.0.0-dev.101"], new Map([["patch-0.0.0-dev.101", patchManifest]]), - new Map() + new Map(), ); const chain = await resolveNightlyChain({ @@ -1427,7 +1430,7 @@ describe("resolveNightlyChain", () => { setupNightlyMocks( ["patch-0.0.0-dev.101"], new Map(), // no manifests — will 404 - new Map() + new Map(), ); const chain = await resolveNightlyChain({ @@ -1454,7 +1457,7 @@ describe("resolveNightlyChain", () => { "org.opencontainers.image.title": PATCH_NAME, }, }, - ] + ], ); // Only patch-dev.101 is in the graph, but target is dev.102 @@ -1463,7 +1466,7 @@ describe("resolveNightlyChain", () => { setupNightlyMocks( ["patch-0.0.0-dev.101"], new Map([["patch-0.0.0-dev.101", patchManifest]]), - new Map() + new Map(), ); const chain = await resolveNightlyChain({ @@ -1491,13 +1494,13 @@ describe("resolveNightlyChain", () => { "org.opencontainers.image.title": PATCH_NAME, }, }, - ] + ], ); setupNightlyMocks( ["patch-0.0.0-dev.101"], new Map([["patch-0.0.0-dev.101", patchManifest]]), - new Map() + new Map(), ); const chain = await resolveNightlyChain({ @@ -1524,13 +1527,13 @@ describe("resolveNightlyChain", () => { "org.opencontainers.image.title": "wrong-name.patch", }, }, - ] + ], ); setupNightlyMocks( ["patch-0.0.0-dev.101"], new Map([["patch-0.0.0-dev.101", patchManifest]]), - new Map() + new Map(), ); const chain = await resolveNightlyChain({ @@ -1608,7 +1611,7 @@ describe("applyPatchChain", () => { try { await expect(applyPatchChain(chain, oldPath, destPath)).rejects.toThrow( - "SHA-256 mismatch" + "SHA-256 mismatch", ); } finally { if (existsSync(destPath)) { @@ -1687,8 +1690,8 @@ describe("applyPatchChain", () => { expect( await access(destPath).then( () => true, - () => false - ) + () => false, + ), ).toBe(true); } finally { if (existsSync(destPath)) { @@ -1713,14 +1716,14 @@ describe("resolveStableDelta", () => { makeAsset({ name: "sentry-linux-x64.patch" }), ]), ]), - { status: 200 } - ) + { status: 200 }, + ), ); const result = await resolveStableDelta( "0.14.0", "/tmp/fake-old", - "/tmp/fake-out" + "/tmp/fake-out", ); expect(result).toBeNull(); }); @@ -1802,7 +1805,7 @@ describe("resolveNightlyDelta", () => { mockFetch(async () => new Response("Unauthorized", { status: 401 })); await expect( - resolveNightlyDelta("0.14.0-dev.123", "/tmp/fake-old", "/tmp/fake-out") + resolveNightlyDelta("0.14.0-dev.123", "/tmp/fake-old", "/tmp/fake-out"), ).resolves.toBeNull(); }); @@ -1841,7 +1844,7 @@ describe("resolveNightlyDelta", () => { const result = await resolveNightlyDelta( "0.14.0-dev.200", "/tmp/fake-old", - "/tmp/fake-out" + "/tmp/fake-out", ); expect(result).toBeNull(); }); @@ -1881,7 +1884,7 @@ describe("resolveNightlyDelta", () => { const result = await resolveNightlyDelta( "0.14.0-dev.200", "/tmp/fake-old", - "/tmp/fake-out" + "/tmp/fake-out", ); expect(result).toBeNull(); }); @@ -1894,7 +1897,7 @@ describe("attemptDeltaUpgrade", () => { const result = await attemptDeltaUpgrade( "0.14.0", "/tmp/fake-old", - "/tmp/fake-out" + "/tmp/fake-out", ); expect(result).toBeNull(); }); @@ -1959,8 +1962,8 @@ describe("selected source affinity", () => { "/tmp/fake-out", false, undefined, - LEGACY_UPGRADE_SOURCE - ) + LEGACY_UPGRADE_SOURCE, + ), ).resolves.toBeNull(); expect(urls).toEqual([ "https://api.github.com/repos/getsentry/cli/releases?per_page=12", @@ -1984,8 +1987,8 @@ describe("selected source affinity", () => { "/tmp/fake-out", false, undefined, - LEGACY_UPGRADE_SOURCE - ) + LEGACY_UPGRADE_SOURCE, + ), ).resolves.toBeNull(); expect(urls).toEqual([ "https://ghcr.io/token?scope=repository:getsentry/cli:pull", @@ -2004,7 +2007,7 @@ describe("selected source affinity", () => { await versionedDelta.prefetchStablePatches( "0.14.0", undefined, - LEGACY_UPGRADE_SOURCE + LEGACY_UPGRADE_SOURCE, ); expect(urls).toEqual([ "https://api.github.com/repos/getsentry/cli/releases?per_page=12", @@ -2024,7 +2027,7 @@ describe("selected source affinity", () => { await versionedDelta.prefetchNightlyPatches( "0.14.0-dev.101", undefined, - LEGACY_UPGRADE_SOURCE + LEGACY_UPGRADE_SOURCE, ); expect(urls).toEqual([ "https://ghcr.io/token?scope=repository:getsentry/cli:pull", diff --git a/packages/cli/test/lib/detect-agent.property.test.ts b/packages/cli/test/lib/detect-agent.property.test.ts index 8efadd2cf..ea4eb3d34 100644 --- a/packages/cli/test/lib/detect-agent.property.test.ts +++ b/packages/cli/test/lib/detect-agent.property.test.ts @@ -47,14 +47,14 @@ const agentNameArb = array(constantFrom(...agentNameChars.split("")), { /^[a-z]/.test(s) && !s.endsWith("-") && !s.includes("--") && - !GARBAGE_NAMES.has(s) + !GARBAGE_NAMES.has(s), ); /** Generate semver-ish version strings. */ const versionArb = tuple( constantFrom(...Array.from({ length: 20 }, (_, i) => String(i))), constantFrom(...Array.from({ length: 20 }, (_, i) => String(i))), - constantFrom(...Array.from({ length: 100 }, (_, i) => String(i))) + constantFrom(...Array.from({ length: 100 }, (_, i) => String(i))), ).map(([major, minor, patch]) => `${major}.${minor}.${patch}`); /** Generate role strings. */ @@ -68,7 +68,7 @@ const falsyGarbageArb = constantFrom("0", "false", "no", "off", "False", "NO"); /** Generate compound agent strings: name/version/role. */ const compoundArb = tuple(agentNameArb, versionArb, roleArb).map( - ([name, version, role]) => `${name}/${version}/${role}` + ([name, version, role]) => `${name}/${version}/${role}`, ); describe("property: normalizeAgent", () => { @@ -79,7 +79,7 @@ describe("property: normalizeAgent", () => { expect(result).toBeDefined(); expect(result!.name).toBe(result!.name.toLowerCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -90,7 +90,7 @@ describe("property: normalizeAgent", () => { expect(result).toBeDefined(); expect(result!.name.length).toBeGreaterThan(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -100,7 +100,7 @@ describe("property: normalizeAgent", () => { const result = normalizeAgent(raw); expect(result).toEqual({ name: "unknown" }); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -109,7 +109,7 @@ describe("property: normalizeAgent", () => { property(falsyGarbageArb, (raw) => { expect(normalizeAgent(raw)).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -122,7 +122,7 @@ describe("property: normalizeAgent", () => { expect(result.version).toMatch(/^\d+(\.\d+)*$/); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -136,9 +136,9 @@ describe("property: normalizeAgent", () => { if (result?.version) { expect(result.version.startsWith("v")).toBe(false); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -149,7 +149,7 @@ describe("property: normalizeAgent", () => { expect(result).toBeDefined(); expect(result!.name).toBe(AGENT_ALIASES.get(aliasKey)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -162,7 +162,7 @@ describe("property: normalizeAgent", () => { expect(second).toBeDefined(); expect(second!.name).toBe(first!.name); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -173,7 +173,7 @@ describe("property: normalizeAgent", () => { expect(result).toBeDefined(); expect(result!.version).toBe(version); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -185,9 +185,9 @@ describe("property: normalizeAgent", () => { const result = normalizeAgent(`${name}/${version}/${role}`); expect(result).toBeDefined(); expect(result!.role).toBe(role); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -199,7 +199,7 @@ describe("property: normalizeAgent", () => { expect(result!.version).toBeUndefined(); expect(result!.role).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/detect-agent.test.ts b/packages/cli/test/lib/detect-agent.test.ts index 0d866f3d1..7e80e79c3 100644 --- a/packages/cli/test/lib/detect-agent.test.ts +++ b/packages/cli/test/lib/detect-agent.test.ts @@ -165,15 +165,13 @@ describe("detectAgent", () => { expect(detectAgent()).toEqual(named("cursor")); }); - test.each([ - "", - "terminal", - "agent-exec-helper", - "AGENT-EXEC", - ])("CURSOR_EXTENSION_HOST_ROLE=%j does not trigger detection", (role) => { - withEnv({ CURSOR_EXTENSION_HOST_ROLE: role }); - expect(detectAgent()).toBeUndefined(); - }); + test.each(["", "terminal", "agent-exec-helper", "AGENT-EXEC"])( + "CURSOR_EXTENSION_HOST_ROLE=%j does not trigger detection", + (role) => { + withEnv({ CURSOR_EXTENSION_HOST_ROLE: role }); + expect(detectAgent()).toBeUndefined(); + }, + ); test("a different Cursor role falls through to other agent markers", () => { withEnv({ CURSOR_EXTENSION_HOST_ROLE: "terminal", CLINE_ACTIVE: "1" }); @@ -257,14 +255,17 @@ describe("detectAgent", () => { ["GROK_PLUGIN_DATA", "CLAUDECODE", ""], ["GROK_PLUGIN_ROOT", "CLAUDECODE", "1"], ["GROK_PLUGIN_DATA", "CLAUDE_CODE", "1"], - ])("%s takes priority over %s with CLAUDE_CODE_IS_COWORK=%j", (grokVar, claudeVar, cowork) => { - withEnv({ - [grokVar]: "/tmp/grok", - [claudeVar]: "1", - CLAUDE_CODE_IS_COWORK: cowork, - }); - expect(detectAgent()).toEqual(named("grok")); - }); + ])( + "%s takes priority over %s with CLAUDE_CODE_IS_COWORK=%j", + (grokVar, claudeVar, cowork) => { + withEnv({ + [grokVar]: "/tmp/grok", + [claudeVar]: "1", + CLAUDE_CODE_IS_COWORK: cowork, + }); + expect(detectAgent()).toEqual(named("grok")); + }, + ); // ── Excluded env vars (false positive risks) ────────────────────── diff --git a/packages/cli/test/lib/dev-script.property.test.ts b/packages/cli/test/lib/dev-script.property.test.ts index 0e60740dd..0a883128d 100644 --- a/packages/cli/test/lib/dev-script.property.test.ts +++ b/packages/cli/test/lib/dev-script.property.test.ts @@ -42,7 +42,7 @@ describe("property: detectDevCommand", () => { try { await writeFile( join(dir, "package.json"), - JSON.stringify({ scripts: { [name]: value } }) + JSON.stringify({ scripts: { [name]: value } }), ); const result = await detectDevCommand(dir); expect(result).not.toBeNull(); @@ -53,9 +53,9 @@ describe("property: detectDevCommand", () => { /* intentionally empty */ }); } - } + }, ), - { numRuns: 20 } + { numRuns: 20 }, ); }); }); diff --git a/packages/cli/test/lib/dev-script.test.ts b/packages/cli/test/lib/dev-script.test.ts index 0182cf6e6..b51785346 100644 --- a/packages/cli/test/lib/dev-script.test.ts +++ b/packages/cli/test/lib/dev-script.test.ts @@ -30,7 +30,7 @@ describe("detectDevCommand", () => { test("detects package.json scripts.dev", async () => { await writeFile( join(tmpDir, "package.json"), - JSON.stringify({ scripts: { dev: "next dev" } }) + JSON.stringify({ scripts: { dev: "next dev" } }), ); const result = await detectDevCommand(tmpDir); expect(result).not.toBeNull(); @@ -41,7 +41,7 @@ describe("detectDevCommand", () => { test("detects package.json scripts.start when dev is absent", async () => { await writeFile( join(tmpDir, "package.json"), - JSON.stringify({ scripts: { start: "node server.js" } }) + JSON.stringify({ scripts: { start: "node server.js" } }), ); const result = await detectDevCommand(tmpDir); expect(result).not.toBeNull(); @@ -52,7 +52,7 @@ describe("detectDevCommand", () => { test("falls through package.json with no scripts", async () => { await writeFile( join(tmpDir, "package.json"), - JSON.stringify({ name: "test", version: "1.0.0" }) + JSON.stringify({ name: "test", version: "1.0.0" }), ); const result = await detectDevCommand(tmpDir); expect(result).toBeNull(); @@ -114,7 +114,7 @@ describe("detectDevCommand", () => { test("package.json takes priority over manage.py", async () => { await writeFile( join(tmpDir, "package.json"), - JSON.stringify({ scripts: { dev: "vite" } }) + JSON.stringify({ scripts: { dev: "vite" } }), ); await writeFile(join(tmpDir, "manage.py"), "#!/usr/bin/env python"); const result = await detectDevCommand(tmpDir); @@ -125,7 +125,7 @@ describe("detectDevCommand", () => { test("prefers dev over start in package.json", async () => { await writeFile( join(tmpDir, "package.json"), - JSON.stringify({ scripts: { start: "node index.js", dev: "vite" } }) + JSON.stringify({ scripts: { start: "node index.js", dev: "vite" } }), ); const result = await detectDevCommand(tmpDir); expect(result).not.toBeNull(); @@ -138,7 +138,7 @@ describe("detectDevCommand", () => { join(tmpDir, "package.json"), JSON.stringify({ scripts: { serve: "serve dist", develop: "gatsby develop" }, - }) + }), ); const result = await detectDevCommand(tmpDir); expect(result).not.toBeNull(); diff --git a/packages/cli/test/lib/dif/embedded-ppdb.test.ts b/packages/cli/test/lib/dif/embedded-ppdb.test.ts index 984693333..3b164a296 100644 --- a/packages/cli/test/lib/dif/embedded-ppdb.test.ts +++ b/packages/cli/test/lib/dif/embedded-ppdb.test.ts @@ -17,7 +17,7 @@ import { /** Read a committed binary DIF fixture as raw bytes. */ function readFixture(name: string): Uint8Array { return new Uint8Array( - readFileSync(new URL(`../../fixtures/dif/${name}`, import.meta.url)) + readFileSync(new URL(`../../fixtures/dif/${name}`, import.meta.url)), ); } @@ -54,7 +54,7 @@ describe("extractEmbeddedPpdb", () => { [ "MODULE Linux x86_64 0F13A5DA412AFBF7C8662048F3294F3D0 example", "FUNC 1000 10 0 main", - ].join("\n") + ].join("\n"), ); expect(extractEmbeddedPpdb(breakpad)).toBeNull(); }); diff --git a/packages/cli/test/lib/dif/find.test.ts b/packages/cli/test/lib/dif/find.test.ts index 41f9c9727..f5ebec9b6 100644 --- a/packages/cli/test/lib/dif/find.test.ts +++ b/packages/cli/test/lib/dif/find.test.ts @@ -30,12 +30,12 @@ describe("idHint", () => { test("classifies ids by shape", () => { expect(idHint("00000000-0000-6000-0000-000000000000-1")).toBe("likely PDB"); expect(idHint("00000000-0000-5000-0000-000000000000")).toBe( - "likely Proguard" + "likely Proguard", ); expect(idHint("00000000-0000-3000-0000-000000000000")).toBe("likely dSYM"); expect(idHint("00000000-0000-4000-0000-000000000000")).toBe("unknown"); expect(idHint("00000000-0000-0000-0000-000000000000")).toBe( - "likely ELF Debug" + "likely ELF Debug", ); }); }); @@ -112,7 +112,7 @@ describe("findDebugFiles", () => { // `a.sym` sorts before `z.txt`, so the breakpad is seen first. writeFileSync( join(dir, "a.sym"), - `MODULE Linux x86_64 ${hex}0 example\nFUNC 1000 10 0 main\n` + `MODULE Linux x86_64 ${hex}0 example\nFUNC 1000 10 0 main\n`, ); writeFileSync(join(dir, "z.txt"), mapping); diff --git a/packages/cli/test/lib/dif/il2cpp.test.ts b/packages/cli/test/lib/dif/il2cpp.test.ts index 01ac8bb78..42130eb0d 100644 --- a/packages/cli/test/lib/dif/il2cpp.test.ts +++ b/packages/cli/test/lib/dif/il2cpp.test.ts @@ -59,7 +59,7 @@ describe("createIl2cppLineMapping", () => { const result = createIl2cppLineMapping( bytes(BREAKPAD_WITH_CPP), () => bytes(CPP_WITH_SOURCE_INFO), - KNOWN_DEBUG_ID + KNOWN_DEBUG_ID, ); expect(result?.debugId).toBe(KNOWN_DEBUG_ID); }); @@ -68,22 +68,22 @@ describe("createIl2cppLineMapping", () => { const result = createIl2cppLineMapping( bytes(BREAKPAD_WITH_CPP), () => bytes(CPP_WITH_SOURCE_INFO), - "ffffffff-ffff-ffff-ffff-ffffffffffff" + "ffffffff-ffff-ffff-ffff-ffffffffffff", ); expect(result?.debugId).toBe(KNOWN_DEBUG_ID); }); test("returns null when no referenced source is available", () => { expect( - createIl2cppLineMapping(bytes(BREAKPAD_WITH_CPP), () => null) + createIl2cppLineMapping(bytes(BREAKPAD_WITH_CPP), () => null), ).toBeNull(); }); test("returns null when the C++ has no source_info markers", () => { expect( createIl2cppLineMapping(bytes(BREAKPAD_WITH_CPP), () => - bytes("int plain = 0;\n") - ) + bytes("int plain = 0;\n"), + ), ).toBeNull(); }); }); @@ -109,7 +109,7 @@ describe("createSourceBundle collectIl2cppSources", () => { read, { collectIl2cppSources: true, - } + }, ); const files = bundleFiles(result.bundle); expect(files).toContain(`files${CPP_PATH}`); @@ -120,7 +120,7 @@ describe("createSourceBundle collectIl2cppSources", () => { const result = createSourceBundle( bytes(BREAKPAD_WITH_CPP), "example", - read + read, ); const files = bundleFiles(result.bundle); expect(files).toContain(`files${CPP_PATH}`); diff --git a/packages/cli/test/lib/dif/index.test.ts b/packages/cli/test/lib/dif/index.test.ts index c493be2c1..8dc0a1afb 100644 --- a/packages/cli/test/lib/dif/index.test.ts +++ b/packages/cli/test/lib/dif/index.test.ts @@ -69,7 +69,7 @@ describe("parseDebugFile", () => { test("normalizes the debug id to lowercase UUID form", () => { const archive = parseDebugFile(toBytes(BREAKPAD_FIXTURE)); expect(archive.objects[0]?.debugId).toMatch( - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, ); }); @@ -90,10 +90,10 @@ describe("parseDebugFile", () => { test("renders a nil debug id as the hyphenated nil UUID", () => { // Guards the check command's nil-id sentinel against format drift. const archive = parseDebugFile( - toBytes("MODULE Linux x86_64 000000000000000000000000000000000 x") + toBytes("MODULE Linux x86_64 000000000000000000000000000000000 x"), ); expect(archive.objects[0]?.debugId).toBe( - "00000000-0000-0000-0000-000000000000" + "00000000-0000-0000-0000-000000000000", ); }); }); @@ -107,7 +107,7 @@ describe("createSourceBundle", () => { (path) => { requested.push(path); return toBytes(`// ${path}`); - } + }, ); expect(requested).toContain("/src/example.c"); @@ -121,7 +121,7 @@ describe("createSourceBundle", () => { const result = createSourceBundle( toBytes(BREAKPAD_WITH_SOURCE), "example", - () => null + () => null, ); expect(result.fileCount).toBe(0); expect(result.bundle).toBeNull(); @@ -131,7 +131,7 @@ describe("createSourceBundle", () => { const result = createSourceBundle( toBytes(BREAKPAD_FIXTURE), "example", - () => toBytes("unused") + () => toBytes("unused"), ); expect(result.fileCount).toBe(0); expect(result.bundle).toBeNull(); @@ -143,13 +143,13 @@ describe("createSourceBundle", () => { expect(() => createSourceBundle(toBytes(BREAKPAD_WITH_SOURCE), "example", () => { throw new Error("read failed"); - }) + }), ).toThrow(); }); test("throws on unrecognized data", () => { expect(() => - createSourceBundle(toBytes("not an object file"), "x", () => null) + createSourceBundle(toBytes("not an object file"), "x", () => null), ).toThrow(); }); }); diff --git a/packages/cli/test/lib/dif/scan.property.test.ts b/packages/cli/test/lib/dif/scan.property.test.ts index 6bc8b385e..ad3e17b98 100644 --- a/packages/cli/test/lib/dif/scan.property.test.ts +++ b/packages/cli/test/lib/dif/scan.property.test.ts @@ -34,7 +34,7 @@ const NIL_DEBUG_ID = "00000000-0000-0000-0000-000000000000"; const hexGroup = (length: number) => tuple( - ...Array.from({ length }, () => constantFrom(..."0123456789abcdef")) + ...Array.from({ length }, () => constantFrom(..."0123456789abcdef")), ).map((chars) => chars.join("")); /** A non-nil UUID (8-4-4-4-12). May coincide with nil with negligible odds. */ @@ -43,14 +43,14 @@ const uuidArb = tuple( hexGroup(4), hexGroup(4), hexGroup(4), - hexGroup(12) + hexGroup(12), ).map((parts) => parts.join("-")); /** A debug id: valid UUID, UUID+age suffix, or the nil id. */ const debugIdArb = oneof( uuidArb, tuple(uuidArb, hexGroup(8)).map(([id, age]) => `${id}-${age}`), - constant(NIL_DEBUG_ID) + constant(NIL_DEBUG_ID), ); const formatArb = constantFrom( @@ -62,7 +62,7 @@ const formatArb = constantFrom( "wasm", "breakpad", "sourcebundle", - "unknown" + "unknown", ); const difObjectArb: Arbitrary = record({ @@ -87,7 +87,7 @@ describe("property: normalizeDebugId", () => { const once = normalizeDebugId(id); expect(normalizeDebugId(once)).toBe(once); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -100,7 +100,7 @@ describe("property: normalizeDebugId", () => { expect(normalized).not.toContain("{"); expect(normalized).not.toContain("}"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -112,7 +112,7 @@ describe("property: objectPassesFilters", () => { const nilObj = { ...obj, debugId: NIL_DEBUG_ID }; expect(objectPassesFilters(nilObj, defaultFilters())).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -128,7 +128,7 @@ describe("property: objectPassesFilters", () => { }; expect(objectPassesFilters(featureless, defaultFilters())).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -142,7 +142,7 @@ describe("property: objectPassesFilters", () => { property(difObjectArb, (obj) => { expect(objectPassesFilters(obj, noFeatures)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -155,7 +155,7 @@ describe("property: objectPassesFilters", () => { expect(objectPassesFilters(obj, elfOnly)).toBe(false); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -175,7 +175,7 @@ describe("property: objectPassesFilters", () => { }; expect(objectPassesFilters(obj, defaultFilters())).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -197,7 +197,7 @@ describe("property: objectPassesFilters", () => { const idFilter = buildDifFilters({ ids: [id] }); expect(objectPassesFilters(obj, idFilter)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -209,7 +209,7 @@ describe("property: debugIdMatches", () => { expect(debugIdMatches(id, id)).toBe(true); expect(debugIdMatches(id, `{${id.toUpperCase()}}`)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -219,7 +219,7 @@ describe("property: debugIdMatches", () => { expect(debugIdMatches(id, `${id}-${age}`)).toBe(true); expect(debugIdMatches(`${id}-${age}`, id)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -233,7 +233,7 @@ describe("buildDifFilters", () => { test("throws ValidationError on an unknown type", () => { expect(() => buildDifFilters({ types: ["bogus"] })).toThrow( - ValidationError + ValidationError, ); }); diff --git a/packages/cli/test/lib/dif/scan.test.ts b/packages/cli/test/lib/dif/scan.test.ts index 2626c19a5..615618bfc 100644 --- a/packages/cli/test/lib/dif/scan.test.ts +++ b/packages/cli/test/lib/dif/scan.test.ts @@ -44,7 +44,7 @@ describe("prepareDifs size gate", () => { const { prepared, oversizedCount } = await prepareDifs( files, buildDifFilters({}), - { maxFileSize: 10 * 1024 } + { maxFileSize: 10 * 1024 }, ); expect(prepared).toHaveLength(1); expect(oversizedCount).toBe(0); @@ -58,7 +58,7 @@ describe("prepareDifs size gate", () => { const { prepared, oversizedCount } = await prepareDifs( files, buildDifFilters({}), - { maxFileSize: 1 } + { maxFileSize: 1 }, ); expect(prepared).toHaveLength(0); expect(oversizedCount).toBe(1); @@ -70,7 +70,7 @@ describe("prepareDifs size gate", () => { const files = await scanPaths([path]); const { prepared, oversizedCount } = await prepareDifs( files, - buildDifFilters({}) + buildDifFilters({}), ); expect(prepared).toHaveLength(1); expect(oversizedCount).toBe(0); @@ -86,7 +86,7 @@ describe("prepareDifs size gate", () => { const { prepared, oversizedCount } = await prepareDifs( files, buildDifFilters({ types: ["elf"] }), - { maxFileSize: 1 } + { maxFileSize: 1 }, ); expect(prepared).toHaveLength(0); expect(oversizedCount).toBe(0); @@ -99,7 +99,7 @@ describe("prepareDifs size gate", () => { const { prepared, oversizedCount } = await prepareDifs( files, buildDifFilters({ types: ["breakpad"] }), - { maxFileSize: 1 } + { maxFileSize: 1 }, ); expect(prepared).toHaveLength(0); expect(oversizedCount).toBe(1); @@ -169,7 +169,7 @@ describe("scanPaths traversal", () => { test("throws ValidationError for a non-existent path", async () => { await expect(scanPaths([join(tempDir, "does-not-exist")])).rejects.toThrow( - /does not exist/ + /does not exist/, ); }); }); diff --git a/packages/cli/test/lib/dif/zip.test.ts b/packages/cli/test/lib/dif/zip.test.ts index d21860607..c58f3ef41 100644 --- a/packages/cli/test/lib/dif/zip.test.ts +++ b/packages/cli/test/lib/dif/zip.test.ts @@ -46,7 +46,7 @@ afterEach(async () => { /** Write a ZIP built from `entries` to `name` under the temp dir, return path. */ async function writeZip( name: string, - entries: Record + entries: Record, ): Promise { const path = join(tempDir, name); await writeFile(path, zipSync(entries)); @@ -63,7 +63,7 @@ async function writeZip( function corruptCompressionMethod( zip: Uint8Array, targetName: string, - method: number + method: number, ): Uint8Array { const out = new Uint8Array(zip); const target = new TextEncoder().encode(targetName); @@ -78,7 +78,7 @@ function corruptCompressionMethod( } return true; }; - // Little-endian 16-bit read/write via arithmetic (Biome bans bitwise ops). + // Little-endian 16-bit read/write via arithmetic. const readU16 = (offset: number): number => (out[offset] ?? 0) + (out[offset + 1] ?? 0) * 256; const setMethod = (offset: number) => { @@ -128,7 +128,7 @@ describe("readZipDifEntries", () => { const inner = zipSync({ "example.sym": BREAKPAD_BYTES }); await writeFile( path, - Buffer.concat([Buffer.from(header), Buffer.from(inner)]) + Buffer.concat([Buffer.from(header), Buffer.from(inner)]), ); expect(await readZipDifEntries(path)).toBeNull(); }); @@ -167,7 +167,7 @@ describe("readZipDifEntries", () => { // unsupported entry must be dropped while its sibling DIF still extracts. const raw = zipSync( { "good.sym": BREAKPAD_BYTES, "bad.bin": new Uint8Array([1, 2, 3, 4]) }, - { level: 0 } + { level: 0 }, ); const path = join(tempDir, "mixed.zip"); await writeFile(path, corruptCompressionMethod(raw, "bad.bin", 99)); @@ -203,7 +203,7 @@ describe("prepareDifs ZIP scanning", () => { const files = await scanPaths([path]); const { prepared, oversizedCount } = await prepareDifs( files, - buildDifFilters({}) + buildDifFilters({}), ); expect(prepared).toHaveLength(1); expect(prepared[0]?.path).toBe(`${path}/example.sym`); @@ -254,7 +254,7 @@ describe("prepareDifs ZIP scanning", () => { const { prepared, oversizedCount } = await prepareDifs( files, buildDifFilters({}), - { maxFileSize: 1 } + { maxFileSize: 1 }, ); expect(prepared).toHaveLength(0); expect(oversizedCount).toBe(0); @@ -281,7 +281,7 @@ describe("prepareDifs ZIP scanning", () => { // ...and kept by --type breakpad. const bp = await prepareDifs( files, - buildDifFilters({ types: ["breakpad"] }) + buildDifFilters({ types: ["breakpad"] }), ); expect(bp.prepared).toHaveLength(1); }); diff --git a/packages/cli/test/lib/docs-service.test.ts b/packages/cli/test/lib/docs-service.test.ts index 6aefae2ba..078232887 100644 --- a/packages/cli/test/lib/docs-service.test.ts +++ b/packages/cli/test/lib/docs-service.test.ts @@ -46,7 +46,7 @@ describe("queryDocs", () => { JSON.stringify({ code: "DOCS_MODEL_UNAVAILABLE", error: "Sentry Docs AI is temporarily unavailable in this region.", - }) + }), ), }); @@ -55,7 +55,7 @@ describe("queryDocs", () => { frameworks: [], languages: [], sentryConfigured: false, - }) + }), ).rejects.toMatchObject({ exitCode: EXIT.API, message: @@ -72,7 +72,7 @@ describe("queryDocs", () => { JSON.stringify({ code: "DOCS_UNGROUNDED", error: "Sentry documentation answer could not be verified", - }) + }), ), }); @@ -81,11 +81,11 @@ describe("queryDocs", () => { frameworks: [], languages: [], sentryConfigured: false, - }) + }), ).rejects.toMatchObject({ exitCode: EXIT.API, message: expect.stringContaining( - "Could not produce a verified documentation answer." + "Could not produce a verified documentation answer.", ), }); }); @@ -99,7 +99,7 @@ describe("queryDocs", () => { JSON.stringify({ code: "DOCS_UNGROUNDED", error: "Sentry documentation answer could not be verified", - }) + }), ), }); diff --git a/packages/cli/test/lib/dsn.property.test.ts b/packages/cli/test/lib/dsn.property.test.ts index 5cccd8296..9327966ec 100644 --- a/packages/cli/test/lib/dsn.property.test.ts +++ b/packages/cli/test/lib/dsn.property.test.ts @@ -45,7 +45,9 @@ const regionArb = constantFrom("us", "de", "eu", ""); /** Generate valid SaaS ingest hosts with org ID */ const saasHostArb = tuple(orgIdArb, regionArb).map(([orgId, region]) => - region ? `o${orgId}.ingest.${region}.sentry.io` : `o${orgId}.ingest.sentry.io` + region + ? `o${orgId}.ingest.${region}.sentry.io` + : `o${orgId}.ingest.sentry.io`, ); /** Generate self-hosted hosts (no org ID pattern) */ @@ -53,19 +55,19 @@ const selfHostedHostArb = constantFrom( "sentry.mycompany.com", "errors.internal.corp", "sentry.localhost", - "my-sentry.example.org" + "my-sentry.example.org", ); /** Generate complete valid DSN strings */ const validDsnArb = tuple(publicKeyArb, saasHostArb, projectIdArb).map( - ([key, host, projectId]) => `https://${key}@${host}/${projectId}` + ([key, host, projectId]) => `https://${key}@${host}/${projectId}`, ); /** Generate valid self-hosted DSN strings */ const selfHostedDsnArb = tuple( publicKeyArb, selfHostedHostArb, - projectIdArb + projectIdArb, ).map(([key, host, projectId]) => `https://${key}@${host}/${projectId}`); /** Generate invalid DSN-like strings */ @@ -76,7 +78,7 @@ const invalidDsnArb = constantFrom( "https://@sentry.io/123", // empty key "ftp://key@sentry.io/123", // still valid URL, but will parse "https://key@/123", // no host - "://key@sentry.io/123" // invalid protocol + "://key@sentry.io/123", // invalid protocol ); /** Generate monorepo-style paths */ @@ -86,7 +88,7 @@ const monorepoPathArb = tuple( minLength: 1, maxLength: 15, }), - constantFrom("src/index.ts", ".env", "config.js", "main.py") + constantFrom("src/index.ts", ".env", "config.js", "main.py"), ).map(([root, pkgChars, file]) => `${root}/${pkgChars.join("")}/${file}`); /** Generate non-monorepo paths */ @@ -96,7 +98,7 @@ const rootPathArb = constantFrom( "config.js", "main.py", "index.js", - "app/main.ts" + "app/main.ts", ); // Properties for parseDsn @@ -112,7 +114,7 @@ describe("property: parseDsn", () => { expect(result?.host).toBeDefined(); expect(result?.projectId).toBeDefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -123,7 +125,7 @@ describe("property: parseDsn", () => { // SaaS DSNs should have orgId extracted expect(result?.orgId).toBeDefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -134,7 +136,7 @@ describe("property: parseDsn", () => { expect(result).not.toBeNull(); expect(result?.orgId).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -148,7 +150,7 @@ describe("property: parseDsn", () => { expect(result).toBeNull(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -161,7 +163,7 @@ describe("property: parseDsn", () => { expect(reconstructed).toBe(dsn); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -179,7 +181,7 @@ describe("property: extractOrgIdFromHost", () => { const result = extractOrgIdFromHost(host); expect(result).toBe(String(orgId)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -189,7 +191,7 @@ describe("property: extractOrgIdFromHost", () => { const result = extractOrgIdFromHost(host); expect(result).toBeNull(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -199,7 +201,7 @@ describe("property: extractOrgIdFromHost", () => { "ingest.sentry.io", "o.ingest.sentry.io", // no number "oabc.ingest.sentry.io", // letters instead of number - "o123.sentry.io" // missing "ingest" + "o123.sentry.io", // missing "ingest" ); fcAssert( @@ -207,7 +209,7 @@ describe("property: extractOrgIdFromHost", () => { const result = extractOrgIdFromHost(host); expect(result).toBeNull(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -220,7 +222,7 @@ describe("property: isValidDsn", () => { property(validDsnArb, (dsn) => { expect(isValidDsn(dsn)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -229,7 +231,7 @@ describe("property: isValidDsn", () => { property(selfHostedDsnArb, (dsn) => { expect(isValidDsn(dsn)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -240,7 +242,7 @@ describe("property: isValidDsn", () => { const parsed = parseDsn(input); expect(isValid).toBe(parsed !== null); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -257,7 +259,7 @@ describe("property: createDetectedDsn", () => { expect(result).toBeNull(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -267,7 +269,7 @@ describe("property: createDetectedDsn", () => { const result = createDetectedDsn(dsn, "code", "src/index.ts"); expect(result?.raw).toBe(dsn); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -280,16 +282,16 @@ describe("property: createDetectedDsn", () => { "env" as const, "env_file" as const, "code" as const, - "config" as const - ) + "config" as const, + ), ), ([dsn, source]) => { const result = createDetectedDsn(dsn, source, "test/path.ts"); expect(result?.source).toBe(source); expect(result?.sourcePath).toBe("test/path.ts"); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -308,7 +310,7 @@ describe("property: createDsnFingerprint", () => { const fp2 = createDsnFingerprint(detected); expect(fp1).toBe(fp2); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -325,7 +327,7 @@ describe("property: createDsnFingerprint", () => { const fp2 = createDsnFingerprint([...detected].reverse()); expect(fp1).toBe(fp2); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -341,7 +343,7 @@ describe("property: createDsnFingerprint", () => { expect(fpSingle).toBe(fpDuplicate); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -367,7 +369,7 @@ describe("property: createDsnFingerprint", () => { } } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -394,7 +396,7 @@ describe("property: createDsnFingerprint", () => { } } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -412,7 +414,7 @@ describe("property: inferPackagePath", () => { const parts = result!.split("/"); expect(parts.length).toBe(2); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -422,7 +424,7 @@ describe("property: inferPackagePath", () => { const result = inferPackagePath(path); expect(result).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -438,7 +440,7 @@ describe("property: inferPackagePath", () => { expect(result.length).toBeGreaterThan(0); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/dsn.test.ts b/packages/cli/test/lib/dsn.test.ts index 80dfee23d..45dcd7482 100644 --- a/packages/cli/test/lib/dsn.test.ts +++ b/packages/cli/test/lib/dsn.test.ts @@ -82,13 +82,12 @@ describe("placeholder helpers", () => { expect(isPlaceholderNumericId(id)).toBe(true); }); - test.each([ - "1", - "10", - "1169445", - ])("isPlaceholderNumericId rejects %s", (id) => { - expect(isPlaceholderNumericId(id)).toBe(false); - }); + test.each(["1", "10", "1169445"])( + "isPlaceholderNumericId rejects %s", + (id) => { + expect(isPlaceholderNumericId(id)).toBe(false); + }, + ); test.each([ "examplePublicKey", @@ -101,14 +100,12 @@ describe("placeholder helpers", () => { expect(isPlaceholderPublicKey(key)).toBe(true); }); - test.each([ - "abc123def456", - "public", - "publickey", - "public-app-key", - ])("isPlaceholderPublicKey rejects %s", (key) => { - expect(isPlaceholderPublicKey(key)).toBe(false); - }); + test.each(["abc123def456", "public", "publickey", "public-app-key"])( + "isPlaceholderPublicKey rejects %s", + (key) => { + expect(isPlaceholderPublicKey(key)).toBe(false); + }, + ); }); describe("createDsnFingerprint: self-hosted DSNs", () => { @@ -158,7 +155,7 @@ describe("createDetectedDsn edge cases", () => { "https://abc123@o123.ingest.sentry.io/456", "code", "packages/web/src/config.ts", - "packages/web" + "packages/web", ); expect(result?.packagePath).toBe("packages/web"); }); @@ -167,7 +164,7 @@ describe("createDetectedDsn edge cases", () => { describe("inferPackagePath", () => { test("infers package path from packages/ directory", () => { expect(inferPackagePath("packages/frontend/src/index.ts")).toBe( - "packages/frontend" + "packages/frontend", ); }); diff --git a/packages/cli/test/lib/dsn/code-scanner.property.test.ts b/packages/cli/test/lib/dsn/code-scanner.property.test.ts index 218505799..8847b9c52 100644 --- a/packages/cli/test/lib/dsn/code-scanner.property.test.ts +++ b/packages/cli/test/lib/dsn/code-scanner.property.test.ts @@ -63,7 +63,7 @@ describe("property: extractDsnsFromContent fast-path invariance", () => { .replace(/HTTP/g, "XXXX"); expect(extractDsnsFromContent(clean)).toEqual([]); }), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 100) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 100) }, ); }); @@ -80,7 +80,7 @@ describe("property: extractDsnsFromContent fast-path invariance", () => { expect(reference.has(dsn)).toBe(true); } }), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 100) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 100) }, ); }); }); diff --git a/packages/cli/test/lib/dsn/code-scanner.test.ts b/packages/cli/test/lib/dsn/code-scanner.test.ts index 276dfa12f..09156d00f 100644 --- a/packages/cli/test/lib/dsn/code-scanner.test.ts +++ b/packages/cli/test/lib/dsn/code-scanner.test.ts @@ -66,17 +66,20 @@ describe("Code Scanner", () => { ["title", "Https://abc@o1.ingest.sentry.io/456"], ["mixed", "hTtPs://abc@o1.ingest.sentry.io/456"], ["http no s", "HTTP://abc@o1.ingest.sentry.io/456"], - ])("detects DSN with %s scheme casing (regression: literal-prefix fast path)", (_label, url) => { - // An earlier version of the literal-prefix probe used two - // case-sensitive `indexOf` calls (covering only all-lower and - // all-upper), which silently dropped mixed-case schemes like - // `Https://`. The probe is now `/http/i`. This test pins the - // correctness contract. - const content = `const DSN = "${url}";`; - const dsns = extractDsnsFromContent(content); - expect(dsns).toHaveLength(1); - expect(dsns[0]).toBe(url); - }); + ])( + "detects DSN with %s scheme casing (regression: literal-prefix fast path)", + (_label, url) => { + // An earlier version of the literal-prefix probe used two + // case-sensitive `indexOf` calls (covering only all-lower and + // all-upper), which silently dropped mixed-case schemes like + // `Https://`. The probe is now `/http/i`. This test pins the + // correctness contract. + const content = `const DSN = "${url}";`; + const dsns = extractDsnsFromContent(content); + expect(dsns).toHaveLength(1); + expect(dsns[0]).toBe(url); + }, + ); test("extracts multiple DSNs", () => { const content = ` @@ -232,7 +235,7 @@ describe("Code Scanner", () => { // Invalid SENTRY_URL should throw immediately since nothing will work expect(() => extractDsnsFromContent(content)).toThrow( - /SENTRY_HOST\/SENTRY_URL.*not a valid URL/ + /SENTRY_HOST\/SENTRY_URL.*not a valid URL/, ); }); @@ -278,7 +281,7 @@ describe("Code Scanner", () => { test("finds DSN in root file", async () => { writeFileSync( join(testDir, "config.ts"), - 'const DSN = "https://abc@o123.ingest.sentry.io/456";' + 'const DSN = "https://abc@o123.ingest.sentry.io/456";', ); const result = await scanCodeForFirstDsn(testDir); @@ -291,7 +294,7 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "src"), { recursive: true }); writeFileSync( join(testDir, "src/sentry.ts"), - 'Sentry.init({ dsn: "https://abc@o123.ingest.sentry.io/456" });' + 'Sentry.init({ dsn: "https://abc@o123.ingest.sentry.io/456" });', ); const result = await scanCodeForFirstDsn(testDir); @@ -312,7 +315,7 @@ describe("Code Scanner", () => { }); writeFileSync( join(testDir, "node_modules/some-package/index.js"), - 'const DSN = "https://abc@o123.ingest.sentry.io/456";' + 'const DSN = "https://abc@o123.ingest.sentry.io/456";', ); const result = await scanCodeForFirstDsn(testDir); @@ -324,7 +327,7 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "test/lib"), { recursive: true }); writeFileSync( join(testDir, "test/lib/scanner.test.ts"), - 'const DSN = "https://testkey@o123.ingest.sentry.io/456";' + 'const DSN = "https://testkey@o123.ingest.sentry.io/456";', ); const result = await scanCodeForFirstDsn(testDir); @@ -334,7 +337,7 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "tests/unit"), { recursive: true }); writeFileSync( join(testDir, "tests/unit/app.test.ts"), - 'const DSN = "https://testkey@o999.ingest.sentry.io/789";' + 'const DSN = "https://testkey@o999.ingest.sentry.io/789";', ); const allResult = await scanCodeForDsns(testDir); @@ -345,12 +348,12 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "__mocks__"), { recursive: true }); writeFileSync( join(testDir, "__mocks__/sentry.ts"), - 'export const DSN = "https://mock@o123.ingest.sentry.io/111";' + 'export const DSN = "https://mock@o123.ingest.sentry.io/111";', ); mkdirSync(join(testDir, "fixtures"), { recursive: true }); writeFileSync( join(testDir, "fixtures/config.ts"), - 'export const DSN = "https://fixture@o123.ingest.sentry.io/222";' + 'export const DSN = "https://fixture@o123.ingest.sentry.io/222";', ); const result = await scanCodeForDsns(testDir); @@ -362,18 +365,18 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "src"), { recursive: true }); writeFileSync( join(testDir, "src/config.ts"), - 'const DSN = "https://realkey@o123.ingest.sentry.io/456";' + 'const DSN = "https://realkey@o123.ingest.sentry.io/456";', ); mkdirSync(join(testDir, "test"), { recursive: true }); writeFileSync( join(testDir, "test/fixture.ts"), - 'const DSN = "https://fakekey@o999.ingest.sentry.io/999";' + 'const DSN = "https://fakekey@o999.ingest.sentry.io/999";', ); const result = await scanCodeForDsns(testDir); expect(result.dsns).toHaveLength(1); expect(result.dsns[0].raw).toBe( - "https://realkey@o123.ingest.sentry.io/456" + "https://realkey@o123.ingest.sentry.io/456", ); }); @@ -382,11 +385,11 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "ignored"), { recursive: true }); writeFileSync( join(testDir, "ignored/config.ts"), - 'const DSN = "https://ignored@o123.ingest.sentry.io/456";' + 'const DSN = "https://ignored@o123.ingest.sentry.io/456";', ); writeFileSync( join(testDir, "real.ts"), - 'const DSN = "https://real@o456.ingest.sentry.io/789";' + 'const DSN = "https://real@o456.ingest.sentry.io/789";', ); const result = await scanCodeForFirstDsn(testDir); @@ -398,7 +401,7 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "packages/frontend"), { recursive: true }); writeFileSync( join(testDir, "packages/frontend/sentry.ts"), - 'const DSN = "https://abc@o123.ingest.sentry.io/456";' + 'const DSN = "https://abc@o123.ingest.sentry.io/456";', ); const result = await scanCodeForFirstDsn(testDir); @@ -409,7 +412,7 @@ describe("Code Scanner", () => { // Test Python writeFileSync( join(testDir, "app.py"), - 'sentry_sdk.init(dsn="https://py@o123.ingest.sentry.io/1")' + 'sentry_sdk.init(dsn="https://py@o123.ingest.sentry.io/1")', ); let result = await scanCodeForFirstDsn(testDir); @@ -419,7 +422,7 @@ describe("Code Scanner", () => { rmSync(join(testDir, "app.py")); writeFileSync( join(testDir, "main.go"), - 'sentry.Init(sentry.ClientOptions{Dsn: "https://go@o123.ingest.sentry.io/2"})' + 'sentry.Init(sentry.ClientOptions{Dsn: "https://go@o123.ingest.sentry.io/2"})', ); result = await scanCodeForFirstDsn(testDir); @@ -429,7 +432,7 @@ describe("Code Scanner", () => { rmSync(join(testDir, "main.go")); writeFileSync( join(testDir, "config.rb"), - 'Sentry.init do |config|\n config.dsn = "https://rb@o123.ingest.sentry.io/3"\nend' + 'Sentry.init do |config|\n config.dsn = "https://rb@o123.ingest.sentry.io/3"\nend', ); result = await scanCodeForFirstDsn(testDir); @@ -442,11 +445,11 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "src"), { recursive: true }); writeFileSync( join(testDir, "src/frontend.ts"), - 'const DSN = "https://frontend@o123.ingest.sentry.io/111";' + 'const DSN = "https://frontend@o123.ingest.sentry.io/111";', ); writeFileSync( join(testDir, "src/backend.ts"), - 'const DSN = "https://backend@o456.ingest.sentry.io/222";' + 'const DSN = "https://backend@o456.ingest.sentry.io/222";', ); const result = await scanCodeForDsns(testDir); @@ -465,11 +468,11 @@ describe("Code Scanner", () => { test("deduplicates same DSN from multiple files", async () => { writeFileSync( join(testDir, "a.ts"), - 'const DSN = "https://same@o123.ingest.sentry.io/456";' + 'const DSN = "https://same@o123.ingest.sentry.io/456";', ); writeFileSync( join(testDir, "b.ts"), - 'const DSN = "https://same@o123.ingest.sentry.io/456";' + 'const DSN = "https://same@o123.ingest.sentry.io/456";', ); const result = await scanCodeForDsns(testDir); @@ -525,13 +528,13 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "packages/spotlight/src"), { recursive: true }); writeFileSync( join(testDir, "packages/spotlight/src/instrument.ts"), - 'Sentry.init({ dsn: "https://spotlight@o123.ingest.sentry.io/111" });' + 'Sentry.init({ dsn: "https://spotlight@o123.ingest.sentry.io/111" });', ); const result = await scanCodeForDsns(testDir); expect(result.dsns).toHaveLength(1); expect(result.dsns[0]?.raw).toBe( - "https://spotlight@o123.ingest.sentry.io/111" + "https://spotlight@o123.ingest.sentry.io/111", ); expect(result.dsns[0]?.packagePath).toBe("packages/spotlight"); }); @@ -541,11 +544,11 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "packages/backend/src"), { recursive: true }); writeFileSync( join(testDir, "packages/frontend/src/sentry.ts"), - 'const DSN = "https://fe@o123.ingest.sentry.io/111";' + 'const DSN = "https://fe@o123.ingest.sentry.io/111";', ); writeFileSync( join(testDir, "packages/backend/src/sentry.ts"), - 'const DSN = "https://be@o456.ingest.sentry.io/222";' + 'const DSN = "https://be@o456.ingest.sentry.io/222";', ); const result = await scanCodeForDsns(testDir); @@ -571,13 +574,13 @@ describe("Code Scanner", () => { }); writeFileSync( join(testDir, "packages/spotlight/src/electron/main/index.ts"), - 'Sentry.init({ dsn: "https://electron@o123.ingest.sentry.io/333" });' + 'Sentry.init({ dsn: "https://electron@o123.ingest.sentry.io/333" });', ); const result = await scanCodeForDsns(testDir); expect(result.dsns).toHaveLength(1); expect(result.dsns[0]?.raw).toBe( - "https://electron@o123.ingest.sentry.io/333" + "https://electron@o123.ingest.sentry.io/333", ); expect(result.dsns[0]?.packagePath).toBe("packages/spotlight"); }); @@ -589,7 +592,7 @@ describe("Code Scanner", () => { mkdirSync(join(testDir, "src/very/deeply/nested"), { recursive: true }); writeFileSync( join(testDir, "src/very/deeply/nested/config.ts"), - 'const DSN = "https://deep@o123.ingest.sentry.io/999";' + 'const DSN = "https://deep@o123.ingest.sentry.io/999";', ); const result = await scanCodeForDsns(testDir); @@ -601,7 +604,7 @@ describe("Code Scanner", () => { const filePath = join(testDir, "secret.ts"); writeFileSync( filePath, - 'const DSN = "https://abc@o123.ingest.sentry.io/456";' + 'const DSN = "https://abc@o123.ingest.sentry.io/456";', ); chmodSync(filePath, 0o000); diff --git a/packages/cli/test/lib/dsn/detector.test.ts b/packages/cli/test/lib/dsn/detector.test.ts index 4676edca2..e3d7e44a9 100644 --- a/packages/cli/test/lib/dsn/detector.test.ts +++ b/packages/cli/test/lib/dsn/detector.test.ts @@ -89,7 +89,7 @@ describe("DSN Detector (New Module)", () => { mkdirSync(join(testDir, "src"), { recursive: true }); writeFileSync( join(testDir, "src/config.ts"), - `Sentry.init({ dsn: "${codeDsn}" })` + `Sentry.init({ dsn: "${codeDsn}" })`, ); // Code DSN takes priority over .env file DSN @@ -110,7 +110,7 @@ describe("DSN Detector (New Module)", () => { mkdirSync(join(testDir, "src"), { recursive: true }); writeFileSync( join(testDir, "src/config.ts"), - `Sentry.init({ dsn: "${codeDsn}" })` + `Sentry.init({ dsn: "${codeDsn}" })`, ); // Should return code DSN (highest priority) @@ -198,7 +198,7 @@ describe("DSN Detector (New Module)", () => { mkdirSync(join(testDir, "src"), { recursive: true }); writeFileSync( join(testDir, "src/config.ts"), - `Sentry.init({ dsn: "${codeDsn}" })` + `Sentry.init({ dsn: "${codeDsn}" })`, ); // Next detection should find the code DSN (higher priority) @@ -223,13 +223,13 @@ describe("DSN Detector (New Module)", () => { }); writeFileSync( join(testDir, "node_modules/some-package/index.js"), - `Sentry.init({ dsn: "${nodeModulesDsn}" })` + `Sentry.init({ dsn: "${nodeModulesDsn}" })`, ); mkdirSync(join(testDir, "dist"), { recursive: true }); writeFileSync( join(testDir, "dist/bundle.js"), - `Sentry.init({ dsn: "${distDsn}" })` + `Sentry.init({ dsn: "${distDsn}" })`, ); // Should not find any DSN (skipped directories) @@ -287,7 +287,7 @@ describe("DSN Detector (New Module)", () => { mkdirSync(join(testDir, "src"), { recursive: true }); writeFileSync( join(testDir, "src/config.ts"), - `Sentry.init({ dsn: "${codeDsn}" })` + `Sentry.init({ dsn: "${codeDsn}" })`, ); const result = await detectAllDsns(testDir); @@ -323,11 +323,11 @@ describe("DSN Detector (New Module)", () => { writeFileSync( join(testDir, "packages/frontend/.env"), - `SENTRY_DSN=${frontendDsn}` + `SENTRY_DSN=${frontendDsn}`, ); writeFileSync( join(testDir, "packages/backend/.env"), - `SENTRY_DSN=${backendDsn}` + `SENTRY_DSN=${backendDsn}`, ); const result = await detectAllDsns(testDir); @@ -355,7 +355,7 @@ describe("DSN Detector (New Module)", () => { writeFileSync(join(testDir, "apps/web/.env"), `SENTRY_DSN=${webDsn}`); writeFileSync( join(testDir, "apps/mobile/.env"), - `SENTRY_DSN=${mobileDsn}` + `SENTRY_DSN=${mobileDsn}`, ); const result = await detectAllDsns(testDir); @@ -383,7 +383,7 @@ describe("DSN Detector (New Module)", () => { }; expect(getDsnSourceDescription(dsn)).toBe( - "SENTRY_DSN environment variable" + "SENTRY_DSN environment variable", ); }); @@ -400,7 +400,7 @@ describe("DSN Detector (New Module)", () => { }; expect(getDsnSourceDescription(dsn)).toBe( - "NEXT_PUBLIC_SENTRY_DSN environment variable" + "NEXT_PUBLIC_SENTRY_DSN environment variable", ); }); diff --git a/packages/cli/test/lib/dsn/env-file.test.ts b/packages/cli/test/lib/dsn/env-file.test.ts index 1dc945694..d7460ea50 100644 --- a/packages/cli/test/lib/dsn/env-file.test.ts +++ b/packages/cli/test/lib/dsn/env-file.test.ts @@ -31,12 +31,12 @@ import { cleanupTestDir, createTestConfigDir } from "../../helpers.js"; /** Valid DSN format for testing */ const validDsnArb = string({ minLength: 1, maxLength: 20 }).map( - (key) => `https://${key.replace(/[^a-z0-9]/gi, "a")}@sentry.io/123` + (key) => `https://${key.replace(/[^a-z0-9]/gi, "a")}@sentry.io/123`, ); /** Generate content without SENTRY_DSN */ const envContentWithoutDsnArb = oneof( - constantFrom("", "# Just a comment", "OTHER_VAR=value", "SENTRY_OTHER=value") + constantFrom("", "# Just a comment", "OTHER_VAR=value", "SENTRY_OTHER=value"), ); // ============================================================================ @@ -50,7 +50,7 @@ describe("property: extractDsnFromEnvContent", () => { const content = `SENTRY_DSN=${dsn}`; expect(extractDsnFromEnvContent(content)).toBe(dsn); }), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -60,7 +60,7 @@ describe("property: extractDsnFromEnvContent", () => { const content = `SENTRY_DSN="${dsn}"`; expect(extractDsnFromEnvContent(content)).toBe(dsn); }), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -70,7 +70,7 @@ describe("property: extractDsnFromEnvContent", () => { const content = `SENTRY_DSN='${dsn}'`; expect(extractDsnFromEnvContent(content)).toBe(dsn); }), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -79,7 +79,7 @@ describe("property: extractDsnFromEnvContent", () => { property(envContentWithoutDsnArb, (content) => { expect(extractDsnFromEnvContent(content)).toBeNull(); }), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -90,7 +90,7 @@ describe("property: extractDsnFromEnvContent", () => { // Should return the first one expect(extractDsnFromEnvContent(content)).toBe(dsn1); }), - { numRuns: 30 } + { numRuns: 30 }, ); }); @@ -100,7 +100,7 @@ describe("property: extractDsnFromEnvContent", () => { const content = `SENTRY_DSN = ${dsn}`; expect(extractDsnFromEnvContent(content)).toBe(dsn); }), - { numRuns: 30 } + { numRuns: 30 }, ); }); }); @@ -129,7 +129,7 @@ describe("extractDsnFromEnvContent edge cases", () => { const content = `# SENTRY_DSN=commented-out SENTRY_DSN=https://real@sentry.io/123`; expect(extractDsnFromEnvContent(content)).toBe( - "https://real@sentry.io/123" + "https://real@sentry.io/123", ); }); @@ -156,7 +156,7 @@ ANOTHER_VAR=bar test("handles value with equals signs", () => { const content = "SENTRY_DSN=https://key@sentry.io/123?key=value"; expect(extractDsnFromEnvContent(content)).toBe( - "https://key@sentry.io/123?key=value" + "https://key@sentry.io/123?key=value", ); }); @@ -175,7 +175,7 @@ ANOTHER_VAR=bar const content = `SENTRY_DSN_OTHER=https://wrong@sentry.io/123 SENTRY_DSN=https://correct@sentry.io/456`; expect(extractDsnFromEnvContent(content)).toBe( - "https://correct@sentry.io/456" + "https://correct@sentry.io/456", ); }); @@ -219,7 +219,7 @@ describe("extractDsnFromEnvContent framework-prefixed vars", () => { const content = `SENTRY_DSN=https://canonical@sentry.io/1 NEXT_PUBLIC_SENTRY_DSN=https://next@sentry.io/2`; expect(extractDsnFromEnvContent(content)).toBe( - "https://canonical@sentry.io/1" + "https://canonical@sentry.io/1", ); }); @@ -274,7 +274,7 @@ describe("integration: file-based detection", () => { test("detects DSN from .env file", async () => { writeFileSync( join(testDir, ".env"), - "SENTRY_DSN=https://key@sentry.io/123" + "SENTRY_DSN=https://key@sentry.io/123", ); const result = await detectFromEnvFiles(testDir); @@ -286,11 +286,11 @@ describe("integration: file-based detection", () => { test("detects DSN from .env.local with higher priority", async () => { writeFileSync( join(testDir, ".env"), - "SENTRY_DSN=https://default@sentry.io/1" + "SENTRY_DSN=https://default@sentry.io/1", ); writeFileSync( join(testDir, ".env.local"), - "SENTRY_DSN=https://local@sentry.io/2" + "SENTRY_DSN=https://local@sentry.io/2", ); const result = await detectFromEnvFiles(testDir); @@ -320,11 +320,11 @@ describe("integration: file-based detection", () => { // Create multiple files writeFileSync( join(testDir, ".env.development"), - "SENTRY_DSN=https://dev@sentry.io/3" + "SENTRY_DSN=https://dev@sentry.io/3", ); writeFileSync( join(testDir, ".env.local"), - "SENTRY_DSN=https://local@sentry.io/2" + "SENTRY_DSN=https://local@sentry.io/2", ); const result = await detectFromEnvFiles(testDir); @@ -343,11 +343,11 @@ describe("integration: file-based detection", () => { test("detects multiple DSNs from different .env files", async () => { writeFileSync( join(testDir, ".env"), - "SENTRY_DSN=https://default@sentry.io/1" + "SENTRY_DSN=https://default@sentry.io/1", ); writeFileSync( join(testDir, ".env.local"), - "SENTRY_DSN=https://local@sentry.io/2" + "SENTRY_DSN=https://local@sentry.io/2", ); const result = await detectFromAllEnvFiles(testDir); @@ -362,7 +362,7 @@ describe("integration: file-based detection", () => { test("includes source mtimes for caching", async () => { writeFileSync( join(testDir, ".env"), - "SENTRY_DSN=https://key@sentry.io/123" + "SENTRY_DSN=https://key@sentry.io/123", ); const result = await detectFromAllEnvFiles(testDir); @@ -383,7 +383,7 @@ describe("integration: file-based detection", () => { mkdirSync(pkgDir, { recursive: true }); writeFileSync( join(pkgDir, ".env"), - "SENTRY_DSN=https://frontend@sentry.io/1" + "SENTRY_DSN=https://frontend@sentry.io/1", ); const result = await detectFromMonorepoEnvFiles(testDir); @@ -411,11 +411,11 @@ describe("integration: file-based detection", () => { mkdirSync(pkg2, { recursive: true }); writeFileSync( join(pkg1, ".env"), - "SENTRY_DSN=https://frontend@sentry.io/1" + "SENTRY_DSN=https://frontend@sentry.io/1", ); writeFileSync( join(pkg2, ".env"), - "SENTRY_DSN=https://backend@sentry.io/2" + "SENTRY_DSN=https://backend@sentry.io/2", ); const result = await detectFromMonorepoEnvFiles(testDir); @@ -444,7 +444,7 @@ describe("integration: file-based detection", () => { // This file should be ignored (not in a package subdir) writeFileSync( join(testDir, "packages", ".env"), - "SENTRY_DSN=https://root@sentry.io/0" + "SENTRY_DSN=https://root@sentry.io/0", ); const result = await detectFromMonorepoEnvFiles(testDir); diff --git a/packages/cli/test/lib/dsn/errors.test.ts b/packages/cli/test/lib/dsn/errors.test.ts index 8015d78b9..00e5c6bc7 100644 --- a/packages/cli/test/lib/dsn/errors.test.ts +++ b/packages/cli/test/lib/dsn/errors.test.ts @@ -300,11 +300,11 @@ describe("formatMultipleProjectsFooter", () => { expect(footer).toContain("Found 2 Sentry projects:"); expect(footer).toContain( - "• my-org / frontend (from packages/frontend/.env)" + "• my-org / frontend (from packages/frontend/.env)", ); expect(footer).toContain("• my-org / backend (from src/sentry.ts)"); expect(footer).toContain( - "Use / to target a specific project." + "Use / to target a specific project.", ); }); @@ -354,7 +354,7 @@ describe("formatMultipleProjectsFooter", () => { expect(footer).toContain("• my-org / backend"); expect(footer).not.toContain("• my-org / backend (from"); expect(footer).toContain( - "• other-org / shared (from libs/shared/sentry.config.js)" + "• other-org / shared (from libs/shared/sentry.config.js)", ); }); diff --git a/packages/cli/test/lib/dsn/fifo-safety.test.ts b/packages/cli/test/lib/dsn/fifo-safety.test.ts index a326573fa..c17b2f307 100644 --- a/packages/cli/test/lib/dsn/fifo-safety.test.ts +++ b/packages/cli/test/lib/dsn/fifo-safety.test.ts @@ -37,7 +37,7 @@ describe("isRegularFile", () => { beforeEach(() => { testDir = join( tmpdir(), - `sentry-fifo-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `sentry-fifo-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -98,7 +98,7 @@ describe("FIFO: env file detection", () => { beforeEach(() => { testDir = join( tmpdir(), - `sentry-fifo-env-${Date.now()}-${Math.random().toString(36).slice(2)}` + `sentry-fifo-env-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); // Create .git so this is treated as project root (stops walk-up) @@ -121,16 +121,15 @@ describe("FIFO: env file detection", () => { const fifoPath = join(testDir, ".env"); createFifo(fifoPath); - const { detectFromEnvFiles } = await import( - "../../../src/lib/dsn/env-file.js" - ); + const { detectFromEnvFiles } = + await import("../../../src/lib/dsn/env-file.js"); // This would hang indefinitely before the fix. // 2-second timeout ensures the test fails fast if the guard is broken. const result = await Promise.race([ detectFromEnvFiles(testDir), new Promise<"timeout">((resolve) => - setTimeout(() => resolve("timeout"), 2000) + setTimeout(() => resolve("timeout"), 2000), ), ]); @@ -143,12 +142,11 @@ describe("FIFO: env file detection", () => { writeFileSync( join(testDir, ".env"), - "SENTRY_DSN=https://abc123@o456.ingest.sentry.io/789\n" + "SENTRY_DSN=https://abc123@o456.ingest.sentry.io/789\n", ); - const { detectFromEnvFiles } = await import( - "../../../src/lib/dsn/env-file.js" - ); + const { detectFromEnvFiles } = + await import("../../../src/lib/dsn/env-file.js"); const result = await detectFromEnvFiles(testDir); expect(result).not.toBeNull(); @@ -163,16 +161,15 @@ describe("FIFO: env file detection", () => { // .env.local is a regular file with a DSN writeFileSync( join(testDir, ".env.local"), - "SENTRY_DSN=https://key@o1.ingest.sentry.io/111\n" + "SENTRY_DSN=https://key@o1.ingest.sentry.io/111\n", ); - const { detectFromEnvFiles } = await import( - "../../../src/lib/dsn/env-file.js" - ); + const { detectFromEnvFiles } = + await import("../../../src/lib/dsn/env-file.js"); const result = await Promise.race([ detectFromEnvFiles(testDir), new Promise<"timeout">((resolve) => - setTimeout(() => resolve("timeout"), 2000) + setTimeout(() => resolve("timeout"), 2000), ), ]); @@ -189,14 +186,13 @@ describe("FIFO: env file detection", () => { mkdirSync(subDir); createFifo(join(subDir, ".env")); - const { findProjectRoot } = await import( - "../../../src/lib/dsn/project-root.js" - ); + const { findProjectRoot } = + await import("../../../src/lib/dsn/project-root.js"); const result = await Promise.race([ findProjectRoot(subDir), new Promise<"timeout">((resolve) => - setTimeout(() => resolve("timeout"), 2000) + setTimeout(() => resolve("timeout"), 2000), ), ]); @@ -213,7 +209,7 @@ describe("FIFO: env file detection", () => { symlinkSync(fifoPath, join(testDir, ".env.local")); writeFileSync( join(testDir, ".env"), - "SENTRY_DSN=https://fallback@o1.ingest.sentry.io/222\n" + "SENTRY_DSN=https://fallback@o1.ingest.sentry.io/222\n", ); const { detectDsn } = await import("../../../src/lib/dsn/detector.js"); @@ -221,7 +217,7 @@ describe("FIFO: env file detection", () => { const result = await Promise.race([ detectDsn(testDir), new Promise<"timeout">((resolve) => - setTimeout(() => resolve("timeout"), 2000) + setTimeout(() => resolve("timeout"), 2000), ), ]); @@ -254,7 +250,7 @@ describe("FIFO: env file detection", () => { const result = await Promise.race([ detectDsn(testDir), new Promise<"timeout">((resolve) => - setTimeout(() => resolve("timeout"), 2000) + setTimeout(() => resolve("timeout"), 2000), ), ]); diff --git a/packages/cli/test/lib/dsn/fs-utils.test.ts b/packages/cli/test/lib/dsn/fs-utils.test.ts index 3508812d6..2f6bc6bfc 100644 --- a/packages/cli/test/lib/dsn/fs-utils.test.ts +++ b/packages/cli/test/lib/dsn/fs-utils.test.ts @@ -65,7 +65,7 @@ describe("handleFileError", () => { { operation: "checkEnvForDsn", path: "/project/.env", - } + }, ); expect(captureException).not.toHaveBeenCalled(); }); @@ -96,7 +96,7 @@ describe("handleFileError", () => { test("Unknown system error — macOS non-POSIX errno with no standard code", () => { const err = new Error( - "Unknown system error -11: Unknown system error -11, scandir '/Users/austin/Documents/app-audit.trace'" + "Unknown system error -11: Unknown system error -11, scandir '/Users/austin/Documents/app-audit.trace'", ) as NodeJS.ErrnoException; // No code property — Node.js couldn't map errno to a POSIX name handleFileError(err, { diff --git a/packages/cli/test/lib/dsn/home-fallback.test.ts b/packages/cli/test/lib/dsn/home-fallback.test.ts index 424c24e49..9f89b3c16 100644 --- a/packages/cli/test/lib/dsn/home-fallback.test.ts +++ b/packages/cli/test/lib/dsn/home-fallback.test.ts @@ -54,12 +54,12 @@ describe("DSN detection from $HOME fallback", () => { }); writeFileSync( join(home, "Library", "Group Containers", "x.1password", "config.json"), - `{ "dsn": "${sensitiveDsn}" }` + `{ "dsn": "${sensitiveDsn}" }`, ); mkdirSync(join(home, ".aws"), { recursive: true }); writeFileSync( join(home, ".aws", "credentials"), - `sentry_dsn=${sensitiveDsn}` + `sentry_dsn=${sensitiveDsn}`, ); const result = await detectDsn(home); @@ -82,7 +82,7 @@ describe("DSN detection from $HOME fallback", () => { const scannedDsn = "https://leaked@o999.ingest.sentry.io/999"; writeFileSync( join(home, "config.ts"), - `Sentry.init({ dsn: "${scannedDsn}" });` + `Sentry.init({ dsn: "${scannedDsn}" });`, ); const result = await detectDsn(home); @@ -109,7 +109,7 @@ describe("DSN detection from $HOME fallback", () => { mkdirSync(join(project, "src"), { recursive: true }); writeFileSync( join(project, "src", "config.ts"), - `Sentry.init({ dsn: "${projectDsn}" })` + `Sentry.init({ dsn: "${projectDsn}" })`, ); const result = await detectDsn(project); @@ -139,14 +139,14 @@ describe("DSN detection from $HOME fallback", () => { // downward scan only if the home-fallback skip fails to trigger. writeFileSync( join(ancestor, "config.ts"), - `Sentry.init({ dsn: "${scannedDsn}" });` + `Sentry.init({ dsn: "${scannedDsn}" });`, ); // And one inside a would-be sibling home, to mirror the real risk. const sibling = join(ancestor, "bob"); mkdirSync(sibling, { recursive: true }); writeFileSync( join(sibling, "config.ts"), - `Sentry.init({ dsn: "${scannedDsn}" });` + `Sentry.init({ dsn: "${scannedDsn}" });`, ); const result = await detectDsn(ancestor); diff --git a/packages/cli/test/lib/dsn/project-root.test.ts b/packages/cli/test/lib/dsn/project-root.test.ts index 09280f201..26c1e6faf 100644 --- a/packages/cli/test/lib/dsn/project-root.test.ts +++ b/packages/cli/test/lib/dsn/project-root.test.ts @@ -51,7 +51,7 @@ describe("project-root", () => { // Create a unique temp directory for each test testDir = join( tmpdir(), - `sentry-cli-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `sentry-cli-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); createDir(testDir); }); @@ -130,7 +130,7 @@ describe("project-root", () => { test("detects .editorconfig with root=true", async () => { createFile( join(testDir, ".editorconfig"), - "root = true\n[*]\nindent_style = space" + "root = true\n[*]\nindent_style = space", ); const result = await hasRepoRootMarker(testDir); expect(result.found).toBe(true); @@ -261,7 +261,7 @@ describe("project-root", () => { createDir(join(testDir, "src", "lib", "utils")); const result = await findProjectRoot( - join(testDir, "src", "lib", "utils") + join(testDir, "src", "lib", "utils"), ); expect(result.projectRoot).toBe(testDir); @@ -378,7 +378,7 @@ describe("stat() concurrency limiting", () => { function makeTempConcurrencyDir(prefix: string): string { const dir = join( tmpdir(), - `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}` + `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(dir, { recursive: true }); return dir; diff --git a/packages/cli/test/lib/dsn/resolver.test.ts b/packages/cli/test/lib/dsn/resolver.test.ts index a9543f68d..8df92fb39 100644 --- a/packages/cli/test/lib/dsn/resolver.test.ts +++ b/packages/cli/test/lib/dsn/resolver.test.ts @@ -12,7 +12,7 @@ const { mockListProjects: vi.fn(() => Promise.resolve([])), mockFindProjectByDsnKey: vi.fn(() => Promise.resolve(null)), mockResolveOrgDisplayName: vi.fn( - (slug: string, name?: string) => name ?? slug + (slug: string, name?: string) => name ?? slug, ), })); @@ -120,7 +120,7 @@ describe("resolveProject", () => { mockFindProjectByDsnKey.mockResolvedValue(null); await expect(resolveProject("/test/path", dsn)).rejects.toThrow( - /Cannot resolve project.*DSN could not be matched/ + /Cannot resolve project.*DSN could not be matched/, ); }); @@ -142,7 +142,7 @@ describe("resolveProject", () => { }); await expect(resolveProject("/test/path", dsn)).rejects.toThrow( - /Cannot resolve project/ + /Cannot resolve project/, ); }); @@ -194,7 +194,7 @@ describe("resolveProject", () => { ]); await expect(resolveProject("/test/path", dsn)).rejects.toThrow( - /Could not find organization with ID 999/ + /Could not find organization with ID 999/, ); }); @@ -218,7 +218,7 @@ describe("resolveProject", () => { ]); await expect(resolveProject("/test/path", dsn)).rejects.toThrow( - /Could not find project with ID 999 in organization my-org/ + /Could not find project with ID 999 in organization my-org/, ); }); diff --git a/packages/cli/test/lib/envelope/event-builder.test.ts b/packages/cli/test/lib/envelope/event-builder.test.ts index 4a6fb5aa1..8943bc7db 100644 --- a/packages/cli/test/lib/envelope/event-builder.test.ts +++ b/packages/cli/test/lib/envelope/event-builder.test.ts @@ -126,14 +126,14 @@ describe("buildEventFromFlags", () => { test("--timestamp with Unix epoch float", async () => { const event = await buildEventFromFlags( - flags({ timestamp: "1700000000.123" }) + flags({ timestamp: "1700000000.123" }), ); expect(event.timestamp).toBe(1_700_000_000.123); }); test("--timestamp with ISO 8601 string", async () => { const event = await buildEventFromFlags( - flags({ timestamp: "2024-01-15T12:00:00Z" }) + flags({ timestamp: "2024-01-15T12:00:00Z" }), ); // ISO 8601 → parsed via Date.parse, converted to seconds expect(event.timestamp).toBe(Date.parse("2024-01-15T12:00:00Z") / 1000); @@ -141,7 +141,7 @@ describe("buildEventFromFlags", () => { test("--timestamp with invalid string throws ValidationError", async () => { await expect( - buildEventFromFlags(flags({ timestamp: "not-a-date" })) + buildEventFromFlags(flags({ timestamp: "not-a-date" })), ).rejects.toBeInstanceOf(ValidationError); }); @@ -153,27 +153,27 @@ describe("buildEventFromFlags", () => { test("--level sets level", async () => { expect((await buildEventFromFlags(flags({ level: "warning" }))).level).toBe( - "warning" + "warning", ); }); test("--message joined with newline", async () => { const event = await buildEventFromFlags( - flags({ message: ["hello", "world"] }) + flags({ message: ["hello", "world"] }), ); expect(event.logentry?.message).toBe("hello\nworld"); }); test("--message-arg sets params", async () => { const event = await buildEventFromFlags( - flags({ message: ["hello %s"], "message-arg": ["world"] }) + flags({ message: ["hello %s"], "message-arg": ["world"] }), ); expect(event.logentry?.params).toEqual(["world"]); }); test("--tag parses into tags object", async () => { const event = await buildEventFromFlags( - flags({ tag: ["env:prod", "ver:1.0"] }) + flags({ tag: ["env:prod", "ver:1.0"] }), ); expect(event.tags).toEqual({ env: "prod", ver: "1.0" }); }); @@ -195,7 +195,7 @@ describe("buildEventFromFlags", () => { test("--user routes known fields correctly", async () => { const event = await buildEventFromFlags( - flags({ user: ["id:99", "email:a@b.com"] }) + flags({ user: ["id:99", "email:a@b.com"] }), ); expect(event.user?.id).toBe("99"); expect(event.user?.email).toBe("a@b.com"); @@ -203,32 +203,32 @@ describe("buildEventFromFlags", () => { test("--fingerprint sets fingerprint array", async () => { const event = await buildEventFromFlags( - flags({ fingerprint: ["my-error", "{{ default }}"] }) + flags({ fingerprint: ["my-error", "{{ default }}"] }), ); expect(event.fingerprint).toEqual(["my-error", "{{ default }}"]); }); test("--release sets release", async () => { expect( - (await buildEventFromFlags(flags({ release: "1.2.3" }))).release + (await buildEventFromFlags(flags({ release: "1.2.3" }))).release, ).toBe("1.2.3"); }); test("--env sets environment", async () => { expect( - (await buildEventFromFlags(flags({ env: "staging" }))).environment + (await buildEventFromFlags(flags({ env: "staging" }))).environment, ).toBe("staging"); }); test("--platform sets platform", async () => { expect( - (await buildEventFromFlags(flags({ platform: "python" }))).platform + (await buildEventFromFlags(flags({ platform: "python" }))).platform, ).toBe("python"); }); test("--dist sets dist", async () => { expect((await buildEventFromFlags(flags({ dist: "x86" }))).dist).toBe( - "x86" + "x86", ); }); @@ -265,10 +265,10 @@ describe("buildEventFromFlags", () => { try { writeFileSync( logPath, - "INFO: Server started\nERROR: Connection lost\nplain line\n" + "INFO: Server started\nERROR: Connection lost\nplain line\n", ); const event = await buildEventFromFlags( - flags({ logfile: logPath, "with-categories": true }) + flags({ logfile: logPath, "with-categories": true }), ); expect(event.breadcrumbs).toHaveLength(3); expect(event.breadcrumbs?.[0]).toMatchObject({ @@ -291,7 +291,7 @@ describe("buildEventFromFlags", () => { test("--logfile with nonexistent file throws ValidationError", async () => { await expect( - buildEventFromFlags(flags({ logfile: "/nonexistent/logfile.log" })) + buildEventFromFlags(flags({ logfile: "/nonexistent/logfile.log" })), ).rejects.toBeInstanceOf(ValidationError); }); @@ -303,7 +303,7 @@ describe("buildEventFromFlags", () => { const logPath = join(dir, "big.log"); try { const lines = Array.from({ length: 150 }, (_, i) => `line ${i}`).join( - "\n" + "\n", ); writeFileSync(logPath, lines); const event = await buildEventFromFlags(flags({ logfile: logPath })); diff --git a/packages/cli/test/lib/envelope/event-send-dsn.test.ts b/packages/cli/test/lib/envelope/event-send-dsn.test.ts index fa0535050..e0f9ad5e6 100644 --- a/packages/cli/test/lib/envelope/event-send-dsn.test.ts +++ b/packages/cli/test/lib/envelope/event-send-dsn.test.ts @@ -3,11 +3,11 @@ */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn import * as projectsApi from "../../../src/lib/api/projects.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn import * as auth from "../../../src/lib/db/auth.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn import * as dsnIndex from "../../../src/lib/dsn/index.js"; import { EVENT_SEND_NO_DSN_MESSAGE, @@ -25,11 +25,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([key, value]) => [ key, typeof value === "function" ? vi.fn(value) : value, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for mocked access import * as resolveTarget from "../../../src/lib/resolve-target.js"; useTestConfigDir("event-send-dsn-"); @@ -147,7 +147,7 @@ describe("resolveEventSendDsn", () => { let keysSpy: ReturnType; let authSpy: ReturnType; const resolveProjectMock = vi.mocked( - resolveTarget.resolveProjectBoundFromArg + resolveTarget.resolveProjectBoundFromArg, ); beforeEach(() => { @@ -195,7 +195,7 @@ describe("resolveEventSendDsn", () => { expect(resolveProjectMock).toHaveBeenCalledWith( "acme/web", "/tmp", - "event send" + "event send", ); expect(keysSpy).toHaveBeenCalledWith("acme", "web", { status: "active", @@ -215,7 +215,7 @@ describe("resolveEventSendDsn", () => { expect(resolveProjectMock).toHaveBeenCalledWith( "web", "/tmp", - "event send" + "event send", ); expect(keysSpy).toHaveBeenCalledWith("acme", "web", { status: "active", @@ -228,7 +228,7 @@ describe("resolveEventSendDsn", () => { resolveEventSendDsn("/tmp", { kind: "project", target: "acme/web", - }) + }), ).rejects.toBeInstanceOf(ConfigError); expect(resolveProjectMock).not.toHaveBeenCalled(); expect(keysSpy).not.toHaveBeenCalled(); @@ -256,7 +256,7 @@ describe("resolveEventSendDsn", () => { resolveEventSendDsn("/tmp", { kind: "project", target: "acme/web", - }) + }), ).rejects.toMatchObject({ name: "ConfigError", message: @@ -272,7 +272,7 @@ describe("resolveEventSendDsn", () => { resolveEventSendDsn("/tmp", { kind: "project", target: "acme/web", - }) + }), ).rejects.toMatchObject({ name: "ConfigError", message: @@ -294,7 +294,7 @@ describe("resolveEventSendDsn", () => { test("throws ConfigError listing every source when nothing resolves", async () => { const err = await resolveEventSendDsn("/tmp", undefined).catch( - (error) => error + (error) => error, ); expect(err).toBeInstanceOf(ConfigError); expect((err as ConfigError).message).toBe(EVENT_SEND_NO_DSN_MESSAGE); diff --git a/packages/cli/test/lib/envelope/transport.test.ts b/packages/cli/test/lib/envelope/transport.test.ts index 7a5fd0bc2..eda43f86e 100644 --- a/packages/cli/test/lib/envelope/transport.test.ts +++ b/packages/cli/test/lib/envelope/transport.test.ts @@ -10,7 +10,7 @@ */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn import * as dsnIndex from "../../../src/lib/dsn/index.js"; import { buildEnvelopeUrl, @@ -138,13 +138,13 @@ describe("sendEnvelopeRequest", () => { await sendEnvelopeRequest( SAAS_DSN, - '{"event_id":"abc"}\n{"type":"event","length":2}\n{}' + '{"event_id":"abc"}\n{"type":"event","length":2}\n{}', ); expect(capturedRequest).toBeDefined(); expect(capturedRequest!.method).toBe("POST"); expect(capturedRequest!.headers.get("Content-Type")).toBe( - "application/x-sentry-envelope" + "application/x-sentry-envelope", ); }); @@ -165,7 +165,7 @@ describe("sendEnvelopeRequest", () => { globalThis.fetch = async () => new Response("{}", { status: 200 }); // should not throw await expect( - sendEnvelopeRequest(SAAS_DSN, new TextEncoder().encode("bytes")) + sendEnvelopeRequest(SAAS_DSN, new TextEncoder().encode("bytes")), ).resolves.toBeUndefined(); }); @@ -174,7 +174,7 @@ describe("sendEnvelopeRequest", () => { new Response(JSON.stringify({ detail: "invalid DSN" }), { status: 403 }); await expect(sendEnvelopeRequest(SAAS_DSN, "body")).rejects.toBeInstanceOf( - ApiError + ApiError, ); }); diff --git a/packages/cli/test/lib/error-reporting.property.test.ts b/packages/cli/test/lib/error-reporting.property.test.ts index 9b4a740c9..a1aef9906 100644 --- a/packages/cli/test/lib/error-reporting.property.test.ts +++ b/packages/cli/test/lib/error-reporting.property.test.ts @@ -20,7 +20,7 @@ import { DEFAULT_NUM_RUNS } from "../model-based/helpers.js"; /** Lowercase alphanumeric slug with optional hyphens. */ const slugArb = array( constantFrom(..."abcdefghijklmnopqrstuvwxyz0123456789-".split("")), - { minLength: 1, maxLength: 25 } + { minLength: 1, maxLength: 25 }, ) .map((chars) => chars.join("")) .filter((s) => !(s.startsWith("-") || s.endsWith("-")) && s.length > 0); @@ -30,7 +30,7 @@ const hyphenatedSlugArb = slugArb.filter((s) => s.includes("-")); /** 32-character lowercase hex id (trace/event/log id). */ const hexIdArb = stringMatching(/^[0-9a-f]{32}$/).filter( - (s) => s.length === 32 + (s) => s.length === 32, ); /** Arbitrary numeric id across the full range (small issue IDs to large). */ @@ -45,7 +45,7 @@ describe("extractResourceKind — property tests", () => { expect(ka).toBe(kb); expect(ka).toBe("Project"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -53,10 +53,10 @@ describe("extractResourceKind — property tests", () => { fcAssert( property(slugArb, (slug) => { expect(extractResourceKind(`Project '${slug}'`)).toBe( - extractResourceKind(`Project "${slug}"`) + extractResourceKind(`Project "${slug}"`), ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -64,10 +64,10 @@ describe("extractResourceKind — property tests", () => { fcAssert( property(numericIdArb, numericIdArb, (id1, id2) => { expect(extractResourceKind(`Issue ${id1} not found.`)).toBe( - extractResourceKind(`Issue ${id2} not found.`) + extractResourceKind(`Issue ${id2} not found.`), ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -75,10 +75,10 @@ describe("extractResourceKind — property tests", () => { fcAssert( property(hexIdArb, hexIdArb, (h1, h2) => { expect(extractResourceKind(`Trace ${h1} not found`)).toBe( - extractResourceKind(`Trace ${h2} not found`) + extractResourceKind(`Trace ${h2} not found`), ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -88,7 +88,7 @@ describe("extractResourceKind — property tests", () => { const once = extractResourceKind(`Project '${slug}' not found.`); expect(extractResourceKind(once)).toBe(once); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -97,15 +97,15 @@ describe("extractResourceKind — property tests", () => { property(slugArb, slugArb, slugArb, slugArb, (o1, p1, o2, p2) => { expect( extractResourceKind( - `Project '${p1}' not found in organization '${o1}'` - ) + `Project '${p1}' not found in organization '${o1}'`, + ), ).toBe( extractResourceKind( - `Project '${p2}' not found in organization '${o2}'` - ) + `Project '${p2}' not found in organization '${o2}'`, + ), ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -113,13 +113,13 @@ describe("extractResourceKind — property tests", () => { fcAssert( property(slugArb, slugArb, slugArb, slugArb, (o1, p1, o2, p2) => { expect(extractResourceKind(`not found in ${o1}/${p1}`)).toBe( - extractResourceKind(`not found in ${o2}/${p2}`) + extractResourceKind(`not found in ${o2}/${p2}`), ); expect(extractResourceKind(`not found in ${o1}/${p1}`)).toBe( - "not found" + "not found", ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -127,10 +127,10 @@ describe("extractResourceKind — property tests", () => { fcAssert( property(integer({ min: 1, max: 99 }), numericIdArb, (small, large) => { expect(extractResourceKind(`Issue ${small} not found.`)).toBe( - extractResourceKind(`Issue ${large} not found.`) + extractResourceKind(`Issue ${large} not found.`), ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -138,11 +138,11 @@ describe("extractResourceKind — property tests", () => { fcAssert( property(slugArb, slugArb, (a, b) => { expect(extractResourceKind(`not found in ${a}`)).toBe( - extractResourceKind(`not found in ${b}`) + extractResourceKind(`not found in ${b}`), ); expect(extractResourceKind(`not found in ${a}`)).toBe("not found"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -150,11 +150,11 @@ describe("extractResourceKind — property tests", () => { fcAssert( property(hyphenatedSlugArb, hyphenatedSlugArb, (a, b) => { expect(extractResourceKind(`Organization ${a}`)).toBe( - extractResourceKind(`Organization ${b}`) + extractResourceKind(`Organization ${b}`), ); expect(extractResourceKind(`Organization ${a}`)).toBe("Organization"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -167,11 +167,11 @@ describe("extractResourceKind — property tests", () => { slugArb, (n1, s1, n2, s2) => { expect(extractResourceKind(`Dashboard ${n1} in ${s1}`)).toBe( - extractResourceKind(`Dashboard ${n2} in ${s2}`) + extractResourceKind(`Dashboard ${n2} in ${s2}`), ); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/error-reporting.test.ts b/packages/cli/test/lib/error-reporting.test.ts index b8b4da4a3..f70d5785e 100644 --- a/packages/cli/test/lib/error-reporting.test.ts +++ b/packages/cli/test/lib/error-reporting.test.ts @@ -8,7 +8,7 @@ * - End-to-end behavior of reportCliError (metric emission + capture) */ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { formatAuthHeader } from "../../src/lib/auth-header.js"; @@ -42,25 +42,27 @@ import { describe("extractResourceKind", () => { test("strips single-quoted user data", () => { expect( - extractResourceKind("Project 'api-track' not found in organization 'foo'") + extractResourceKind( + "Project 'api-track' not found in organization 'foo'", + ), ).toBe("Project not found"); }); test("strips double-quoted user data", () => { expect(extractResourceKind('Event "abc123" not found in org "foo"')).toBe( - "Event not found" + "Event not found", ); }); test("strips long hex IDs", () => { expect( - extractResourceKind("Trace abcdef0123456789abcdef0123456789 not found") + extractResourceKind("Trace abcdef0123456789abcdef0123456789 not found"), ).toBe("Trace not found"); }); test("strips long numeric IDs", () => { expect(extractResourceKind("Issue 7420431306 not found.")).toBe( - "Issue not found." + "Issue not found.", ); }); @@ -71,13 +73,13 @@ describe("extractResourceKind", () => { test("strips org/project paths after 'in'", () => { expect(extractResourceKind("not found in neurio/installer-app")).toBe( - "not found" + "not found", ); expect(extractResourceKind("not found in olli-inc/olli-app")).toBe( - "not found" + "not found", ); expect(extractResourceKind("access denied in sentry-sdks/cli")).toBe( - "access denied" + "access denied", ); }); @@ -96,7 +98,7 @@ describe("extractResourceKind", () => { test("strips 'in ' combined with entity names and numeric IDs", () => { expect(extractResourceKind("Dashboard 42 in my-org")).toBe("Dashboard"); expect( - extractResourceKind("Organization my-org not found or has no dashboards") + extractResourceKind("Organization my-org not found or has no dashboards"), ).toBe("Organization not found or has no dashboards"); }); @@ -116,20 +118,20 @@ describe("extractResourceKind", () => { describe("extractMessagePrefix", () => { test("returns first 3 words by default", () => { expect( - extractMessagePrefix('Invalid trace ID "abc". Expected a 32-character.') + extractMessagePrefix('Invalid trace ID "abc". Expected a 32-character.'), ).toBe("Invalid trace ID"); }); test("strips quoted substrings before word-counting", () => { // Quoted input doesn't push the real content past the word limit. expect(extractMessagePrefix('Invalid event ID "anything"')).toBe( - "Invalid event ID" + "Invalid event ID", ); }); test("stops at first newline", () => { expect( - extractMessagePrefix("Invalid slug.\n\nTry: sentry project create") + extractMessagePrefix("Invalid slug.\n\nTry: sentry project create"), ).toBe("Invalid slug."); }); @@ -144,7 +146,7 @@ describe("extractMessagePrefix", () => { test("same kind across different user-supplied values", () => { // Invariant: slug variation should not change the kind expect(extractMessagePrefix('Invalid trace ID "abc"')).toBe( - extractMessagePrefix('Invalid trace ID "def"') + extractMessagePrefix('Invalid trace ID "def"'), ); }); }); @@ -156,49 +158,49 @@ describe("extractMessagePrefix", () => { describe("normalizeEndpoint", () => { test("parameterizes org slug in organizations path", () => { expect(normalizeEndpoint("/api/0/organizations/my-org/issues/")).toBe( - "/api/0/organizations/{org}/issues/" + "/api/0/organizations/{org}/issues/", ); }); test("parameterizes org and project in projects path", () => { expect( - normalizeEndpoint("/api/0/projects/my-org/my-project/events/abc123/") + normalizeEndpoint("/api/0/projects/my-org/my-project/events/abc123/"), ).toBe("/api/0/projects/{org}/{project}/events/{id}/"); }); test("parameterizes issue, event, group, release IDs", () => { expect(normalizeEndpoint("/api/0/issues/12345/")).toBe( - "/api/0/issues/{id}/" + "/api/0/issues/{id}/", ); expect(normalizeEndpoint("/api/0/groups/99/events/abc/")).toBe( - "/api/0/groups/{id}/events/{id}/" + "/api/0/groups/{id}/events/{id}/", ); expect(normalizeEndpoint("/api/0/releases/1.0.0/")).toBe( - "/api/0/releases/{version}/" + "/api/0/releases/{version}/", ); }); test("parameterizes teams path", () => { expect(normalizeEndpoint("/api/0/teams/my-org/backend/")).toBe( - "/api/0/teams/{org}/{team}/" + "/api/0/teams/{org}/{team}/", ); }); test("parameterizes dashboards path", () => { expect(normalizeEndpoint("/api/0/dashboards/42/")).toBe( - "/api/0/dashboards/{id}/" + "/api/0/dashboards/{id}/", ); }); test("parameterizes customers path", () => { expect(normalizeEndpoint("/api/0/customers/my-org/")).toBe( - "/api/0/customers/{org}/" + "/api/0/customers/{org}/", ); }); test("parameterizes bare numeric segments", () => { expect(normalizeEndpoint("/api/0/some/123/thing/456")).toBe( - "/api/0/some/{id}/thing/{id}" + "/api/0/some/{id}/thing/{id}", ); }); @@ -218,7 +220,7 @@ describe("classifySilenced", () => { test("silences AuthError(not_authenticated)", () => { expect(classifySilenced(new AuthError("not_authenticated"))).toBe( - "auth_expected" + "auth_expected", ); }); @@ -235,11 +237,14 @@ describe("classifySilenced", () => { expect(classifySilenced(new MalformedAuthTokenError())).toBeNull(); }); - test.each([ - 401, 403, 404, 429, 418, - ])("silences ApiError with status %i", (status) => { - expect(classifySilenced(new ApiError("x", status))).toBe("api_user_error"); - }); + test.each([401, 403, 404, 429, 418])( + "silences ApiError with status %i", + (status) => { + expect(classifySilenced(new ApiError("x", status))).toBe( + "api_user_error", + ); + }, + ); test("does NOT silence ApiError 400 (CLI bug)", () => { expect(classifySilenced(new ApiError("bad", 400))).toBeNull(); @@ -247,7 +252,7 @@ describe("classifySilenced", () => { test("silences a raw 'fetch failed' TypeError (network failure)", () => { expect(classifySilenced(new TypeError("fetch failed"))).toBe( - "network_error" + "network_error", ); }); @@ -255,7 +260,7 @@ describe("classifySilenced", () => { // status 0 is shared by network failures and TLS cert errors; the latter // are actionable (missing CA) and must stay captured. expect( - classifySilenced(new ApiError("TLS certificate error", 0)) + classifySilenced(new ApiError("TLS certificate error", 0)), ).toBeNull(); }); @@ -273,26 +278,27 @@ describe("classifySilenced", () => { new ApiError( "Failed to list issues: 400 Bad Request", 400, - "Error parsing search query: invalid status value of '403'" - ) - ) + "Error parsing search query: invalid status value of '403'", + ), + ), ).toBeNull(); }); test("does NOT silence any 400 regardless of detail", () => { expect( classifySilenced( - new ApiError("bad", 400, "Invalid dashboard widget configuration") - ) + new ApiError("bad", 400, "Invalid dashboard widget configuration"), + ), ).toBeNull(); expect(classifySilenced(new ApiError("bad", 400))).toBeNull(); }); - test.each([ - 500, 502, 503, - ])("does NOT silence ApiError with 5xx status %i", (status) => { - expect(classifySilenced(new ApiError("x", status))).toBeNull(); - }); + test.each([500, 502, 503])( + "does NOT silence ApiError with 5xx status %i", + (status) => { + expect(classifySilenced(new ApiError("x", status))).toBeNull(); + }, + ); test.each([ ["auto-detect failure", new ContextError("Organization and project", "x")], @@ -310,9 +316,9 @@ describe("classifySilenced", () => { classifySilenced( new ValidationError( 'Project "webapp-backend" exists in multiple organizations.', - "project.ambiguous_org" - ) - ) + "project.ambiguous_org", + ), + ), ).toBe("user_validation"); }); @@ -320,16 +326,19 @@ describe("classifySilenced", () => { // A missing --input file is pure user-input noise, not a CLI bug (CLI-1JY). expect( classifySilenced( - new ValidationError("File not found: /tmp/does-not-exist.json", "input") - ) + new ValidationError( + "File not found: /tmp/does-not-exist.json", + "input", + ), + ), ).toBe("user_input_error"); }); test("silences ResolutionError (user provided a value that wasn't found)", () => { expect( classifySilenced( - new ResolutionError("Project 'x'", "not found", "sentry issue list") - ) + new ResolutionError("Project 'x'", "not found", "sentry issue list"), + ), ).toBe("user_input_error"); }); test.each([ @@ -345,17 +354,16 @@ describe("classifySilenced", () => { expect(classifySilenced(err)).toBeNull(); }); - test.each([ - "not_enabled", - "no_budget", - "ai_disabled", - ] as const)("silences SeerError(%s) on self-hosted (CLI-1WP)", (reason) => { - withSentryUrl("https://sentry.example.com", () => { - expect(classifySilenced(new SeerError(reason, "my-org"))).toBe( - "seer_unavailable_self_hosted" - ); - }); - }); + test.each(["not_enabled", "no_budget", "ai_disabled"] as const)( + "silences SeerError(%s) on self-hosted (CLI-1WP)", + (reason) => { + withSentryUrl("https://sentry.example.com", () => { + expect(classifySilenced(new SeerError(reason, "my-org"))).toBe( + "seer_unavailable_self_hosted", + ); + }); + }, + ); test("does NOT silence SeerError when SENTRY_URL points at SaaS", () => { withSentryUrl("https://sentry.io", () => { @@ -394,7 +402,7 @@ function withSentryUrl(url: string, fn: () => void): void { describe("enrichEventWithGroupingTags", () => { function makeEvent( type: string, - tags?: Record + tags?: Record, ): Sentry.ErrorEvent { return { exception: { values: [{ type, value: "msg" }] }, @@ -456,7 +464,7 @@ describe("enrichEventWithGroupingTags", () => { const result1 = enrichEventWithGroupingTags(event1); const result2 = enrichEventWithGroupingTags(event2); expect(result1.tags?.["cli_error.kind"]).toBe( - result2.tags?.["cli_error.kind"] + result2.tags?.["cli_error.kind"], ); }); @@ -532,7 +540,7 @@ describe("reportCliError integration", () => { test("captures ContextError (no longer silenced) so its volume stays visible", () => { reportCliError( - new ContextError("Organization and project", "sentry org view ") + new ContextError("Organization and project", "sentry org view "), ); // CLI-3B: ContextError is the signal for auto-detection/UX improvements, so // it is captured rather than dropped to a silenced-metric. @@ -542,7 +550,7 @@ describe("reportCliError integration", () => { 1, expect.objectContaining({ attributes: expect.objectContaining({ error_class: "ContextError" }), - }) + }), ); }); @@ -556,7 +564,7 @@ describe("reportCliError integration", () => { // Without a stable fallback, every unfielded ValidationError would get // kind="" and collapse into one huge mixed group. const err = new ValidationError( - 'Invalid trace ID "d2ad4a2d947b5983". Expected 32-char hex.' + 'Invalid trace ID "d2ad4a2d947b5983". Expected 32-char hex.', ); const { tags } = capturedScopeTags(err); expect(tags["cli_error.class"]).toBe("ValidationError"); @@ -565,20 +573,20 @@ describe("reportCliError integration", () => { test("ValidationError kind is stable across different user inputs", () => { const a = capturedScopeTags( - new ValidationError('Invalid trace ID "abc"') + new ValidationError('Invalid trace ID "abc"'), ).tags; const b = capturedScopeTags( - new ValidationError('Invalid trace ID "xyz-different"') + new ValidationError('Invalid trace ID "xyz-different"'), ).tags; expect(a["cli_error.kind"]).toBe(b["cli_error.kind"]); }); test("ValidationError kind differentiates by validator", () => { const traceErr = capturedScopeTags( - new ValidationError('Invalid trace ID "abc"') + new ValidationError('Invalid trace ID "abc"'), ).tags; const eventErr = capturedScopeTags( - new ValidationError('Invalid event ID "abc"') + new ValidationError('Invalid event ID "abc"'), ).tags; expect(traceErr["cli_error.kind"]).not.toBe(eventErr["cli_error.kind"]); }); @@ -589,7 +597,7 @@ describe("reportCliError integration", () => { const err = new ResolutionError( "Project 'x'", "not found", - "sentry issue list /x" + "sentry issue list /x", ); reportCliError(err); expect(captureSpy).not.toHaveBeenCalled(); @@ -601,7 +609,7 @@ describe("reportCliError integration", () => { error_class: "ResolutionError", reason: "user_input_error", }), - }) + }), ); }); @@ -625,7 +633,7 @@ describe("reportCliError integration", () => { reason: "seer_unavailable_self_hosted", seer_reason: "not_enabled", }), - }) + }), ); }); @@ -640,7 +648,7 @@ describe("reportCliError integration", () => { reason: "auth_expected", auth_reason: "invalid", }), - }) + }), ); }); @@ -679,13 +687,13 @@ describe("reportCliError integration", () => { "failed", 400, undefined, - "/api/0/organizations/my-org/issues/" + "/api/0/organizations/my-org/issues/", ); const { tags } = capturedScopeTags(err); expect(tags["cli_error.api_status"]).toBe("400"); expect(tags["cli_error.kind"]).toBe("400"); expect(tags["cli_error.api_endpoint"]).toBe( - "/api/0/organizations/{org}/issues/" + "/api/0/organizations/{org}/issues/", ); }); @@ -697,7 +705,11 @@ describe("reportCliError integration", () => { test("HostScopeError gets kind=host_scope", () => { const { tags } = capturedScopeTags( - new HostScopeError("URL argument", "https://other.sentry.io", "sentry.io") + new HostScopeError( + "URL argument", + "https://other.sentry.io", + "sentry.io", + ), ); expect(tags["cli_error.class"]).toBe("HostScopeError"); expect(tags["cli_error.kind"]).toBe("host_scope"); @@ -705,7 +717,7 @@ describe("reportCliError integration", () => { test("WizardError gets kind=wizard", () => { const { tags } = capturedScopeTags( - new WizardError("Workflow returned an error") + new WizardError("Workflow returned an error"), ); expect(tags["cli_error.class"]).toBe("WizardError"); expect(tags["cli_error.kind"]).toBe("wizard"); @@ -713,7 +725,7 @@ describe("reportCliError integration", () => { test("bare CliError gets kind from message prefix", () => { const { tags } = capturedScopeTags( - new CliError("Failed to create project 'my-app' in my-org.") + new CliError("Failed to create project 'my-app' in my-org."), ); expect(tags["cli_error.class"]).toBe("CliError"); expect(tags["cli_error.kind"]).toBe("Failed to create project"); @@ -721,31 +733,32 @@ describe("reportCliError integration", () => { test("bare CliError kind is stable across different user inputs", () => { const a = capturedScopeTags( - new CliError("Failed to create project 'app-a' in org-a.") + new CliError("Failed to create project 'app-a' in org-a."), ).tags; const b = capturedScopeTags( - new CliError("Failed to create project 'app-b' in org-b.") + new CliError("Failed to create project 'app-b' in org-b."), ).tags; expect(a["cli_error.kind"]).toBe(b["cli_error.kind"]); }); - test.each([ - 401, 403, 404, 429, - ])("SILENCES ApiError(%i) and emits metric", (status) => { - reportCliError(new ApiError("user err", status, "detail", "/api/0/foo/")); - expect(captureSpy).not.toHaveBeenCalled(); - expect(metricSpy).toHaveBeenCalledWith( - "cli.error.silenced", - 1, - expect.objectContaining({ - attributes: expect.objectContaining({ - error_class: "ApiError", - reason: "api_user_error", - api_status: status, + test.each([401, 403, 404, 429])( + "SILENCES ApiError(%i) and emits metric", + (status) => { + reportCliError(new ApiError("user err", status, "detail", "/api/0/foo/")); + expect(captureSpy).not.toHaveBeenCalled(); + expect(metricSpy).toHaveBeenCalledWith( + "cli.error.silenced", + 1, + expect.objectContaining({ + attributes: expect.objectContaining({ + error_class: "ApiError", + reason: "api_user_error", + api_status: status, + }), }), - }) - ); - }); + ); + }, + ); test("silences OutputError and emits metric", () => { reportCliError(new OutputError(null)); @@ -755,7 +768,7 @@ describe("reportCliError integration", () => { 1, expect.objectContaining({ attributes: expect.objectContaining({ reason: "output_error" }), - }) + }), ); }); @@ -767,27 +780,27 @@ describe("reportCliError integration", () => { 1, expect.objectContaining({ attributes: expect.objectContaining({ reason: "network_error" }), - }) - ); - }); - - test.each([ - "not_authenticated", - "expired", - ] as const)("silences AuthError(%s) and emits metric", (reason) => { - reportCliError(new AuthError(reason)); - expect(captureSpy).not.toHaveBeenCalled(); - expect(metricSpy).toHaveBeenCalledWith( - "cli.error.silenced", - 1, - expect.objectContaining({ - attributes: expect.objectContaining({ - reason: "auth_expected", - auth_reason: reason, + }), + ); + }); + + test.each(["not_authenticated", "expired"] as const)( + "silences AuthError(%s) and emits metric", + (reason) => { + reportCliError(new AuthError(reason)); + expect(captureSpy).not.toHaveBeenCalled(); + expect(metricSpy).toHaveBeenCalledWith( + "cli.error.silenced", + 1, + expect.objectContaining({ + attributes: expect.objectContaining({ + reason: "auth_expected", + auth_reason: reason, + }), }), - }) - ); - }); + ); + }, + ); test("captures ApiError(500) without silencing metric", () => { reportCliError(new ApiError("server fail", 500)); diff --git a/packages/cli/test/lib/errors.test.ts b/packages/cli/test/lib/errors.test.ts index ea91399cd..e20aae7b8 100644 --- a/packages/cli/test/lib/errors.test.ts +++ b/packages/cli/test/lib/errors.test.ts @@ -50,7 +50,7 @@ describe("AuthError", () => { test("not_authenticated has default message", () => { const err = new AuthError("not_authenticated"); expect(err.message).toBe( - "Not authenticated. Run 'sentry auth login' first." + "Not authenticated. Run 'sentry auth login' first.", ); expect(err.reason).toBe("not_authenticated"); }); @@ -58,7 +58,7 @@ describe("AuthError", () => { test("expired has default message", () => { const err = new AuthError("expired"); expect(err.message).toBe( - "Authentication expired. Run 'sentry auth login' to re-authenticate." + "Authentication expired. Run 'sentry auth login' to re-authenticate.", ); expect(err.reason).toBe("expired"); }); @@ -103,7 +103,7 @@ describe("ConfigError", () => { test("format() includes suggestion", () => { const err = new ConfigError("Invalid config", "Check the config file"); expect(err.format()).toBe( - "Invalid config\n\nSuggestion: Check the config file" + "Invalid config\n\nSuggestion: Check the config file", ); }); @@ -121,10 +121,10 @@ describe("ContextError", () => { expect(formatted).toContain("Provide it explicitly:"); expect(formatted).toContain("sentry org list"); expect(formatted).toContain( - "Run from a directory with a Sentry DSN in source code or .env files" + "Run from a directory with a Sentry DSN in source code or .env files", ); expect(formatted).toContain( - "Set SENTRY_ORG and SENTRY_PROJECT (or SENTRY_DSN) environment variables" + "Set SENTRY_ORG and SENTRY_PROJECT (or SENTRY_DSN) environment variables", ); }); @@ -150,18 +150,18 @@ describe("ContextError", () => { "Organization", "sentry org list", undefined, - "Found 2 DSN(s) that could not be resolved" + "Found 2 DSN(s) that could not be resolved", ); const formatted = err.format(); expect(formatted).toContain("Could not auto-detect organization."); // Default alternatives are present expect(formatted).toContain("Or:"); expect(formatted).toContain( - "Run from a directory with a Sentry DSN in source code or .env files" + "Run from a directory with a Sentry DSN in source code or .env files", ); // Note appears as a separate section expect(formatted).toContain( - "Note: Found 2 DSN(s) that could not be resolved" + "Note: Found 2 DSN(s) that could not be resolved", ); // Note appears after alternatives const orIndex = formatted.indexOf("Or:"); @@ -174,7 +174,7 @@ describe("ContextError", () => { "Resource", "sentry resource get", [], - "Some diagnostic info" + "Some diagnostic info", ); const formatted = err.format(); expect(formatted).toContain("Resource is required."); @@ -187,7 +187,7 @@ describe("ContextError", () => { "Organization", "sentry org list", undefined, - "test note" + "test note", ); expect(err.note).toBe("test note"); }); @@ -202,7 +202,7 @@ describe("ResolutionError", () => { [ "No issue with numeric ID 99124558 found", "If this is a short ID suffix, try: sentry issue view -99124558", - ] + ], ); const formatted = err.format(); expect(formatted).toContain("Issue 99124558 not found."); @@ -219,7 +219,7 @@ describe("ResolutionError", () => { "Project 'cli'", "is ambiguous", "sentry issue view /cli-G", - ["Found in: sentry, acme"] + ["Found in: sentry, acme"], ); const formatted = err.format(); expect(formatted).toContain("Project 'cli' is ambiguous."); @@ -232,11 +232,11 @@ describe("ResolutionError", () => { const err = new ResolutionError( 'Event abc123 in organization "acme"', "not found", - "sentry event view acme/ abc123" + "sentry event view acme/ abc123", ); const formatted = err.format(); expect(formatted).toContain( - 'Event abc123 in organization "acme" not found.' + 'Event abc123 in organization "acme" not found.', ); expect(formatted).toContain("Try:"); expect(formatted).not.toContain("Or:"); @@ -246,7 +246,7 @@ describe("ResolutionError", () => { const err = new ResolutionError( "Issue suffix 'G'", "could not be resolved without project context", - "sentry issue view /-G" + "sentry issue view /-G", ); expect(err.resource).toBe("Issue suffix 'G'"); expect(err.headline).toBe("could not be resolved without project context"); @@ -258,7 +258,7 @@ describe("ResolutionError", () => { const err = new ResolutionError( "Issue 1", "not found", - "sentry issue view 1" + "sentry issue view 1", ); expect(err).toBeInstanceOf(CliError); expect(err.name).toBe("ResolutionError"); @@ -285,8 +285,8 @@ describe("buildValidationMessage", () => { buildValidationMessage( "Invalid flag.", ["sentry release set-commits 1.0.0 --from v0.9.0"], - "Range is always ..HEAD." - ) + "Range is always ..HEAD.", + ), ).toBe( [ "Invalid flag.", @@ -295,7 +295,7 @@ describe("buildValidationMessage", () => { " sentry release set-commits 1.0.0 --from v0.9.0", "", "Note: Range is always ..HEAD.", - ].join("\n") + ].join("\n"), ); }); @@ -324,7 +324,7 @@ describe("UpgradeError", () => { test("unknown_method has default message", () => { const err = new UpgradeError("unknown_method"); expect(err.message).toBe( - "Could not detect installation method. Use --method to specify." + "Could not detect installation method. Use --method to specify.", ); expect(err.reason).toBe("unknown_method"); }); @@ -408,7 +408,7 @@ describe("SeerError", () => { const err = new SeerError("no_budget"); const formatted = err.format(); expect(formatted).toContain( - "upgrade your plan in your organization's billing settings" + "upgrade your plan in your organization's billing settings", ); }); @@ -427,17 +427,17 @@ describe("stringifyUnknown", () => { test("extracts message from Error instances", () => { expect(stringifyUnknown(new Error("something broke"))).toBe( - "something broke" + "something broke", ); expect(stringifyUnknown(new TypeError("bad type"))).toBe("bad type"); }); test("serializes plain objects to JSON", () => { expect(stringifyUnknown({ code: "not_found" })).toBe( - '{"code":"not_found"}' + '{"code":"not_found"}', ); expect(stringifyUnknown({ detail: { message: "Forbidden" } })).toBe( - '{"detail":{"message":"Forbidden"}}' + '{"detail":{"message":"Forbidden"}}', ); }); @@ -569,7 +569,7 @@ describe("isNetworkError", () => { // status 0 is also used for TLS cert errors, which must stay actionable. expect(isNetworkError(new ApiError("Network error", 0))).toBe(false); expect(isNetworkError(new ApiError("TLS certificate error", 0))).toBe( - false + false, ); }); @@ -593,8 +593,8 @@ describe("isSearchQueryParseError", () => { test("true for a 400 whose detail reports an unparseable query", () => { expect( isSearchQueryParseError( - new ApiError("bad", 400, "Error parsing search query: invalid status") - ) + new ApiError("bad", 400, "Error parsing search query: invalid status"), + ), ).toBe(true); }); @@ -602,13 +602,13 @@ describe("isSearchQueryParseError", () => { const detail = "Error parsing search query: Empty string after 'status:'\n\nSuggestions:"; expect(isSearchQueryParseError(new ApiError("wrapped", 400, detail))).toBe( - true + true, ); }); test("false for a 400 without a query parse detail", () => { expect( - isSearchQueryParseError(new ApiError("bad", 400, "Other failure")) + isSearchQueryParseError(new ApiError("bad", 400, "Other failure")), ).toBe(false); expect(isSearchQueryParseError(new ApiError("no detail", 400))).toBe(false); }); @@ -616,8 +616,8 @@ describe("isSearchQueryParseError", () => { test("false when status is not 400 (other 4xx handled elsewhere)", () => { expect( isSearchQueryParseError( - new ApiError("x", 422, "Error parsing search query: ...") - ) + new ApiError("x", 422, "Error parsing search query: ..."), + ), ).toBe(false); }); }); @@ -627,7 +627,7 @@ describe("toSearchQueryError", () => { new ApiError( "Failed to fetch issues", 400, - "Error parsing search query: bad" + "Error parsing search query: bad", ); test("converts a parse 400 to a ValidationError when the user supplied --query", () => { @@ -639,7 +639,7 @@ describe("toSearchQueryError", () => { test("includes the server detail and a search-syntax suggestion in the message", () => { const result = toSearchQueryError( parseError(), - "is:403" + "is:403", ) as ValidationError; expect(result.message).toContain("Error parsing search query: bad"); expect(result.message).toContain("Sentry search reference"); @@ -677,7 +677,7 @@ describe("exit codes", () => { test("AuthError maps reasons to exit codes", () => { expect(new AuthError("not_authenticated").exitCode).toBe( - EXIT.AUTH_NOT_AUTHENTICATED + EXIT.AUTH_NOT_AUTHENTICATED, ); expect(new AuthError("expired").exitCode).toBe(EXIT.AUTH_EXPIRED); expect(new AuthError("invalid").exitCode).toBe(EXIT.AUTH_INVALID); @@ -688,15 +688,15 @@ describe("exit codes", () => { expect(new HostScopeError("blocked").exitCode).toBe(EXIT.AUTH_HOST_SCOPE); // URL mismatch form (no tokenHost) expect( - new HostScopeError("Request", "https://other.sentry.io").exitCode + new HostScopeError("Request", "https://other.sentry.io").exitCode, ).toBe(EXIT.AUTH_HOST_SCOPE); // URL mismatch form (with tokenHost) expect( new HostScopeError( "Request", "https://other.sentry.io", - "https://sentry.io" - ).exitCode + "https://sentry.io", + ).exitCode, ).toBe(EXIT.AUTH_HOST_SCOPE); }); @@ -714,13 +714,13 @@ describe("exit codes", () => { test("ContextError has exit code CONTEXT_MISSING", () => { expect(new ContextError("Organization", "sentry org list").exitCode).toBe( - EXIT.CONTEXT_MISSING + EXIT.CONTEXT_MISSING, ); }); test("ResolutionError has exit code RESOLUTION", () => { expect( - new ResolutionError("X", "not found", "sentry x view").exitCode + new ResolutionError("X", "not found", "sentry x view").exitCode, ).toBe(EXIT.RESOLUTION); }); @@ -752,15 +752,15 @@ describe("exit codes", () => { test("WizardError accepts custom exit code for workflow sub-codes", () => { expect( - new WizardError("deps failed", { exitCode: EXIT.WIZARD_DEPS }).exitCode + new WizardError("deps failed", { exitCode: EXIT.WIZARD_DEPS }).exitCode, ).toBe(EXIT.WIZARD_DEPS); expect( new WizardError("codemod failed", { exitCode: EXIT.WIZARD_CODEMOD }) - .exitCode + .exitCode, ).toBe(EXIT.WIZARD_CODEMOD); expect( new WizardError("verify stopped", { exitCode: EXIT.WIZARD_VERIFY }) - .exitCode + .exitCode, ).toBe(EXIT.WIZARD_VERIFY); }); @@ -786,19 +786,19 @@ describe("withAuthGuard", () => { test("rethrows AuthError('not_authenticated')", async () => { await expect( - withAuthGuard(() => Promise.reject(new AuthError("not_authenticated"))) + withAuthGuard(() => Promise.reject(new AuthError("not_authenticated"))), ).rejects.toBeInstanceOf(AuthError); }); test("rethrows AuthError('expired')", async () => { await expect( - withAuthGuard(() => Promise.reject(new AuthError("expired"))) + withAuthGuard(() => Promise.reject(new AuthError("expired"))), ).rejects.toBeInstanceOf(AuthError); }); test("rethrows AuthError('invalid')", async () => { await expect( - withAuthGuard(() => Promise.reject(new AuthError("invalid"))) + withAuthGuard(() => Promise.reject(new AuthError("invalid"))), ).rejects.toBeInstanceOf(AuthError); }); diff --git a/packages/cli/test/lib/formatters/auto-compact.property.test.ts b/packages/cli/test/lib/formatters/auto-compact.property.test.ts index 36a014ac8..85959e59e 100644 --- a/packages/cli/test/lib/formatters/auto-compact.property.test.ts +++ b/packages/cli/test/lib/formatters/auto-compact.property.test.ts @@ -48,7 +48,7 @@ describe("property: shouldAutoCompact", () => { setTermHeight(undefined); expect(shouldAutoCompact(rowCount)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -58,7 +58,7 @@ describe("property: shouldAutoCompact", () => { setTermHeight(0); expect(shouldAutoCompact(rowCount)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -69,7 +69,7 @@ describe("property: shouldAutoCompact", () => { // With 0 rows, estimated = OVERHEAD. Compact iff OVERHEAD > termHeight. expect(shouldAutoCompact(0)).toBe(OVERHEAD > termHeight); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -83,9 +83,9 @@ describe("property: shouldAutoCompact", () => { setTermHeight(termHeight); expect(shouldAutoCompact(rowCount)).toBe(true); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -99,9 +99,9 @@ describe("property: shouldAutoCompact", () => { setTermHeight(termHeight); expect(shouldAutoCompact(rowCount)).toBe(false); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -119,9 +119,9 @@ describe("property: shouldAutoCompact", () => { if (compactSmall) { expect(compactLarge).toBe(true); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -134,9 +134,9 @@ describe("property: shouldAutoCompact", () => { const result1 = shouldAutoCompact(rowCount); const result2 = shouldAutoCompact(rowCount); expect(result1).toBe(result2); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/formatters/chart-core.test.ts b/packages/cli/test/lib/formatters/chart-core.test.ts index c442bc2d7..b1b7d21d7 100644 --- a/packages/cli/test/lib/formatters/chart-core.test.ts +++ b/packages/cli/test/lib/formatters/chart-core.test.ts @@ -14,7 +14,7 @@ import { import type { TimeseriesResult } from "../../../src/types/dashboard.js"; function makeTimeseries( - overrides: Partial = {} + overrides: Partial = {}, ): TimeseriesResult { return { type: "timeseries", @@ -67,7 +67,7 @@ describe("buildChartModel", () => { { label: "a", values: [] }, { label: "b", values: [] }, ], - }) + }), ); expect(model).toBeUndefined(); }); @@ -100,7 +100,7 @@ describe("buildChartModel", () => { ], }, ], - }) + }), ); expect(model?.stacked).toBe(true); expect(model?.buckets).toBe(2); @@ -118,7 +118,7 @@ describe("buildCategoricalChartModel", () => { { label: "US", values: [{ timestamp: 1, value: 20 }] }, { label: "GB", values: [{ timestamp: 1, value: 10 }] }, ], - }) + }), ); expect(model?.kind).toBe("categorical"); @@ -180,7 +180,7 @@ describe("rasterizeChart", () => { value: i < 300 ? 0 : i, })); const model = buildChartModel( - makeTimeseries({ series: [{ label: "c", values }] }) + makeTimeseries({ series: [{ label: "c", values }] }), ); const width = 64; const img = rasterizeChart(model!, { width, height: 32 }); @@ -205,7 +205,7 @@ describe("rasterizeChart", () => { { label: "alpha", values: [{ timestamp: 1, value: 10 }] }, { label: "beta", values: [{ timestamp: 1, value: 20 }] }, ], - }) + }), ); const image = rasterizeChart(model!, { width: 40, height: 20 }); const data = image?.data ?? new Uint8Array(); diff --git a/packages/cli/test/lib/formatters/colors.test.ts b/packages/cli/test/lib/formatters/colors.test.ts index 549618aae..53d4301c1 100644 --- a/packages/cli/test/lib/formatters/colors.test.ts +++ b/packages/cli/test/lib/formatters/colors.test.ts @@ -19,7 +19,7 @@ chalk.level = 3; /** Strip ANSI escape codes for content assertions */ function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars + // oxlint-disable-next-line no-control-regex -- ANSI codes use control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -176,7 +176,7 @@ describe("terminalLink", () => { test("preserves display text on TTY", () => { withTTY(() => { const result = terminalLink("display", "https://url.com"); - // biome-ignore lint/suspicious/noControlCharactersInRegex: OSC 8 uses control chars + // oxlint-disable-next-line no-control-regex -- OSC 8 uses control chars const stripped = result.replace(/\x1b\]8;;[^\x07]*\x07/g, ""); expect(stripped).toBe("display"); }); @@ -188,7 +188,7 @@ describe("terminalLink", () => { expect(result).toContain("]8;;https://example.com"); expect(result).toContain("https://example.com"); expect(result).toBe( - terminalLink("https://example.com", "https://example.com") + terminalLink("https://example.com", "https://example.com"), ); }); }); diff --git a/packages/cli/test/lib/formatters/conversation.test.ts b/packages/cli/test/lib/formatters/conversation.test.ts index b648f9aeb..99332c1df 100644 --- a/packages/cli/test/lib/formatters/conversation.test.ts +++ b/packages/cli/test/lib/formatters/conversation.test.ts @@ -12,7 +12,7 @@ import type { } from "../../../src/types/conversation.js"; function makeListItem( - overrides: Partial = {} + overrides: Partial = {}, ): ConversationListItem { return { conversationId: "conv-abc-123", @@ -27,7 +27,7 @@ function makeListItem( } function makeSpan( - overrides: Partial = {} + overrides: Partial = {}, ): AgentConversationSpan { return { span_id: "aabb112233445566", @@ -247,7 +247,7 @@ describe("buildTranscriptResult", () => { "conv-123", "my-org", [makeSpan()], - "Refund a duplicate charge" + "Refund a duplicate charge", ); expect(result.title).toBe("Refund a duplicate charge"); }); @@ -322,7 +322,7 @@ describe("formatTranscriptResult", () => { "conv-123", "my-org", [makeSpan()], - "Refund a duplicate charge" + "Refund a duplicate charge", ); const output = formatTranscriptResult(transcript); expect(output).toContain("Agent Conversation: conv-123"); diff --git a/packages/cli/test/lib/formatters/dashboard-sixel-integration.test.ts b/packages/cli/test/lib/formatters/dashboard-sixel-integration.test.ts index a89859c95..3cd013cc7 100644 --- a/packages/cli/test/lib/formatters/dashboard-sixel-integration.test.ts +++ b/packages/cli/test/lib/formatters/dashboard-sixel-integration.test.ts @@ -14,16 +14,16 @@ import { formatDashboardWithData, } from "../../../src/lib/formatters/dashboard.js"; import { logger } from "../../../src/lib/logger.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for vi.spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.spyOn mocking import * as sixelModule from "../../../src/lib/sixel.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for vi.spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.spyOn mocking import * as sixelImageModule from "../../../src/lib/sixel-image.js"; import type { TimeseriesResult } from "../../../src/types/dashboard.js"; const ESC = "\x1b"; function makeTimeseries( - overrides: Partial = {} + overrides: Partial = {}, ): TimeseriesResult { return { type: "timeseries", @@ -43,7 +43,7 @@ function makeTimeseries( } function makeWidget( - overrides: Partial = {} + overrides: Partial = {}, ): DashboardViewWidget { return { title: "Test Widget", @@ -54,7 +54,7 @@ function makeWidget( } function makeDashboardData( - overrides: Partial = {} + overrides: Partial = {}, ): DashboardViewData { return { id: "12345", @@ -122,10 +122,10 @@ describe("dashboard sixel integration", () => { formatDashboardWithData(makeDashboardData()); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("Dashboard graphics renderer: sixel") + expect.stringContaining("Dashboard graphics renderer: sixel"), ); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("capabilities: kitty=no, sixel=yes") + expect.stringContaining("capabilities: kitty=no, sixel=yes"), ); }); @@ -153,11 +153,11 @@ describe("dashboard sixel integration", () => { test("uses a requested sixel renderer instead of an available kitty renderer", () => { vi.mocked(sixelModule.selectGraphicsFormat).mockImplementation( - (renderer) => (renderer === "sixel" ? "sixel" : "kitty") + (renderer) => (renderer === "sixel" ? "sixel" : "kitty"), ); const output = formatDashboardWithData( - makeDashboardData({ rendererPreference: "sixel" }) + makeDashboardData({ rendererPreference: "sixel" }), ); expect(output).toContain(`${ESC}P`); @@ -173,12 +173,12 @@ describe("dashboard sixel integration", () => { const debugSpy = vi.spyOn(logger, "debug"); const output = formatDashboardWithData( - makeDashboardData({ rendererPreference: "sixel" }) + makeDashboardData({ rendererPreference: "sixel" }), ); expect(output).toContain(`${ESC}_G`); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("requested=sixel") + expect.stringContaining("requested=sixel"), ); }); @@ -193,10 +193,10 @@ describe("dashboard sixel integration", () => { formatDashboardWithData(makeDashboardData()); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("Dashboard graphics renderer: kitty") + expect.stringContaining("Dashboard graphics renderer: kitty"), ); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("capabilities: kitty=yes, sixel=no") + expect.stringContaining("capabilities: kitty=yes, sixel=no"), ); }); @@ -258,7 +258,7 @@ describe("dashboard sixel integration", () => { data: { type: "unsupported", reason: "Not implemented" }, }), ], - }) + }), ); expect(output.split(`${ESC}P`)).toHaveLength(2); @@ -313,7 +313,7 @@ describe("dashboard sixel integration", () => { const output = formatDashboardWithData( makeDashboardData({ widgets: [makeWidget({ layout: { x: 0, y: 0, w: 6, h: 1 } })], - }) + }), ); expect(output).toContain('"1;1;1024;72'); @@ -324,7 +324,7 @@ describe("dashboard sixel integration", () => { const output = formatDashboardWithData( makeDashboardData({ widgets: [makeWidget({ layout: { x: 0, y: 0, w: 6, h: 1 } })], - }) + }), ); expect(output).toContain('"1;1;320;72'); @@ -342,7 +342,7 @@ describe("dashboard sixel integration", () => { layout: { x: 1, y: 0, w: 1, h: 1 }, }), ], - }) + }), ); expect(output).toContain('"1;1;32;72'); @@ -362,7 +362,7 @@ describe("dashboard sixel integration", () => { layout: { x: 0, y: 0, w: 6, h: 1 }, }), ], - }) + }), ); expect(output).not.toContain(`${ESC}P`); @@ -381,12 +381,12 @@ describe("dashboard sixel integration", () => { formatDashboardWithData(makeDashboardData()); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("Dashboard graphics renderer: ascii") + expect.stringContaining("Dashboard graphics renderer: ascii"), ); expect(debugSpy).toHaveBeenCalledWith( expect.stringContaining( - "reason: no compatible graphics protocol detected" - ) + "reason: no compatible graphics protocol detected", + ), ); }); @@ -398,10 +398,10 @@ describe("dashboard sixel integration", () => { expect(output).not.toContain(`${ESC}P`); expect(output).not.toContain(`${ESC}_G`); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("Dashboard graphics renderer: ascii") + expect.stringContaining("Dashboard graphics renderer: ascii"), ); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("reason: no dashboard widgets") + expect.stringContaining("reason: no dashboard widgets"), ); }); @@ -418,19 +418,19 @@ describe("dashboard sixel integration", () => { makeDashboardData({ graphicsCap: false, widgets: [makeWidget({ layout: { x: 0, y: 12, w: 6, h: 1 } })], - }) + }), ); expect(output).not.toContain(`${ESC}P`); expect(debugSpy).toHaveBeenCalledWith( expect.stringContaining( - "reason: canvas 3416x2496 (8.53M pixels) exceeds the 8M pixel limit" - ) + "reason: canvas 3416x2496 (8.53M pixels) exceeds the 8M pixel limit", + ), ); expect(debugSpy).toHaveBeenCalledWith( expect.stringContaining( - "native pixel width=3416; effective pixel width=3416; graphics cap=not applied" - ) + "native pixel width=3416; effective pixel width=3416; graphics cap=not applied", + ), ); }); @@ -442,21 +442,21 @@ describe("dashboard sixel integration", () => { cellHeight: 32, }); vi.spyOn(sixelImageModule, "encodeImageToSixel").mockImplementation( - (image) => `${ESC}P${image.width}x${image.height}${ESC}\\` + (image) => `${ESC}P${image.width}x${image.height}${ESC}\\`, ); const debugSpy = vi.spyOn(logger, "debug"); const output = formatDashboardWithData( makeDashboardData({ widgets: [makeWidget({ layout: { x: 0, y: 12, w: 6, h: 1 } })], - }) + }), ); expect(output).toContain(`${ESC}P2548x2496${ESC}\\`); expect(debugSpy).toHaveBeenCalledWith( expect.stringContaining( - "native pixel width=3416; effective pixel width=2548; graphics cap=applied" - ) + "native pixel width=3416; effective pixel width=2548; graphics cap=applied", + ), ); }); @@ -479,7 +479,7 @@ describe("dashboard sixel integration", () => { layout: { x: 0, y: 0, w: 6, h: 1 }, }), ], - }) + }), ); expect(output).toContain(`${ESC}_G`); diff --git a/packages/cli/test/lib/formatters/dashboard.test.ts b/packages/cli/test/lib/formatters/dashboard.test.ts index 649220192..f8d041502 100644 --- a/packages/cli/test/lib/formatters/dashboard.test.ts +++ b/packages/cli/test/lib/formatters/dashboard.test.ts @@ -53,9 +53,9 @@ const LINES_PER_UNIT = 6; function stripAnsi(str: string): string { return ( str - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI SGR escape codes + // oxlint-disable-next-line no-control-regex -- ANSI SGR escape codes .replace(/\x1b\[[0-9;]*m/g, "") - // biome-ignore lint/suspicious/noControlCharactersInRegex: OSC 8 hyperlink sequences + // oxlint-disable-next-line no-control-regex -- OSC 8 hyperlink sequences .replace(/\x1b\]8;;[^\x07]*\x07/g, "") ); } @@ -143,7 +143,7 @@ function useRenderedMode() { // --------------------------------------------------------------------------- function makeTimeseriesData( - overrides: Partial = {} + overrides: Partial = {}, ): TimeseriesResult { return { type: "timeseries", @@ -194,7 +194,7 @@ function makeTextData(overrides: Partial = {}): TextResult { } function makeUnsupportedData( - overrides: Partial = {} + overrides: Partial = {}, ): UnsupportedResult { return { type: "unsupported", @@ -212,7 +212,7 @@ function makeErrorData(overrides: Partial = {}): ErrorResult { } function makeWidget( - overrides: Partial = {} + overrides: Partial = {}, ): DashboardViewWidget { return { title: "Test Widget", @@ -223,7 +223,7 @@ function makeWidget( } function makeDashboardData( - overrides: Partial = {} + overrides: Partial = {}, ): DashboardViewData { return { id: "12345", @@ -1239,7 +1239,7 @@ describe("formatDashboardWithData", () => { // Both titles should appear on the same terminal line // (framebuffer composes widgets side-by-side within the same row range) const titleLine = lines.find( - (l) => l.includes("Left Widget") && l.includes("Right Widget") + (l) => l.includes("Left Widget") && l.includes("Right Widget"), ); expect(titleLine).toBeDefined(); }); @@ -1266,7 +1266,7 @@ describe("formatDashboardWithData", () => { // Titles should NOT be on the same line const combinedLine = lines.find( - (l) => l.includes("Top Widget") && l.includes("Bottom Widget") + (l) => l.includes("Top Widget") && l.includes("Bottom Widget"), ); expect(combinedLine).toBeUndefined(); @@ -1276,7 +1276,7 @@ describe("formatDashboardWithData", () => { // Top appears before Bottom expect(output.indexOf("Top Widget")).toBeLessThan( - output.indexOf("Bottom Widget") + output.indexOf("Bottom Widget"), ); }); @@ -1364,7 +1364,7 @@ describe("formatDashboardWithData", () => { // All three titles should be on the same line const titleLine = lines.find( - (l) => l.includes("W1") && l.includes("W2") && l.includes("W3") + (l) => l.includes("W1") && l.includes("W2") && l.includes("W3"), ); expect(titleLine).toBeDefined(); }); @@ -1393,13 +1393,13 @@ describe("formatDashboardWithData", () => { expect(output).toContain("No Layout"); const lines = output.split("\n"); const combinedLine = lines.find( - (l) => l.includes("Laid Out") && l.includes("No Layout") + (l) => l.includes("Laid Out") && l.includes("No Layout"), ); expect(combinedLine).toBeUndefined(); // "No Layout" appears after "Laid Out" (appended after grid) expect(output.indexOf("Laid Out")).toBeLessThan( - output.indexOf("No Layout") + output.indexOf("No Layout"), ); }); @@ -1426,11 +1426,11 @@ describe("formatDashboardWithData", () => { // Both on same line, LeftFirst before RightFirst const titleLine = lines.find( - (l) => l.includes("LeftFirst") && l.includes("RightFirst") + (l) => l.includes("LeftFirst") && l.includes("RightFirst"), ); expect(titleLine).toBeDefined(); expect(titleLine!.indexOf("LeftFirst")).toBeLessThan( - titleLine!.indexOf("RightFirst") + titleLine!.indexOf("RightFirst"), ); }); @@ -1534,7 +1534,7 @@ describe("createDashboardViewRenderer", () => { test("returns hint wrapped in newlines", () => { const renderer = createDashboardViewRenderer(); const result = renderer.finalize!( - "Tip: use --json for machine-readable output" + "Tip: use --json for machine-readable output", ); expect(result).toContain("Tip: use --json for machine-readable output"); expect(result.startsWith("\n")).toBe(true); diff --git a/packages/cli/test/lib/formatters/feedback.test.ts b/packages/cli/test/lib/formatters/feedback.test.ts index 2ead2b53e..a1e845254 100644 --- a/packages/cli/test/lib/formatters/feedback.test.ts +++ b/packages/cli/test/lib/formatters/feedback.test.ts @@ -50,7 +50,7 @@ describe.each([ ])("$action feedback", ({ format, action }) => { test("renders a terminal-safe confirmation", () => { const output = format( - feedback({ shortId: "\x1b[31mWEB-1\x1b[0m\n\u202e" }) + feedback({ shortId: "\x1b[31mWEB-1\x1b[0m\n\u202e" }), ); expect(output).toContain(`${action} feedback`); @@ -182,7 +182,7 @@ describe("formatFeedbackView", () => { expect(listOutput).toContain("…"); expect( - Math.max(...listOutput.split("\n").map((line) => stringWidth(line))) + Math.max(...listOutput.split("\n").map((line) => stringWidth(line))), ).toBeLessThanOrEqual(60); expect(viewOutput).toContain("TAIL **marker**"); } finally { diff --git a/packages/cli/test/lib/formatters/human.details.test.ts b/packages/cli/test/lib/formatters/human.details.test.ts index 3d0849231..ad61d0d05 100644 --- a/packages/cli/test/lib/formatters/human.details.test.ts +++ b/packages/cli/test/lib/formatters/human.details.test.ts @@ -31,14 +31,14 @@ useTestConfigDir("human-details-"); // Helper to strip ANSI codes for content testing function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars + // oxlint-disable-next-line no-control-regex -- ANSI codes use control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } // Mock data factories function createMockOrg( - overrides: Partial = {} + overrides: Partial = {}, ): SentryOrganization { return { id: "123", @@ -53,7 +53,7 @@ function createMockOrg( } function createMockProject( - overrides: Partial = {} + overrides: Partial = {}, ): SentryProject & { orgSlug?: string } { return { id: "456", @@ -214,7 +214,7 @@ describe("formatProjectDetails", () => { // fired; a stray cached display name would show up between the // "Organization" label and `(acme-only-slug)`. expect(result).toMatch( - /Organization\s*\S\s*acme-only-slug\s*\(\s*acme-only-slug\s*\)/ + /Organization\s*\S\s*acme-only-slug\s*\(\s*acme-only-slug\s*\)/, ); }); @@ -538,7 +538,7 @@ describe("formatEventDetails", () => { test("includes custom header text", () => { const result = stripAnsi( - formatEventDetails(createMockEvent(), "My Custom Header") + formatEventDetails(createMockEvent(), "My Custom Header"), ); expect(result).toContain("My Custom Header"); }); @@ -552,7 +552,7 @@ describe("formatEventDetails", () => { test("includes location when present", () => { const result = stripAnsi( - formatEventDetails(createMockEvent({ location: "app/main.py" })) + formatEventDetails(createMockEvent({ location: "app/main.py" })), ); expect(result).toContain("Location"); expect(result).toContain("app/main.py"); @@ -563,8 +563,8 @@ describe("formatEventDetails", () => { formatEventDetails( createMockEvent({ contexts: { trace: { trace_id: "aabbccdd11223344" } }, - }) - ) + }), + ), ); expect(result).toContain("Trace"); expect(result).toContain("aabbccdd11223344"); @@ -575,8 +575,8 @@ describe("formatEventDetails", () => { formatEventDetails( createMockEvent({ sdk: { name: "sentry.javascript.browser", version: "7.0.0" }, - }) - ) + }), + ), ); expect(result).toContain("SDK"); expect(result).toContain("sentry.javascript.browser"); @@ -588,8 +588,8 @@ describe("formatEventDetails", () => { formatEventDetails( createMockEvent({ release: { version: "1.0.0", shortVersion: "1.0.0" }, - }) - ) + }), + ), ); expect(result).toContain("Release"); expect(result).toContain("1.0.0"); @@ -605,8 +605,8 @@ describe("formatEventDetails", () => { id: "42", ip_address: "192.168.1.1", }, - }) - ) + }), + ), ); expect(result).toContain("User"); expect(result).toContain("test@example.com"); @@ -622,8 +622,8 @@ describe("formatEventDetails", () => { email: "test@example.com", geo: { city: "Berlin", region: "Berlin", country_code: "DE" }, }, - }) - ) + }), + ), ); expect(result).toContain("Location"); expect(result).toContain("Berlin"); @@ -639,8 +639,8 @@ describe("formatEventDetails", () => { os: { name: "Windows", version: "11" }, device: { family: "Desktop", brand: "Apple" }, }, - }) - ) + }), + ), ); expect(result).toContain("Environment"); expect(result).toContain("Chrome"); @@ -662,8 +662,8 @@ describe("formatEventDetails", () => { }, }, ], - }) - ) + }), + ), ); expect(result).toContain("Request"); expect(result).toContain("POST https://api.example.com/users"); @@ -699,8 +699,8 @@ describe("formatEventDetails", () => { }, }, ], - }) - ) + }), + ), ); expect(result).toContain("Stack Trace"); expect(result).toContain("TypeError: Cannot read property"); @@ -742,8 +742,8 @@ describe("formatEventDetails", () => { }, }, ], - }) - ) + }), + ), ); expect(result).toContain("const x = 1"); expect(result).toContain("throw new Error"); @@ -778,8 +778,8 @@ describe("formatEventDetails", () => { }, }, ], - }) - ) + }), + ), ); expect(result).toContain("Breadcrumbs"); expect(result).toContain("navigation"); @@ -800,13 +800,13 @@ describe("formatEventDetails", () => { ], }), "Latest Event", - "https://acme.sentry.io/issues/789/" - ) + "https://acme.sentry.io/issues/789/", + ), ); expect(result).toContain("Replay"); expect(result).toContain("346789a703f6454384f1de473b8b9fcc"); expect(result).toContain( - "https://acme.sentry.io/explore/replays/346789a703f6454384f1de473b8b9fcc/" + "https://acme.sentry.io/explore/replays/346789a703f6454384f1de473b8b9fcc/", ); }); @@ -822,8 +822,8 @@ describe("formatEventDetails", () => { ], }), "Latest Event", - "https://acme.sentry.io/issues/789/" - ) + "https://acme.sentry.io/issues/789/", + ), ); expect(result).toContain("346789a703f6454384f1de473b8b9fcc"); }); @@ -838,8 +838,8 @@ describe("formatEventDetails", () => { tags: [], }), "Latest Event", - "https://acme.sentry.io/issues/789/" - ) + "https://acme.sentry.io/issues/789/", + ), ); expect(result).toContain("346789a703f6454384f1de473b8b9fcc"); }); @@ -852,8 +852,8 @@ describe("formatEventDetails", () => { { key: "browser", value: "Chrome 120" }, { key: "os", value: "Windows 11" }, ], - }) - ) + }), + ), ); expect(result).toContain("Tags"); expect(result).toContain("browser"); @@ -862,7 +862,7 @@ describe("formatEventDetails", () => { test("handles minimal event", () => { const result = formatEventDetails( - createMockEvent({ dateReceived: undefined }) + createMockEvent({ dateReceived: undefined }), ); expect(typeof result).toBe("string"); expect(result.length).toBeGreaterThan(0); @@ -885,8 +885,8 @@ describe("formatEventDetails", () => { }, }, ], - }) - ) + }), + ), ); expect(result).toContain("/home"); expect(result).toContain("/profile"); @@ -907,8 +907,8 @@ describe("formatEventDetails", () => { }, }, ], - }) - ) + }), + ), ); expect(result).not.toContain(longMsg); expect(result).toContain("..."); @@ -919,8 +919,8 @@ describe("formatEventDetails", () => { formatEventDetails( createMockEvent({ entries: [{ type: "breadcrumbs", data: { values: [] } }], - }) - ) + }), + ), ); expect(result).not.toContain("Breadcrumbs"); }); @@ -930,8 +930,8 @@ describe("formatEventDetails", () => { formatEventDetails( createMockEvent({ user: { name: "John Doe" }, - }) - ) + }), + ), ); expect(result).toContain("Name"); expect(result).toContain("John Doe"); @@ -944,7 +944,7 @@ describe("formatEventDetails", () => { test("skips environment section when no contexts", () => { const result = stripAnsi( - formatEventDetails(createMockEvent({ contexts: null })) + formatEventDetails(createMockEvent({ contexts: null })), ); expect(result).not.toContain("Environment"); }); @@ -954,8 +954,8 @@ describe("formatEventDetails", () => { formatEventDetails( createMockEvent({ entries: [{ type: "request", data: {} }], - }) - ) + }), + ), ); expect(result).not.toContain("Request"); }); diff --git a/packages/cli/test/lib/formatters/human.property.test.ts b/packages/cli/test/lib/formatters/human.property.test.ts index f0431df53..a8a5b0207 100644 --- a/packages/cli/test/lib/formatters/human.property.test.ts +++ b/packages/cli/test/lib/formatters/human.property.test.ts @@ -26,7 +26,7 @@ import { DEFAULT_NUM_RUNS } from "../../model-based/helpers.js"; // Helper to strip ANSI codes for content testing function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars + // oxlint-disable-next-line no-control-regex -- ANSI codes use control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -45,7 +45,7 @@ const suffixArb = stringMatching(/^[a-zA-Z0-9]{1,10}$/); /** Full short ID like "PROJECT-A3" */ const shortIdArb = tuple(projectSlugArb, suffixArb).map( - ([project, suffix]) => `${project}-${suffix}` + ([project, suffix]) => `${project}-${suffix}`, ); /** User name (non-empty string with letters and spaces) */ @@ -67,7 +67,7 @@ describe("formatShortId properties", () => { const result = formatShortId(shortId); expect(stripFormatting(result)).toBe(shortId.toUpperCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -77,7 +77,7 @@ describe("formatShortId properties", () => { const result = formatShortId(shortId); expect(stripFormatting(result).length).toBe(shortId.length); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -88,7 +88,7 @@ describe("formatShortId properties", () => { const result = formatShortId(shortId, { projectSlug: project }); expect(stripFormatting(result)).toBe(shortId.toUpperCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -103,7 +103,7 @@ describe("formatShortId properties", () => { }); expect(stripFormatting(result)).toBe(shortId.toUpperCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -113,7 +113,7 @@ describe("formatShortId properties", () => { const result = stripAnsi(formatShortId(shortId)); expect(result).toBe(result.toUpperCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -124,7 +124,7 @@ describe("formatShortId properties", () => { const result2 = formatShortId(shortId); expect(result1).toBe(result2); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -143,7 +143,7 @@ describe("formatShortId properties", () => { expect(lowerResult).toBe(upperResult); expect(upperResult).toBe(mixedResult); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -155,7 +155,7 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ id: "1", name, email }); expect(result).toBe(`${name} <${email}>`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -165,7 +165,7 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ id: "1", username, email }); expect(result).toBe(`${username} <${email}>`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -177,9 +177,9 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ id: "1", name, username, email }); // If this passes, name is used (proving username is ignored when name exists) expect(result).toBe(`${name} <${email}>`); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -189,7 +189,7 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ id: "1", name }); expect(result).toBe(name); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -199,7 +199,7 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ id: "1", username }); expect(result).toBe(username); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -209,7 +209,7 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ id: "1", email }); expect(result).toBe(email); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -219,7 +219,7 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ id }); expect(result).toBe(`user ${id}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -229,7 +229,7 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ userId }); expect(result).toBe(`user ${userId}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -239,7 +239,7 @@ describe("formatUserIdentity properties", () => { const result = formatUserIdentity({ id, userId }); expect(result).toBe(`user ${id}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -253,7 +253,7 @@ const scoreArb = double({ min: 0, max: 1, noNaN: true }); const nullableScoreArb = oneof( scoreArb, constant(null as null), - constant(undefined as undefined) + constant(undefined as undefined), ); describe("property: getSeerFixabilityLabel", () => { @@ -263,7 +263,7 @@ describe("property: getSeerFixabilityLabel", () => { const label = getSeerFixabilityLabel(score); expect(["high", "med", "low"]).toContain(label); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -279,7 +279,7 @@ describe("property: getSeerFixabilityLabel", () => { expect(rankA).toBeLessThanOrEqual(rankB); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -291,7 +291,7 @@ describe("property: formatFixability", () => { const result = formatFixability(score); expect(result).toMatch(/^(high|med|low)\(\d+%\)$/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -300,7 +300,7 @@ describe("property: formatFixability", () => { property(scoreArb, (score) => { expect(formatFixability(score).length).toBeLessThanOrEqual(10); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -311,7 +311,7 @@ describe("property: formatFixability", () => { expect(formatFixability(score)).toBe(""); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -323,7 +323,7 @@ describe("property: formatFixabilityDetail", () => { const result = formatFixabilityDetail(score); expect(result).toMatch(/^(High|Med|Low) \(\d+%\)$/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -334,7 +334,7 @@ describe("property: formatFixabilityDetail", () => { expect(formatFixabilityDetail(score)).toBe(""); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/formatters/human.test.ts b/packages/cli/test/lib/formatters/human.test.ts index 6ac42f73c..4ced119b4 100644 --- a/packages/cli/test/lib/formatters/human.test.ts +++ b/packages/cli/test/lib/formatters/human.test.ts @@ -22,22 +22,13 @@ import { substatusLabel, writeIssueTable, } from "../../../src/lib/formatters/human.js"; +import { stripColorTags } from "../../../src/lib/formatters/markdown.js"; +import { stripAnsi } from "../../../src/lib/formatters/plain-detect.js"; import type { SentryIssue } from "../../../src/types/index.js"; -// Helper to strip ANSI codes for content testing -function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars - return str.replace(/\x1b\[[0-9;]*m/g, ""); -} - -/** Strip ANSI escape codes and color tags for content testing. */ +/** Normalize styling for text assertions; this is not HTML sanitization. */ function stripFormatting(s: string): string { - return ( - s - .replace(/<\/?[a-z]+>/g, "") - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars - .replace(/\x1b\[[0-9;]*m/g, "") - ); + return stripAnsi(stripColorTags(s)); } describe("formatShortId edge cases", () => { @@ -47,7 +38,7 @@ describe("formatShortId edge cases", () => { test("handles undefined options", () => { expect(stripFormatting(formatShortId("CRAFT-G", undefined))).toBe( - "CRAFT-G" + "CRAFT-G", ); }); @@ -457,7 +448,7 @@ describe("formatIssueSubtitle", () => { test("returns value when present", () => { expect(formatIssueSubtitle({ value: "Some error message" })).toBe( - "Some error message" + "Some error message", ); }); @@ -475,7 +466,7 @@ describe("formatIssueSubtitle", () => { test("falls back to type + function", () => { expect( - formatIssueSubtitle({ type: "TypeError", function: "handleClick" }) + formatIssueSubtitle({ type: "TypeError", function: "handleClick" }), ).toBe("TypeError in handleClick"); }); @@ -489,7 +480,7 @@ describe("formatIssueSubtitle", () => { value: "Error msg", type: "TypeError", function: "fn", - }) + }), ).toBe("Error msg"); }); }); @@ -646,7 +637,7 @@ describe("formatProjectCreated", () => { test("omits DSN row when null", () => { const result = stripAnsi( - formatProjectCreated({ ...baseResult, dsn: null }) + formatProjectCreated({ ...baseResult, dsn: null }), ); expect(result).not.toContain("DSN"); }); @@ -661,7 +652,7 @@ describe("formatProjectCreated", () => { slug: "my-project-1", } as ProjectCreatedResult["project"], expectedSlug: "my-project", - }) + }), ); expect(result).toContain("my-project-1"); expect(result).toContain("already taken"); @@ -669,7 +660,7 @@ describe("formatProjectCreated", () => { test("shows auto-created team note", () => { const result = stripAnsi( - formatProjectCreated({ ...baseResult, teamSource: "auto-created" }) + formatProjectCreated({ ...baseResult, teamSource: "auto-created" }), ); expect(result).toContain("Created team"); expect(result).toContain("my-team"); @@ -677,7 +668,7 @@ describe("formatProjectCreated", () => { test("shows auto-selected team note", () => { const result = stripAnsi( - formatProjectCreated({ ...baseResult, teamSource: "auto-selected" }) + formatProjectCreated({ ...baseResult, teamSource: "auto-selected" }), ); expect(result).toContain("Using team"); expect(result).toContain("sentry team list"); @@ -691,7 +682,7 @@ describe("formatProjectCreated", () => { ...baseResult.project, platform: "", } as ProjectCreatedResult["project"], - }) + }), ); expect(result).toContain("javascript"); }); @@ -709,7 +700,7 @@ describe("formatDashboardCreated", () => { id: "42", title: "My Dashboard", url: "https://acme.sentry.io/dashboard/42/", - }) + }), ); expect(result).toContain("My Dashboard"); expect(result).toContain("42"); @@ -722,7 +713,7 @@ describe("formatDashboardCreated", () => { id: "1", title: "Dash | with * special", url: "https://acme.sentry.io/dashboard/1/", - }) + }), ); expect(result).toContain("Dash"); expect(result).toContain("special"); @@ -880,7 +871,7 @@ describe("formatStatusIcon", () => { test("resolvedInNextRelease shows green icon", () => { expect( - stripFormatting(formatStatusIcon("resolvedInNextRelease")) + stripFormatting(formatStatusIcon("resolvedInNextRelease")), ).toContain("✓"); }); @@ -896,13 +887,13 @@ describe("formatStatusIcon", () => { describe("formatStatusLabel", () => { test("resolved → Resolved label", () => { expect(stripFormatting(formatStatusLabel("resolved"))).toContain( - "Resolved" + "Resolved", ); }); test("resolvedInNextRelease → Resolved in Next Release label", () => { expect( - stripFormatting(formatStatusLabel("resolvedInNextRelease")) + stripFormatting(formatStatusLabel("resolvedInNextRelease")), ).toContain("Resolved in Next Release"); }); diff --git a/packages/cli/test/lib/formatters/human.utils.test.ts b/packages/cli/test/lib/formatters/human.utils.test.ts index d6801b9f1..636a80e35 100644 --- a/packages/cli/test/lib/formatters/human.utils.test.ts +++ b/packages/cli/test/lib/formatters/human.utils.test.ts @@ -21,17 +21,14 @@ import { formatStatusLabel, maskToken, } from "../../../src/lib/formatters/human.js"; +import { stripColorTags } from "../../../src/lib/formatters/markdown.js"; +import { stripAnsi as stripTerminalCodes } from "../../../src/lib/formatters/plain-detect.js"; import { formatRelativeTime } from "../../../src/lib/formatters/time-utils.js"; import { DEFAULT_NUM_RUNS } from "../../model-based/helpers.js"; -// Helper to strip ANSI codes and markdown color tags for content testing. -// Strips color tags first to avoid incomplete multi-character sanitization -// (ANSI removal could otherwise join fragments into tag-like sequences). +// Normalize styling for text assertions; this is not HTML sanitization. function stripAnsi(str: string): string { - let result = str.replace(/<\/?[a-z]+>/g, ""); - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars - result = result.replace(/\x1b\[[0-9;]*m/g, ""); - return result; + return stripTerminalCodes(stripColorTags(str)); } // Status Formatting @@ -110,7 +107,7 @@ describe("formatRelativeTime", () => { test("formats hours ago for times within 24 hours", () => { const threeHoursAgo = new Date( - Date.now() - 3 * 60 * 60 * 1000 + Date.now() - 3 * 60 * 60 * 1000, ).toISOString(); const result = stripAnsi(formatRelativeTime(threeHoursAgo)); expect(result.trim()).toMatch(/^\d+h ago$/); @@ -118,7 +115,7 @@ describe("formatRelativeTime", () => { test("formats days ago for times within 3 days", () => { const twoDaysAgo = new Date( - Date.now() - 2 * 24 * 60 * 60 * 1000 + Date.now() - 2 * 24 * 60 * 60 * 1000, ).toISOString(); const result = stripAnsi(formatRelativeTime(twoDaysAgo)); expect(result.trim()).toMatch(/^\d+d ago$/); @@ -126,7 +123,7 @@ describe("formatRelativeTime", () => { test("formats short date for times older than 3 days", () => { const tenDaysAgo = new Date( - Date.now() - 10 * 24 * 60 * 60 * 1000 + Date.now() - 10 * 24 * 60 * 60 * 1000, ).toISOString(); const result = stripAnsi(formatRelativeTime(tenDaysAgo)); // Should be like "Jan 18" or "Dec 5" @@ -172,10 +169,10 @@ describe("maskToken", () => { const masked = maskToken(token); // Should show first 8, then ..., then last 4 expect(masked).toBe( - `${token.substring(0, 8)}...${token.substring(token.length - 4)}` + `${token.substring(0, 8)}...${token.substring(token.length - 4)}`, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -186,7 +183,7 @@ describe("maskToken", () => { property(shortTokenArb, (token) => { expect(maskToken(token)).toBe("****"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -269,7 +266,7 @@ describe("formatDuration", () => { const result = formatDuration(seconds); expect(result).toMatch(/minute|hour|day|week/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -305,7 +302,7 @@ describe("formatExpiration", () => { const pastTime = Date.now() - msAgo; expect(formatExpiration(pastTime)).toBe("Expired"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -316,7 +313,7 @@ describe("formatExpiration", () => { const result = formatExpiration(futureTime); expect(result).toContain("remaining"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/formatters/json.property.test.ts b/packages/cli/test/lib/formatters/json.property.test.ts index f7d9535a1..bf1d697e8 100644 --- a/packages/cli/test/lib/formatters/json.property.test.ts +++ b/packages/cli/test/lib/formatters/json.property.test.ts @@ -31,12 +31,12 @@ import { DEFAULT_NUM_RUNS } from "../../model-based/helpers.js"; /** Generates simple field names (lowercase letters, 1-8 chars) */ const fieldNameArb = array( constantFrom(..."abcdefghijklmnopqrstuvwxyz".split("")), - { minLength: 1, maxLength: 8 } + { minLength: 1, maxLength: 8 }, ).map((chars) => chars.join("")); /** Generates dot-notated paths (1-3 segments) */ const fieldPathArb = array(fieldNameArb, { minLength: 1, maxLength: 3 }).map( - (segments) => segments.join(".") + (segments) => segments.join("."), ); /** Generates a flat object with known string keys and JSON-compatible values */ @@ -56,7 +56,7 @@ describe("property: filterFields", () => { const result = filterFields(obj, []); expect(result).toEqual({}); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -71,9 +71,9 @@ describe("property: filterFields", () => { for (const key of resultKeys) { expect(fields).toContain(key); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -89,7 +89,7 @@ describe("property: filterFields", () => { expect(result[key]).toEqual(obj[key]); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -103,11 +103,11 @@ describe("property: filterFields", () => { const result = filterFields(obj, extraFields); // Result should never have more keys than the source expect( - Object.keys(result as Record).length + Object.keys(result as Record).length, ).toBeLessThanOrEqual(Object.keys(obj).length); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -120,9 +120,9 @@ describe("property: filterFields", () => { const once = filterFields(obj, fields); const twice = filterFields(once, fields); expect(twice).toEqual(once); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -140,9 +140,9 @@ describe("property: filterFields", () => { for (let i = 0; i < items.length; i++) { expect(result[i]).toEqual(filterFields(items[i], fields)); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -152,7 +152,7 @@ describe("property: filterFields", () => { const safeNestedArb = dictionary( fieldNameArb, dictionary(fieldNameArb, jsonValue(), { minKeys: 1, maxKeys: 5 }), - { minKeys: 1, maxKeys: 5 } + { minKeys: 1, maxKeys: 5 }, ); fcAssert( @@ -182,7 +182,7 @@ describe("property: filterFields", () => { >; expect(result[nestedKey]?.[innerKey]).toEqual(inner[innerKey]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -194,9 +194,9 @@ describe("property: filterFields", () => { (value, fields) => { const result = filterFields(value, fields); expect(result).toBe(value); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -214,9 +214,9 @@ describe("property: parseFieldsList", () => { const joined = fields.join(","); const parsed = parseFieldsList(joined); expect(new Set(parsed)).toEqual(new Set(fields)); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -228,9 +228,9 @@ describe("property: parseFieldsList", () => { const input = fields.join(","); const parsed = parseFieldsList(input); expect(parsed.length).toBe(new Set(parsed).size); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -242,9 +242,9 @@ describe("property: parseFieldsList", () => { const withSpaces = fields.map((f) => ` ${f} `).join(" , "); const clean = fields.join(","); expect(parseFieldsList(withSpaces)).toEqual(parseFieldsList(clean)); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -256,9 +256,9 @@ describe("property: parseFieldsList", () => { const withEmpty = `,${fields.join(",,")},`; const parsed = parseFieldsList(withEmpty); expect(parsed).toEqual(parseFieldsList(fields.join(","))); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -268,7 +268,7 @@ describe("property: parseFieldsList", () => { const input = ",".repeat(count); expect(parseFieldsList(input)).toEqual([]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/formatters/json.test.ts b/packages/cli/test/lib/formatters/json.test.ts index 63bd21c3b..8d95838c8 100644 --- a/packages/cli/test/lib/formatters/json.test.ts +++ b/packages/cli/test/lib/formatters/json.test.ts @@ -33,7 +33,7 @@ describe("filterFields edge cases", () => { contexts: { trace: { traceId: "abc" } }, }; expect( - filter(data, ["id", "metadata.value", "contexts.trace.traceId"]) + filter(data, ["id", "metadata.value", "contexts.trace.traceId"]), ).toEqual({ id: 1, metadata: { value: "ReferenceError" }, @@ -68,7 +68,7 @@ describe("filterFields edge cases", () => { filter({ user: { name: "Alice", email: "alice@example.com", age: 30 } }, [ "user.name", "user.email", - ]) + ]), ).toEqual({ user: { name: "Alice", email: "alice@example.com" }, }); @@ -79,7 +79,7 @@ describe("filterFields edge cases", () => { filter({ active: true, deleted: false, name: "test" }, [ "active", "deleted", - ]) + ]), ).toEqual({ active: true, deleted: false, @@ -91,7 +91,7 @@ describe("filterFields edge cases", () => { filter({ id: 1, tags: ["bug", "critical"], title: "test" }, [ "id", "tags", - ]) + ]), ).toEqual({ id: 1, tags: ["bug", "critical"], @@ -118,7 +118,7 @@ describe("parseFieldsList", () => { test("handles dot-notation fields", () => { expect(parseFieldsList("id,metadata.value,contexts.trace.traceId")).toEqual( - ["id", "metadata.value", "contexts.trace.traceId"] + ["id", "metadata.value", "contexts.trace.traceId"], ); }); @@ -185,7 +185,7 @@ describe("writeJson with fields", () => { issue: { id: 1, title: "bug" }, event: { id: "abc", contexts: { trace: { traceId: "def" } } }, }, - ["issue.id", "event.contexts.trace.traceId"] + ["issue.id", "event.contexts.trace.traceId"], ); expect(JSON.parse(output())).toEqual({ issue: { id: 1 }, @@ -201,7 +201,7 @@ describe("writeJson with fields", () => { { id: 1, title: "first", extra: true }, { id: 2, title: "second", extra: false }, ], - ["id", "title"] + ["id", "title"], ); expect(JSON.parse(output())).toEqual([ { id: 1, title: "first" }, diff --git a/packages/cli/test/lib/formatters/local.property.test.ts b/packages/cli/test/lib/formatters/local.property.test.ts index 158e35769..7d37f7f45 100644 --- a/packages/cli/test/lib/formatters/local.property.test.ts +++ b/packages/cli/test/lib/formatters/local.property.test.ts @@ -27,7 +27,7 @@ import { import { DEFAULT_NUM_RUNS } from "../../model-based/helpers.js"; /** ANSI escape pattern — should not appear in sanitize output. */ -// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI control chars +// oxlint-disable-next-line no-control-regex -- ANSI control chars const ANSI_RE = /\x1b\[[0-9;]*m/; describe("property: sanitize", () => { @@ -37,7 +37,7 @@ describe("property: sanitize", () => { const result = sanitize(input); expect(ANSI_RE.test(result)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -48,7 +48,7 @@ describe("property: sanitize", () => { expect(result).not.toContain("\n"); expect(result).not.toContain("\r"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -59,7 +59,7 @@ describe("property: sanitize", () => { const twice = sanitize(once); expect(twice).toBe(once); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -72,7 +72,7 @@ describe("property: formatTime", () => { expect(typeof result).toBe("string"); expect(result.length).toBeGreaterThan(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -83,7 +83,7 @@ describe("property: formatTime", () => { expect(typeof result).toBe("string"); expect(result.length).toBeGreaterThan(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -101,7 +101,7 @@ describe("property: formatTime", () => { const result = formatTime(epoch); expect(result).toMatch(/^\d{2}:\d{2}:\d{2}$/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -115,18 +115,18 @@ describe("property: isItemIncluded", () => { "transaction", "log", "attachment", - "session" + "session", ), - option(string(), { nil: undefined }) + option(string(), { nil: undefined }), ); fcAssert( property(itemTypes, (itemType) => { const empty = new Set(); expect(isItemIncluded(itemType as string | undefined, empty)).toBe( - true + true, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -144,7 +144,7 @@ describe("property: isItemIncluded", () => { expect(included).toBe(false); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -156,7 +156,7 @@ describe("property: itemTypeToFilterCategory", () => { property(errorTypes, (itemType) => { expect(itemTypeToFilterCategory(itemType)).toBe("error"); }), - { numRuns: 10 } + { numRuns: 10 }, ); }); @@ -171,13 +171,13 @@ describe("property: itemTypeToFilterCategory", () => { test("random non-matching strings return undefined", () => { const nonMatching = stringMatching(/^[a-z]{3,10}$/).filter( (s) => - s !== "error" && s !== "event" && s !== "transaction" && s !== "log" + s !== "error" && s !== "event" && s !== "transaction" && s !== "log", ); fcAssert( property(nonMatching, (itemType) => { expect(itemTypeToFilterCategory(itemType)).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/lib/formatters/local.test.ts b/packages/cli/test/lib/formatters/local.test.ts index 530b92b46..95a6c3ea8 100644 --- a/packages/cli/test/lib/formatters/local.test.ts +++ b/packages/cli/test/lib/formatters/local.test.ts @@ -381,8 +381,8 @@ describe("formatSingleLog", () => { const result = stripAnsi( formatSingleLog( { level: "info", body: "User logged in", timestamp: 1_700_000_000 }, - "[SERVER] " - ) + "[SERVER] ", + ), ); expect(result).toContain("[INFO]"); expect(result).toContain("User logged in"); @@ -399,8 +399,8 @@ describe("formatSingleLog", () => { user_id: { value: 42 }, }, }, - "[SERVER] " - ) + "[SERVER] ", + ), ); expect(result).toContain("[user_id=42]"); expect(result).not.toContain("sentry.sdk.name"); @@ -428,8 +428,8 @@ describe("formatSingleLog", () => { mid: { value: 3 }, }, }, - "[SERVER] " - ) + "[SERVER] ", + ), ); const alphaIdx = result.indexOf("[alpha=2]"); const midIdx = result.indexOf("[mid=3]"); @@ -451,8 +451,8 @@ describe("formatSingleLog", () => { }, }, }, - "[SERVER] " - ) + "[SERVER] ", + ), ); expect(result).toContain("[trace:abcdef01]"); expect(result).not.toContain("sentry.trace.trace_id"); @@ -644,7 +644,7 @@ describe("extractTraceId", () => { test("returns undefined for a malformed trace ID", () => { expect( - extractTraceId({ contexts: { trace: { trace_id: "nope" } } }) + extractTraceId({ contexts: { trace: { trace_id: "nope" } } }), ).toBeUndefined(); }); @@ -688,35 +688,35 @@ describe("inferSource", () => { test("detects mobile SDK (react-native)", () => { const result = stripAnsi( - inferSource({ sdk: { name: "sentry.javascript.react-native" } }) + inferSource({ sdk: { name: "sentry.javascript.react-native" } }), ); expect(result).toContain("[MOBILE]"); }); test("detects mobile SDK (flutter)", () => { const result = stripAnsi( - inferSource({ sdk: { name: "sentry.dart.flutter" } }) + inferSource({ sdk: { name: "sentry.dart.flutter" } }), ); expect(result).toContain("[MOBILE]"); }); test("detects browser SDK", () => { const result = stripAnsi( - inferSource({ sdk: { name: "sentry.javascript.browser" } }) + inferSource({ sdk: { name: "sentry.javascript.browser" } }), ); expect(result).toContain("[BROWSER]"); }); test("detects server JS SDK (node)", () => { const result = stripAnsi( - inferSource({ sdk: { name: "sentry.javascript.node" } }) + inferSource({ sdk: { name: "sentry.javascript.node" } }), ); expect(result).toContain("[SERVER]"); }); test("detects server JS SDK (nextjs)", () => { const result = stripAnsi( - inferSource({ sdk: { name: "sentry.javascript.nextjs" } }) + inferSource({ sdk: { name: "sentry.javascript.nextjs" } }), ); expect(result).toContain("[SERVER]"); }); @@ -776,7 +776,7 @@ describe("formatItemJson", () => { const lines = formatItemJson( "error", { timestamp: 1_700_000_000, message: "boom" }, - serverHeader + serverHeader, ); expect(JSON.parse(lines[0]).schema_version).toBe(1); @@ -794,7 +794,7 @@ describe("formatItemJson", () => { ...serverHeader, // Spotlight stores its internal envelope identity as a UUID object. __spotlight_envelope_id: { toString: () => "envelope-123" }, - } + }, ); expect(JSON.parse(lines[0])).toMatchObject({ @@ -831,7 +831,7 @@ describe("formatItemJson", () => { message: "boom", contexts: { trace: { trace_id: traceId } }, }, - serverHeader + serverHeader, ); expect(JSON.parse(errorLines[0]).trace_id).toBe(traceId); @@ -843,7 +843,7 @@ describe("formatItemJson", () => { transaction: "GET /", contexts: { trace: { op: "http.server", trace_id: traceId } }, }, - serverHeader + serverHeader, ); expect(JSON.parse(txnLines[0]).trace_id).toBe(traceId); }); @@ -863,7 +863,7 @@ describe("formatItemJson", () => { }, ], }, - serverHeader + serverHeader, ); expect(JSON.parse(lines[0]).trace_id).toBe(traceId); }); @@ -955,7 +955,7 @@ describe("formatItemJson", () => { const lines = formatItemJson( "attachment\u202e", { timestamp: "2026-09-08\u009b", event_id: "event\u202e-123" }, - serverHeader + serverHeader, ); const parsed = JSON.parse(lines[0]); @@ -977,7 +977,7 @@ describe("formatItemJson", () => { }, ], }, - serverHeader + serverHeader, ); expect(JSON.parse(logLines[0]).attributes).toEqual({ nested: { child: "unsafevalue" }, @@ -997,11 +997,11 @@ describe("formatItemJson", () => { contexts: { trace: { op: "http.server", data: { "user.id": "42" } } }, }; const without = JSON.parse( - formatItemJson("transaction", event, serverHeader)[0] + formatItemJson("transaction", event, serverHeader)[0], ); expect(without.attributes).toBeUndefined(); const withAttrs = JSON.parse( - formatItemJson("transaction", event, serverHeader, true)[0] + formatItemJson("transaction", event, serverHeader, true)[0], ); expect(withAttrs.attributes.user).toEqual({ "user.id": "42" }); }); @@ -1032,7 +1032,7 @@ describe("formatAttributeTable", () => { expect(out).toContain("http.method"); // user group is rendered before the sdk group expect(out.indexOf("user attributes")).toBeLessThan( - out.indexOf("sdk attributes") + out.indexOf("sdk attributes"), ); }); @@ -1235,7 +1235,7 @@ describe("standalone span support", () => { describe("inferSource cloudflare SDK", () => { test("detects Cloudflare Workers SDK as server", () => { const result = stripAnsi( - inferSource({ sdk: { name: "sentry.javascript.cloudflare" } }) + inferSource({ sdk: { name: "sentry.javascript.cloudflare" } }), ); expect(result).toContain("[SERVER]"); }); diff --git a/packages/cli/test/lib/formatters/log.property.test.ts b/packages/cli/test/lib/formatters/log.property.test.ts index 7d4350517..f684d525f 100644 --- a/packages/cli/test/lib/formatters/log.property.test.ts +++ b/packages/cli/test/lib/formatters/log.property.test.ts @@ -40,7 +40,7 @@ const severityArb = oneof( constant("warning"), constant("error"), constant("debug"), - constant("fatal") + constant("fatal"), ); /** Optional string (string or null) */ @@ -76,7 +76,7 @@ const detailedLogArb = createDetailedLogArb(); /** Strip ANSI escape codes */ function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI control chars + // oxlint-disable-next-line no-control-regex -- ANSI control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -90,9 +90,9 @@ describe("formatLogDetails properties", () => { const result = formatLogDetails(log, orgSlug); expect(typeof result).toBe("string"); expect(result.length).toBeGreaterThan(0); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -104,9 +104,9 @@ describe("formatLogDetails properties", () => { (log: DetailedSentryLog, orgSlug: string) => { const result = stripAnsi(formatLogDetails(log, orgSlug)); expect(result).toContain(log["sentry.item_id"]); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -118,9 +118,9 @@ describe("formatLogDetails properties", () => { (log: DetailedSentryLog, orgSlug: string) => { const result = stripAnsi(formatLogDetails(log, orgSlug)); expect(result).toContain("Timestamp"); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -132,9 +132,9 @@ describe("formatLogDetails properties", () => { (log: DetailedSentryLog, orgSlug: string) => { const result = stripAnsi(formatLogDetails(log, orgSlug)); expect(result).toContain("Severity"); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -151,9 +151,9 @@ describe("formatLogDetails properties", () => { } else { expect(result).not.toContain("/traces/"); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -168,9 +168,9 @@ describe("formatLogDetails properties", () => { expect(result).toContain("SDK"); expect(result).toContain(log["sdk.name"]); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -187,9 +187,9 @@ describe("formatLogDetails properties", () => { } else { expect(result).not.toContain("Source Location"); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -202,9 +202,9 @@ describe("formatLogDetails properties", () => { const result1 = formatLogDetails(log, orgSlug); const result2 = formatLogDetails(log, orgSlug); expect(result1).toEqual(result2); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -217,9 +217,9 @@ describe("formatLogDetails properties", () => { const result = formatLogDetails(log, orgSlug); expect(typeof result).toBe("string"); expect(Array.isArray(result)).toBe(false); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/formatters/log.test.ts b/packages/cli/test/lib/formatters/log.test.ts index 96ede2b47..b6c811b0a 100644 --- a/packages/cli/test/lib/formatters/log.test.ts +++ b/packages/cli/test/lib/formatters/log.test.ts @@ -64,7 +64,7 @@ function createTestLog(overrides: Partial = {}): SentryLog { // Strip ANSI color codes for easier testing function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ANSI stripping + // oxlint-disable-next-line no-control-regex -- intentional ANSI stripping return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -286,13 +286,13 @@ describe("formatLogsHeader (plain mode)", () => { test("includes extra columns when provided", () => { const result = formatLogsHeader(["email", "user_id"]); expect(result).toContain( - "| ID | Timestamp | Level | Message | email | user_id |" + "| ID | Timestamp | Level | Message | email | user_id |", ); }); }); function createDetailedTestLog( - overrides: Partial = {} + overrides: Partial = {}, ): DetailedSentryLog { return { "sentry.item_id": "test-log-id-123456789012345678901234", @@ -478,7 +478,7 @@ describe("formatLogDetails", () => { ]; const log = createDetailedTestLog(); const result = stripAnsi( - formatLogDetails(log, "test-org", attrsWithRedundant) + formatLogDetails(log, "test-org", attrsWithRedundant), ); expect(result).toContain("user.id"); @@ -489,7 +489,7 @@ describe("formatLogDetails", () => { test("extraFields limits which custom attributes are shown", () => { const log = createDetailedTestLog(); const result = stripAnsi( - formatLogDetails(log, "test-org", customAttrs, ["user.id"]) + formatLogDetails(log, "test-org", customAttrs, ["user.id"]), ); expect(result).toContain("Custom Attributes"); @@ -501,7 +501,7 @@ describe("formatLogDetails", () => { test("shows no Custom Attributes section when all are filtered by extraFields", () => { const log = createDetailedTestLog(); const result = stripAnsi( - formatLogDetails(log, "test-org", customAttrs, ["nonexistent.field"]) + formatLogDetails(log, "test-org", customAttrs, ["nonexistent.field"]), ); expect(result).not.toContain("Custom Attributes"); @@ -512,7 +512,7 @@ describe("formatLogDetails", () => { "user.id": "u_99", } as DetailedSentryLog); const result = stripAnsi( - formatLogDetails(log, "test-org", undefined, ["user.id"]) + formatLogDetails(log, "test-org", undefined, ["user.id"]), ); expect(result).toContain("Custom Attributes"); @@ -527,7 +527,7 @@ describe("formatLogDetails", () => { ]; const log = createDetailedTestLog(); const result = stripAnsi( - formatLogDetails(log, "test-org", attrsWithArray) + formatLogDetails(log, "test-org", attrsWithArray), ); expect(result).toContain('["prod","web"]'); @@ -547,13 +547,13 @@ describe("formatLogDetails", () => { describe("getLogId", () => { test("returns sentry.item_id when present", () => { expect(getLogId({ "sentry.item_id": "abc123", timestamp: "" })).toBe( - "abc123" + "abc123", ); }); test("falls back to id when sentry.item_id is absent", () => { expect(getLogId({ id: "trace-log-id", timestamp: "" })).toBe( - "trace-log-id" + "trace-log-id", ); }); @@ -615,14 +615,14 @@ describe("formatLogTable", () => { test("includes severity levels", () => { const result = stripAnsi( - formatLogTable([createTestLog({ severity: "error" })]) + formatLogTable([createTestLog({ severity: "error" })]), ); expect(result).toContain("ERROR"); }); test("includes trace IDs when present", () => { const result = stripAnsi( - formatLogTable([createTestLog({ trace: "abcdef1234567890" })]) + formatLogTable([createTestLog({ trace: "abcdef1234567890" })]), ); expect(result).toContain("abcdef12"); }); diff --git a/packages/cli/test/lib/formatters/markdown.test.ts b/packages/cli/test/lib/formatters/markdown.test.ts index 6c6534ce1..0ece61fd8 100644 --- a/packages/cli/test/lib/formatters/markdown.test.ts +++ b/packages/cli/test/lib/formatters/markdown.test.ts @@ -27,7 +27,7 @@ import { /** Strip ANSI escape codes for content-only assertions */ function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars + // oxlint-disable-next-line no-control-regex -- ANSI codes use control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -40,7 +40,7 @@ function withEnv( > >, isTTY: boolean | undefined, - fn: () => void + fn: () => void, ): void { const savedEnv: Record = {}; const savedTTY = process.stdout.isTTY; @@ -87,7 +87,7 @@ describe("isPlainOutput", () => { true, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); @@ -97,7 +97,7 @@ describe("isPlainOutput", () => { true, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); @@ -113,7 +113,7 @@ describe("isPlainOutput", () => { false, () => { expect(isPlainOutput()).toBe(false); - } + }, ); }); @@ -123,7 +123,7 @@ describe("isPlainOutput", () => { false, () => { expect(isPlainOutput()).toBe(false); - } + }, ); }); @@ -159,7 +159,7 @@ describe("isPlainOutput", () => { true, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); @@ -177,7 +177,7 @@ describe("isPlainOutput", () => { false, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); @@ -199,7 +199,7 @@ describe("isPlainOutput", () => { true, () => { expect(isPlainOutput()).toBe(false); - } + }, ); }); @@ -213,7 +213,7 @@ describe("isPlainOutput", () => { false, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); @@ -227,7 +227,7 @@ describe("isPlainOutput", () => { true, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); @@ -241,7 +241,7 @@ describe("isPlainOutput", () => { true, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); }); @@ -257,7 +257,7 @@ describe("isPlainOutput", () => { false, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); @@ -271,7 +271,7 @@ describe("isPlainOutput", () => { true, () => { expect(isPlainOutput()).toBe(false); - } + }, ); }); @@ -285,7 +285,7 @@ describe("isPlainOutput", () => { undefined, () => { expect(isPlainOutput()).toBe(true); - } + }, ); }); }); @@ -586,7 +586,7 @@ describe("escapeMarkdownInline", () => { test("URLs with underscores render without backslashes", () => { withEnv({ SENTRY_PLAIN_OUTPUT: "0", NO_COLOR: undefined }, false, () => { const escaped = escapeMarkdownInline( - "https://spotlightjs.com/_astro/ui-core.js" + "https://spotlightjs.com/_astro/ui-core.js", ); const result = stripAnsi(renderInlineMarkdown(escaped)); expect(result).not.toContain("\\_"); diff --git a/packages/cli/test/lib/formatters/output.test.ts b/packages/cli/test/lib/formatters/output.test.ts index 200bd960a..6501af33c 100644 --- a/packages/cli/test/lib/formatters/output.test.ts +++ b/packages/cli/test/lib/formatters/output.test.ts @@ -19,14 +19,14 @@ function createTestWriter() { get output() { return chunks .map((c) => - typeof c === "string" ? c : Buffer.from(c).toString("latin1") + typeof c === "string" ? c : Buffer.from(c).toString("latin1"), ) .join(""); }, /** Concatenated raw bytes from all writes. */ get bytes() { const parts = chunks.map((c) => - typeof c === "string" ? Buffer.from(c, "utf8") : Buffer.from(c) + typeof c === "string" ? Buffer.from(c, "utf8") : Buffer.from(c), ); return Buffer.concat(parts); }, @@ -41,7 +41,7 @@ function render( w: ReturnType, data: unknown, config: OutputConfig, - ctx: { json: boolean; fields?: string[] } + ctx: { json: boolean; fields?: string[] }, ) { const renderer = resolveRenderer(config.human); renderCommandOutput(w, data, config, renderer, ctx); @@ -156,7 +156,7 @@ describe("renderCommandOutput", () => { w, { id: 1, name: "Alice", spanTreeLines: ["line1", "line2"] }, config, - { json: true } + { json: true }, ); const parsed = JSON.parse(w.output); expect(parsed).toEqual({ id: 1, name: "Alice" }); @@ -205,7 +205,7 @@ describe("renderCommandOutput", () => { { id: 2, name: "b" }, ], config, - { json: true } + { json: true }, ); const parsed = JSON.parse(w.output); expect(Array.isArray(parsed)).toBe(true); @@ -233,7 +233,7 @@ describe("renderCommandOutput", () => { w, { items: [{ id: 1, name: "Alice" }], hasMore: true, org: "test-org" }, config, - { json: true } + { json: true }, ); const parsed = JSON.parse(w.output); expect(parsed).toEqual({ @@ -275,7 +275,7 @@ describe("renderCommandOutput", () => { hasMore: false, }, config, - { json: true, fields: ["id", "name"] } + { json: true, fields: ["id", "name"] }, ); const parsed = JSON.parse(w.output); expect(parsed.data[0]).toEqual({ id: 1, name: "Alice" }); diff --git a/packages/cli/test/lib/formatters/pixel-canvas.test.ts b/packages/cli/test/lib/formatters/pixel-canvas.test.ts index 4ff92a40b..ab7dd667e 100644 --- a/packages/cli/test/lib/formatters/pixel-canvas.test.ts +++ b/packages/cli/test/lib/formatters/pixel-canvas.test.ts @@ -30,6 +30,7 @@ describe("drawPixelText", () => { expect(opaquePixelCount(image)).toBe(44); expect(image.data[(2 * image.width + 2) * 4 + 3]).toBe(255); + // oxlint-disable-next-line oxc/erasing-op -- This test intentionally exercises the zero-valued pixel operation. expect(image.data[(0 * image.width + 1) * 4 + 3]).toBe(0); }); diff --git a/packages/cli/test/lib/formatters/replay.test.ts b/packages/cli/test/lib/formatters/replay.test.ts index 31d4ebaa6..ea571dc4e 100644 --- a/packages/cli/test/lib/formatters/replay.test.ts +++ b/packages/cli/test/lib/formatters/replay.test.ts @@ -44,7 +44,7 @@ describe("extractReplayActivityEvents", () => { }, ], ], - 10 + 10, ); expect(events).toEqual([ @@ -88,7 +88,7 @@ describe("extractReplayActivityEvents", () => { }, ], ], - 10 + 10, ); expect(events).toEqual([ diff --git a/packages/cli/test/lib/formatters/seer.test.ts b/packages/cli/test/lib/formatters/seer.test.ts index 588c70770..54110414e 100644 --- a/packages/cli/test/lib/formatters/seer.test.ts +++ b/packages/cli/test/lib/formatters/seer.test.ts @@ -27,7 +27,7 @@ import type { /** Strip ANSI escape codes */ function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI control chars + // oxlint-disable-next-line no-control-regex -- ANSI control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -335,7 +335,7 @@ describe("issue explain batch formatting", () => { jsonTransformIssueExplain({ results: data.results.slice(0, 1), requestedCount: 1, - }) + }), ).toEqual([expect.objectContaining({ description: "First cause" })]); }); @@ -442,7 +442,7 @@ describe("SeerError formatting", () => { const formatted = error.format(); expect(formatted).toContain("Seer requires a paid plan"); expect(formatted).toContain( - "https://my-org.sentry.io/settings/billing/overview/?product=seer" + "https://my-org.sentry.io/settings/billing/overview/?product=seer", ); }); @@ -451,7 +451,7 @@ describe("SeerError formatting", () => { const formatted = error.format(); expect(formatted).toContain("AI features are disabled"); expect(formatted).toContain( - "https://my-org.sentry.io/settings/#hideAiFeatures" + "https://my-org.sentry.io/settings/#hideAiFeatures", ); }); @@ -468,7 +468,7 @@ describe("SeerError formatting", () => { describe("formatSolution", () => { function makeSolution( - overrides: Partial = {} + overrides: Partial = {}, ): SolutionArtifact { return { key: "solution", @@ -508,8 +508,8 @@ describe("formatSolution", () => { { title: "Step One", description: "Do the first thing." }, { title: "Step Two", description: "Do the second thing." }, ], - }) - ) + }), + ), ); expect(result).toContain("Step One"); expect(result).toContain("Step Two"); @@ -534,8 +534,8 @@ describe("formatSolution", () => { description: "Change `foo()` to `bar()`\nThen redeploy.", }, ], - }) - ) + }), + ), ); expect(result).toContain("Fix code"); expect(result).toContain("foo()"); diff --git a/packages/cli/test/lib/formatters/semantic-display.test.ts b/packages/cli/test/lib/formatters/semantic-display.test.ts index 599252614..e88bceb2e 100644 --- a/packages/cli/test/lib/formatters/semantic-display.test.ts +++ b/packages/cli/test/lib/formatters/semantic-display.test.ts @@ -203,7 +203,7 @@ describe("formatSemanticSpanDisplay", () => { }; const result = formatSemanticSpanDisplay(attrs, "fallback"); expect(result.label).toBe( - "SELECT * FROM users WHERE id = ? AND name = ?" + "SELECT * FROM users WHERE id = ? AND name = ?", ); }); }); @@ -486,7 +486,7 @@ describe("inferSemanticOp", () => { test("returns process for process attributes", () => { expect(inferSemanticOp({ "process.executable.name": "git" })).toBe( - "process" + "process", ); }); @@ -497,7 +497,7 @@ describe("inferSemanticOp", () => { "aws.s3.bucket": "my-bucket", "rpc.method": "PutObject", "rpc.service": "S3", - }) + }), ).toBe("s3"); }); diff --git a/packages/cli/test/lib/formatters/sixel-dashboard.test.ts b/packages/cli/test/lib/formatters/sixel-dashboard.test.ts index 99e23a63c..9976ada9c 100644 --- a/packages/cli/test/lib/formatters/sixel-dashboard.test.ts +++ b/packages/cli/test/lib/formatters/sixel-dashboard.test.ts @@ -12,23 +12,23 @@ describe("formatTimestamp", () => { test("uses clock time for periods shorter than two days", () => { expect(formatTimestamp(timestamp, 1)).toBe( - `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}` + `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`, ); }); test("formats the Unix epoch instead of treating it as missing", () => { const epoch = new Date(0); expect(formatTimestamp(0, 1)).toBe( - `${String(epoch.getHours()).padStart(2, "0")}:${String(epoch.getMinutes()).padStart(2, "0")}` + `${String(epoch.getHours()).padStart(2, "0")}:${String(epoch.getMinutes()).padStart(2, "0")}`, ); expect(formatTimestamp(0, 7)).toBe( - `${String(epoch.getMonth() + 1).padStart(2, "0")}/${String(epoch.getDate()).padStart(2, "0")}` + `${String(epoch.getMonth() + 1).padStart(2, "0")}/${String(epoch.getDate()).padStart(2, "0")}`, ); }); test("uses calendar dates for multi-day periods", () => { expect(formatTimestamp(timestamp, 7)).toBe( - `${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}` + `${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}`, ); expect(formatTimestamp(timestamp, 31)).toBe(`Jan ${date.getDate()}`); }); @@ -70,7 +70,7 @@ function renderWidget(displayType: string): { captured = image; return "rendered"; }, - } + }, ); if (!captured) { @@ -81,7 +81,7 @@ function renderWidget(displayType: string): { function countPixels( image: DecodedImage, - color: [number, number, number] + color: [number, number, number], ): number { let count = 0; for (let offset = 0; offset < image.data.length; offset += 4) { @@ -111,10 +111,10 @@ describe("dashboard chart rendering", () => { test("uses compact aggregate names in the graphics legend", () => { expect(formatLegendLabel("p50(span.duration)")).toBe("p50 span.duration"); expect(formatLegendLabel("p95(value,web.vital,distribution,none)")).toBe( - "p95 web.vital" + "p95 web.vital", ); expect( - formatLegendLabel("p95(value,backend.duration,distribution,none)") + formatLegendLabel("p95(value,backend.duration,distribution,none)"), ).toBe("p95 backend.duration"); expect(formatLegendLabel("GET /api/projects")).toBe("GET /api/projects"); }); @@ -141,7 +141,7 @@ describe("dashboard chart rendering", () => { captured = image; return "rendered"; }, - } + }, ); if (!captured) { @@ -155,7 +155,7 @@ describe("dashboard chart rendering", () => { y: 20, width: 70, height: 20, - }) + }), ).toBe(0); expect( countPixelsInRegion(captured, [128, 128, 128], { @@ -163,7 +163,7 @@ describe("dashboard chart rendering", () => { y: 160, width: 80, height: 40, - }) + }), ).toBeGreaterThan(0); }); }); @@ -171,7 +171,7 @@ describe("dashboard chart rendering", () => { function countPixelsInRegion( image: DecodedImage, color: [number, number, number], - region: { x: number; y: number; width: number; height: number } + region: { x: number; y: number; width: number; height: number }, ): number { let count = 0; const endX = region.x + region.width; diff --git a/packages/cli/test/lib/formatters/sixel-timeseries.test.ts b/packages/cli/test/lib/formatters/sixel-timeseries.test.ts index a68fec308..bdc54dc55 100644 --- a/packages/cli/test/lib/formatters/sixel-timeseries.test.ts +++ b/packages/cli/test/lib/formatters/sixel-timeseries.test.ts @@ -9,7 +9,7 @@ import type { TimeseriesResult } from "../../../src/types/dashboard.js"; const ESC = "\x1b"; function makeTimeseries( - overrides: Partial = {} + overrides: Partial = {}, ): TimeseriesResult { return { type: "timeseries", diff --git a/packages/cli/test/lib/formatters/span-tree.test.ts b/packages/cli/test/lib/formatters/span-tree.test.ts index 6c8854d07..80ef52640 100644 --- a/packages/cli/test/lib/formatters/span-tree.test.ts +++ b/packages/cli/test/lib/formatters/span-tree.test.ts @@ -15,7 +15,7 @@ import type { TraceSpan } from "../../../src/types/index.js"; * Allows tests to verify text content without color interference. */ function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars + // oxlint-disable-next-line no-control-regex -- ANSI codes use control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -32,7 +32,7 @@ function makeTraceSpan( op: string, description: string, children: TraceSpan[] = [], - overrides?: Partial + overrides?: Partial, ): TraceSpan { return { span_id: `span-${op}`, @@ -241,7 +241,7 @@ describe("formatSimpleSpanTree", () => { timestamp: 1000.05, }), ], - { start_timestamp: 1000.0, timestamp: 1000.5 } + { start_timestamp: 1000.0, timestamp: 1000.5 }, ), ]; const result = formatSimpleSpanTree("trace-123", spans); @@ -307,7 +307,7 @@ describe("formatSimpleSpanTree", () => { const result = formatSimpleSpanTree( "trace-123", spans, - Number.POSITIVE_INFINITY + Number.POSITIVE_INFINITY, ); const output = stripAnsi(result.join("\n")); diff --git a/packages/cli/test/lib/formatters/sparkline.property.test.ts b/packages/cli/test/lib/formatters/sparkline.property.test.ts index 880f00725..957e372b8 100644 --- a/packages/cli/test/lib/formatters/sparkline.property.test.ts +++ b/packages/cli/test/lib/formatters/sparkline.property.test.ts @@ -29,7 +29,7 @@ describe("property: sparkline", () => { const result = sparkline(values, width); expect(result.length).toBeLessThanOrEqual(width); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -39,7 +39,7 @@ describe("property: sparkline", () => { const result = sparkline(values, width); expect(result.length).toBe(Math.min(values.length, width)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -51,20 +51,21 @@ describe("property: sparkline", () => { expect(VALID_CHARS).toContain(char); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); test("all-zero input produces all scan-line characters", () => { fcAssert( property(integer({ min: 1, max: 50 }), widthArb, (len, width) => { + // oxlint-disable-next-line unicorn/no-new-array -- This explicitly allocates a fixed-length array. const values = new Array(len).fill(0); const result = sparkline(values, width); for (const char of result) { expect(char).toBe("⎽"); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -79,7 +80,7 @@ describe("property: sparkline", () => { const result = sparkline([v]); expect(result).toBe("█"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -95,7 +96,7 @@ describe("property: sparkline", () => { const maxIdx = values.indexOf(max); expect(result[maxIdx]).toBe("█"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -111,7 +112,7 @@ describe("property: sparkline", () => { expect(curr).toBeGreaterThanOrEqual(prev); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -122,7 +123,7 @@ describe("property: sparkline", () => { const b = sparkline(values, width); expect(a).toBe(b); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/formatters/sql.property.test.ts b/packages/cli/test/lib/formatters/sql.property.test.ts index d449cfdbe..1bb86ac17 100644 --- a/packages/cli/test/lib/formatters/sql.property.test.ts +++ b/packages/cli/test/lib/formatters/sql.property.test.ts @@ -72,7 +72,7 @@ const sqlKeywordArb = constantFrom( "NULL", "AS", "ON", - "RETURNING" + "RETURNING", ); /** Table/column identifiers */ @@ -85,7 +85,7 @@ const parameterArb = constantFrom("%s", "$1", "$2", "$3", "?", "%d"); const sqlTokenArb = oneof(sqlKeywordArb, identifierArb, parameterArb); const sqlStringArb = array(sqlTokenArb, { minLength: 1, maxLength: 12 }).map( - (tokens) => tokens.join(" ") + (tokens) => tokens.join(" "), ); /** Span op values */ @@ -95,14 +95,14 @@ const dbOpArb = constantFrom( "db.sql.query", "db.sql.execute", "db.redis", - "db.mongodb" + "db.mongodb", ); const nonDbOpArb = constantFrom( "http.client", "http.server", "cache.get", "browser", - "queue.process" + "queue.process", ); describe("property: isDbSpanOp", () => { @@ -111,7 +111,7 @@ describe("property: isDbSpanOp", () => { property(dbOpArb, (op) => { expect(isDbSpanOp(op)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -120,7 +120,7 @@ describe("property: isDbSpanOp", () => { property(nonDbOpArb, (op) => { expect(isDbSpanOp(op)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -135,7 +135,7 @@ describe("property: colorizeSql", () => { // Case-insensitive: @sentry/sqlish uppercases SQL keywords (e.g. "by" → "BY") expect(stripped.toLowerCase()).toBe(sql.toLowerCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -145,7 +145,7 @@ describe("property: colorizeSql", () => { property(sqlStringArb, (sql) => { expect(colorizeSql(sql)).toBe(sql); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -155,7 +155,7 @@ describe("property: colorizeSql", () => { property(sqlStringArb, (sql) => { expect(colorizeSql(sql)).toBe(colorizeSql(sql)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -167,7 +167,7 @@ describe("property: colorizeSql", () => { const twice = stripAnsi(colorizeSql(stripAnsi(colorizeSql(sql)))); expect(once).toBe(twice); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -179,7 +179,7 @@ describe("property: colorizeSql", () => { expect(colorizeSql(sql).length).toBeGreaterThan(0); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/formatters/table.test.ts b/packages/cli/test/lib/formatters/table.test.ts index 3510a06e1..f1afbaaad 100644 --- a/packages/cli/test/lib/formatters/table.test.ts +++ b/packages/cli/test/lib/formatters/table.test.ts @@ -19,7 +19,7 @@ const columns: Column[] = [ /** Strip ANSI escape codes */ function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI control chars + // oxlint-disable-next-line no-control-regex -- ANSI control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -134,7 +134,7 @@ describe("writeTable (TTY mode)", () => { writeTable( { write }, [{ name: "alice", count: 42, status: "active" }], - columns + columns, ); const output = stripAnsi(write.mock.calls.map((c) => c[0]).join("")); expect(output).toContain("NAME"); @@ -155,7 +155,7 @@ describe("writeTable (TTY mode)", () => { { name: "c", count: 3, status: "z" }, ], columns, - { rowSeparator: true } + { rowSeparator: true }, ); const raw = write.mock.calls.map((c) => c[0]).join(""); const output = stripAnsi(raw); @@ -177,7 +177,7 @@ describe("writeTable (TTY mode)", () => { { name: "b", count: 2, status: "y" }, ], columns, - { rowSeparator: color } + { rowSeparator: color }, ); const raw = write.mock.calls.map((c) => c[0]).join(""); // The color escape should appear in the output @@ -248,7 +248,7 @@ describe("writeTable (plain mode)", () => { writeTable( { write }, [{ name: "alice", count: 1, status: "ok" }], - columns + columns, ); const output = write.mock.calls.map((c) => c[0]).join(""); // Box-drawing table format (not raw markdown pipes) diff --git a/packages/cli/test/lib/formatters/text-table.test.ts b/packages/cli/test/lib/formatters/text-table.test.ts index 465e8990c..55cb6095c 100644 --- a/packages/cli/test/lib/formatters/text-table.test.ts +++ b/packages/cli/test/lib/formatters/text-table.test.ts @@ -32,7 +32,7 @@ describe("renderTextTable", () => { [ ["1", "Alice", "Admin"], ["2", "Bob", "User"], - ] + ], ); expect(out).toContain("ID"); expect(out).toContain("Name"); @@ -118,12 +118,12 @@ describe("renderTextTable", () => { const withHide = renderTextTable( ["LongHeader1", "LongHeader2"], [["a", "b"]], - { hideHeaders: true, maxWidth: 80 } + { hideHeaders: true, maxWidth: 80 }, ); const withoutHide = renderTextTable( ["LongHeader1", "LongHeader2"], [["a", "b"]], - { hideHeaders: false, maxWidth: 80 } + { hideHeaders: false, maxWidth: 80 }, ); // Both should produce the same column widths (same top border line) const topBorderHide = withHide.split("\n")[0]; @@ -199,7 +199,7 @@ describe("renderTextTable", () => { const out = renderTextTable( ["Short", "This is a very long header that needs shrinking"], [["a", "b"]], - { maxWidth: 30, columnFitter: "proportional" } + { maxWidth: 30, columnFitter: "proportional" }, ); // Content is present (may be wrapped) expect(out.length).toBeGreaterThan(0); @@ -211,7 +211,7 @@ describe("renderTextTable", () => { const out = renderTextTable( ["Short", "This is a very long header that needs shrinking"], [["a", "b"]], - { maxWidth: 30, columnFitter: "balanced" } + { maxWidth: 30, columnFitter: "balanced" }, ); // Content is present (may be wrapped) expect(out.length).toBeGreaterThan(0); @@ -223,7 +223,7 @@ describe("renderTextTable", () => { const out = renderTextTable( ["Header One", "Header Two", "Header Three"], [["data1", "data2", "data3"]], - { maxWidth: 15 } + { maxWidth: 15 }, ); expect(out.length).toBeGreaterThan(0); expect(out.endsWith("\n")).toBe(true); @@ -251,7 +251,7 @@ describe("renderTextTable", () => { const out = renderTextTable( ["Name"], [["This is a very long cell value that should wrap"]], - { maxWidth: 20 } + { maxWidth: 20 }, ); const dataLines = out .split("\n") @@ -320,7 +320,7 @@ describe("truncate option", () => { const out = renderTextTable( ["Name"], [["This is a very long cell value that should be truncated"]], - { maxWidth: 20, truncate: true } + { maxWidth: 20, truncate: true }, ); const dataLines = out .split("\n") @@ -329,7 +329,7 @@ describe("truncate option", () => { l.includes("\u2502") && !l.includes("Name") && !l.includes("\u2500") && - l.trim().length > 2 + l.trim().length > 2, ); // Should be exactly 1 data line (not wrapped to multiple) expect(dataLines.length).toBe(1); @@ -350,7 +350,7 @@ describe("truncate option", () => { const out = renderTextTable( ["Very Long Header Name"], [["This is an even longer cell value that should be truncated"]], - { maxWidth: 30, truncate: true } + { maxWidth: 30, truncate: true }, ); // Header should not have ellipsis (only data rows truncate) const headerLine = out @@ -385,7 +385,7 @@ describe("truncate option", () => { expect(dataLine).toBeDefined(); // The ellipsis should appear BEFORE the ANSI reset, not after it. // i.e., the pattern should be: … not … - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape detection + // oxlint-disable-next-line no-control-regex -- ANSI escape detection expect(dataLine).toMatch(/\u2026\x1b\[/); }); }); @@ -395,7 +395,7 @@ describe("minWidths option", () => { const out = renderTextTable( ["Short", "Long Column Header"], [["data", "other data"]], - { maxWidth: 25, minWidths: [10, 0] } + { maxWidth: 25, minWidths: [10, 0] }, ); // First column should maintain at least 10-char content width // (10 + 2 padding = 12 total column width) @@ -409,7 +409,7 @@ describe("minWidths option", () => { const out = renderTextTable( ["ID", "TITLE"], [["SPOTLIGHT-WEB-28", "Very long error message that gets truncated"]], - { maxWidth: 50, minWidths: [20, 0], truncate: true } + { maxWidth: 50, minWidths: [20, 0], truncate: true }, ); expect(out).toContain("SPOTLIGHT-WEB-28"); // TITLE should be truncated, not SHORT ID @@ -422,7 +422,7 @@ describe("shrinkable option", () => { const out = renderTextTable( ["FIXED", "ELASTIC"], [["SPOTLIGHT-WEB-28", "A very long title that should absorb all shrink"]], - { maxWidth: 50, shrinkable: [false, true] } + { maxWidth: 50, shrinkable: [false, true] }, ); // The FIXED column should show the full value without wrapping expect(out).toContain("SPOTLIGHT-WEB-28"); @@ -435,7 +435,7 @@ describe("shrinkable option", () => { const out = renderTextTable( ["FIXED", "ELASTIC"], [["keep-me", "shrink this very long text value please"]], - { maxWidth: 30, shrinkable: [false, true] } + { maxWidth: 30, shrinkable: [false, true] }, ); // FIXED column should be intact expect(out).toContain("keep-me"); diff --git a/packages/cli/test/lib/formatters/trace.property.test.ts b/packages/cli/test/lib/formatters/trace.property.test.ts index 585f39cb7..6cd773908 100644 --- a/packages/cli/test/lib/formatters/trace.property.test.ts +++ b/packages/cli/test/lib/formatters/trace.property.test.ts @@ -38,7 +38,7 @@ const invalidDurationArb = constantFrom( Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, -1, - -1000 + -1000, ); /** 32-char hex string for trace/event IDs */ @@ -54,7 +54,7 @@ const transactionNameArb = stringMatching(/^[A-Z]{3,6} \/[a-z/]{1,40}$/); const isoTimestampArb = constantFrom( "2025-01-15T10:30:00Z", "2024-12-01T00:00:00Z", - "2025-06-15T23:59:59Z" + "2025-06-15T23:59:59Z", ); /** Realistic Unix timestamp in seconds (2020-2030) */ @@ -82,13 +82,13 @@ function makeSpanArb(): import("fast-check").Arbitrary { constantFrom("http.server", "db.query", "cache.get", "http.client"), { nil: undefined, - } + }, ), description: option( constantFrom("GET /api", "SELECT *", "Redis GET", null), { nil: undefined, - } + }, ), start_timestamp: unixTimestampArb, timestamp: unixTimestampArb, @@ -96,7 +96,7 @@ function makeSpanArb(): import("fast-check").Arbitrary { transaction: option(transactionNameArb, { nil: undefined }), "transaction.op": option( constantFrom("http.server", "browser", "celery_task"), - { nil: undefined } + { nil: undefined }, ), }) as unknown as import("fast-check").Arbitrary; } @@ -112,7 +112,7 @@ describe("property: formatTraceDuration", () => { expect(result.length).toBeGreaterThan(0); expect(result).not.toBe("—"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -121,7 +121,7 @@ describe("property: formatTraceDuration", () => { property(invalidDurationArb, (ms) => { expect(formatTraceDuration(ms)).toBe("—"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -130,7 +130,7 @@ describe("property: formatTraceDuration", () => { property(positiveDurationArb, (ms) => { expect(formatTraceDuration(ms)).toBe(formatTraceDuration(ms)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -147,7 +147,7 @@ describe("property: formatTraceDuration", () => { expect(result).toContain("s"); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -169,7 +169,7 @@ describe("property: formatTraceRow", () => { // Trace ID is sliced to 32 chars max expect(row).toContain(item.trace.slice(0, 32)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -178,7 +178,7 @@ describe("property: formatTraceRow", () => { property(transactionItemArb, (item) => { expect(formatTraceRow(item).endsWith("\n")).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -187,7 +187,7 @@ describe("property: formatTraceRow", () => { property(transactionItemArb, (item) => { expect(formatTraceRow(item)).toBe(formatTraceRow(item)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -199,7 +199,7 @@ describe("property: computeTraceSummary", () => { const summary = computeTraceSummary(traceId, spans); expect(summary.spanCount).toBe(spans.length); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -209,7 +209,7 @@ describe("property: computeTraceSummary", () => { const summary = computeTraceSummary(traceId, spans); expect(summary.traceId).toBe(traceId); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -221,7 +221,7 @@ describe("property: computeTraceSummary", () => { expect(summary.duration).toBeGreaterThanOrEqual(0); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -232,7 +232,7 @@ describe("property: computeTraceSummary", () => { const uniqueProjects = new Set(summary.projects); expect(uniqueProjects.size).toBe(summary.projects.length); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -243,7 +243,7 @@ describe("property: computeTraceSummary", () => { const b = computeTraceSummary(traceId, spans); expect(a).toEqual(b); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -254,14 +254,14 @@ describe("property: computeTraceSummary", () => { expect(summary.spanCount).toBe(0); expect(summary.projects).toEqual([]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); /** Strip ANSI escape codes */ function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI control chars + // oxlint-disable-next-line no-control-regex -- ANSI control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -273,7 +273,7 @@ describe("property: formatTraceSummary", () => { const output = stripAnsi(formatTraceSummary(summary)); expect(output).toContain(traceId); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -284,7 +284,7 @@ describe("property: formatTraceSummary", () => { const output = stripAnsi(formatTraceSummary(summary)); expect(output).toContain("Duration"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -295,7 +295,7 @@ describe("property: formatTraceSummary", () => { const output = stripAnsi(formatTraceSummary(summary)); expect(output).toContain("Spans"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -307,7 +307,7 @@ describe("property: formatTraceSummary", () => { expect(typeof result).toBe("string"); expect(result.length).toBeGreaterThan(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -319,7 +319,7 @@ describe("property: formatTraceSummary", () => { const b = formatTraceSummary(summary); expect(a).toEqual(b); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/formatters/trace.test.ts b/packages/cli/test/lib/formatters/trace.test.ts index 5e1296bc5..19265e8cb 100644 --- a/packages/cli/test/lib/formatters/trace.test.ts +++ b/packages/cli/test/lib/formatters/trace.test.ts @@ -33,7 +33,7 @@ import type { * Strip ANSI escape codes for content assertions. */ function stripAnsi(str: string): string { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI codes use control chars + // oxlint-disable-next-line no-control-regex -- ANSI codes use control chars return str.replace(/\x1b\[[0-9;]*m/g, ""); } @@ -85,7 +85,7 @@ function makeSpan(overrides: Partial = {}): TraceSpan { * Create a minimal TransactionListItem for testing. */ function makeTransaction( - overrides: Partial = {} + overrides: Partial = {}, ): TransactionListItem { return { trace: "a".repeat(32), @@ -154,7 +154,7 @@ describe("formatTraceRow (rendered mode)", () => { test("includes transaction name", () => { const row = formatTraceRow( - makeTransaction({ transaction: "POST /api/data" }) + makeTransaction({ transaction: "POST /api/data" }), ); expect(row).toContain("POST /api/data"); }); @@ -200,7 +200,7 @@ describe("formatTraceRow (plain mode)", () => { test("includes transaction name", () => { const row = formatTraceRow( - makeTransaction({ transaction: "POST /api/data" }) + makeTransaction({ transaction: "POST /api/data" }), ); expect(row).toContain("POST /api/data"); }); @@ -412,21 +412,21 @@ describe("formatTraceTable", () => { test("includes trace IDs", () => { const traceId = "a".repeat(32); const result = stripAnsi( - formatTraceTable([makeTransaction({ trace: traceId })]) + formatTraceTable([makeTransaction({ trace: traceId })]), ); expect(result).toContain(traceId); }); test("includes formatted durations", () => { const result = stripAnsi( - formatTraceTable([makeTransaction({ "span.duration": 1500 })]) + formatTraceTable([makeTransaction({ "span.duration": 1500 })]), ); expect(result).toContain("1.50s"); }); test("shows 'unknown' for empty transaction", () => { const result = stripAnsi( - formatTraceTable([makeTransaction({ transaction: "" })]) + formatTraceTable([makeTransaction({ transaction: "" })]), ); expect(result).toContain("unknown"); }); @@ -451,19 +451,19 @@ describe("translateSpanQuery", () => { test("mixed shorthand and bare words", () => { expect(translateSpanQuery("op:http GET duration:>50ms")).toBe( - "span.op:http GET span.duration:>50ms" + "span.op:http GET span.duration:>50ms", ); }); test("native keys pass through unchanged", () => { expect(translateSpanQuery("description:fetch project:backend")).toBe( - "description:fetch project:backend" + "description:fetch project:backend", ); }); test("transaction: passes through unchanged", () => { expect(translateSpanQuery("transaction:checkout")).toBe( - "transaction:checkout" + "transaction:checkout", ); }); @@ -478,14 +478,14 @@ describe("translateSpanQuery", () => { test("quoted values are preserved", () => { expect(translateSpanQuery('description:"GET /api"')).toBe( - 'description:"GET /api"' + 'description:"GET /api"', ); }); test("negated shorthand keys are translated correctly", () => { expect(translateSpanQuery("!op:db")).toBe("!span.op:db"); expect(translateSpanQuery("!duration:>100ms")).toBe( - "!span.duration:>100ms" + "!span.duration:>100ms", ); }); diff --git a/packages/cli/test/lib/fuzzy.test.ts b/packages/cli/test/lib/fuzzy.test.ts index 97bb5d5d0..2f0a43adf 100644 --- a/packages/cli/test/lib/fuzzy.test.ts +++ b/packages/cli/test/lib/fuzzy.test.ts @@ -20,7 +20,7 @@ describe("property: levenshtein", () => { property(shortStringArb, (s) => { expect(levenshtein(s, s)).toBe(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -29,7 +29,7 @@ describe("property: levenshtein", () => { property(shortStringArb, shortStringArb, (a, b) => { expect(levenshtein(a, b)).toBe(levenshtein(b, a)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -39,7 +39,7 @@ describe("property: levenshtein", () => { const dist = levenshtein(a, b); expect(dist).toBeLessThanOrEqual(Math.max(a.length, b.length)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -48,7 +48,7 @@ describe("property: levenshtein", () => { property(shortStringArb, shortStringArb, (a, b) => { expect(levenshtein(a, b)).toBeGreaterThanOrEqual(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -58,7 +58,7 @@ describe("property: levenshtein", () => { expect(levenshtein(s, "")).toBe(s.length); expect(levenshtein("", s)).toBe(s.length); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -70,7 +70,7 @@ describe("property: levenshtein", () => { const dbc = levenshtein(b, c); expect(dac).toBeLessThanOrEqual(dab + dbc); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -147,7 +147,7 @@ describe("fuzzyMatch: unit tests", () => { const items = ["prefix-match", "contains-prefix", "no-match"]; const result = fuzzyMatch("prefix", items); expect(result.indexOf("prefix-match")).toBeLessThan( - result.indexOf("contains-prefix") + result.indexOf("contains-prefix"), ); }); }); @@ -165,7 +165,7 @@ describe("property: fuzzyMatch", () => { const result = fuzzyMatch(partial, candidates); expect(result).toContain(partial); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -179,7 +179,7 @@ describe("property: fuzzyMatch", () => { const result = fuzzyMatch("", candidates); expect(result.length).toBe(candidates.length); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -195,7 +195,7 @@ describe("property: fuzzyMatch", () => { expect(candidates).toContain(r); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/ghcr.test.ts b/packages/cli/test/lib/ghcr.test.ts index a55612b5a..533e4c6ee 100644 --- a/packages/cli/test/lib/ghcr.test.ts +++ b/packages/cli/test/lib/ghcr.test.ts @@ -28,7 +28,7 @@ let originalFetch: typeof globalThis.fetch; /** Helper to mock fetch without TypeScript errors about missing Bun-specific properties */ function mockFetch( - fn: (url: string | URL | Request, init?: RequestInit) => Promise + fn: (url: string | URL | Request, init?: RequestInit) => Promise, ): void { globalThis.fetch = fn as typeof globalThis.fetch; } @@ -81,7 +81,7 @@ describe("getAnonymousToken", () => { test("returns token from successful response", async () => { mockFetch(async (url) => { expect(String(url)).toContain( - `https://ghcr.io/token?scope=repository:${GHCR_REPO}:pull` + `https://ghcr.io/token?scope=repository:${GHCR_REPO}:pull`, ); return new Response(JSON.stringify({ token: "test-token-abc" }), { status: 200, @@ -102,7 +102,7 @@ describe("getAnonymousToken", () => { }); await expect(getAnonymousToken(UPGRADE_SOURCES[0])).resolves.toBe( - "toolkit-token" + "toolkit-token", ); }); @@ -111,7 +111,7 @@ describe("getAnonymousToken", () => { await expect(getAnonymousToken()).rejects.toThrow(UpgradeError); await expect(getAnonymousToken()).rejects.toThrow( - "GHCR token exchange failed: HTTP 401" + "GHCR token exchange failed: HTTP 401", ); }); @@ -122,7 +122,7 @@ describe("getAnonymousToken", () => { await expect(getAnonymousToken()).rejects.toThrow(UpgradeError); await expect(getAnonymousToken()).rejects.toThrow( - "Failed to connect to GHCR: fetch failed" + "Failed to connect to GHCR: fetch failed", ); }); @@ -135,7 +135,7 @@ describe("getAnonymousToken", () => { init?.signal?.addEventListener( "abort", () => reject(new DOMException("aborted", "AbortError")), - { once: true } + { once: true }, ); }); }); @@ -157,7 +157,7 @@ describe("getAnonymousToken", () => { }); await expect(getAnonymousToken(undefined, controller.signal)).rejects.toBe( - "cancelled" + "cancelled", ); expect(requests).toBe(1); }); @@ -168,12 +168,12 @@ describe("getAnonymousToken", () => { new Response(JSON.stringify({}), { status: 200, headers: { "Content-Type": "application/json" }, - }) + }), ); await expect(getAnonymousToken()).rejects.toThrow(UpgradeError); await expect(getAnonymousToken()).rejects.toThrow( - "GHCR token exchange returned no token" + "GHCR token exchange returned no token", ); }); @@ -181,7 +181,7 @@ describe("getAnonymousToken", () => { mockFetch(async () => Response.json({ token: {} })); await expect(getAnonymousToken()).rejects.toThrow( - "GHCR token exchange returned no token" + "GHCR token exchange returned no token", ); }); @@ -189,7 +189,7 @@ describe("getAnonymousToken", () => { mockFetch(async () => Response.json({ token })); await expect(getAnonymousToken()).rejects.toThrow( - "GHCR token exchange returned no token" + "GHCR token exchange returned no token", ); }); @@ -206,7 +206,7 @@ describe("getAnonymousToken", () => { }); await expect(getAnonymousToken(undefined, controller.signal)).rejects.toBe( - reason + reason, ); }); }); @@ -219,7 +219,7 @@ describe("fetchNightlyManifest", () => { mockFetch(async (url, init) => { expect(String(url)).toContain(`/v2/${GHCR_REPO}/manifests/${GHCR_TAG}`); capturedHeaders = Object.fromEntries( - new Headers(init?.headers as HeadersInit).entries() + new Headers(init?.headers as HeadersInit).entries(), ); return new Response(JSON.stringify(manifest), { status: 200, @@ -233,7 +233,7 @@ describe("fetchNightlyManifest", () => { expect(result).toEqual(manifest); expect(capturedHeaders.authorization).toBe("Bearer my-token"); expect(capturedHeaders.accept).toBe( - "application/vnd.oci.image.manifest.v1+json" + "application/vnd.oci.image.manifest.v1+json", ); }); @@ -245,7 +245,7 @@ describe("fetchNightlyManifest", () => { }); await expect( - fetchNightlyManifest("token", undefined, UPGRADE_SOURCES[0]) + fetchNightlyManifest("token", undefined, UPGRADE_SOURCES[0]), ).resolves.toEqual(manifest); }); @@ -254,7 +254,7 @@ describe("fetchNightlyManifest", () => { await expect(fetchNightlyManifest("token")).rejects.toThrow(UpgradeError); await expect(fetchNightlyManifest("token")).rejects.toThrow( - 'Failed to fetch manifest for tag "nightly": HTTP 404' + 'Failed to fetch manifest for tag "nightly": HTTP 404', ); }); @@ -265,7 +265,7 @@ describe("fetchNightlyManifest", () => { await expect(fetchNightlyManifest("token")).rejects.toThrow(UpgradeError); await expect(fetchNightlyManifest("token")).rejects.toThrow( - 'Failed to fetch manifest for tag "nightly": fetch failed' + 'Failed to fetch manifest for tag "nightly": fetch failed', ); }); }); @@ -280,7 +280,7 @@ describe("getNightlyVersion", () => { const manifest = makeManifest({ annotations: {} }); expect(() => getNightlyVersion(manifest)).toThrow(UpgradeError); expect(() => getNightlyVersion(manifest)).toThrow( - "Nightly manifest has no version annotation" + "Nightly manifest has no version annotation", ); }); @@ -289,17 +289,16 @@ describe("getNightlyVersion", () => { expect(() => getNightlyVersion(manifest)).toThrow(UpgradeError); }); - test.each([ - "not-semver", - "1.2.3", - "1.2.3-dev.foo", - ])("rejects invalid nightly version annotation %s", (version) => { - const manifest = makeManifest({ annotations: { version } }); + test.each(["not-semver", "1.2.3", "1.2.3-dev.foo"])( + "rejects invalid nightly version annotation %s", + (version) => { + const manifest = makeManifest({ annotations: { version } }); - expect(() => getNightlyVersion(manifest)).toThrow( - "Nightly manifest has invalid version annotation" - ); - }); + expect(() => getNightlyVersion(manifest)).toThrow( + "Nightly manifest has invalid version annotation", + ); + }, + ); }); describe("findLayerByFilename", () => { @@ -318,10 +317,10 @@ describe("findLayerByFilename", () => { test("throws UpgradeError when filename not found", () => { const manifest = makeManifest(); expect(() => - findLayerByFilename(manifest, "sentry-freebsd-x64.gz") + findLayerByFilename(manifest, "sentry-freebsd-x64.gz"), ).toThrow(UpgradeError); expect(() => - findLayerByFilename(manifest, "sentry-freebsd-x64.gz") + findLayerByFilename(manifest, "sentry-freebsd-x64.gz"), ).toThrow("No nightly build found for sentry-freebsd-x64.gz"); }); @@ -337,7 +336,7 @@ describe("findLayerByFilename", () => { ], }); expect(() => findLayerByFilename(manifest, "sentry-linux-x64.gz")).toThrow( - UpgradeError + UpgradeError, ); }); }); @@ -364,19 +363,19 @@ describe("downloadNightlyBlob", () => { // First request: return 307 redirect to Azure return Response.redirect( "https://blob.storage.azure.com/signed?token=xyz", - 307 + 307, ); } // Second request: the actual download (no auth header expected) secondRequestHeaders = Object.fromEntries( - new Headers(init?.headers as HeadersInit).entries() + new Headers(init?.headers as HeadersInit).entries(), ); return new Response(binaryContent, { status: 200 }); }); const response = await downloadNightlyBlob( "my-bearer-token", - "sha256:abc123" + "sha256:abc123", ); expect(requestCount).toBe(2); expect(response.status).toBe(200); @@ -404,10 +403,10 @@ describe("downloadNightlyBlob", () => { mockFetch(async () => new Response(null, { status: 307 })); await expect(downloadNightlyBlob("token", "sha256:abc")).rejects.toThrow( - UpgradeError + UpgradeError, ); await expect(downloadNightlyBlob("token", "sha256:abc")).rejects.toThrow( - "GHCR blob redirect (307) had no Location header" + "GHCR blob redirect (307) had no Location header", ); }); @@ -423,7 +422,7 @@ describe("downloadNightlyBlob", () => { // Call once and capture to avoid stateful mock issues on repeated calls const error = await downloadNightlyBlob("token", "sha256:abc").catch( - (e) => e + (e) => e, ); expect(error).toBeInstanceOf(UpgradeError); expect(error.message).toContain("Blob storage download failed: HTTP 403"); @@ -433,7 +432,7 @@ describe("downloadNightlyBlob", () => { mockFetch(async () => new Response("Server Error", { status: 500 })); const error = await downloadNightlyBlob("token", "sha256:abc").catch( - (e) => e + (e) => e, ); expect(error).toBeInstanceOf(UpgradeError); expect(error.message).toContain("Unexpected GHCR blob response: HTTP 500"); @@ -445,7 +444,7 @@ describe("downloadNightlyBlob", () => { }); const error = await downloadNightlyBlob("token", "sha256:abc").catch( - (e) => e + (e) => e, ); expect(error).toBeInstanceOf(UpgradeError); expect(error.message).toContain("Failed to connect to GHCR: fetch failed"); @@ -463,11 +462,11 @@ describe("downloadNightlyBlob", () => { // Call once and capture to avoid stateful mock issues on repeated calls const error = await downloadNightlyBlob("token", "sha256:abc").catch( - (e) => e + (e) => e, ); expect(error).toBeInstanceOf(UpgradeError); expect(error.message).toContain( - "Failed to download from blob storage: fetch failed" + "Failed to download from blob storage: fetch failed", ); }); @@ -481,7 +480,7 @@ describe("downloadNightlyBlob", () => { }); await expect( - downloadNightlyBlob("token", "sha256:abc", controller.signal) + downloadNightlyBlob("token", "sha256:abc", controller.signal), ).rejects.toMatchObject({ name: "AbortError" }); expect(requestCount).toBe(1); }); @@ -497,7 +496,7 @@ describe("downloadNightlyBlob", () => { }); await expect( - downloadNightlyBlob("token", "sha256:abc", controller.signal) + downloadNightlyBlob("token", "sha256:abc", controller.signal), ).rejects.toBe(reason); expect(requestCount).toBe(1); }); @@ -516,7 +515,7 @@ describe("downloadNightlyBlob", () => { }); await expect( - downloadNightlyBlob("token", "sha256:abc", controller.signal) + downloadNightlyBlob("token", "sha256:abc", controller.signal), ).rejects.toBe(reason); expect(headers).toHaveLength(2); expect(headers[1]?.has("authorization")).toBe(false); @@ -548,7 +547,7 @@ describe("fetchManifest", () => { mockFetch(async () => Response.json(manifest)); await expect(fetchManifest("token", "nightly")).rejects.toThrow( - 'Manifest for tag "nightly" returned invalid metadata' + 'Manifest for tag "nightly" returned invalid metadata', ); }); @@ -587,7 +586,7 @@ describe("fetchManifest", () => { mockFetch(async () => new Response("Not Found", { status: 404 })); const error = await fetchManifest("token", "patch-0.13.0").catch( - (reason: unknown) => reason + (reason: unknown) => reason, ); expect(error).toBeInstanceOf(GhcrManifestHttpError); expect(error).toMatchObject({ @@ -603,10 +602,10 @@ describe("fetchManifest", () => { }); await expect(fetchManifest("token", "some-tag")).rejects.toThrow( - UpgradeError + UpgradeError, ); await expect(fetchManifest("token", "some-tag")).rejects.toThrow( - 'Failed to fetch manifest for tag "some-tag": fetch failed' + 'Failed to fetch manifest for tag "some-tag": fetch failed', ); }); }); @@ -623,7 +622,7 @@ describe("listTags", () => { }); await expect(listTags("token")).rejects.toThrow( - "GHCR tag pagination returned a repeated cursor" + "GHCR tag pagination returned a repeated cursor", ); expect(requests).toBe(2); }); @@ -632,7 +631,7 @@ describe("listTags", () => { expect(String(url)).toContain(`/v2/${GHCR_REPO}/tags/list`); return new Response( JSON.stringify({ tags: ["nightly", "patch-0.13.0", "patch-0.14.0"] }), - { status: 200 } + { status: 200 }, ); }); @@ -645,8 +644,8 @@ describe("listTags", () => { async () => new Response( JSON.stringify({ tags: ["nightly", "patch-0.13.0", "patch-0.14.0"] }), - { status: 200 } - ) + { status: 200 }, + ), ); const tags = await listTags("token", "patch-"); @@ -658,7 +657,7 @@ describe("listTags", () => { async () => new Response(JSON.stringify({ tags: ["nightly", "latest"] }), { status: 200, - }) + }), ); const tags = await listTags("token", "patch-"); @@ -677,7 +676,7 @@ describe("listTags", () => { await expect(listTags("token")).rejects.toThrow(UpgradeError); await expect(listTags("token")).rejects.toThrow( - "Failed to list GHCR tags: HTTP 500" + "Failed to list GHCR tags: HTTP 500", ); }); @@ -688,7 +687,7 @@ describe("listTags", () => { await expect(listTags("token")).rejects.toThrow(UpgradeError); await expect(listTags("token")).rejects.toThrow( - "Failed to list GHCR tags: fetch failed" + "Failed to list GHCR tags: fetch failed", ); }); @@ -696,7 +695,7 @@ describe("listTags", () => { // Generate exactly 100 tags for page 1 (triggers pagination), then 2 for page 2 const page1Tags = Array.from( { length: 100 }, - (_, i) => `tag-${String(i).padStart(3, "0")}` + (_, i) => `tag-${String(i).padStart(3, "0")}`, ); const page2Tags = ["tag-100", "tag-101"]; const responses = [page1Tags, page2Tags]; @@ -730,7 +729,7 @@ describe("listTags", () => { async () => new Response(JSON.stringify({ tags: mixedTags }), { status: 200, - }) + }), ); const tags = await listTags("token", "patch-"); @@ -767,7 +766,7 @@ describe("downloadLayerBlob", () => { }); await expect(downloadLayerBlob("token", "sha256:abc")).rejects.toThrow( - UpgradeError + UpgradeError, ); }); }); diff --git a/packages/cli/test/lib/git-commit-log.test.ts b/packages/cli/test/lib/git-commit-log.test.ts index c0b6b91af..9408d5069 100644 --- a/packages/cli/test/lib/git-commit-log.test.ts +++ b/packages/cli/test/lib/git-commit-log.test.ts @@ -78,13 +78,13 @@ describe("getCommitLog pathspec argv", () => { // (no reliance on --end-of-options, which requires git >= 2.24). test("throws on an option-like `from` ref", () => { expect(() => getCommitLog("/repo", { from: "--format=%H" })).toThrow( - "must be a git ref, not a CLI flag" + "must be a git ref, not a CLI flag", ); expect(() => getCommitLog("/repo", { from: "--format=%H" })).toThrow( - "Git refs cannot start with '-'" + "Git refs cannot start with '-'", ); expect(() => getCommitLog("/repo", { from: "--format=%H" })).not.toThrow( - "use the equals form" + "use the equals form", ); expect(execFileSyncMock).not.toHaveBeenCalled(); }); @@ -99,10 +99,10 @@ describe("getCommitLog pathspec argv", () => { }); expect(() => getCommitLog("/repo", { from: "bogus-ref" })).toThrow( - "Unknown git ref 'bogus-ref': not found in this repository." + "Unknown git ref 'bogus-ref': not found in this repository.", ); expect(() => getCommitLog("/repo", { from: "bogus-ref" })).toThrow( - "git rev-parse bogus-ref" + "git rev-parse bogus-ref", ); }); @@ -117,7 +117,7 @@ describe("getCommitLog pathspec argv", () => { test("parses NUL-delimited git output into commits", () => { execFileSyncMock.mockReturnValue( - "abc\x00subject\x00Jane\x00jane@example.com\x002026-01-01T00:00:00Z" + "abc\x00subject\x00Jane\x00jane@example.com\x002026-01-01T00:00:00Z", ); const commits = getCommitLog("/repo", { paths: ["src"] }); diff --git a/packages/cli/test/lib/git.property.test.ts b/packages/cli/test/lib/git.property.test.ts index 97e75ec25..cb4904499 100644 --- a/packages/cli/test/lib/git.property.test.ts +++ b/packages/cli/test/lib/git.property.test.ts @@ -27,7 +27,7 @@ describe("property: parseRemoteUrl", () => { const result = parseRemoteUrl(url); expect(result).toBe(`${owner}/${repo}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -38,7 +38,7 @@ describe("property: parseRemoteUrl", () => { const result = parseRemoteUrl(url); expect(result).toBe(`${owner}/${repo}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -49,7 +49,7 @@ describe("property: parseRemoteUrl", () => { const result = parseRemoteUrl(url); expect(result).toBe(`${owner}/${repo}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -60,7 +60,7 @@ describe("property: parseRemoteUrl", () => { const result = parseRemoteUrl(url); expect(result).toBe(`${owner}/${repo}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -68,19 +68,19 @@ describe("property: parseRemoteUrl", () => { describe("unit: parseRemoteUrl edge cases", () => { test("standard GitHub HTTPS", () => { expect(parseRemoteUrl("https://github.com/getsentry/cli.git")).toBe( - "getsentry/cli" + "getsentry/cli", ); }); test("standard GitHub SSH", () => { expect(parseRemoteUrl("git@github.com:getsentry/cli.git")).toBe( - "getsentry/cli" + "getsentry/cli", ); }); test("GitLab HTTPS", () => { expect(parseRemoteUrl("https://gitlab.com/my-group/my-project.git")).toBe( - "my-group/my-project" + "my-group/my-project", ); }); @@ -90,13 +90,13 @@ describe("unit: parseRemoteUrl edge cases", () => { test("SSH with port (ssh:// protocol)", () => { expect(parseRemoteUrl("ssh://git@github.com:22/owner/repo.git")).toBe( - "owner/repo" + "owner/repo", ); }); test("SSH with port (no .git)", () => { expect(parseRemoteUrl("ssh://git@github.com:443/owner/repo")).toBe( - "owner/repo" + "owner/repo", ); }); diff --git a/packages/cli/test/lib/global-flags.test.ts b/packages/cli/test/lib/global-flags.test.ts index 43cb2aae7..d532ade11 100644 --- a/packages/cli/test/lib/global-flags.test.ts +++ b/packages/cli/test/lib/global-flags.test.ts @@ -60,10 +60,10 @@ describe("buildTopLevelFlags", () => { test("matches the current GLOBAL_FLAGS definition", () => { const { booleanFlags, valueFlags } = buildTopLevelFlags(); expect([...booleanFlags].sort()).toEqual( - ["--verbose", "-v", "--no-verbose", "--json", "--no-json"].sort() + ["--verbose", "-v", "--no-verbose", "--json", "--no-json"].sort(), ); expect([...valueFlags].sort()).toEqual( - ["--log-level", "--fields", "--org", "--project"].sort() + ["--log-level", "--fields", "--org", "--project"].sort(), ); }); }); diff --git a/packages/cli/test/lib/help-json.test.ts b/packages/cli/test/lib/help-json.test.ts index 132592bf8..4c6d06ee7 100644 --- a/packages/cli/test/lib/help-json.test.ts +++ b/packages/cli/test/lib/help-json.test.ts @@ -50,7 +50,7 @@ describe("renderJsonHelp", () => { test("--fields (spaced) narrows the JSON output", () => { const out = renderJsonHelp( [APP, "issue", "list"], - ["--json", "--fields", "path"] + ["--json", "--fields", "path"], ); const parsed = JSON.parse(out as string); expect(Object.keys(parsed)).toEqual(["path"]); @@ -59,7 +59,7 @@ describe("renderJsonHelp", () => { test("--fields= inline form narrows the JSON output", () => { const out = renderJsonHelp( [APP, "issue", "list"], - ["--json", "--fields=path"] + ["--json", "--fields=path"], ); const parsed = JSON.parse(out as string); expect(Object.keys(parsed)).toEqual(["path"]); @@ -68,7 +68,7 @@ describe("renderJsonHelp", () => { test("--fields accepts a comma-separated list", () => { const out = renderJsonHelp( [APP, "issue", "list"], - ["--json", "--fields", "path,brief"] + ["--json", "--fields", "path,brief"], ); const parsed = JSON.parse(out as string); expect(Object.keys(parsed).sort()).toEqual(["brief", "path"]); @@ -76,7 +76,7 @@ describe("renderJsonHelp", () => { test("a --json after a -- escape is ignored (wrapped command's flag)", () => { expect( - renderJsonHelp([APP, "monitor", "run"], ["--", "tool", "--json"]) + renderJsonHelp([APP, "monitor", "run"], ["--", "tool", "--json"]), ).toBeUndefined(); }); @@ -108,7 +108,7 @@ describe("renderJsonHelp", () => { // the resolved `issue list` command rather than an `acme`-prefixed path. const out = renderJsonHelp( [APP, "issue", "list"], - ["--org", "acme", "--json"] + ["--org", "acme", "--json"], ); const parsed = JSON.parse(out as string); expect(parsed).toHaveProperty("path"); @@ -120,16 +120,16 @@ describe("rewriteHelpJsonToHelpCommand", () => { test("returns undefined when not a --help --json request", () => { expect(rewriteHelpJsonToHelpCommand(["cli", "nope"])).toBeUndefined(); expect( - rewriteHelpJsonToHelpCommand(["cli", "nope", "--help"]) + rewriteHelpJsonToHelpCommand(["cli", "nope", "--help"]), ).toBeUndefined(); expect( - rewriteHelpJsonToHelpCommand(["cli", "nope", "--json"]) + rewriteHelpJsonToHelpCommand(["cli", "nope", "--json"]), ).toBeUndefined(); }); test("rewrites an unknown-command --help --json to the help command", () => { expect( - rewriteHelpJsonToHelpCommand(["cli", "nope", "--help", "--json"]) + rewriteHelpJsonToHelpCommand(["cli", "nope", "--help", "--json"]), ).toEqual(["help", "--json", "cli", "nope"]); }); @@ -150,7 +150,7 @@ describe("rewriteHelpJsonToHelpCommand", () => { "--json", "--fields", "path", - ]) + ]), ).toEqual(["help", "--json", "issue", "list", "--fields", "path"]); }); @@ -168,7 +168,7 @@ describe("rewriteHelpJsonToHelpCommand", () => { "nope", "--help", "--json", - ]) + ]), ).toEqual(["help", "--json", "cli", "nope"]); }); @@ -180,7 +180,7 @@ describe("rewriteHelpJsonToHelpCommand", () => { "nope", "--help", "--json", - ]) + ]), ).toEqual(["help", "--json", "cli", "nope"]); }); @@ -193,7 +193,7 @@ describe("rewriteHelpJsonToHelpCommand", () => { "tool", "--help", "--json", - ]) + ]), ).toBeUndefined(); }); }); @@ -226,7 +226,7 @@ describe("isVersionRequest", () => { describe("isRecoverableUnknownCommand", () => { test("true for a --version request", () => { expect(isRecoverableUnknownCommand(["cli", "nope", "--version"])).toBe( - true + true, ); }); @@ -234,7 +234,7 @@ describe("isRecoverableUnknownCommand", () => { // Regression: the old guard only checked `argv.at(-1) === "help"`, so this // shape leaked a spurious unknown_command telemetry event. expect( - isRecoverableUnknownCommand(["cli", "nope", "--help", "--json"]) + isRecoverableUnknownCommand(["cli", "nope", "--help", "--json"]), ).toBe(true); }); diff --git a/packages/cli/test/lib/help-positional.test.ts b/packages/cli/test/lib/help-positional.test.ts index cb141ed3b..598c92a67 100644 --- a/packages/cli/test/lib/help-positional.test.ts +++ b/packages/cli/test/lib/help-positional.test.ts @@ -31,7 +31,7 @@ beforeEach(() => { originalFetch = globalThis.fetch; globalThis.fetch = mockFetch( async () => - new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }) + new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }), ); }); @@ -183,18 +183,18 @@ describe("help command unchanged", () => { const { stdout, stderr } = await runCommand(["help", "project", "create"]); expect(stdout).toContain( - "sentry project create [/]:..." + "sentry project create [/]:...", ); expect(stderr).not.toContain("Tip"); }); test("project create --help shows the required paired syntax", () => { const help = generateHelpTextForAllCommands(app).find( - ([route]) => route === "sentry project create" + ([route]) => route === "sentry project create", )?.[1]; expect(help).toContain( - "sentry project create [/]:..." + "sentry project create [/]:...", ); expect(help).not.toContain("--platform"); expect(help).not.toContain(""); @@ -203,30 +203,30 @@ describe("help command unchanged", () => { }); describe("project create parser contract", () => { - test.each([ - "--platform", - "-p", - ])("rejects the removed %s flag", async (flag) => { - const captured = { stdout: "", stderr: "" }; - const mockContext = buildMockContext(captured); - const stderrWrite = vi - .spyOn(process.stderr, "write") - .mockImplementation(() => true); - - try { - await run( - app, - ["project", "create", "my-app:node", flag, "javascript"], - mockContext - ); - - const errorOutput = stderrWrite.mock.calls - .map(([data]) => String(data)) - .join(""); - expect(errorOutput).toContain(flag); - expect(errorOutput).toMatch(/No (flag|alias) registered/); - } finally { - stderrWrite.mockRestore(); - } - }); + test.each(["--platform", "-p"])( + "rejects the removed %s flag", + async (flag) => { + const captured = { stdout: "", stderr: "" }; + const mockContext = buildMockContext(captured); + const stderrWrite = vi + .spyOn(process.stderr, "write") + .mockImplementation(() => true); + + try { + await run( + app, + ["project", "create", "my-app:node", flag, "javascript"], + mockContext, + ); + + const errorOutput = stderrWrite.mock.calls + .map(([data]) => String(data)) + .join(""); + expect(errorOutput).toContain(flag); + expect(errorOutput).toMatch(/No (flag|alias) registered/); + } finally { + stderrWrite.mockRestore(); + } + }, + ); }); diff --git a/packages/cli/test/lib/help.test.ts b/packages/cli/test/lib/help.test.ts index 37955e7ac..35c56dd5e 100644 --- a/packages/cli/test/lib/help.test.ts +++ b/packages/cli/test/lib/help.test.ts @@ -11,7 +11,7 @@ import { introspectAllCommands, printCustomHelp } from "../../src/lib/help.js"; import { useTestConfigDir } from "../helpers.js"; /** Strip ANSI escape sequences for content assertions */ -// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape codes use control chars by definition +// oxlint-disable-next-line no-control-regex -- ANSI escape codes use control chars by definition const ANSI_RE = /\u001B\[[0-9;]*m/g; function stripAnsi(str: string): string { return str.replace(ANSI_RE, ""); @@ -23,7 +23,7 @@ function maxLineWidth(banner: string): number { 0, ...stripAnsi(banner) .split("\n") - .map((line) => [...line].length) + .map((line) => [...line].length), ); } diff --git a/packages/cli/test/lib/hex-id-recovery.adapters.test.ts b/packages/cli/test/lib/hex-id-recovery.adapters.test.ts index 05d1ea260..ad5742815 100644 --- a/packages/cli/test/lib/hex-id-recovery.adapters.test.ts +++ b/packages/cli/test/lib/hex-id-recovery.adapters.test.ts @@ -218,7 +218,7 @@ describe("adapter query params", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); }); diff --git a/packages/cli/test/lib/hex-id-recovery.property.test.ts b/packages/cli/test/lib/hex-id-recovery.property.test.ts index 58df132d1..7bc1bc8be 100644 --- a/packages/cli/test/lib/hex-id-recovery.property.test.ts +++ b/packages/cli/test/lib/hex-id-recovery.property.test.ts @@ -75,7 +75,7 @@ describe("property: stripTrailingNonHex", () => { throw new Error(`Expected stripped=${junk}, got ${result.stripped}`); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -86,7 +86,7 @@ describe("property: stripTrailingNonHex", () => { throw new Error(`Expected null for exact-length input ${hex}`); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -105,17 +105,17 @@ describe("property: stripTrailingNonHex", () => { if (result) { if (result.hex.length !== 32) { throw new Error( - `hex result length ${result.hex.length} !== 32 for input ${input}` + `hex result length ${result.hex.length} !== 32 for input ${input}`, ); } if (!input.startsWith(result.hex)) { throw new Error( - `input ${input} does not start with returned hex ${result.hex}` + `input ${input} does not start with returned hex ${result.hex}`, ); } } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -127,7 +127,7 @@ describe("property: stripTrailingNonHex", () => { throw new Error(`Unexpected result for ${hex}${junk}`); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -142,14 +142,14 @@ describe("property: extractHexCandidate", () => { } if (result.prefix && !/^[0-9a-f]*$/.test(result.prefix)) { throw new Error( - `prefix '${result.prefix}' contains non-hex chars (input: ${input})` + `prefix '${result.prefix}' contains non-hex chars (input: ${input})`, ); } if (result.suffix && !/^[0-9a-f]*$/.test(result.suffix)) { throw new Error(`suffix '${result.suffix}' contains non-hex chars`); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -164,7 +164,7 @@ describe("property: extractHexCandidate", () => { throw new Error(`Unexpected suffix ${result.suffix}`); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -179,7 +179,7 @@ describe("property: valid hex IDs never need recovery", () => { throw new Error(`Expected ${hex}, got ${result}`); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -191,11 +191,11 @@ describe("property: preNormalize", () => { const { cleaned } = preNormalize(input); if (cleaned.length > input.length) { throw new Error( - `Output grew: input=${input.length}, output=${cleaned.length}` + `Output grew: input=${input.length}, output=${cleaned.length}`, ); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -208,7 +208,7 @@ describe("property: preNormalize", () => { throw new Error(`Not idempotent: ${once} !== ${twice}`); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -221,7 +221,7 @@ describe("property: looksLikeSlug vs extractHexCandidate", () => { throw new Error(`Pure hex ${hex} classified as slug`); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -248,11 +248,11 @@ describe("property: UUIDv7 round-trip via decode/age", () => { } if (decoded.createdAt.getTime() !== ms) { throw new Error( - `Round-trip failed: ${ms} → ${decoded.createdAt.getTime()}` + `Round-trip failed: ${ms} → ${decoded.createdAt.getTime()}`, ); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -268,11 +268,11 @@ describe("property: UUIDv7 round-trip via decode/age", () => { // Older ms → larger age (more days before `now`). if (msA < msB && ageA < ageB) { throw new Error( - `Monotonicity broken: ${msA}<${msB} but ${ageA}<${ageB}` + `Monotonicity broken: ${msA}<${msB} but ${ageA}<${ageB}`, ); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -290,12 +290,12 @@ describe("property: isOverNestedPath", () => { const expected = segments.length >= 4; if (isOverNestedPath(input) !== expected) { throw new Error( - `isOverNestedPath(${input}) expected ${expected}, got ${!expected}` + `isOverNestedPath(${input}) expected ${expected}, got ${!expected}`, ); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/hex-id-recovery.test.ts b/packages/cli/test/lib/hex-id-recovery.test.ts index 66f3c369b..9c0146477 100644 --- a/packages/cli/test/lib/hex-id-recovery.test.ts +++ b/packages/cli/test/lib/hex-id-recovery.test.ts @@ -254,7 +254,7 @@ describe("isOverNestedPath", () => { test("4 segments: over-nested", () => { expect(isOverNestedPath("gohighlevel/highlevel-flutter/27bc063/abc")).toBe( - true + true, ); }); @@ -277,7 +277,7 @@ describe("recoverHexId decision tree", () => { afterEach(() => { // Restore original adapters for (const key of Object.keys( - originalAdapters + originalAdapters, ) as (keyof typeof ADAPTERS)[]) { ADAPTERS[key] = originalAdapters[key]; } @@ -303,7 +303,7 @@ describe("recoverHexId decision tree", () => { const r = await recoverHexId( "gohighlevel/highlevel-flutter/27bc063/abc", "event", - CLEAN_CTX + CLEAN_CTX, ); expect(r.kind).toBe("failed"); expect(r.kind === "failed" && r.reason).toBe("over-nested"); @@ -356,7 +356,7 @@ describe("recoverHexId decision tree", () => { const r = await recoverHexId( "14d9a67fda344df0a138a88d62e41be", "event", - CLEAN_CTX + CLEAN_CTX, ); expect(r.kind).toBe("fuzzy"); expect(r.kind === "fuzzy" && r.id).toBe(full); @@ -556,7 +556,7 @@ describe("handleRecoveryResult", () => { stripped: "ios", }, fallback, - opts + opts, ); expect(id).toBe(VALID_32); }); @@ -570,7 +570,7 @@ describe("handleRecoveryResult", () => { prefix: "05d6975ab9bb", }, fallback, - opts + opts, ); expect(id).toBe(VALID_32); }); @@ -585,7 +585,7 @@ describe("handleRecoveryResult", () => { toEntity: "trace", }, fallback, - { ...opts, entityType: "trace" } + { ...opts, entityType: "trace" }, ); expect(id).toBe(VALID_32); }); @@ -602,7 +602,7 @@ describe("handleRecoveryResult", () => { candidates: [id1, id2], }, fallback, - opts + opts, ); expect.unreachable("Should have thrown"); } catch (err) { @@ -622,7 +622,7 @@ describe("handleRecoveryResult", () => { hint: "shell variable leak", }, fallback, - opts + opts, ); expect.unreachable("Should have thrown"); } catch (err) { @@ -636,7 +636,7 @@ describe("handleRecoveryResult", () => { handleRecoveryResult( { kind: "failed", original: "abc", reason: "api-error" }, fallback, - opts + opts, ); expect.unreachable("Should have thrown"); } catch (err) { diff --git a/packages/cli/test/lib/hex-id.test.ts b/packages/cli/test/lib/hex-id.test.ts index a75f1f8c5..4e70ff80d 100644 --- a/packages/cli/test/lib/hex-id.test.ts +++ b/packages/cli/test/lib/hex-id.test.ts @@ -46,7 +46,7 @@ describe("validateHexId", () => { test("normalizes to lowercase", () => { const mixedCase = "AAAA1111bbbb2222CCCC3333dddd4444"; expect(validateHexId(mixedCase, "test ID")).toBe( - "aaaa1111bbbb2222cccc3333dddd4444" + "aaaa1111bbbb2222cccc3333dddd4444", ); }); @@ -60,13 +60,13 @@ describe("validateHexId", () => { test("throws ValidationError for non-hex chars", () => { expect(() => - validateHexId("zzzz1111bbbb2222cccc3333dddd4444", "test ID") + validateHexId("zzzz1111bbbb2222cccc3333dddd4444", "test ID"), ).toThrow(ValidationError); }); test("throws ValidationError for 33-char hex", () => { expect(() => validateHexId(`${VALID_ID}a`, "test ID")).toThrow( - ValidationError + ValidationError, ); }); @@ -111,7 +111,7 @@ describe("validateHexId", () => { } catch (error) { expect(error).toBeInstanceOf(ValidationError); expect((error as ValidationError).message).toContain( - "32-character hexadecimal" + "32-character hexadecimal", ); } }); @@ -208,32 +208,32 @@ describe("validateHexId", () => { test("strips dashes from UUID format and returns 32-char hex", () => { expect( - validateHexId("aaaa1111-bbbb-2222-cccc-3333dddd4444", "test ID") + validateHexId("aaaa1111-bbbb-2222-cccc-3333dddd4444", "test ID"), ).toBe(VALID_ID); }); test("strips dashes from real user UUID (CLI-7Z)", () => { expect( - validateHexId("ed29abc8-71c4-475b-9675-4655ef1a02d0", "test ID") + validateHexId("ed29abc8-71c4-475b-9675-4655ef1a02d0", "test ID"), ).toBe("ed29abc871c4475b96754655ef1a02d0"); }); test("strips dashes from uppercase UUID and normalizes to lowercase", () => { expect( - validateHexId("AAAA1111-BBBB-2222-CCCC-3333DDDD4444", "test ID") + validateHexId("AAAA1111-BBBB-2222-CCCC-3333DDDD4444", "test ID"), ).toBe(VALID_ID); }); test("strips dashes from UUID with whitespace padding", () => { expect( - validateHexId(" aaaa1111-bbbb-2222-cccc-3333dddd4444 ", "test ID") + validateHexId(" aaaa1111-bbbb-2222-cccc-3333dddd4444 ", "test ID"), ).toBe(VALID_ID); }); test("UUID validation is idempotent — validated UUID validates again unchanged", () => { const first = validateHexId( "aaaa1111-bbbb-2222-cccc-3333dddd4444", - "test ID" + "test ID", ); const second = validateHexId(first, "test ID"); expect(second).toBe(first); @@ -245,7 +245,7 @@ describe("validateHexId", () => { test("rejects dashes in wrong positions (not 8-4-4-4-12)", () => { expect(() => - validateHexId("aaaa-1111bbbb-2222cccc-3333dddd-4444", "test ID") + validateHexId("aaaa-1111bbbb-2222cccc-3333dddd-4444", "test ID"), ).toThrow(ValidationError); }); }); @@ -290,7 +290,7 @@ describe("property: validateHexId", () => { property(validIdArb, (id) => { expect(validateHexId(id, "test ID")).toBe(id.toLowerCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -301,7 +301,7 @@ describe("property: validateHexId", () => { const second = validateHexId(first, "test ID"); expect(second).toBe(first); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -312,7 +312,7 @@ describe("property: validateHexId", () => { expect(validateHexId(` ${id} `, "test ID")).toBe(expected); expect(validateHexId(`\t${id}\n`, "test ID")).toBe(expected); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -329,7 +329,7 @@ describe("property: validateHexId", () => { property(wrongLengthHexArb, (id) => { expect(() => validateHexId(id, "test ID")).toThrow(ValidationError); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -347,7 +347,7 @@ describe("property: validateHexId", () => { const uuid = toUuidFormat(id); expect(validateHexId(uuid, "test ID")).toBe(expected); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -358,7 +358,7 @@ describe("property: validateHexId", () => { const fromUuid = validateHexId(toUuidFormat(id), "test ID"); expect(fromUuid).toBe(fromPlain); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -400,7 +400,7 @@ describe("decodeUuidV7Timestamp", () => { test("returns null for 32-char hex with version char outside v7", () => { // Swap position 12 to "8" → not v7 expect( - decodeUuidV7Timestamp("019da22381848d8a98508aaedbafdc11") + decodeUuidV7Timestamp("019da22381848d8a98508aaedbafdc11"), ).toBeNull(); }); }); diff --git a/packages/cli/test/lib/index.test.ts b/packages/cli/test/lib/index.test.ts index 62dd8ceeb..c0b508120 100644 --- a/packages/cli/test/lib/index.test.ts +++ b/packages/cli/test/lib/index.test.ts @@ -93,7 +93,7 @@ describe("createSentrySDK() library API", () => { await sdk.run("--version"); // Check that no new SENTRY_OUTPUT_FORMAT key leaked expect(process.env.SENTRY_OUTPUT_FORMAT).toBe( - envBefore.SENTRY_OUTPUT_FORMAT + envBefore.SENTRY_OUTPUT_FORMAT, ); expect(process.env.SENTRY_AUTH_TOKEN).toBe(envBefore.SENTRY_AUTH_TOKEN); }); @@ -107,7 +107,7 @@ describe("createSentrySDK() library API", () => { // expected } expect(process.env.SENTRY_OUTPUT_FORMAT).toBe( - envBefore.SENTRY_OUTPUT_FORMAT + envBefore.SENTRY_OUTPUT_FORMAT, ); }); diff --git a/packages/cli/test/lib/ini.property.test.ts b/packages/cli/test/lib/ini.property.test.ts index 549fe8422..8ee89cc24 100644 --- a/packages/cli/test/lib/ini.property.test.ts +++ b/packages/cli/test/lib/ini.property.test.ts @@ -93,7 +93,7 @@ const commentArb = constantFrom( "# this is a comment", "; another comment", "# key = value", - "; [section]" + "; [section]", ); describe("property: parseIni", () => { @@ -113,7 +113,7 @@ describe("property: parseIni", () => { } } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -137,7 +137,7 @@ describe("property: parseIni", () => { expect(clean2).toEqual(clean1); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -164,7 +164,7 @@ describe("property: parseIni", () => { } } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -181,10 +181,10 @@ describe("property: parseIni", () => { const normalizedKey = key.toLowerCase(); expect(parsedUpper[normalizedSection]?.[normalizedKey]).toBe( - parsedLower[normalizedSection]?.[normalizedKey] + parsedLower[normalizedSection]?.[normalizedKey], ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -201,10 +201,10 @@ describe("property: parseIni", () => { const normalizedKey = key.toLowerCase(); expect(parsedUpper[normalizedSection]?.[normalizedKey]).toBe( - parsedLower[normalizedSection]?.[normalizedKey] + parsedLower[normalizedSection]?.[normalizedKey], ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -221,9 +221,9 @@ describe("property: parseIni", () => { const normalizedSection = section.toLowerCase(); const normalizedKey = key.toLowerCase(); expect(parsed[normalizedSection]?.[normalizedKey]).toBe(value2); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/ini.test.ts b/packages/cli/test/lib/ini.test.ts index d6b50f32f..b7c7bbd3f 100644 --- a/packages/cli/test/lib/ini.test.ts +++ b/packages/cli/test/lib/ini.test.ts @@ -27,7 +27,7 @@ describe("parseIni", () => { test("handles Windows line endings (CRLF)", () => { const result = parseIni( - "[defaults]\r\norg = my-org\r\nproject = my-proj\r\n" + "[defaults]\r\norg = my-org\r\nproject = my-proj\r\n", ); expect(result.defaults?.org).toBe("my-org"); expect(result.defaults?.project).toBe("my-proj"); @@ -68,7 +68,7 @@ describe("parseIni", () => { test("preserves quotes in middle of value", () => { const result = parseIni( - '[defaults]\nurl = https://example.com/path?foo="bar"' + '[defaults]\nurl = https://example.com/path?foo="bar"', ); expect(result.defaults?.url).toBe('https://example.com/path?foo="bar"'); }); @@ -90,7 +90,7 @@ describe("parseIni", () => { test("duplicate sections: merged", () => { const result = parseIni( - "[defaults]\norg = my-org\n[defaults]\nproject = my-proj" + "[defaults]\norg = my-org\n[defaults]\nproject = my-proj", ); expect(result.defaults?.org).toBe("my-org"); expect(result.defaults?.project).toBe("my-proj"); @@ -98,7 +98,7 @@ describe("parseIni", () => { test("duplicate section with duplicate key: last wins", () => { const result = parseIni( - "[defaults]\norg = first\n[defaults]\norg = second" + "[defaults]\norg = first\n[defaults]\norg = second", ); expect(result.defaults?.org).toBe("second"); }); @@ -151,7 +151,7 @@ token = sntrys_eyJpYXQiOjE3MTkzODM1MjQuNjQ0OTgyfQ==_abc123 expect(result.defaults?.org).toBe("my-org"); expect(result.defaults?.project).toBe("my-project"); expect(result.auth?.token).toBe( - "sntrys_eyJpYXQiOjE3MTkzODM1MjQuNjQ0OTgyfQ==_abc123" + "sntrys_eyJpYXQiOjE3MTkzODM1MjQuNjQ0OTgyfQ==_abc123", ); }); diff --git a/packages/cli/test/lib/init/clack-plain.test.ts b/packages/cli/test/lib/init/clack-plain.test.ts index 567897932..1aaa519f0 100644 --- a/packages/cli/test/lib/init/clack-plain.test.ts +++ b/packages/cli/test/lib/init/clack-plain.test.ts @@ -5,7 +5,7 @@ * isPlainOutput() is true, and delegate to real @clack/prompts when false. */ -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as clack from "@clack/prompts"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { diff --git a/packages/cli/test/lib/init/clack-utils.test.ts b/packages/cli/test/lib/init/clack-utils.test.ts index 82935cdaa..444b69d2c 100644 --- a/packages/cli/test/lib/init/clack-utils.test.ts +++ b/packages/cli/test/lib/init/clack-utils.test.ts @@ -66,43 +66,43 @@ describe("featureLabel", () => { describe("featureDescription", () => { test("returns description for known feature", () => { expect(featureDescription("errorMonitoring")).toBe( - "Automatically capture exceptions and stack traces" + "Automatically capture exceptions and stack traces", ); expect(featureDescription("performanceMonitoring")).toBe( - "Find bottlenecks, broken requests, and understand application flow end-to-end" + "Find bottlenecks, broken requests, and understand application flow end-to-end", ); expect(featureDescription("sessionReplay")).toBe( - "Watch real user sessions to see what went wrong" + "Watch real user sessions to see what went wrong", ); expect(featureDescription("profiling")).toBe( - "Pinpoint the functions and lines of code responsible for performance issues" + "Pinpoint the functions and lines of code responsible for performance issues", ); expect(featureDescription("logs")).toBe( - "See logs in context with errors and performance issues" + "See logs in context with errors and performance issues", ); expect(featureDescription("metrics")).toBe( - "Track application performance and usage over time with custom metrics" + "Track application performance and usage over time with custom metrics", ); expect(featureDescription("sourceMaps")).toBe( - "Turn minified production stack traces back into your original source code" + "Turn minified production stack traces back into your original source code", ); expect(featureDescription("crons")).toBe( - "Detect failed, missed, or delayed scheduled jobs" + "Detect failed, missed, or delayed scheduled jobs", ); expect(featureDescription("attachments")).toBe( - "Link user-supplied data to captured events" + "Link user-supplied data to captured events", ); expect(featureDescription("aiMonitoring")).toBe( - "Understand AI calls, latency, token usage, cost, and failures" + "Understand AI calls, latency, token usage, cost, and failures", ); expect(featureDescription("mcpObservability")).toBe( - "Trace MCP tool calls and understand failures across agent workflows" + "Trace MCP tool calls and understand failures across agent workflows", ); expect(featureDescription("userFeedback")).toBe( - "Collect user reports with the error and session context needed to investigate" + "Collect user reports with the error and session context needed to investigate", ); expect(featureDescription("reactFeatures")).toBe( - "Capture React-specific errors with component and rendering context" + "Capture React-specific errors with component and rendering context", ); }); @@ -123,7 +123,7 @@ describe("sortFeatures", () => { "attachments", "aiMonitoring", "mcpObservability", - ]) + ]), ).toEqual([ "errorMonitoring", "logs", diff --git a/packages/cli/test/lib/init/feedback.test.ts b/packages/cli/test/lib/init/feedback.test.ts index 5f02c42dc..8fea3b3de 100644 --- a/packages/cli/test/lib/init/feedback.test.ts +++ b/packages/cli/test/lib/init/feedback.test.ts @@ -7,21 +7,21 @@ describe("formatFeedbackHint", () => { [ "Tell us what felt great or rough:", '$ sentry cli feedback "sentry init worked well"', - ].join("\n") + ].join("\n"), ); expect(formatFeedbackHint("cancelled")).toBe( [ "Sad to see setup stop. Was something going sideways?", "Tell us so we can fix it:", '$ sentry cli feedback "sentry init was cancelled"', - ].join("\n") + ].join("\n"), ); expect(formatFeedbackHint("failed")).toBe( [ "Setup hit a wall.", "Tell us what happened so we can fix it:", '$ sentry cli feedback "sentry init failed"', - ].join("\n") + ].join("\n"), ); }); }); diff --git a/packages/cli/test/lib/init/formatters.test.ts b/packages/cli/test/lib/init/formatters.test.ts index a6d3d2768..6e8ac4e58 100644 --- a/packages/cli/test/lib/init/formatters.test.ts +++ b/packages/cli/test/lib/init/formatters.test.ts @@ -24,7 +24,8 @@ function summaryCall(calls: MockCall[]): WizardSummary | undefined { function warnMessages(calls: MockCall[]): string[] { return calls .filter( - (c): c is Extract => c.kind === "log.warn" + (c): c is Extract => + c.kind === "log.warn", ) .map((c) => c.message); } @@ -33,7 +34,7 @@ function errorMessages(calls: MockCall[]): string[] { return calls .filter( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ) .map((c) => c.message); } @@ -41,7 +42,8 @@ function errorMessages(calls: MockCall[]): string[] { function infoMessages(calls: MockCall[]): string[] { return calls .filter( - (c): c is Extract => c.kind === "log.info" + (c): c is Extract => + c.kind === "log.info", ) .map((c) => c.message); } @@ -49,7 +51,8 @@ function infoMessages(calls: MockCall[]): string[] { function feedbackOutcomes(calls: MockCall[]): string[] { return calls .filter( - (c): c is Extract => c.kind === "feedback" + (c): c is Extract => + c.kind === "feedback", ) .map((c) => c.outcome); } @@ -75,7 +78,7 @@ describe("formatResult", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -105,8 +108,8 @@ describe("formatResult", () => { expect(feedbackOutcomes(calls)).toEqual(["success"]); expect( infoMessages(calls).some((message) => - message.includes("one of the first") - ) + message.includes("one of the first"), + ), ).toBe(false); }); @@ -127,7 +130,7 @@ describe("formatResult", () => { warnings: ["Source maps not configured", "Missing DSN"], }, }, - ui + ui, ); const warns = warnMessages(calls); @@ -156,7 +159,7 @@ describe("formatResult", () => { changedFiles: [], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -173,7 +176,7 @@ describe("formatResult", () => { changedFiles: [{ action: "create", path: "instrument.ts" }], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -205,7 +208,7 @@ describe("formatResult completion payload", () => { changedFiles: [{ action: "create", path: "instrument.ts" }], }, }, - ui + ui, ); const completion = summaryCall(calls)?.completion; @@ -229,7 +232,7 @@ describe("formatResult completion payload", () => { result: { orgSlug: "acme", projectSlug: "my-app", projectId: "4507" }, }, ui, - { verified: true, kind: "envelope", eventId: "abc123def" } + { verified: true, kind: "envelope", eventId: "abc123def" }, ); const completion = summaryCall(calls)?.completion; @@ -248,7 +251,7 @@ describe("formatResult completion payload", () => { sentryProjectUrl: "https://acme.sentry.io/settings/projects/api/", }, }, - ui + ui, ); const completion = summaryCall(calls)?.completion; @@ -263,7 +266,7 @@ describe("formatResult completion payload", () => { status: "success", result: { platform: "python", sentryProjectUrl: "not-a-url" }, }, - ui + ui, ); const completion = summaryCall(calls)?.completion; @@ -293,12 +296,12 @@ describe("formatResult with locally-captured project identity", () => { }, ui, undefined, - identity + identity, ); const completion = summaryCall(calls)?.completion; expect(completion?.issuesUrl).toBe( - "https://acme.sentry.io/issues/?project=4507" + "https://acme.sentry.io/issues/?project=4507", ); expect(completion?.projectName).toBe("my-app"); }); @@ -317,7 +320,7 @@ describe("formatResult with locally-captured project identity", () => { }, ui, undefined, - identity + identity, ); const completion = summaryCall(calls)?.completion; @@ -333,7 +336,7 @@ describe("formatResult with locally-captured project identity", () => { { status: "success", result: { platform: "next.js" } }, ui, undefined, - identity + identity, ); const summary = summaryCall(calls); @@ -354,7 +357,7 @@ describe("formatResult with locally-captured project identity", () => { projectSlug: "my-app", projectId: "(dry-run)", url: "https://sentry.io/dry-run", - } + }, ); const completion = summaryCall(calls)?.completion; @@ -380,7 +383,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -403,7 +406,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -420,7 +423,7 @@ describe("formatResult with featureBlurbs", () => { features: ["errorMonitoring", "sessionReplay"], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -452,7 +455,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -479,7 +482,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -506,7 +509,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -530,7 +533,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -555,7 +558,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -579,7 +582,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -600,7 +603,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -624,7 +627,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -646,7 +649,7 @@ describe("formatResult with featureBlurbs", () => { ], }, }, - ui + ui, ); const summary = summaryCall(calls); @@ -681,7 +684,7 @@ describe("formatError", () => { formatError({ status: "failed" }, ui); expect(errorMessages(calls)).toContain( - "Wizard failed with an unknown error" + "Wizard failed with an unknown error", ); }); @@ -702,11 +705,11 @@ describe("formatError", () => { commands: ["npm install @sentry/node"], }, }, - ui + ui, ); expect( - warnMessages(calls).some((m) => m.includes("$ npm install @sentry/node")) + warnMessages(calls).some((m) => m.includes("$ npm install @sentry/node")), ).toBe(true); }); @@ -715,7 +718,7 @@ describe("formatError", () => { formatError({ status: "failed", result: { exitCode: 50 } }, ui); expect(warnMessages(calls).some((m) => m.includes("verification"))).toBe( - true + true, ); }); @@ -727,7 +730,7 @@ describe("formatError", () => { status: "failed", result: { docsUrl }, }, - ui + ui, ); // Pull every URL out of the info messages and check the docs URL @@ -737,7 +740,7 @@ describe("formatError", () => { // explicit (and silences the false positive). const urlRe = /https?:\/\/[^\s)]+/g; const seenUrls = infoMessages(calls).flatMap( - (msg) => msg.match(urlRe) ?? [] + (msg) => msg.match(urlRe) ?? [], ); expect(seenUrls).toContain(docsUrl); }); diff --git a/packages/cli/test/lib/init/git.test.ts b/packages/cli/test/lib/init/git.test.ts index e423873bd..6fdbfcfd8 100644 --- a/packages/cli/test/lib/init/git.test.ts +++ b/packages/cli/test/lib/init/git.test.ts @@ -4,7 +4,7 @@ */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as gitLib from "../../../src/lib/git.js"; import { checkGitStatus, @@ -95,7 +95,7 @@ describe("checkGitStatus", () => { expect(result).toBe(true); const confirmCall = calls.find((c) => c.kind === "confirm"); expect(confirmCall?.kind === "confirm" && confirmCall.message).toContain( - "not inside a git repository" + "not inside a git repository", ); }); @@ -142,7 +142,7 @@ describe("checkGitStatus", () => { expect(lastWarn(calls)).toContain("uncommitted"); const confirmCall = calls.find((c) => c.kind === "confirm"); expect(confirmCall?.kind === "confirm" && confirmCall.message).toContain( - "uncommitted changes" + "uncommitted changes", ); }); diff --git a/packages/cli/test/lib/init/init-service-auth.test.ts b/packages/cli/test/lib/init/init-service-auth.test.ts index d7af0d44e..17f8361b8 100644 --- a/packages/cli/test/lib/init/init-service-auth.test.ts +++ b/packages/cli/test/lib/init/init-service-auth.test.ts @@ -8,7 +8,7 @@ const ENDPOINT = "/api/workflows/sentry-wizard/resume-async"; function authFailure( code: string, safeToRetry = true, - status = 503 + status = 503, ): MastraClientError { return new MastraClientError(status, "Service Unavailable", "auth failed", { code, @@ -17,28 +17,28 @@ function authFailure( } describe("withInitServiceAuthClassification", () => { - test.each([ - "AUTH_UPSTREAM_TIMEOUT", - "AUTH_UPSTREAM_UNAVAILABLE", - ])("retries %s once", async (code) => { - const failure = authFailure(code); - const operation = vi - .fn<() => Promise>() - .mockRejectedValueOnce(failure) - .mockResolvedValueOnce("ok"); + test.each(["AUTH_UPSTREAM_TIMEOUT", "AUTH_UPSTREAM_UNAVAILABLE"])( + "retries %s once", + async (code) => { + const failure = authFailure(code); + const operation = vi + .fn<() => Promise>() + .mockRejectedValueOnce(failure) + .mockResolvedValueOnce("ok"); - await expect( - withInitServiceAuthClassification(operation, ENDPOINT) - ).resolves.toBe("ok"); - expect(operation).toHaveBeenCalledTimes(2); - }); + await expect( + withInitServiceAuthClassification(operation, ENDPOINT), + ).resolves.toBe("ok"); + expect(operation).toHaveBeenCalledTimes(2); + }, + ); test("stops after one retry when the auth service remains unavailable", async () => { const failure = authFailure("AUTH_UPSTREAM_TIMEOUT"); const operation = vi.fn<() => Promise>().mockRejectedValue(failure); await expect( - withInitServiceAuthClassification(operation, ENDPOINT) + withInitServiceAuthClassification(operation, ENDPOINT), ).rejects.toBe(failure); expect(operation).toHaveBeenCalledTimes(2); }); @@ -49,13 +49,13 @@ describe("withInitServiceAuthClassification", () => { .mockRejectedValueOnce(authFailure("AUTH_UPSTREAM_TIMEOUT")) .mockRejectedValueOnce( new Error( - 'HTTP error! status: 401 - {"error":"Unauthorized: invalid token"}' - ) + 'HTTP error! status: 401 - {"error":"Unauthorized: invalid token"}', + ), ); const error = await withInitServiceAuthClassification( operation, - ENDPOINT + ENDPOINT, ).catch((caught) => caught); expect(error).toBeInstanceOf(ApiError); @@ -76,7 +76,7 @@ describe("withInitServiceAuthClassification", () => { const operation = vi.fn<() => Promise>().mockRejectedValue(failure); await expect( - withInitServiceAuthClassification(operation, ENDPOINT) + withInitServiceAuthClassification(operation, ENDPOINT), ).rejects.toBe(failure); expect(operation).toHaveBeenCalledTimes(1); }); diff --git a/packages/cli/test/lib/init/interactive.test.ts b/packages/cli/test/lib/init/interactive.test.ts index 108b810c3..3fce6e21d 100644 --- a/packages/cli/test/lib/init/interactive.test.ts +++ b/packages/cli/test/lib/init/interactive.test.ts @@ -7,7 +7,7 @@ * terminal. */ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterEach, describe, expect, test, vi } from "vitest"; import { WizardError } from "../../../src/lib/errors.js"; @@ -17,7 +17,7 @@ import { CANCELLED } from "../../../src/lib/init/ui/types.js"; import { createMockUI } from "./ui/mock-ui.js"; function makeOptions( - overrides?: Partial + overrides?: Partial, ): InteractiveContext { return { yes: false, @@ -37,8 +37,8 @@ describe("handleInteractive dispatcher", () => { handleInteractive( { type: "interactive", prompt: "test", kind: "unknown" as "select" }, makeOptions(), - ui - ) + ui, + ), ).rejects.toBeInstanceOf(WizardError); }); }); @@ -54,12 +54,12 @@ describe("handleSelect", () => { options: ["my-app"], }, makeOptions({ yes: true }), - ui + ui, ); expect(result).toEqual({ selectedApp: "my-app" }); expect( - calls.some((c) => c.kind === "log.info" && c.message.includes("my-app")) + calls.some((c) => c.kind === "log.info" && c.message.includes("my-app")), ).toBe(true); }); @@ -77,8 +77,8 @@ describe("handleSelect", () => { ], }, makeOptions({ yes: true }), - ui - ) + ui, + ), ).rejects.toBeInstanceOf(WizardError); expect(calls.some((c) => c.kind === "log.error")).toBe(true); }); @@ -96,7 +96,7 @@ describe("handleSelect", () => { options: ["existing", "create"], }, makeOptions({ yes: true }), - ui + ui, ); expect(result).toEqual({ selectedApp: "create" }); }); @@ -112,8 +112,8 @@ describe("handleSelect", () => { options: [], }, makeOptions(), - ui - ) + ui, + ), ).rejects.toBeInstanceOf(WizardError); }); @@ -127,7 +127,7 @@ describe("handleSelect", () => { apps: [{ name: "express-app", path: "/app", framework: "Express" }], }, makeOptions({ yes: true }), - ui + ui, ); expect(result).toEqual({ selectedApp: "express-app" }); @@ -145,7 +145,7 @@ describe("handleSelect", () => { options: ["react", "vue"], }, makeOptions({ yes: false }), - ui + ui, ); expect(result).toEqual({ selectedApp: "vue" }); @@ -165,8 +165,8 @@ describe("handleSelect", () => { options: ["react", "vue"], }, makeOptions({ yes: false }), - ui - ) + ui, + ), ).rejects.toThrow("Setup cancelled"); }); }); @@ -185,12 +185,12 @@ describe("handleSelect with --app flag", () => { ], }, makeOptions({ yes: true, app: "web" }), - ui + ui, ); expect(result).toEqual({ selectedApp: "web" }); expect( - calls.some((c) => c.kind === "log.info" && c.message.includes("web")) + calls.some((c) => c.kind === "log.info" && c.message.includes("web")), ).toBe(true); }); @@ -204,7 +204,7 @@ describe("handleSelect with --app flag", () => { apps: [{ name: "Web", path: "/repo/apps/web" }], }, makeOptions({ app: "WEB" }), - ui + ui, ); expect(result).toEqual({ selectedApp: "Web" }); @@ -224,8 +224,8 @@ describe("handleSelect with --app flag", () => { ], }, makeOptions({ yes: true, app: "missing" }), - ui - ) + ui, + ), ).rejects.toBeInstanceOf(WizardError); const errorCall = calls.find((c) => c.kind === "log.error"); expect(errorCall?.message).toContain("missing"); @@ -245,7 +245,7 @@ describe("handleSelect with --app flag", () => { options: ["existing", "create"], }, makeOptions({ app: "web" }), - ui + ui, ); expect(result).toEqual({ selectedApp: "existing" }); @@ -265,8 +265,8 @@ describe("handleSelect with --app flag", () => { ], }, makeOptions({ yes: true }), - ui - ) + ui, + ), ).rejects.toBeInstanceOf(WizardError); const errorCall = calls.find((c) => c.kind === "log.error"); expect(errorCall?.message).toContain("web"); @@ -295,16 +295,16 @@ describe("handleMultiSelect", () => { ], }, makeOptions(), - ui + ui, ); expect(setTagSpy).toHaveBeenCalledWith( "wizard.features.offered", - "errorMonitoring,performanceMonitoring,sessionReplay" + "errorMonitoring,performanceMonitoring,sessionReplay", ); expect(setTagSpy).toHaveBeenCalledWith( "wizard.features.selected", - "errorMonitoring,sessionReplay" + "errorMonitoring,sessionReplay", ); }); @@ -323,7 +323,7 @@ describe("handleMultiSelect", () => { ], }, makeOptions({ yes: true }), - ui + ui, ); expect(result.features).toEqual([ @@ -345,7 +345,7 @@ describe("handleMultiSelect", () => { availableFeatures: [], }, makeOptions(), - ui + ui, ); expect(result).toEqual({ features: ["errorMonitoring"] }); @@ -366,7 +366,7 @@ describe("handleMultiSelect", () => { availableFeatures: ["sessionReplay", "performanceMonitoring"], }, makeOptions(), - ui + ui, ); expect(result).toEqual({ @@ -394,7 +394,7 @@ describe("handleMultiSelect", () => { ], }, makeOptions({ yes: false }), - ui + ui, ); const features = result.features as string[]; @@ -416,16 +416,16 @@ describe("handleMultiSelect", () => { availableFeatures: ["errorMonitoring", "performanceMonitoring"], }, makeOptions({ yes: false }), - ui - ) + ui, + ), ).rejects.toThrow("Setup cancelled"); expect(setTagSpy).toHaveBeenCalledWith( "wizard.features.offered", - "errorMonitoring,performanceMonitoring" + "errorMonitoring,performanceMonitoring", ); expect(setTagSpy).not.toHaveBeenCalledWith( "wizard.features.selected", - expect.anything() + expect.anything(), ); }); @@ -441,7 +441,7 @@ describe("handleMultiSelect", () => { availableFeatures: ["errorMonitoring"], }, makeOptions({ yes: false }), - ui + ui, ); expect(result).toEqual({ features: ["errorMonitoring"] }); @@ -450,7 +450,7 @@ describe("handleMultiSelect", () => { expect(multiselectCall?.initialValues).toEqual(["errorMonitoring"]); const reviewCall = calls.find((call) => call.kind === "select"); expect(reviewCall?.details?.map((detail) => detail.text)).toContain( - "✓ Error Monitoring" + "✓ Error Monitoring", ); }); @@ -467,7 +467,7 @@ describe("handleMultiSelect", () => { availableFeatures: ["errorMonitoring", "performanceMonitoring"], }, makeOptions({ yes: false }), - ui + ui, ); const multiselectCall = calls.find((c) => c.kind === "multiselect") as @@ -482,8 +482,8 @@ describe("handleMultiSelect", () => { ]); expect( multiselectCall?.optionDetails.find( - (option) => option.value === "errorMonitoring" - ) + (option) => option.value === "errorMonitoring", + ), ).toMatchObject({ description: "Automatically capture exceptions and stack traces", locked: true, @@ -516,7 +516,7 @@ describe("handleMultiSelect", () => { ], }, makeOptions({ yes: false }), - ui + ui, ); expect(result.features).toEqual(["errorMonitoring", "sessionReplay"]); @@ -578,14 +578,14 @@ describe("handleMultiSelect", () => { ], }, makeOptions(), - ui + ui, ); expect(result).toEqual({ features: ["errorMonitoring", "performanceMonitoring"], }); const multiselectCalls = calls.filter( - (call) => call.kind === "multiselect" + (call) => call.kind === "multiselect", ); expect(multiselectCalls).toHaveLength(2); expect(multiselectCalls[1]?.initialValues).toEqual([ @@ -594,18 +594,18 @@ describe("handleMultiSelect", () => { ]); const reviewCalls = calls.filter((call) => call.kind === "select"); expect(reviewCalls[0]?.details?.map((detail) => detail.text)).toContain( - "✓ Session Replay" + "✓ Session Replay", ); expect(reviewCalls[0]?.details?.map((detail) => detail.text)).not.toContain( - "✓ Tracing" + "✓ Tracing", ); expect( reviewCalls[0]?.details ?.map((detail) => detail.text) - .filter((line) => line.startsWith("✓ ")) + .filter((line) => line.startsWith("✓ ")), ).toEqual(["✓ Error Monitoring", "✓ Session Replay"]); expect(reviewCalls[1]?.details?.map((detail) => detail.text)).toContain( - "✓ Tracing" + "✓ Tracing", ); expect(reviewCalls[1]?.options).toEqual(["continue", "back"]); }); @@ -614,35 +614,42 @@ describe("handleMultiSelect", () => { ["aiMonitoring", "Agent Tracing"], ["mcpObservability", "MCP Observability"], ["profiling", "Profiling"], - ])("review includes tracing when %s enables it implicitly", async (dependencyFeature, dependencyLabel) => { - const { ui, calls, respond } = createMockUI(); - respond.multiselect([dependencyFeature]); - respond.select("continue"); + ])( + "review includes tracing when %s enables it implicitly", + async (dependencyFeature, dependencyLabel) => { + const { ui, calls, respond } = createMockUI(); + respond.multiselect([dependencyFeature]); + respond.select("continue"); + + const result = await handleInteractive( + { + type: "interactive", + prompt: "Select features", + kind: "multi-select", + availableFeatures: [ + "errorMonitoring", + "performanceMonitoring", + dependencyFeature, + ], + }, + makeOptions(), + ui, + ); - const result = await handleInteractive( - { - type: "interactive", - prompt: "Select features", - kind: "multi-select", - availableFeatures: [ + expect(result).toEqual({ + features: [ "errorMonitoring", "performanceMonitoring", dependencyFeature, ], - }, - makeOptions(), - ui - ); - - expect(result).toEqual({ - features: ["errorMonitoring", "performanceMonitoring", dependencyFeature], - }); - const reviewCall = calls.find((call) => call.kind === "select"); - const reviewDetails = reviewCall?.details?.map((detail) => detail.text); - expect(reviewDetails).toContain("✓ Error Monitoring"); - expect(reviewDetails).toContain("✓ Tracing"); - expect(reviewDetails).toContain(`✓ ${dependencyLabel}`); - }); + }); + const reviewCall = calls.find((call) => call.kind === "select"); + const reviewDetails = reviewCall?.details?.map((detail) => detail.text); + expect(reviewDetails).toContain("✓ Error Monitoring"); + expect(reviewDetails).toContain("✓ Tracing"); + expect(reviewDetails).toContain(`✓ ${dependencyLabel}`); + }, + ); test("Back restores the normalized AI selection including Tracing", async () => { const { ui, calls, respond } = createMockUI(); @@ -663,11 +670,11 @@ describe("handleMultiSelect", () => { ], }, makeOptions(), - ui + ui, ); const multiselectCalls = calls.filter( - (call) => call.kind === "multiselect" + (call) => call.kind === "multiselect", ); expect(multiselectCalls[1]?.initialValues).toEqual([ "errorMonitoring", @@ -687,7 +694,7 @@ describe("handleConfirm", () => { kind: "confirm", }, makeOptions({ yes: true }), - ui + ui, ); expect(result).toEqual({ action: "continue" }); @@ -705,8 +712,8 @@ describe("handleConfirm", () => { kind: "confirm", }, makeOptions({ yes: false }), - ui - ) + ui, + ), ).rejects.toThrow("Setup cancelled"); }); @@ -721,7 +728,7 @@ describe("handleConfirm", () => { kind: "confirm", }, makeOptions({ yes: false }), - ui + ui, ); expect(result).toEqual({ action: "stop" }); diff --git a/packages/cli/test/lib/init/preflight.test.ts b/packages/cli/test/lib/init/preflight.test.ts index b471a84b1..ee795beea 100644 --- a/packages/cli/test/lib/init/preflight.test.ts +++ b/packages/cli/test/lib/init/preflight.test.ts @@ -12,7 +12,7 @@ vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); @@ -20,7 +20,7 @@ vi.mock("../../../src/lib/scope-recovery.js", () => ({ captureOAuthScopeRecoveryGate: vi.fn(), })); -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as apiClient from "../../../src/lib/api-client.js"; vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { @@ -30,11 +30,11 @@ vi.mock("../../../src/lib/db/auth.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as auth from "../../../src/lib/db/auth.js"; vi.mock("../../../src/lib/dsn/index.js", async (importOriginal) => { @@ -44,11 +44,11 @@ vi.mock("../../../src/lib/dsn/index.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as dsnIndex from "../../../src/lib/dsn/index.js"; import { ApiError, @@ -66,16 +66,16 @@ vi.mock("../../../src/lib/init/org-prefetch.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as prefetch from "../../../src/lib/init/org-prefetch.js"; import { resolveInitContext } from "../../../src/lib/init/preflight.js"; import type { WizardOptions } from "../../../src/lib/init/types.js"; import { CANCELLED } from "../../../src/lib/init/ui/types.js"; -// biome-ignore lint/performance/noNamespaceImport: scope decision is mocked at the module boundary +// oxlint-disable-next-line sentry-cli/no-namespace-import -- scope decision is mocked at the module boundary import * as scopeRecovery from "../../../src/lib/scope-recovery.js"; vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { @@ -85,11 +85,11 @@ vi.mock("../../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as resolveTarget from "../../../src/lib/resolve-target.js"; vi.mock("../../../src/lib/resolve-team.js", async (importOriginal) => { @@ -99,11 +99,11 @@ vi.mock("../../../src/lib/resolve-team.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as resolveTeam from "../../../src/lib/resolve-team.js"; import { createMockUI, type MockCall } from "./ui/mock-ui.js"; @@ -119,7 +119,8 @@ function makeOptions(overrides?: Partial): WizardOptions { function feedbackOutcomes(calls: MockCall[]): string[] { return calls .filter( - (c): c is Extract => c.kind === "feedback" + (c): c is Extract => + c.kind === "feedback", ) .map((c) => c.outcome); } @@ -233,7 +234,7 @@ describe("resolveInitContext", () => { orgSlug: "acme", projectSlug: "my-app", }), - }) + }), ); expect(getProjectSpy).toHaveBeenCalledTimes(1); expect(tryGetPrimaryDsnSpy).toHaveBeenCalledTimes(1); @@ -262,7 +263,7 @@ describe("resolveInitContext", () => { org: "acme", project: "my-app", team: "platform", - }) + }), ); expect(context?.existingProject).toBeUndefined(); }); @@ -297,7 +298,7 @@ describe("resolveInitContext", () => { expect.objectContaining({ orgSlug: "acme", projectSlug: "my-app", - }) + }), ); expect(getProjectSpy).toHaveBeenCalledTimes(2); expect(tryGetPrimaryDsnSpy).toHaveBeenCalledTimes(1); @@ -321,7 +322,7 @@ describe("resolveInitContext", () => { const context = await resolveInitContext( makeOptions({ yes: false, project: "my-app" }), - ui + ui, ); expect(context?.project).toBe("my-app"); @@ -334,7 +335,7 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); const context = await resolveInitContext( makeOptions({ yes: false, project: "my-app" }), - ui + ui, ); expect(context?.project).toBe("my-app"); @@ -366,7 +367,7 @@ describe("resolveInitContext", () => { const context = await resolveInitContext( makeOptions({ yes: false, project: "my-app" }), - ui + ui, ); expect(context?.project).toBeUndefined(); @@ -382,7 +383,7 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); const context = await resolveInitContext( makeOptions({ team: "backend", yes: false }), - ui + ui, ); expect(context?.team).toBe("backend"); @@ -391,7 +392,7 @@ describe("resolveInitContext", () => { expect.objectContaining({ team: "backend", deferAutoCreateOnEmptyOrg: true, - }) + }), ); }); @@ -499,7 +500,7 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); await expect( - resolveInitContext(makeOptions({ yes: true }), ui) + resolveInitContext(makeOptions({ yes: true }), ui), ).rejects.toThrow("Multiple organizations found (a-org, z-org)."); }); @@ -518,7 +519,7 @@ describe("resolveInitContext", () => { expect(context).toBeNull(); const cancelCall = calls.find((c) => c.kind === "cancel"); expect(cancelCall?.kind === "cancel" && cancelCall.message).toBe( - "Setup cancelled." + "Setup cancelled.", ); expect(feedbackOutcomes(calls)).toEqual(["cancelled"]); }); @@ -529,18 +530,18 @@ describe("resolveInitContext", () => { new ApiError( "Failed to list organizations", 403, - "You do not have permission." - ) + "You do not have permission.", + ), ); const { ui, calls } = createMockUI(); await expect( - resolveInitContext(makeOptions({ yes: true }), ui) + resolveInitContext(makeOptions({ yes: true }), ui), ).rejects.toThrow("403 Forbidden"); const errorCall = calls.find( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ); expect(errorCall?.message).toContain("403 Forbidden"); expect(errorCall?.message).toContain("sentry init /"); @@ -550,7 +551,7 @@ describe("resolveInitContext", () => { const error = new ApiError( "Failed to list organizations", 403, - "Missing org:read" + "Missing org:read", ); resolveOrgPrefetchedSpy.mockResolvedValue(null); listOrganizationsSpy.mockRejectedValueOnce(error); @@ -559,24 +560,24 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); await expect( - resolveInitContext(makeOptions({ yes: false }), ui) + resolveInitContext(makeOptions({ yes: false }), ui), ).rejects.toBe(error); }); test("surfaces 401 guidance when listOrganizations is unauthorized", async () => { resolveOrgPrefetchedSpy.mockResolvedValue(null); listOrganizationsSpy.mockRejectedValueOnce( - new ApiError("Failed to list organizations", 401, "Token expired") + new ApiError("Failed to list organizations", 401, "Token expired"), ); const { ui, calls } = createMockUI(); await expect( - resolveInitContext(makeOptions({ yes: true }), ui) + resolveInitContext(makeOptions({ yes: true }), ui), ).rejects.toThrow("401 Unauthorized"); const errorCall = calls.find( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ); expect(errorCall?.message).toContain("401 Unauthorized"); expect(errorCall?.message).toContain("Token expired"); @@ -598,7 +599,7 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); const context = await resolveInitContext( makeOptions({ team: "backend" }), - ui + ui, ); expect(context?.isExplicitTeam).toBe(true); @@ -614,13 +615,13 @@ describe("resolveInitContext", () => { test("keeps the org-scoped fallback for an unattended explicit-team 403", async () => { resolveOrCreateTeamSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No team:admin access") + new ApiError("Forbidden", 403, "No team:admin access"), ); const { ui } = createMockUI(); const context = await resolveInitContext( makeOptions({ team: "backend", yes: true }), - ui + ui, ); expect(context?.team).toBeUndefined(); @@ -634,13 +635,13 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); await expect( - resolveInitContext(makeOptions({ team: "backend", yes: false }), ui) + resolveInitContext(makeOptions({ team: "backend", yes: false }), ui), ).rejects.toBe(error); }); test("swallows 403 from listTeams and resolves context with team:undefined", async () => { listTeamsSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No team:read access") + new ApiError("Forbidden", 403, "No team:read access"), ); const { ui } = createMockUI(); @@ -661,7 +662,7 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); await expect( - resolveInitContext(makeOptions({ yes: false }), ui) + resolveInitContext(makeOptions({ yes: false }), ui), ).rejects.toBe(error); expect(shouldDelegateScopeRecovery).toHaveBeenCalledWith(error, { unattended: false, @@ -676,13 +677,13 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); await expect( - resolveInitContext(makeOptions({ yes: false }), ui) + resolveInitContext(makeOptions({ yes: false }), ui), ).rejects.toBe(error); }); test("keeps the org-scoped fallback when OAuth recovery is unattended", async () => { listTeamsSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No team:read access") + new ApiError("Forbidden", 403, "No team:read access"), ); const { ui } = createMockUI(); @@ -693,7 +694,7 @@ describe("resolveInitContext", () => { expect.any(ApiError), { unattended: true, - } + }, ); }); @@ -704,17 +705,17 @@ describe("resolveInitContext", () => { { id: "2", slug: "beta", name: "Beta" }, ]); listTeamsSpy.mockRejectedValueOnce( - new ApiError("Not found", 404, "Organization not found") + new ApiError("Not found", 404, "Organization not found"), ); const { ui, calls } = createMockUI(); await expect(resolveInitContext(makeOptions(), ui)).rejects.toThrow( - "Organization 'missing-org'" + "Organization 'missing-org'", ); const errorCall = calls.find( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ); expect(errorCall?.message).toContain("Your organizations:"); expect(errorCall?.message).toContain("acme"); @@ -728,8 +729,8 @@ describe("resolveInitContext", () => { new ApiError( "Failed to list teams", 401, - "Your account is disabled in this organization because it is over its member limit." - ) + "Your account is disabled in this organization because it is over its member limit.", + ), ); const { ui, calls } = createMockUI(); @@ -737,7 +738,7 @@ describe("resolveInitContext", () => { const errorCall = calls.find( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ); expect(errorCall?.message).toContain("over its member limit"); }); @@ -747,35 +748,35 @@ describe("resolveInitContext", () => { new ApiError( "Failed to list teams", 401, - "Your account is disabled in this organization because it is over its member limit." - ) + "Your account is disabled in this organization because it is over its member limit.", + ), ); const { ui, calls } = createMockUI(); await expect( - resolveInitContext(makeOptions({ team: "backend" }), ui) + resolveInitContext(makeOptions({ team: "backend" }), ui), ).rejects.toThrow(); const errorCall = calls.find( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ); expect(errorCall?.message).toContain("over its member limit"); }); test("passes a pre-rendered WizardError through team resolution unchanged", async () => { listTeamsSpy.mockRejectedValueOnce( - new WizardError("custom preflight failure") + new WizardError("custom preflight failure"), ); const { ui, calls } = createMockUI(); await expect(resolveInitContext(makeOptions(), ui)).rejects.toThrow( - "custom preflight failure" + "custom preflight failure", ); const errorCall = calls.find( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ); expect(errorCall?.message).toBe("custom preflight failure"); }); @@ -783,35 +784,38 @@ describe("resolveInitContext", () => { test.each([ ["AuthError", new AuthError("expired")], ["HostScopeError", new HostScopeError("host mismatch")], - ])("propagates %s without turning it into a wizard failure", async (_, error) => { - listTeamsSpy.mockRejectedValueOnce(error); - - const { ui, calls } = createMockUI(); - await expect(resolveInitContext(makeOptions(), ui)).rejects.toBe(error); - - expect(calls.some((call) => call.kind === "log.error")).toBe(false); - expect(calls.some((call) => call.kind === "cancel")).toBe(false); - expect(calls.some((call) => call.kind === "feedback")).toBe(false); - }); + ])( + "propagates %s without turning it into a wizard failure", + async (_, error) => { + listTeamsSpy.mockRejectedValueOnce(error); + + const { ui, calls } = createMockUI(); + await expect(resolveInitContext(makeOptions(), ui)).rejects.toBe(error); + + expect(calls.some((call) => call.kind === "log.error")).toBe(false); + expect(calls.some((call) => call.kind === "cancel")).toBe(false); + expect(calls.some((call) => call.kind === "feedback")).toBe(false); + }, + ); test("surfaces a non-API error message from implicit team resolution", async () => { listTeamsSpy.mockRejectedValueOnce(new Error("network down")); const { ui, calls } = createMockUI(); await expect(resolveInitContext(makeOptions(), ui)).rejects.toThrow( - "network down" + "network down", ); const errorCall = calls.find( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ); expect(errorCall?.message).toContain("network down"); }); test("fails early when listTeams is forbidden and member project creation is disabled", async () => { listTeamsSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No team:read access") + new ApiError("Forbidden", 403, "No team:read access"), ); getOrganizationSpy.mockResolvedValueOnce({ id: "1", @@ -823,7 +827,7 @@ describe("resolveInitContext", () => { const { ui } = createMockUI(); await expect(resolveInitContext(makeOptions(), ui)).rejects.toThrow( - "Project creation is disabled for members" + "Project creation is disabled for members", ); }); @@ -839,12 +843,12 @@ describe("resolveInitContext", () => { const { ui, calls } = createMockUI(); await expect(resolveInitContext(makeOptions(), ui)).rejects.toThrow( - "Project creation is disabled for members" + "Project creation is disabled for members", ); const errorCall = calls.find( (c): c is Extract => - c.kind === "log.error" + c.kind === "log.error", ); expect(errorCall?.message).toContain("sentry init acme/"); }); @@ -899,7 +903,7 @@ describe("resolveInitContext", () => { test("allows org-scoped creation when listTeams returns 403 and user has project:admin scope", async () => { listTeamsSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No team:read access") + new ApiError("Forbidden", 403, "No team:read access"), ); getOrganizationSpy.mockResolvedValueOnce({ id: "1", diff --git a/packages/cli/test/lib/init/spinner.test.ts b/packages/cli/test/lib/init/spinner.test.ts index b2efcb0c0..b4df3658f 100644 --- a/packages/cli/test/lib/init/spinner.test.ts +++ b/packages/cli/test/lib/init/spinner.test.ts @@ -2,11 +2,11 @@ import { Writable } from "node:stream"; import { describe, expect, test } from "vitest"; import { createWizardSpinner } from "../../../src/lib/init/spinner.js"; -// biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escape sequences in rendered terminal output +// oxlint-disable-next-line no-control-regex -- matching ANSI escape sequences in rendered terminal output const ANSI_CSI_RE = /\u001B\[[0-9;?]*[ -/]*[@-~]/g; -// biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escape sequences in rendered terminal output +// oxlint-disable-next-line no-control-regex -- matching ANSI escape sequences in rendered terminal output const ANSI_OSC_RE = /\u001B\][^\u0007]*\u0007/g; -// biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI clear-screen escape sequence in rendered terminal output +// oxlint-disable-next-line no-control-regex -- matching ANSI clear-screen escape sequence in rendered terminal output const ANSI_CLEAR_RE = /\u001B\[(?:0)?J/; class CaptureStream extends Writable { @@ -22,7 +22,7 @@ class CaptureStream extends Writable { _write( chunk: string | Buffer, _encoding: BufferEncoding, - callback: (error?: Error | null) => void + callback: (error?: Error | null) => void, ): void { this.chunks.push(String(chunk)); callback(); diff --git a/packages/cli/test/lib/init/stdin-reopen.test.ts b/packages/cli/test/lib/init/stdin-reopen.test.ts index 094550034..6f747d2cc 100644 --- a/packages/cli/test/lib/init/stdin-reopen.test.ts +++ b/packages/cli/test/lib/init/stdin-reopen.test.ts @@ -383,7 +383,7 @@ describe("forwardFreshTtyToStdin → closeFreshTtyForwarding round trip", () => try { expect( - (process.stdin as { readableFlowing?: boolean | null }).readableFlowing + (process.stdin as { readableFlowing?: boolean | null }).readableFlowing, ).toBe(true); const handle = forwardFreshTtyToStdin({ @@ -398,7 +398,7 @@ describe("forwardFreshTtyToStdin → closeFreshTtyForwarding round trip", () => // (paused). After teardown we must be NOT true — otherwise the // libuv event loop stays alive. expect( - (process.stdin as { readableFlowing?: boolean | null }).readableFlowing + (process.stdin as { readableFlowing?: boolean | null }).readableFlowing, ).not.toBe(true); } finally { // Defensive: ensure we don't leak a flowing-mode stdin into the diff --git a/packages/cli/test/lib/init/tools/create-sentry-project.test.ts b/packages/cli/test/lib/init/tools/create-sentry-project.test.ts index 9f898ca4d..5c921a938 100644 --- a/packages/cli/test/lib/init/tools/create-sentry-project.test.ts +++ b/packages/cli/test/lib/init/tools/create-sentry-project.test.ts @@ -7,7 +7,7 @@ vi.mock("../../../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); @@ -15,7 +15,7 @@ vi.mock("../../../../src/lib/scope-recovery.js", () => ({ captureOAuthScopeRecoveryGate: vi.fn(), })); -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as apiClient from "../../../../src/lib/api-client.js"; import { ApiError } from "../../../../src/lib/errors.js"; import { @@ -27,7 +27,7 @@ import type { CreateSentryProjectPayload, EnsureSentryProjectPayload, } from "../../../../src/lib/init/types.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as scopeRecovery from "../../../../src/lib/scope-recovery.js"; vi.mock("../../../../src/lib/resolve-team.js", async (importOriginal) => { @@ -39,16 +39,16 @@ vi.mock("../../../../src/lib/resolve-team.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as resolveTeam from "../../../../src/lib/resolve-team.js"; function makePayload( overrides?: Partial, - operation: CreateSentryProjectPayload["operation"] = "create-sentry-project" + operation: CreateSentryProjectPayload["operation"] = "create-sentry-project", ): CreateSentryProjectPayload { return { type: "tool", @@ -63,7 +63,7 @@ function makePayload( } function makeEnsurePayload( - overrides?: Partial + overrides?: Partial, ): EnsureSentryProjectPayload { return { ...makePayload(overrides), @@ -215,7 +215,7 @@ describe("createSentryProject", () => { expect.objectContaining({ name: "my-app", platform: "javascript-react", - }) + }), ); }); @@ -266,7 +266,7 @@ describe("createSentryProject", () => { expect.objectContaining({ orgSlug: "acme", projectId: "(dry-run)", - }) + }), ); expect(createProjectWithDsnSpy).not.toHaveBeenCalled(); }); @@ -299,8 +299,8 @@ describe("createSentryProject", () => { 403, "Your organization has disabled this feature for members.", undefined, - true - ) + true, + ), ); const result = await createSentryProject(makePayload(), { @@ -320,7 +320,7 @@ describe("createSentryProject", () => { test("tool describe uses payload.detail when provided", () => { const payload = { ...makePayload(), detail: "Setting up my-app..." }; expect(createSentryProjectTool.describe(payload)).toBe( - "Setting up my-app..." + "Setting up my-app...", ); }); @@ -331,7 +331,7 @@ describe("createSentryProject", () => { test("falls back to org-scoped endpoint on 403 from team-based creation", async () => { getProjectSpy.mockRejectedValueOnce(new ApiError("Not found", 404)); createProjectWithDsnSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No project:write access") + new ApiError("Forbidden", 403, "No project:write access"), ); const result = await createSentryProject(makePayload(), { @@ -352,7 +352,7 @@ describe("createSentryProject", () => { test("suppresses fallback when team was set via --team (isExplicitTeam)", async () => { getProjectSpy.mockRejectedValueOnce(new ApiError("Not found", 404)); createProjectWithDsnSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No project:write access") + new ApiError("Forbidden", 403, "No project:write access"), ); const result = await createSentryProject(makePayload(), { @@ -372,7 +372,7 @@ describe("createSentryProject", () => { shouldDelegateScopeRecovery.mockResolvedValueOnce(true); getProjectSpy.mockRejectedValueOnce(new ApiError("Not found", 404)); createProjectWithAutoTeamSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No project:write access") + new ApiError("Forbidden", 403, "No project:write access"), ); const error = await executeTool(makePayload(), { @@ -389,14 +389,14 @@ describe("createSentryProject", () => { expect.any(ApiError), { unattended: false, - } + }, ); }); test("keeps the tool fallback when OAuth recovery is unattended", async () => { getProjectSpy.mockRejectedValueOnce(new ApiError("Not found", 404)); createProjectWithAutoTeamSpy.mockRejectedValueOnce( - new ApiError("Forbidden", 403, "No project:write access") + new ApiError("Forbidden", 403, "No project:write access"), ); const result = await createSentryProject(makePayload(), { @@ -412,7 +412,7 @@ describe("createSentryProject", () => { expect.any(ApiError), { unattended: true, - } + }, ); }); @@ -423,8 +423,8 @@ describe("createSentryProject", () => { new ApiError( "Forbidden", 403, - "Your organization has disabled this feature for members." - ) + "Your organization has disabled this feature for members.", + ), ); const result = await createSentryProject(makePayload(), { @@ -446,8 +446,8 @@ describe("createSentryProject", () => { new ApiError( "Forbidden", 403, - "Your organization has disabled this feature for members." - ) + "Your organization has disabled this feature for members.", + ), ); const result = await createSentryProject(makePayload(), { @@ -465,7 +465,7 @@ describe("createSentryProject", () => { test("surfaces friendly 409 error when fallback project already exists", async () => { createProjectWithAutoTeamSpy.mockRejectedValueOnce( - new ApiError("Conflict", 409, "Slug already in use") + new ApiError("Conflict", 409, "Slug already in use"), ); getProjectSpy.mockRejectedValueOnce(new ApiError("Not found", 404)); diff --git a/packages/cli/test/lib/init/tools/file-changes.test.ts b/packages/cli/test/lib/init/tools/file-changes.test.ts index eba19d30d..98bf73049 100644 --- a/packages/cli/test/lib/init/tools/file-changes.test.ts +++ b/packages/cli/test/lib/init/tools/file-changes.test.ts @@ -50,7 +50,7 @@ describe("apply file changes", () => { path: "missing.txt", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result).toMatchObject({ @@ -71,7 +71,7 @@ describe("apply file changes", () => { request(directory, [ { action: "create", patch: "replacement\n", path: "existing.txt" }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result).toMatchObject({ @@ -86,7 +86,7 @@ describe("apply file changes", () => { request(directory, [ { action: "create", patch: "preview\n", path: "preview.txt" }, ]), - { authToken: undefined, dryRun: true } + { authToken: undefined, dryRun: true }, ); const invalid = await applyPatchset( request(directory, [ @@ -96,7 +96,7 @@ describe("apply file changes", () => { path: "missing.txt", }, ]), - { authToken: undefined, dryRun: true } + { authToken: undefined, dryRun: true }, ); expect(valid).toMatchObject({ data: { dryRun: true }, ok: true }); @@ -135,7 +135,7 @@ describe("apply file changes", () => { path: "setup.ts", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result).toMatchObject({ @@ -157,12 +157,12 @@ describe("apply file changes", () => { path: "config.ts", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result.ok).toBe(true); expect(readFileSync(target, "utf-8")).toBe( - "\uFEFFfirst\r\nchanged\r\nthird\r\n" + "\uFEFFfirst\r\nchanged\r\nthird\r\n", ); }); @@ -248,7 +248,7 @@ describe("apply file changes", () => { ok: false, }); expect(readFileSync(path.join(directory, "first.txt"), "utf-8")).toBe( - "first\n" + "first\n", ); }); @@ -273,7 +273,7 @@ describe("apply file changes", () => { const result = await applyPreparedFileChanges( prepared.changes, - false + false, ).finally(() => chmodSync(blockedDirectory, 0o700)); expect(result).toMatchObject({ @@ -284,7 +284,7 @@ describe("apply file changes", () => { ok: false, }); expect(existsSync(path.join(blockedDirectory, "second.txt"))).toBe(false); - } + }, ); test("injects auth locally without returning it in tool data", async () => { @@ -296,27 +296,27 @@ describe("apply file changes", () => { path: ".env.sentry-build-plugin", }, ]), - { authToken: AUTH_TOKEN, dryRun: false } + { authToken: AUTH_TOKEN, dryRun: false }, ); expect(result.ok).toBe(true); expect(JSON.stringify(result)).not.toContain(AUTH_TOKEN); expect( - readFileSync(path.join(directory, ".env.sentry-build-plugin"), "utf-8") + readFileSync(path.join(directory, ".env.sentry-build-plugin"), "utf-8"), ).toContain(AUTH_TOKEN); }); test("rejects malformed and duplicate file-change requests", async () => { const malformed = await applyPatchset( request(directory, [{ action: "modify", edits: [], path: "config.ts" }]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); const duplicate = await applyPatchset( request(directory, [ { action: "create", patch: "first", path: "same.txt" }, { action: "create", patch: "second", path: "same.txt" }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(malformed).toMatchObject({ ok: false }); @@ -340,7 +340,7 @@ describe("apply file changes", () => { path: "script.sh", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result.ok).toBe(true); @@ -362,7 +362,7 @@ describe("apply file changes", () => { expect(result.ok).toBe(true); expect(readFileSync(path.join(directory, "nested/file.txt"), "utf-8")).toBe( - "content\n" + "content\n", ); }); @@ -383,7 +383,7 @@ describe("apply file changes", () => { ok: false, }); expect(readFileSync(path.join(directory, "missing.txt"), "utf-8")).toBe( - "appeared\n" + "appeared\n", ); }); @@ -399,7 +399,7 @@ describe("apply file changes", () => { path: "duplicate.txt", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result).toMatchObject({ @@ -420,7 +420,7 @@ describe("apply file changes", () => { path: "overlap.txt", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); const absentNoOp = await applyPatchset( request(directory, [ @@ -430,7 +430,7 @@ describe("apply file changes", () => { path: "overlap.txt", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(overlapping).toMatchObject({ @@ -442,7 +442,7 @@ describe("apply file changes", () => { ok: false, }); expect(readFileSync(path.join(directory, "overlap.txt"), "utf-8")).toBe( - "aaa" + "aaa", ); }); @@ -452,7 +452,7 @@ describe("apply file changes", () => { { action: "create", patch: "file", path: "nested" }, { action: "create", patch: "child", path: "nested/child.txt" }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result).toMatchObject({ @@ -483,7 +483,7 @@ describe("apply file changes", () => { path: "config.ts", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result.ok).toBe(true); @@ -547,7 +547,7 @@ describe("apply file changes", () => { { action: "create", patch: "first\n", path: "real/file.txt" }, { action: "create", patch: "second\n", path: "alias/file.txt" }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result).toMatchObject({ @@ -578,7 +578,7 @@ describe("apply file changes", () => { path: "second.txt", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result).toMatchObject({ @@ -587,7 +587,7 @@ describe("apply file changes", () => { }); expect(readFileSync(first, "utf-8")).toBe("old\n"); expect(readFileSync(second, "utf-8")).toBe("old\n"); - } + }, ); test("uses exclusive create at apply time", async () => { @@ -624,15 +624,15 @@ describe("apply file changes", () => { }, { action: "delete", path: "delete.txt" }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result.ok).toBe(true); expect(readFileSync(path.join(directory, "create.txt"), "utf-8")).toBe( - "created\n" + "created\n", ); expect(readFileSync(path.join(directory, "modify.txt"), "utf-8")).toBe( - "new\n" + "new\n", ); expect(existsSync(path.join(directory, "delete.txt"))).toBe(false); }); @@ -670,7 +670,7 @@ describe("apply file changes", () => { path: "config.ts", }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result.ok).toBe(false); @@ -682,7 +682,7 @@ describe("apply file changes", () => { request(directory, [ { action: "create", patch: "outside", path: "../outside.txt" }, ]), - { authToken: undefined, dryRun: false } + { authToken: undefined, dryRun: false }, ); expect(result).toMatchObject({ diff --git a/packages/cli/test/lib/init/tools/filesystem-tools.test.ts b/packages/cli/test/lib/init/tools/filesystem-tools.test.ts index feb0ee416..ab23a9bd6 100644 --- a/packages/cli/test/lib/init/tools/filesystem-tools.test.ts +++ b/packages/cli/test/lib/init/tools/filesystem-tools.test.ts @@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as dsnIndex from "../../../../src/lib/dsn/index.js"; import { executeTool } from "../../../../src/lib/init/tools/registry.js"; import type { @@ -52,7 +52,7 @@ describe("filesystem tools", () => { test("rejects an external cwd symlink for every filesystem and shell tool", async () => { const outsideDir = fs.mkdtempSync( - path.join(path.dirname(testDir), "init-tools-outside-") + path.join(path.dirname(testDir), "init-tools-outside-"), ); const escapedCwd = path.join(testDir, "escape"); fs.writeFileSync(path.join(outsideDir, "sentinel.txt"), "OUTSIDE\n"); @@ -115,11 +115,11 @@ describe("filesystem tools", () => { const result = await executeTool(payload, makeContext(testDir)); expect(result.ok, payload.operation).toBe(false); expect(result.error, payload.operation).toContain( - "outside project directory" + "outside project directory", ); } expect(fs.existsSync(path.join(outsideDir, "created-by-tool.txt"))).toBe( - false + false, ); } finally { fs.rmSync(outsideDir, { recursive: true, force: true }); @@ -130,12 +130,12 @@ describe("filesystem tools", () => { fs.mkdirSync(path.join(testDir, "real", "nested"), { recursive: true }); fs.writeFileSync( path.join(testDir, "real", "nested", "inside.txt"), - "inside\n" + "inside\n", ); fs.symlinkSync( path.join(testDir, "real"), path.join(testDir, "alias"), - "dir" + "dir", ); const result = await executeTool( @@ -145,7 +145,7 @@ describe("filesystem tools", () => { cwd: path.join(testDir, "alias"), params: { paths: ["nested/inside.txt"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -172,7 +172,7 @@ describe("filesystem tools", () => { cwd, params: { path: "." }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.ok, cwd).toBe(false); expect(result.error, cwd).toContain("outside project directory"); @@ -187,7 +187,7 @@ describe("filesystem tools", () => { fs.mkdirSync(path.join(testDir, "src")); fs.writeFileSync( path.join(testDir, "src", "app.ts"), - "console.log('x');\n" + "console.log('x');\n", ); const result = await executeTool( @@ -197,7 +197,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { path: ".", recursive: true, maxDepth: 3 }, }, - makeContext(testDir) + makeContext(testDir), ); const entries = (result.data as { entries: Array<{ path: string }> }) .entries; @@ -224,7 +224,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { path: ".", recursive: true, maxDepth: 0, maxEntries: 100 }, }, - makeContext(testDir) + makeContext(testDir), ); const byEntries = await executeTool( { @@ -233,7 +233,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { path: ".", recursive: true, maxDepth: 10, maxEntries: 1 }, }, - makeContext(testDir) + makeContext(testDir), ); expect(byDepth.data).toMatchObject({ truncated: true }); @@ -254,7 +254,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { path: ".", recursive: true }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result).toMatchObject({ @@ -283,7 +283,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { path: "." }, }, - makeContext(testDir) + makeContext(testDir), ); const entry = ( result.data as { entries: Record[] } @@ -304,7 +304,7 @@ describe("filesystem tools", () => { openSpy.mockRestore(); readSpy.mockRestore(); } - } + }, ); test("reads files and checks existence in batches", async () => { @@ -320,7 +320,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); const existsResult = await executeTool( { @@ -329,7 +329,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: ["exists.txt", "missing.txt"] }, }, - makeContext(testDir) + makeContext(testDir), ); expect((readResult.data as any).files["exists.txt"]).toEqual({ @@ -348,7 +348,7 @@ describe("filesystem tools", () => { test("returns independent bounded V2 line ranges", async () => { const lines = Array.from( { length: 8 }, - (_, index) => `line-${index + 1}:${"x".repeat(19_990)}\n` + (_, index) => `line-${index + 1}:${"x".repeat(19_990)}\n`, ); fs.writeFileSync(path.join(testDir, "large.txt"), lines.join("")); @@ -362,7 +362,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); const later = await executeTool( { @@ -375,7 +375,7 @@ describe("filesystem tools", () => { startLine: 5, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(first.data).toEqual({ @@ -404,7 +404,7 @@ describe("filesystem tools", () => { const firstLine = `${"x".repeat(39_997)}\r\n`; fs.writeFileSync( path.join(testDir, "windows.txt"), - `${firstLine}second\r\n` + `${firstLine}second\r\n`, ); const result = await executeTool( @@ -417,7 +417,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -442,8 +442,8 @@ describe("filesystem tools", () => { buffer: Buffer, offset: number, length: number, - position: number | null - ) => actualHandle.read(buffer, offset, Math.min(length, 3), position) + position: number | null, + ) => actualHandle.read(buffer, offset, Math.min(length, 3), position), ); const openSpy = vi.spyOn(fs.promises, "open").mockResolvedValue({ close: vi.fn().mockResolvedValue(undefined), @@ -463,7 +463,7 @@ describe("filesystem tools", () => { startLine: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -491,7 +491,7 @@ describe("filesystem tools", () => { cwd: testDir, params: undefined, } as unknown as ToolPayload, - makeContext(testDir) + makeContext(testDir), ); expect(result).toEqual({ @@ -508,7 +508,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: ["package.json"] }, } as unknown as ToolPayload, - makeContext(testDir) + makeContext(testDir), ); expect(result).toEqual({ @@ -524,7 +524,7 @@ describe("filesystem tools", () => { fs.mkdirSync(path.join(testDir, "posix")); fs.writeFileSync( path.join(testDir, "dir with spaces", "nested dir", "file name.ts"), - "mixed separators\n" + "mixed separators\n", ); fs.writeFileSync(path.join(testDir, "posix", "file.ts"), "posix path\n"); @@ -538,7 +538,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -597,7 +597,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: [filePath], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result, filePath).toEqual({ @@ -623,7 +623,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result).toEqual({ @@ -636,12 +636,12 @@ describe("filesystem tools", () => { fs.writeFileSync(path.join(testDir, ".env.local"), "SECRET=value\n"); fs.writeFileSync( path.join(testDir, ".netrc"), - "machine example.test login user password secret\n" + "machine example.test login user password secret\n", ); fs.writeFileSync(path.join(testDir, ".pypirc"), "token=secret\n"); fs.writeFileSync( path.join(testDir, ".npmrc"), - "//registry/:_authToken=secret\n" + "//registry/:_authToken=secret\n", ); fs.writeFileSync(path.join(testDir, "binary.txt"), Buffer.from([0, 1, 2])); @@ -652,7 +652,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: [".env.local"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); const binary = await executeTool( { @@ -661,7 +661,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: ["binary.txt"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); const packageMetadata = await executeTool( { @@ -670,7 +670,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: [".pypirc"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); const netrc = await executeTool( { @@ -679,7 +679,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: [".netrc"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); const npmConfig = await executeTool( { @@ -688,7 +688,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: [".npmrc"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); expect(environment.data).toEqual({ files: { @@ -739,7 +739,7 @@ describe("filesystem tools", () => { test("rejects aliases to sensitive files", async () => { fs.writeFileSync( path.join(testDir, ".netrc"), - "machine example.test login user password secret\n" + "machine example.test login user password secret\n", ); fs.symlinkSync(".netrc", path.join(testDir, "credentials.txt")); fs.symlinkSync(".netrc", path.join(testDir, ".pypirc")); @@ -751,7 +751,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: ["credentials.txt"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); const sensitiveNameAlias = await executeTool( { @@ -760,7 +760,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: [".pypirc"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); expect(alias.data).toEqual({ @@ -790,7 +790,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: ["inside.txt"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -806,7 +806,7 @@ describe("filesystem tools", () => { test("finds later lines without exposing a cursor protocol", async () => { fs.writeFileSync( path.join(testDir, "deep.txt"), - `${"x".repeat(270_000)}\ntarget\n` + `${"x".repeat(270_000)}\ntarget\n`, ); const result = await executeTool( @@ -820,7 +820,7 @@ describe("filesystem tools", () => { startLine: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -848,7 +848,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths, resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); const overLimit = await executeTool( { @@ -860,7 +860,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); const files = ( result.data as { @@ -872,8 +872,8 @@ describe("filesystem tools", () => { expect( Object.values(files).reduce( (total, file) => total + Buffer.byteLength(file.content), - 0 - ) + 0, + ), ).toBe(40_000); expect(Object.values(files).every((file) => file.truncated)).toBe(true); expect(overLimit).toEqual({ @@ -894,7 +894,7 @@ describe("filesystem tools", () => { startLine: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result).toEqual({ @@ -914,7 +914,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: ["many-lines.txt"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -937,7 +937,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, } as ToolPayload, - makeContext(testDir) + makeContext(testDir), ); expect(result).toEqual({ @@ -957,11 +957,11 @@ describe("filesystem tools", () => { buffer: Buffer, offset: number, length: number, - position: number | null + position: number | null, ) => { requestedLengths.push(length); return actualHandle.read(buffer, offset, length, position); - } + }, ); const openSpy = vi.spyOn(fs.promises, "open").mockResolvedValue({ close: vi.fn().mockResolvedValue(undefined), @@ -981,7 +981,7 @@ describe("filesystem tools", () => { startLine: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -1012,17 +1012,17 @@ describe("filesystem tools", () => { buffer: Buffer, offset: number, length: number, - position: number | null + position: number | null, ) => { const result = await actualHandle.read( buffer, offset, length, - position + position, ); totalBytesRead += result.bytesRead; return result; - } + }, ); const openSpy = vi.spyOn(fs.promises, "open").mockResolvedValue({ close: vi.fn().mockResolvedValue(undefined), @@ -1041,7 +1041,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -1060,11 +1060,11 @@ describe("filesystem tools", () => { test("rejects invalid UTF-8 and incomplete multibyte lines", async () => { fs.writeFileSync( path.join(testDir, "invalid-utf8.txt"), - Buffer.from([0x61, 0x62, 0x63, 0xff]) + Buffer.from([0x61, 0x62, 0x63, 0xff]), ); fs.writeFileSync( path.join(testDir, "multibyte.txt"), - `${"é".repeat(20_001)}\n` + `${"é".repeat(20_001)}\n`, ); const read = (filePath: string) => @@ -1078,7 +1078,7 @@ describe("filesystem tools", () => { resultVersion: 2, }, }, - makeContext(testDir) + makeContext(testDir), ); expect((await read("invalid-utf8.txt")).data).toEqual({ @@ -1110,7 +1110,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { paths: ["directory"], resultVersion: 2 }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.data).toEqual({ @@ -1139,12 +1139,12 @@ describe("filesystem tools", () => { ], }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.ok).toBe(true); expect( - fs.readFileSync(path.join(testDir, ".env.sentry-build-plugin"), "utf-8") + fs.readFileSync(path.join(testDir, ".env.sentry-build-plugin"), "utf-8"), ).toContain("sntrys_test_token_123"); }); @@ -1176,12 +1176,12 @@ describe("filesystem tools", () => { ], }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.ok).toBe(false); expect(result.error).toMatch( - /Invalid (?:file change path|file changes request)/ + /Invalid (?:file change path|file changes request)/, ); } }); @@ -1192,9 +1192,9 @@ describe("filesystem tools", () => { path.join( testDir, "Cary.ConversionFunnels.API", - "Cary.ConversionFunnels.API.csproj" + "Cary.ConversionFunnels.API.csproj", ), - "\n" + "\n", ); const result = await executeTool( @@ -1223,22 +1223,22 @@ describe("filesystem tools", () => { ], }, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.ok).toBe(true); expect( - fs.readFileSync(path.join(testDir, "Directory.Packages.props"), "utf-8") + fs.readFileSync(path.join(testDir, "Directory.Packages.props"), "utf-8"), ).toContain(""); expect( fs.readFileSync( path.join( testDir, "Cary.ConversionFunnels.API", - "Cary.ConversionFunnels.API.csproj" + "Cary.ConversionFunnels.API.csproj", ), - "utf-8" - ) + "utf-8", + ), ).toContain("Sentry.AspNetCore"); }); @@ -1246,7 +1246,7 @@ describe("filesystem tools", () => { fs.mkdirSync(path.join(testDir, "src")); fs.writeFileSync( path.join(testDir, "src", "app.ts"), - "Sentry.captureException(error);\n" + "Sentry.captureException(error);\n", ); const grepResult = await executeTool( @@ -1256,7 +1256,7 @@ describe("filesystem tools", () => { cwd: testDir, params: { searches: [{ pattern: "captureException" }] }, }, - makeContext(testDir) + makeContext(testDir), ); const globResult = await executeTool( { @@ -1265,11 +1265,11 @@ describe("filesystem tools", () => { cwd: testDir, params: { patterns: ["**/*.ts"] }, }, - makeContext(testDir) + makeContext(testDir), ); expect((grepResult.data as any).results[0].matches[0].path).toBe( - "src/app.ts" + "src/app.ts", ); expect((globResult.data as any).results[0].files).toContain("src/app.ts"); }); @@ -1292,7 +1292,7 @@ describe("filesystem tools", () => { cwd: testDir, params: {}, }, - makeContext(testDir) + makeContext(testDir), ); expect(result.ok).toBe(true); @@ -1300,7 +1300,7 @@ describe("filesystem tools", () => { expect.objectContaining({ status: "installed", dsn: "https://abc@o1.ingest.sentry.io/42", - }) + }), ); }); }); diff --git a/packages/cli/test/lib/init/tools/list-dir.test.ts b/packages/cli/test/lib/init/tools/list-dir.test.ts index 86b11bdf1..38b53b302 100644 --- a/packages/cli/test/lib/init/tools/list-dir.test.ts +++ b/packages/cli/test/lib/init/tools/list-dir.test.ts @@ -16,7 +16,7 @@ import type { function makePayload( cwd: string, - params: ListDirPayload["params"] + params: ListDirPayload["params"], ): ListDirPayload { return { type: "tool", @@ -49,7 +49,7 @@ describe("listDir", () => { mkdirSync(join(testDir, "src")); const entries = entriesOf( - await listDir(makePayload(testDir, { path: "." })) + await listDir(makePayload(testDir, { path: "." })), ); // Order depends on `readdir` behavior (FS-dependent — e.g., ext4 // returns entries in insertion order while tmpfs and macOS APFS @@ -69,7 +69,7 @@ describe("listDir", () => { writeFileSync(join(testDir, "src", "deep.ts"), ""); const entries = entriesOf( - await listDir(makePayload(testDir, { path: ".", recursive: false })) + await listDir(makePayload(testDir, { path: ".", recursive: false })), ); const paths = entries.map((e) => e.path).sort(); expect(paths).toEqual(["src", "top.ts"]); @@ -84,8 +84,8 @@ describe("listDir", () => { const entries = entriesOf( await listDir( - makePayload(testDir, { path: ".", recursive: true, maxDepth: 2 }) - ) + makePayload(testDir, { path: ".", recursive: true, maxDepth: 2 }), + ), ); const files = entries.filter((e) => e.type === "file").map((e) => e.path); // With maxDepth: 2, we enter `a/` (depth 1) and `a/b/` (depth 2), see @@ -101,7 +101,7 @@ describe("listDir", () => { writeFileSync(join(testDir, "src", "nested", "deep.ts"), ""); const entries = entriesOf( - await listDir(makePayload(testDir, { path: ".", recursive: true })) + await listDir(makePayload(testDir, { path: ".", recursive: true })), ); for (const entry of entries) { expect(entry.path).not.toContain("\\"); @@ -116,7 +116,7 @@ describe("listDir", () => { mkdirSync(join(testDir, "node_modules")); const entries = entriesOf( - await listDir(makePayload(testDir, { path: ".", recursive: true })) + await listDir(makePayload(testDir, { path: ".", recursive: true })), ); const names = entries.map((e) => e.name).sort(); expect(names).toEqual([".cache", ".env", "node_modules"]); @@ -129,7 +129,7 @@ describe("listDir", () => { writeFileSync(join(testDir, "node_modules", "pkg", "index.js"), ""); const entries = entriesOf( - await listDir(makePayload(testDir, { path: ".", recursive: true })) + await listDir(makePayload(testDir, { path: ".", recursive: true })), ); const paths = entries.map((e) => e.path); expect(paths).toContain(".cache"); @@ -180,7 +180,7 @@ describe("listDir", () => { } const entries = entriesOf( - await listDir(makePayload(testDir, { path: ".", recursive: true })) + await listDir(makePayload(testDir, { path: ".", recursive: true })), ); const paths = entries.map((e) => e.path); @@ -200,11 +200,11 @@ describe("listDir", () => { for (let i = 0; i < 300; i += 1) { writeFileSync( join(testDir, "build", "client", "assets", `chunk-${i}.js`), - "" + "", ); writeFileSync( join(testDir, "dist", "client", "assets", `chunk-${i}.js`), - "" + "", ); } writeFileSync(join(testDir, "package.json"), "{}"); @@ -219,8 +219,8 @@ describe("listDir", () => { path: ".", recursive: true, maxEntries: 50, - }) - ) + }), + ), ); const paths = entries.map((e) => e.path); @@ -242,14 +242,14 @@ describe("listDir", () => { } const entries = entriesOf( - await listDir(makePayload(testDir, { path: ".", maxEntries: 5 })) + await listDir(makePayload(testDir, { path: ".", maxEntries: 5 })), ); expect(entries).toHaveLength(5); }); test("throws on sandbox escape via `..`", async () => { await expect( - listDir(makePayload(testDir, { path: "../../etc" })) + listDir(makePayload(testDir, { path: "../../etc" })), ).rejects.toThrow(/outside project directory/); }); @@ -265,7 +265,7 @@ describe("listDir", () => { } const entries = entriesOf( - await listDir(makePayload(testDir, { path: "." })) + await listDir(makePayload(testDir, { path: "." })), ); const names = entries.map((e) => e.name).sort(); expect(names).toEqual(["link.ts", "real.ts"]); @@ -284,7 +284,7 @@ describe("listDir", () => { try { const entries = entriesOf( - await listDir(makePayload(testDir, { path: "." })) + await listDir(makePayload(testDir, { path: "." })), ); expect(entries.map((e) => e.name)).not.toContain("escape"); } finally { @@ -296,7 +296,7 @@ describe("listDir", () => { // `safePath` allows nonexistent paths under the sandbox; `readdir` // throws, which the walker swallows. const entries = entriesOf( - await listDir(makePayload(testDir, { path: "does-not-exist" })) + await listDir(makePayload(testDir, { path: "does-not-exist" })), ); expect(entries).toEqual([]); }); diff --git a/packages/cli/test/lib/init/tools/registry.test.ts b/packages/cli/test/lib/init/tools/registry.test.ts index 1d8689a43..91ac8cf42 100644 --- a/packages/cli/test/lib/init/tools/registry.test.ts +++ b/packages/cli/test/lib/init/tools/registry.test.ts @@ -29,7 +29,7 @@ describe("tool registry", () => { }; expect(describeTool(payload)).toBe( - "Reviewing official Sentry feature support..." + "Reviewing official Sentry feature support...", ); await expect(executeTool(payload, makeContext())).resolves.toEqual({ data: { acknowledged: true }, diff --git a/packages/cli/test/lib/init/tools/run-commands-spawn.mocked.test.ts b/packages/cli/test/lib/init/tools/run-commands-spawn.mocked.test.ts index e59e09c73..b4b021bdf 100644 --- a/packages/cli/test/lib/init/tools/run-commands-spawn.mocked.test.ts +++ b/packages/cli/test/lib/init/tools/run-commands-spawn.mocked.test.ts @@ -34,7 +34,7 @@ vi.mock("node:child_process", async () => { spawn: ( command: string, args: string[], - options: { shell?: boolean; windowsVerbatimArguments?: boolean } + options: { shell?: boolean; windowsVerbatimArguments?: boolean }, ) => { spawnCalls.push({ command, args, options }); const child = new EventEmitter() as any; @@ -106,7 +106,7 @@ describe("runCommands spawn options", () => { const result = await runCommands( makePayload('pnpm --filter "./apps/web app" add @sentry/nextjs@^8.0.0'), - { dryRun: false } + { dryRun: false }, ); expect(result.ok).toBe(true); @@ -147,7 +147,7 @@ describe("runCommands spawn options", () => { const result = await runCommands( makePayload('pnpm add "value \\"quoted\\""'), - { dryRun: false } + { dryRun: false }, ); const commandLine = spawnCalls[0]?.args.at(-1) ?? ""; @@ -166,7 +166,7 @@ describe("runCommands spawn options", () => { const result = await runCommands( makePayload(String.raw`pnpm add "path\\\"name"`), - { dryRun: false } + { dryRun: false }, ); const commandLine = spawnCalls[0]?.args.at(-1) ?? ""; diff --git a/packages/cli/test/lib/init/tools/run-commands.test.ts b/packages/cli/test/lib/init/tools/run-commands.test.ts index 600a79f00..95ffe29e2 100644 --- a/packages/cli/test/lib/init/tools/run-commands.test.ts +++ b/packages/cli/test/lib/init/tools/run-commands.test.ts @@ -42,7 +42,7 @@ describe("validateCommand", () => { test("allows dependency diagnostics without a package-manager allowlist", () => { expect( - validateCommand("pnpm view @sentry/tanstackstart-react version") + validateCommand("pnpm view @sentry/tanstackstart-react version"), ).toBeUndefined(); expect(validateCommand("dotnet list package")).toBeUndefined(); expect(validateCommand("futurepm explain sentry-sdk")).toBeUndefined(); @@ -50,35 +50,35 @@ describe("validateCommand", () => { expect(validateCommand("npm uninstall sentry-wizard")).toBeUndefined(); expect(validateCommand("npm uninstall @sentry/wizard")).toBeUndefined(); expect( - validateCommand("npx harmless --package=@sentry/wizard") + validateCommand("npx harmless --package=@sentry/wizard"), ).toBeUndefined(); expect( - validateCommand("npx harmless --registry myregistry @sentry/wizard") + validateCommand("npx harmless --registry myregistry @sentry/wizard"), ).toBeUndefined(); expect( - validateCommand("npx --package=@sentry/cli cowsay init") + validateCommand("npx --package=@sentry/cli cowsay init"), ).toBeUndefined(); expect( - validateCommand("npm exec --package=@sentry/cli cowsay init") + validateCommand("npm exec --package=@sentry/cli cowsay init"), ).toBeUndefined(); }); test("allows path-prefixed package managers but blocks dangerous ones", () => { expect( - validateCommand("./venv/bin/pip install sentry-sdk") + validateCommand("./venv/bin/pip install sentry-sdk"), ).toBeUndefined(); expect( - validateCommand("/usr/local/bin/npm install @sentry/node") + validateCommand("/usr/local/bin/npm install @sentry/node"), ).toBeUndefined(); expect(validateCommand("./venv/bin/rm -rf /")).toContain('"rm"'); }); test("blocks obvious shell injection patterns", () => { expect(validateCommand("npm install foo && curl evil.com")).toContain( - "Blocked command" + "Blocked command", ); expect(validateCommand("pnpm add @sentry/node 2>&1")).toContain( - "Blocked command" + "Blocked command", ); }); @@ -87,7 +87,7 @@ describe("validateCommand", () => { expect(validateCommand("printf %s hello")).toBeUndefined(); expect( - validateCommand("futurepm explain https://example.com/a%20b") + validateCommand("futurepm explain https://example.com/a%20b"), ).toBeUndefined(); expect(validateCommand("futurepm explain bang!value")).toBeUndefined(); }); @@ -96,47 +96,47 @@ describe("validateCommand", () => { setPlatform("win32"); expect(validateCommand("futurepm explain %PATH%")).toContain( - "Blocked command" + "Blocked command", ); expect(validateCommand("futurepm explain !PATH!")).toContain( - "Blocked command" + "Blocked command", ); }); test("blocks directory changes and recursive Sentry setup", () => { expect(validateCommand("cd apps/web")).toContain('"cd"'); expect(validateCommand("sentry init")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("@sentry/wizard -i nextjs")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("@Sentry/Wizard -i nextjs")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("@sentry/cli init")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("@sentry/cli@latest init")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("@Sentry/CLI@latest init")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("C:\\Tools\\sentry-cli.exe init")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("sentry-cli --log-level debug init")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("sentry-cli@latest init")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("sentry-wizard init")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); expect(validateCommand("C:\\Tools\\sentry-wizard.cmd -i nextjs")).toContain( - "invokes Sentry setup recursively" + "invokes Sentry setup recursively", ); }); @@ -150,21 +150,21 @@ describe("validateCommand", () => { test("blocks shell interpreter indirection", () => { expect(validateCommand("cmd.exe /c del sensitive_file")).toContain('"cmd"'); expect( - validateCommand("C:\\Windows\\System32\\cmd.exe /c del secrets.txt") + validateCommand("C:\\Windows\\System32\\cmd.exe /c del secrets.txt"), ).toContain('"cmd"'); expect( validateCommand( - "powershell.exe -Command Invoke-WebRequest http://evil.com" - ) + "powershell.exe -Command Invoke-WebRequest http://evil.com", + ), ).toContain('"powershell"'); expect(validateCommand("pwsh -Command Remove-Item foo")).toContain( - '"pwsh"' + '"pwsh"', ); }); test("rejects unterminated quotes", () => { expect(validateCommand('/bin/echo "unterminated')).toContain( - "unterminated double quote" + "unterminated double quote", ); }); }); @@ -182,7 +182,7 @@ describe("runCommands", () => { test("stops on the first failing command", async () => { const result = await runCommands( makePayload(["/usr/bin/false", "/bin/echo should-not-run"]), - { dryRun: false } + { dryRun: false }, ); expect(result.ok).toBe(false); @@ -192,7 +192,7 @@ describe("runCommands", () => { test("validates but skips execution during dry-run", async () => { const result = await runCommands( makePayload(["npm install @sentry/node"]), - { dryRun: true } + { dryRun: true }, ); expect(result.ok).toBe(true); @@ -202,7 +202,7 @@ describe("runCommands", () => { test("rejects the full batch before execution when any command is blocked", async () => { const result = await runCommands( makePayload(["/bin/echo hello", "rm -rf /"]), - { dryRun: false } + { dryRun: false }, ); expect(result.ok).toBe(false); diff --git a/packages/cli/test/lib/init/tools/search-tools.test.ts b/packages/cli/test/lib/init/tools/search-tools.test.ts index 850f92fc8..3e50ebb6a 100644 --- a/packages/cli/test/lib/init/tools/search-tools.test.ts +++ b/packages/cli/test/lib/init/tools/search-tools.test.ts @@ -49,17 +49,17 @@ beforeEach(() => { fs.writeFileSync( path.join(testDir, "app.ts"), - 'import * as Sentry from "@sentry/node";\nSentry.init({ dsn: "..." });\n' + 'import * as Sentry from "@sentry/node";\nSentry.init({ dsn: "..." });\n', ); fs.writeFileSync( path.join(testDir, "utils.ts"), - "export function helper() { return 1; }\n" + "export function helper() { return 1; }\n", ); fs.writeFileSync(path.join(testDir, "config.json"), "{}\n"); fs.mkdirSync(path.join(testDir, "src")); fs.writeFileSync( path.join(testDir, "src", "index.ts"), - 'import { helper } from "./utils";\nSentry.init({});\n' + 'import { helper } from "./utils";\nSentry.init({});\n', ); }); @@ -75,7 +75,7 @@ describe("search tools", () => { cwd: testDir, params: { searches: [{ pattern: "Sentry", include: "app.*" }] }, }), - makeContext(testDir) + makeContext(testDir), ); const grepSubdir = await executeTool( makeToolPayload({ @@ -83,7 +83,7 @@ describe("search tools", () => { cwd: testDir, params: { searches: [{ pattern: "helper", path: "src" }] }, }), - makeContext(testDir) + makeContext(testDir), ); expect(grepWithInclude.ok).toBe(true); @@ -104,7 +104,7 @@ describe("search tools", () => { cwd: testDir, params: { patterns: ["*.ts", "*.json"] }, }), - makeContext(testDir) + makeContext(testDir), ); const empty = await executeTool( makeToolPayload({ @@ -112,16 +112,16 @@ describe("search tools", () => { cwd: testDir, params: { patterns: ["*.xyz"] }, }), - makeContext(testDir) + makeContext(testDir), ); expect(matches.ok).toBe(true); expect((matches.data as any).results).toHaveLength(2); expect( - (matches.data as any).results[0].files.length + (matches.data as any).results[0].files.length, ).toBeGreaterThanOrEqual(2); expect( - (matches.data as any).results[1].files.length + (matches.data as any).results[1].files.length, ).toBeGreaterThanOrEqual(1); expect(empty.ok).toBe(true); @@ -135,7 +135,7 @@ describe("search tools", () => { cwd: testDir, params: { searches: [{ pattern: "test", path: "../../etc" }] }, }), - makeContext(testDir) + makeContext(testDir), ); expect(result.ok).toBe(false); @@ -154,7 +154,7 @@ describe("search tools", () => { cwd: testDir, params: { searches: [{ pattern: "Sentry" }] }, }), - makeContext(testDir) + makeContext(testDir), ); expect(result.ok).toBe(true); @@ -180,7 +180,7 @@ describe("search tools", () => { // Unclosed paren — `new RegExp("(unclosed")` throws. params: { searches: [{ pattern: "(unclosed" }] }, }), - makeContext(testDir) + makeContext(testDir), ); expect(result.ok).toBe(true); expect((result.data as any).results[0]).toEqual({ @@ -201,7 +201,7 @@ describe("search tools", () => { cwd: testDir, params: { searches: [{ pattern: "SENTRY" }] }, }), - makeContext(testDir) + makeContext(testDir), ); const caseInsensitive = await executeTool( makeToolPayload({ @@ -211,14 +211,14 @@ describe("search tools", () => { searches: [{ pattern: "SENTRY", caseInsensitive: true }], }, }), - makeContext(testDir) + makeContext(testDir), ); expect(caseSensitive.ok).toBe(true); expect((caseSensitive.data as any).results[0].matches).toHaveLength(0); expect(caseInsensitive.ok).toBe(true); expect( - (caseInsensitive.data as any).results[0].matches.length + (caseInsensitive.data as any).results[0].matches.length, ).toBeGreaterThan(0); }); }); diff --git a/packages/cli/test/lib/init/ui/file-tree.test.ts b/packages/cli/test/lib/init/ui/file-tree.test.ts index ae3fe379e..0191d8ef0 100644 --- a/packages/cli/test/lib/init/ui/file-tree.test.ts +++ b/packages/cli/test/lib/init/ui/file-tree.test.ts @@ -50,10 +50,10 @@ describe("buildReadTree", () => { ]); const fileRows = flattenTree(tree).filter((row) => row.kind === "file"); expect(fileRows.find((row) => row.label === "a.ts")?.status).toBe( - "reading" + "reading", ); expect(fileRows.find((row) => row.label === "b.ts")?.status).toBe( - "analyzed" + "analyzed", ); }); diff --git a/packages/cli/test/lib/init/ui/ink-app.property.test.ts b/packages/cli/test/lib/init/ui/ink-app.property.test.ts index 5f6d5d47b..eea804ad7 100644 --- a/packages/cli/test/lib/init/ui/ink-app.property.test.ts +++ b/packages/cli/test/lib/init/ui/ink-app.property.test.ts @@ -18,7 +18,7 @@ describe("property: Ink prompt option window", () => { const [start, end] = getOptionWindow( totalCount, highlighted, - maxVisible + maxVisible, ); const expectedSize = Math.min(totalCount, Math.max(1, maxVisible)); @@ -29,14 +29,14 @@ describe("property: Ink prompt option window", () => { if (totalCount > 0) { const normalizedHighlight = Math.min( totalCount - 1, - Math.max(0, highlighted) + Math.max(0, highlighted), ); expect(start).toBeLessThanOrEqual(normalizedHighlight); expect(end).toBeGreaterThan(normalizedHighlight); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/init/ui/ink-app.snapshot.test.tsx b/packages/cli/test/lib/init/ui/ink-app.snapshot.test.tsx index 964477d1e..547895f2e 100644 --- a/packages/cli/test/lib/init/ui/ink-app.snapshot.test.tsx +++ b/packages/cli/test/lib/init/ui/ink-app.snapshot.test.tsx @@ -39,9 +39,9 @@ const ESC_CANCEL_HINT_RE = /esc\s+cancel/; const COMPLETED_SELECTING_FEATURES_RE = /✔\s+Selecting features/; const ANSI_ESCAPE_PREFIX = "\u001B["; const CURSOR_TO_LINE_START = "\u001B[G"; -// biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escape sequences in captured Ink output +// oxlint-disable-next-line no-control-regex -- matching ANSI escape sequences in captured Ink output const ANSI_CSI_RE = /\u001B\[[0-9;?]*[ -/]*[@-~]/g; -// biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escape sequences in captured Ink output +// oxlint-disable-next-line no-control-regex -- matching ANSI escape sequences in captured Ink output const ANSI_OSC_RE = /\u001B\][^\u0007]*(?:\u0007|\u001B\\)/g; const LINE_SPLIT_RE = /\r?\n/; const DOWN_ARROW = "\u001B[B"; @@ -110,7 +110,7 @@ function makeStdin(): Readable { async function renderApp( store: WizardStore, columns: number, - options: { rows?: number; input?: string[] } = {} + options: { rows?: number; input?: string[] } = {}, ): Promise { const out = new CaptureStream(columns, options.rows ?? 40); const stdin = makeStdin(); @@ -215,7 +215,7 @@ function setWelcomePrompt(store: WizardStore): void { function makeReadFiles(count: number): string[] { return Array.from( { length: count }, - (_value, index) => `src/file-${String(index + 1).padStart(2, "0")}.ts` + (_value, index) => `src/file-${String(index + 1).padStart(2, "0")}.ts`, ); } @@ -394,7 +394,7 @@ describe("Ink App snapshot", () => { .find((line) => line.includes("Sentry v") && line.includes("feedback")); expect(bannerLine).toBeDefined(); expect(bannerLine?.indexOf("Sentry v0.32.0-test.0")).toBeLessThan( - bannerLine?.indexOf("$ sentry cli feedback") ?? 0 + bannerLine?.indexOf("$ sentry cli feedback") ?? 0, ); }); @@ -423,11 +423,11 @@ describe("Ink App snapshot", () => { const terminalColumns = 120; const frame = stripAnsi( - (await renderApp(store, terminalColumns)).allOutput() + (await renderApp(store, terminalColumns)).allOutput(), ); const lines = frame.split(LINE_SPLIT_RE); const bannerOrigin = Math.floor( - (terminalColumns - bannerLinesWidth(FULL_BANNER_LINES)) / 2 + (terminalColumns - bannerLinesWidth(FULL_BANNER_LINES)) / 2, ); for (const { content } of FULL_BANNER_LINES) { @@ -437,7 +437,7 @@ describe("Ink App snapshot", () => { expect(renderedRow).toBeDefined(); expect(renderedRow?.indexOf(visibleContent)).toBe( - bannerOrigin + leadingSpaces + bannerOrigin + leadingSpaces, ); } }); @@ -603,7 +603,7 @@ describe("Ink App snapshot", () => { expect(frame).toContain("available to set up."); expect(frame).toContain("Error Monitoring"); expect(frame).toContain( - "Automatically capture exceptions and stack traces" + "Automatically capture exceptions and stack traces", ); expect(frame).toContain("Session Replay"); expect(frame).toContain("Watch real user sessions to see what went wrong"); @@ -612,7 +612,7 @@ describe("Ink App snapshot", () => { expect(frame).toContain("Source Maps"); expect(plainFrame).toContain("4/5"); expect(plainFrame).toContain( - "↑↓ move • space toggle • a all • enter continue" + "↑↓ move • space toggle • a all • enter continue", ); expect(plainFrame).toMatch(SPACE_TOGGLE_HINT_RE); expect(plainFrame).toMatch(A_ALL_HINT_RE); @@ -624,34 +624,34 @@ describe("Ink App snapshot", () => { .split(LINE_SPLIT_RE) .find((line) => line.includes("Error Monitoring")); expect(errorMonitoringRow).toContain( - `${ANSI_ESCAPE_PREFIX}38;2;131;218;144m◼ ` + `${ANSI_ESCAPE_PREFIX}38;2;131;218;144m◼ `, ); expect(frame).not.toContain("Recommended setup"); expect(frame).not.toContain("Apply recommended setup"); expect(plainFrame.indexOf("Error Monitoring")).toBeLessThan( - plainFrame.indexOf("Logging") + plainFrame.indexOf("Logging"), ); expect(plainFrame.indexOf("Logging")).toBeLessThan( - plainFrame.indexOf("Session Replay") + plainFrame.indexOf("Session Replay"), ); expect(plainFrame.indexOf("Session Replay")).toBeLessThan( - plainFrame.indexOf("Tracing") + plainFrame.indexOf("Tracing"), ); expect(plainFrame.indexOf("Tracing")).toBeLessThan( - plainFrame.indexOf("Source Maps") + plainFrame.indexOf("Source Maps"), ); const lines = plainFrame.split(LINE_SPLIT_RE); const shortcutLine = lines.findIndex((line) => - line.includes("↑↓ move • space toggle • a all • enter continue") + line.includes("↑↓ move • space toggle • a all • enter continue"), ); const contextLastLine = lines.findIndex((line) => - line.includes("available to set up.") + line.includes("available to set up."), ); const firstFeatureLine = lines.findIndex((line) => - line.includes("Error Monitoring") + line.includes("Error Monitoring"), ); const lastFeatureLine = lines.findIndex((line) => - line.includes("Source Maps") + line.includes("Source Maps"), ); expect(contextLastLine).toBeGreaterThan(0); expect(firstFeatureLine - contextLastLine).toBeGreaterThan(1); @@ -659,7 +659,7 @@ describe("Ink App snapshot", () => { expect(lastFeatureLine).toBeGreaterThan(0); expect(shortcutLine - lastFeatureLine).toBeGreaterThan(2); expect(plainFrame.indexOf("Tracing")).toBeLessThan( - plainFrame.indexOf("↑↓ move • space toggle • a all • enter continue") + plainFrame.indexOf("↑↓ move • space toggle • a all • enter continue"), ); }); @@ -845,20 +845,20 @@ describe("Ink App snapshot", () => { expect(frame).toContain("Back"); expect(frame).not.toContain("Change features"); expect(frame).toContain( - "We'll modify project files for this Sentry setup." + "We'll modify project files for this Sentry setup.", ); expect(frame.indexOf("✓ Tracing")).toBeLessThan( - frame.indexOf("We'll modify project files for this Sentry setup.") + frame.indexOf("We'll modify project files for this Sentry setup."), ); expect( - frame.indexOf("We'll modify project files for this Sentry setup.") + frame.indexOf("We'll modify project files for this Sentry setup."), ).toBeLessThan(frame.indexOf("Continue")); const reviewLines = frame .split(LINE_SPLIT_RE) .filter((line) => line.includes("✓ ")); expect(reviewLines).toHaveLength(3); expect( - reviewLines.every((line) => (line.match(/✓/g) ?? []).length === 1) + reviewLines.every((line) => (line.match(/✓/g) ?? []).length === 1), ).toBe(true); await renderApp(store, 120, { input: [DOWN_ARROW, "\r"] }); @@ -890,10 +890,10 @@ describe("Ink App snapshot", () => { planStore.startSpinner("Planning Sentry changes"); const reviewFrame = stripAnsi( - (await renderApp(reviewStore, 120)).latestFrame() + (await renderApp(reviewStore, 120)).latestFrame(), ); const planFrame = stripAnsi( - (await renderApp(planStore, 120)).latestFrame() + (await renderApp(planStore, 120)).latestFrame(), ); const reviewLine = reviewFrame .split(LINE_SPLIT_RE) @@ -905,70 +905,71 @@ describe("Ink App snapshot", () => { expect(reviewLine).toBeDefined(); expect(planLine).toBeDefined(); expect(reviewLine?.indexOf("Review your Sentry setup")).toBe( - planLine?.indexOf("Planning Sentry changes") + planLine?.indexOf("Planning Sentry changes"), ); }); - test.each([ - 120, 60, 30, - ])("review prompts keep their warning and actions visible at %i columns", async (columns) => { - const store = new WizardStore({ bannerRows: [] }); - store.appendLog("warn", "Review warning remains visible"); - store.setPrompt({ - kind: "select", - message: "Review your Sentry setup", - details: [ - { text: "We'll add these features:" }, - ...[ - "Agent Tracing", - "Application Metrics", - "Crons", - "Error Monitoring", - "Logging", - "MCP Observability", - "Profiling", - "Session Replay", - "Source Maps", - "Tracing", - ].map((feature) => ({ - text: `✓ ${feature}`, - tone: "success" as const, - })), - ], - footer: { - text: "We'll modify project files for this Sentry setup.", - }, - options: [ - { value: "continue", label: "Continue" }, - { value: "back", label: "Back" }, - ], - initialIndex: 0, - resolve: ignorePromptResolution, - }); + test.each([120, 60, 30])( + "review prompts keep their warning and actions visible at %i columns", + async (columns) => { + const store = new WizardStore({ bannerRows: [] }); + store.appendLog("warn", "Review warning remains visible"); + store.setPrompt({ + kind: "select", + message: "Review your Sentry setup", + details: [ + { text: "We'll add these features:" }, + ...[ + "Agent Tracing", + "Application Metrics", + "Crons", + "Error Monitoring", + "Logging", + "MCP Observability", + "Profiling", + "Session Replay", + "Source Maps", + "Tracing", + ].map((feature) => ({ + text: `✓ ${feature}`, + tone: "success" as const, + })), + ], + footer: { + text: "We'll modify project files for this Sentry setup.", + }, + options: [ + { value: "continue", label: "Continue" }, + { value: "back", label: "Back" }, + ], + initialIndex: 0, + resolve: ignorePromptResolution, + }); - const rendered = await renderApp(store, columns, { - input: Array.from({ length: 12 }, () => PAGE_DOWN), - rows: 16, - }); - const frame = stripFinalLineBreak(stripAnsi(rendered.latestFrame())); - const normalizedFrame = frame.replace(/\s+/g, " "); - expect(frame).toContain("Review your Sentry setup"); - expect(frame).toContain("Review warning remains"); - expect(frame).toContain("10-10/10 · pgup/pgdn"); - expect(frame).toContain("Tracing"); - expect(normalizedFrame).toContain( - "We'll modify project files for this Sentry setup." - ); - expect(frame).toContain("Continue"); - expect(frame).toContain("Back"); - expect(frame).not.toContain("Change features"); - expect(frame).toContain("Status"); - expect(frame).toContain("Files"); - expect(frame).toContain("↑↓ navigate"); - expect(frame).toContain("enter confirm"); - expect(frame).toContain("Sentry"); - expect(frame.split(LINE_SPLIT_RE).length).toBeLessThanOrEqual(16); - }); + const rendered = await renderApp(store, columns, { + input: Array.from({ length: 12 }, () => PAGE_DOWN), + rows: 16, + }); + const frame = stripFinalLineBreak(stripAnsi(rendered.latestFrame())); + const normalizedFrame = frame.replace(/\s+/g, " "); + expect(frame).toContain("Review your Sentry setup"); + expect(frame).toContain("Review warning remains"); + expect(frame).toContain("10-10/10 · pgup/pgdn"); + expect(frame).toContain("Tracing"); + expect(normalizedFrame).toContain( + "We'll modify project files for this Sentry setup.", + ); + expect(frame).toContain("Continue"); + expect(frame).toContain("Back"); + expect(frame).not.toContain("Change features"); + expect(frame).toContain("Status"); + expect(frame).toContain("Files"); + expect(frame).toContain("↑↓ navigate"); + expect(frame).toContain("enter confirm"); + expect(frame).toContain("Sentry"); + expect(frame.split(LINE_SPLIT_RE).length).toBeLessThanOrEqual(16); + }, + ); test("narrow review paging remains reversible when warnings accumulate", async () => { const store = new WizardStore({ bannerRows: [] }); @@ -996,7 +997,7 @@ describe("Ink App snapshot", () => { }); const firstFrame = stripAnsi( - (await renderApp(store, 30, { rows: 16 })).latestFrame() + (await renderApp(store, 30, { rows: 16 })).latestFrame(), ); expect(firstFrame).not.toContain("An older review warning"); expect(firstFrame).toContain("latest review warning"); @@ -1006,7 +1007,7 @@ describe("Ink App snapshot", () => { const nextFrame = stripAnsi( ( await renderApp(store, 30, { input: [PAGE_DOWN], rows: 16 }) - ).latestFrame() + ).latestFrame(), ); expect(nextFrame).toContain("✓ Feature 2"); expect(nextFrame).toContain("2-2/10 · pgup/pgdn"); @@ -1017,7 +1018,7 @@ describe("Ink App snapshot", () => { input: [PAGE_DOWN, PAGE_UP], rows: 16, }) - ).latestFrame() + ).latestFrame(), ); expect(previousFrame).toContain("✓ Feature 1"); expect(previousFrame).toContain("1-1/10 · pgup/pgdn"); @@ -1031,7 +1032,7 @@ describe("Ink App snapshot", () => { const store = new WizardStore({ bannerRows: [] }); store.appendLog( "success", - 'Using existing project "nextjs-sentry-test" in bete-dev' + 'Using existing project "nextjs-sentry-test" in bete-dev', ); store.appendLog("success", "Selecting features"); store.appendLog("info", "Routine context loaded"); @@ -1113,7 +1114,7 @@ describe("Ink App snapshot", () => { input: Array.from({ length: 7 }, () => DOWN_ARROW), rows: 24, }) - ).allOutput() + ).allOutput(), ); expect(scrolledFrame).toContain("(8/20)"); expect(scrolledFrame).toContain("Team 8"); @@ -1123,11 +1124,11 @@ describe("Ink App snapshot", () => { const store = new WizardStore({ bannerRows: [] }); store.appendLog( "warn", - "A warning remains visible while choosing features" + "A warning remains visible while choosing features", ); store.appendLog( "error", - "An error remains visible while choosing features" + "An error remains visible while choosing features", ); store.appendLog("warn", "A second warning also remains visible"); store.setPrompt({ @@ -1160,8 +1161,8 @@ describe("Ink App snapshot", () => { input: Array.from({ length: 19 }, () => DOWN_ARROW), rows: 16, }) - ).latestFrame() - ) + ).latestFrame(), + ), ); expect(scrolledFrame).toContain("0/20 selected • 20/20"); expect(scrolledFrame).toContain("Feature 20"); @@ -1214,7 +1215,7 @@ describe("Ink App snapshot", () => { }); const frame = stripAnsi( - (await renderApp(store, 40, { rows: 24 })).allOutput() + (await renderApp(store, 40, { rows: 24 })).allOutput(), ); expect(frame).toContain("A"); expect(frame).not.toContain("UNIQUE_TAIL"); @@ -1339,7 +1340,7 @@ describe("completion screen", () => { test("guides the user to their first error when unverified", async () => { const text = stripAnsi( - (await renderApp(completionStore(false), 100)).allOutput() + (await renderApp(completionStore(false), 100)).allOutput(), ); // Header flows into the per-feature project info via ", with:". expect(text).toContain("Sentry is set up in my-app"); @@ -1364,7 +1365,7 @@ describe("completion screen", () => { test("celebrates and deep-links the first event when verified", async () => { const text = stripAnsi( - (await renderApp(completionStore(true), 100)).allOutput() + (await renderApp(completionStore(true), 100)).allOutput(), ); expect(text).toContain("Sentry is set up in my-app"); expect(text).toContain("First event received"); @@ -1376,7 +1377,7 @@ describe("completion screen", () => { const text = stripAnsi( ( await renderApp(completionStore(false), 100, { input: ["o"] }) - ).allOutput() + ).allOutput(), ); // The `o` handler fires and confirms via a note. expect(text).toContain("Opened Sentry in your browser."); diff --git a/packages/cli/test/lib/init/ui/ink-report.test.ts b/packages/cli/test/lib/init/ui/ink-report.test.ts index 2598e2036..f85ba784b 100644 --- a/packages/cli/test/lib/init/ui/ink-report.test.ts +++ b/packages/cli/test/lib/init/ui/ink-report.test.ts @@ -14,7 +14,7 @@ describe("formatSuccessReport with summary fields", () => { { label: "Project", value: "my-app" }, { label: "Org", value: "acme" }, ], - }) + }), ); expect(output).toContain("Project"); expect(output).toContain("my-app"); @@ -29,7 +29,7 @@ describe("formatSuccessReport with summary fields", () => { { label: "Short", value: "x" }, { label: "LongerLabel", value: "y" }, ], - }) + }), ); const lines = output .split("\n") @@ -59,7 +59,7 @@ describe("formatSuccessReport with featureBlurbs", () => { { label: "Error Monitoring", blurb: "Captures exceptions." }, { label: "Tracing", blurb: "Traces requests end-to-end." }, ], - }) + }), ); expect(output).toContain("Here's what we set up"); expect(output).toContain("Error Monitoring"); @@ -75,7 +75,7 @@ describe("formatSuccessReport with featureBlurbs", () => { test("no blurbs section when featureBlurbs is empty", () => { const output = stripAnsi( - formatSuccessReport("Done!", { fields: [], featureBlurbs: [] }) + formatSuccessReport("Done!", { fields: [], featureBlurbs: [] }), ); expect(output).not.toContain("Here's what we set up"); }); @@ -90,7 +90,7 @@ describe("formatSuccessReport with changedFiles", () => { { path: "src/index.ts", action: "modify" }, { path: "sentry.config.ts", action: "create" }, ], - }) + }), ); expect(output).toContain("Changed files"); expect(output).toContain("index.ts"); @@ -99,7 +99,7 @@ describe("formatSuccessReport with changedFiles", () => { test("no Changed files heading when list is empty", () => { const output = stripAnsi( - formatSuccessReport("Done!", { fields: [], changedFiles: [] }) + formatSuccessReport("Done!", { fields: [], changedFiles: [] }), ); expect(output).not.toContain("Changed files"); }); @@ -116,7 +116,7 @@ describe("formatFailureReport with error log entries", () => { formatFailureReport("Setup failed", [ { severity: "error", text: "Could not reach Sentry" }, { severity: "error", text: "Auth token missing" }, - ]) + ]), ); expect(output).toContain("Could not reach Sentry"); expect(output).toContain("Auth token missing"); @@ -140,7 +140,7 @@ describe("formatFailureReport with error log entries", () => { formatFailureReport("Setup failed", [ { severity: "error", text: "Setup failed" }, { severity: "error", text: "Underlying cause" }, - ]) + ]), ); const lines = output.split("\n"); // "Setup failed" appears once (the heading), not a second time as a log entry @@ -153,7 +153,7 @@ describe("formatFailureReport with error log entries", () => { const output = stripAnsi( formatFailureReport("Setup failed", [ { severity: "error", text: "Network error: connection refused" }, - ]) + ]), ); const lines = output .split("\n") @@ -171,7 +171,7 @@ describe("formatFailureReport with error log entries", () => { { severity: "info", text: "Info message" }, { severity: "warn", text: "Warn message" }, { severity: "error", text: "Real error" }, - ]) + ]), ); expect(output).not.toContain("Info message"); expect(output).not.toContain("Warn message"); @@ -189,8 +189,8 @@ describe("Ink post-dispose feedback reports", () => { "Nice, setup made it through.", "Tell us what felt great or rough:", '$ sentry cli feedback "sentry init worked well"', - ].join("\n") - ) + ].join("\n"), + ), ); expect(output).toContain("Sentry SDK installed successfully!"); @@ -200,7 +200,7 @@ describe("Ink post-dispose feedback reports", () => { " Tell us what felt great or rough:", ' $ sentry cli feedback "sentry init worked well"', "", - ].join("\n") + ].join("\n"), ); expect(output.endsWith("\n")).toBe(true); }); @@ -214,8 +214,8 @@ describe("Ink post-dispose feedback reports", () => { "Setup hit a wall.", "Tell us what happened so we can fix it:", '$ sentry cli feedback "sentry init failed"', - ].join("\n") - ) + ].join("\n"), + ), ); expect(output).toContain("Setup failed"); @@ -225,7 +225,7 @@ describe("Ink post-dispose feedback reports", () => { " Tell us what happened so we can fix it:", ' $ sentry cli feedback "sentry init failed"', "", - ].join("\n") + ].join("\n"), ); expect(output.endsWith("\n")).toBe(true); }); @@ -239,8 +239,8 @@ describe("Ink post-dispose feedback reports", () => { "Sad to see setup stop. Was something going sideways?", "Tell us so we can fix it:", '$ sentry cli feedback "sentry init was cancelled"', - ].join("\n") - ) + ].join("\n"), + ), ); expect(output).toContain("Setup cancelled."); @@ -250,7 +250,7 @@ describe("Ink post-dispose feedback reports", () => { " Tell us so we can fix it:", ' $ sentry cli feedback "sentry init was cancelled"', "", - ].join("\n") + ].join("\n"), ); expect(output.endsWith("\n")).toBe(true); }); @@ -273,7 +273,7 @@ describe("formatSuccessExitLine", () => { changedFiles: [{ action: "create", path: "instrument.ts" }], featureBlurbs: [{ label: "Errors", blurb: "Captures exceptions." }], completion: { ...baseCompletion, verification: { received: false } }, - }) + }), ); expect(output).toContain("Sentry is watching my-app"); expect(output).toContain("See your first error"); @@ -297,7 +297,7 @@ describe("formatSuccessExitLine", () => { eventUrl: "https://acme.sentry.io/issues/?query=event.id:abc", }, }, - }) + }), ); expect(output).toContain("View your first event"); expect(output).toContain("event.id:abc"); diff --git a/packages/cli/test/lib/init/ui/ink-shortcuts.test.ts b/packages/cli/test/lib/init/ui/ink-shortcuts.test.ts index a342de921..db053e0e1 100644 --- a/packages/cli/test/lib/init/ui/ink-shortcuts.test.ts +++ b/packages/cli/test/lib/init/ui/ink-shortcuts.test.ts @@ -8,7 +8,7 @@ describe("arrangeShortcutHints", () => { { key: "enter", action: "confirm", priority: 40 }, { key: "\u2190\u2192", action: "switch tab", priority: 10 }, { key: "esc", action: "cancel", priority: 40 }, - ]) + ]), ).toEqual([ { key: "\u2190\u2192", action: "switch tab", priority: 10 }, { key: "enter", action: "confirm", priority: 40 }, @@ -22,7 +22,7 @@ describe("arrangeShortcutHints", () => { { key: "s", action: "toggle status", priority: 20 }, { key: "s", action: "toggle status", priority: 20 }, { key: "s", action: "save", priority: 30 }, - ]) + ]), ).toEqual([ { key: "s", action: "toggle status", priority: 20 }, { key: "s", action: "save", priority: 30 }, diff --git a/packages/cli/test/lib/init/ui/ink-ui-teardown.test.ts b/packages/cli/test/lib/init/ui/ink-ui-teardown.test.ts index 94df2e436..59932a1d0 100644 --- a/packages/cli/test/lib/init/ui/ink-ui-teardown.test.ts +++ b/packages/cli/test/lib/init/ui/ink-ui-teardown.test.ts @@ -22,9 +22,8 @@ vi.mock("node:child_process", async (importOriginal) => { }); const { InkUI } = await import("../../../../src/lib/init/ui/ink-ui.js"); -const { WizardStore } = await import( - "../../../../src/lib/init/ui/wizard-store.js" -); +const { WizardStore } = + await import("../../../../src/lib/init/ui/wizard-store.js"); function createUi(): { ui: InstanceType; diff --git a/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts b/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts index d90f2d10d..954ce7b20 100644 --- a/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts +++ b/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts @@ -1,4 +1,4 @@ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterEach, describe, expect, test, vi } from "vitest"; import { InkUI } from "../../../../src/lib/init/ui/ink-ui.js"; @@ -204,10 +204,10 @@ describe("InkUI prompt telemetry", () => { }); await expect(resultPromise).resolves.toBe("continue"); expect(states).not.toContainEqual( - expect.objectContaining({ prompt: null, spinnerActive: false }) + expect.objectContaining({ prompt: null, spinnerActive: false }), ); expect(states).not.toContainEqual( - expect.objectContaining({ prompt: "select", spinnerActive: true }) + expect.objectContaining({ prompt: "select", spinnerActive: true }), ); expect(store.getSnapshot()).toMatchObject({ prompt: null, @@ -221,42 +221,42 @@ describe("InkUI prompt telemetry", () => { await ui[Symbol.asyncDispose](); }); - test.each([ - "select", - "multiselect", - ] as const)("clears a cancelled %s prompt after returning the cancellation", async (kind) => { - vi.spyOn(process.stdout, "write").mockImplementation(() => true); - const { ui, store } = createUi(); - const promptKinds: Array = []; - const unsubscribe = store.subscribe(() => { - promptKinds.push(store.getSnapshot().prompt?.kind ?? null); - }); + test.each(["select", "multiselect"] as const)( + "clears a cancelled %s prompt after returning the cancellation", + async (kind) => { + vi.spyOn(process.stdout, "write").mockImplementation(() => true); + const { ui, store } = createUi(); + const promptKinds: Array = []; + const unsubscribe = store.subscribe(() => { + promptKinds.push(store.getSnapshot().prompt?.kind ?? null); + }); - const resultPromise = - kind === "select" - ? ui.select({ - message: "Review your Sentry setup", - options: [{ label: "Continue", value: "continue" }], - }) - : ui.multiselect({ - message: "Choose features", - options: [{ label: "Tracing", value: "performanceMonitoring" }], - }); - const prompt = store.getSnapshot().prompt; - expect(prompt?.kind).toBe(kind); - if (prompt?.kind !== "select" && prompt?.kind !== "multiselect") { - throw new Error(`Expected a ${kind} prompt`); - } + const resultPromise = + kind === "select" + ? ui.select({ + message: "Review your Sentry setup", + options: [{ label: "Continue", value: "continue" }], + }) + : ui.multiselect({ + message: "Choose features", + options: [{ label: "Tracing", value: "performanceMonitoring" }], + }); + const prompt = store.getSnapshot().prompt; + expect(prompt?.kind).toBe(kind); + if (prompt?.kind !== "select" && prompt?.kind !== "multiselect") { + throw new Error(`Expected a ${kind} prompt`); + } - prompt.resolve(null); - await expect(resultPromise).resolves.toBe(CANCELLED); - await new Promise((resolve) => setImmediate(resolve)); - expect(store.getSnapshot().prompt).toBeNull(); - expect(promptKinds).toEqual([kind, null]); + prompt.resolve(null); + await expect(resultPromise).resolves.toBe(CANCELLED); + await new Promise((resolve) => setImmediate(resolve)); + expect(store.getSnapshot().prompt).toBeNull(); + expect(promptKinds).toEqual([kind, null]); - unsubscribe(); - await ui[Symbol.asyncDispose](); - }); + unsubscribe(); + await ui[Symbol.asyncDispose](); + }, + ); test("attributes workflow prompts to the active step", async () => { const metricSpy = vi.spyOn(Sentry.metrics, "distribution"); @@ -285,7 +285,7 @@ describe("InkUI prompt telemetry", () => { prompt_kind: "multiselect", workflow_step: "select-features", }, - } + }, ); expect(startSpanSpy).toHaveBeenCalledWith( { @@ -298,7 +298,7 @@ describe("InkUI prompt telemetry", () => { "wizard.step.id": "select-features", }, }, - expect.any(Function) + expect.any(Function), ); await ui[Symbol.asyncDispose](); @@ -340,7 +340,7 @@ describe("InkUI prompt telemetry", () => { "wizard.prompt.phase": "preflight", }, }, - expect.any(Function) + expect.any(Function), ); expect(startSpanSpy).toHaveBeenCalledTimes(1); diff --git a/packages/cli/test/lib/init/ui/logging-ui.test.ts b/packages/cli/test/lib/init/ui/logging-ui.test.ts index ad854d537..158948e1a 100644 --- a/packages/cli/test/lib/init/ui/logging-ui.test.ts +++ b/packages/cli/test/lib/init/ui/logging-ui.test.ts @@ -82,7 +82,7 @@ describe("LoggingUI lifecycle messages", () => { '$ sentry cli feedback "sentry init was cancelled"', "", "", - ].join("\n") + ].join("\n"), ); expect(stderr()).toBe(""); }); @@ -181,7 +181,7 @@ describe("LoggingUI prompts throw", () => { ui.select({ message: "Pick one", options: [{ value: "a", label: "A" }], - }) + }), ).rejects.toBeInstanceOf(LoggingUIPromptError); }); @@ -191,14 +191,14 @@ describe("LoggingUI prompts throw", () => { ui.multiselect({ message: "Pick many", options: [{ value: "a", label: "A" }], - }) + }), ).rejects.toBeInstanceOf(LoggingUIPromptError); }); test("confirm rejects with LoggingUIPromptError", async () => { const { ui } = createUI(); expect(ui.confirm({ message: "Sure?" })).rejects.toBeInstanceOf( - LoggingUIPromptError + LoggingUIPromptError, ); }); @@ -242,7 +242,7 @@ describe("LoggingUI disposal", () => { ui.intro("hi"); } expect(stripAnsi(Buffer.concat(stdoutChunks).toString("utf-8"))).toBe( - "hi\n" + "hi\n", ); }); }); diff --git a/packages/cli/test/lib/init/ui/mock-ui.ts b/packages/cli/test/lib/init/ui/mock-ui.ts index 457f387f5..be1beade9 100644 --- a/packages/cli/test/lib/init/ui/mock-ui.ts +++ b/packages/cli/test/lib/init/ui/mock-ui.ts @@ -122,7 +122,7 @@ export function createMockUI(options: MockUIOptions = {}): { }); function takeResponse( - kind: K + kind: K, ): Extract["value"] | Cancelled { const next = responses.shift(); if (!next) { @@ -132,7 +132,7 @@ export function createMockUI(options: MockUIOptions = {}): { } if (next.kind !== kind) { throw new Error( - `MockUI: expected next response of kind "${kind}" but found "${next.kind}"` + `MockUI: expected next response of kind "${kind}" but found "${next.kind}"`, ); } return next.value as Extract["value"]; diff --git a/packages/cli/test/lib/init/ui/wizard-store.test.ts b/packages/cli/test/lib/init/ui/wizard-store.test.ts index fb48c419e..a24d166e7 100644 --- a/packages/cli/test/lib/init/ui/wizard-store.test.ts +++ b/packages/cli/test/lib/init/ui/wizard-store.test.ts @@ -25,16 +25,16 @@ describe("WizardStore step progress", () => { const store = new WizardStore(); const snapshot = store.getSnapshot(); expect(snapshot.steps.map((entry) => entry.id)).toEqual( - CHECKLIST_VISIBLE_STEPS.slice() + CHECKLIST_VISIBLE_STEPS.slice(), ); expect(snapshot.steps.every((entry) => entry.status === "pending")).toBe( - true + true, ); expect(snapshot.steps.some((entry) => entry.id === "install-deps")).toBe( - false + false, ); expect( - snapshot.steps.find((entry) => entry.id === "apply-codemods")?.label + snapshot.steps.find((entry) => entry.id === "apply-codemods")?.label, ).toBe("Applying changes + deps"); }); @@ -73,7 +73,7 @@ describe("WizardStore step progress", () => { } } expect(steps.find((entry) => entry.id === "verify-changes")?.status).toBe( - "in_progress" + "in_progress", ); }); @@ -96,7 +96,7 @@ describe("WizardStore step progress", () => { const initialLength = store.getSnapshot().steps.length; store.setStepStatus("select-target-app", "in_progress"); // Note: variable is deliberately not named `after` because - // Biome's `noDoneCallback` rule pattern-matches Mocha hooks + // The former lint rule pattern-matched Mocha hooks // (`after`, `before`, …) by identifier and would flag the // arrow-function callback inside `.find()` below. const updated = store.getSnapshot().steps; @@ -325,10 +325,10 @@ describe("WizardStore file read state machine", () => { store.markFilesAnalyzed(["a.ts"]); const snap = store.getSnapshot(); expect(snap.filesRead.find((e) => e.path === "a.ts")?.status).toBe( - "analyzed" + "analyzed", ); expect(snap.filesRead.find((e) => e.path === "b.ts")?.status).toBe( - "reading" + "reading", ); }); diff --git a/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts b/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts index 0ed40fb5d..ecc56f3b1 100644 --- a/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts +++ b/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts @@ -48,7 +48,7 @@ vi.mock("node:child_process", async (importOriginal) => { child.stderr.write("SyntaxError: Windows verification fixture\n"); }); return child; - } + }, ), spawnSync: vi.fn( (command: string, args: string[], options: { timeout?: number }) => { @@ -62,7 +62,7 @@ vi.mock("node:child_process", async (importOriginal) => { stderr: null, stdout: null, }; - } + }, ), }; }); @@ -95,7 +95,7 @@ beforeEach(async () => { tmpDir = await mkdtemp(join(TEST_TMP_DIR, "verify-setup-windows-test-")); await writeFile( join(tmpDir, "package.json"), - JSON.stringify({ scripts: { dev: "node server.js" } }) + JSON.stringify({ scripts: { dev: "node server.js" } }), ); }); @@ -112,7 +112,7 @@ describe("verifySetup Windows cleanup", () => { await verifySetup( { status: "success", result: { platform: "javascript-nextjs" } }, ui, - tmpDir + tmpDir, ); expect(mocks.spawnCalls[0]?.options.detached).toBe(false); @@ -134,14 +134,14 @@ describe("verifySetup Windows cleanup", () => { await verifySetup( { status: "success", result: { platform: "javascript-nextjs" } }, ui, - tmpDir + tmpDir, ); expect(mocks.taskkillCalls.map(({ args }) => args)).toEqual([ ["/PID", "4321", "/T", "/F"], ]); expect(debugSpy).toHaveBeenCalledWith( - expect.stringContaining("taskkill exited with status 1") + expect.stringContaining("taskkill exited with status 1"), ); expect(mocks.childKillCalls).toEqual(["SIGKILL"]); }); @@ -151,7 +151,7 @@ describe("verifySetup Windows cleanup", () => { new Error("spawnSync taskkill ETIMEDOUT"), { code: "ETIMEDOUT", - } + }, ); mocks.taskkillErrors.push(timeoutError); const debugSpy = vi.spyOn(logger, "debug"); @@ -160,14 +160,14 @@ describe("verifySetup Windows cleanup", () => { await verifySetup( { status: "success", result: { platform: "javascript-nextjs" } }, ui, - tmpDir + tmpDir, ); expect(debugSpy).toHaveBeenCalledWith( expect.stringContaining( - "Failed to terminate Windows verification process tree" + "Failed to terminate Windows verification process tree", ), - timeoutError + timeoutError, ); expect(mocks.childKillCalls).toEqual(["SIGKILL"]); }); diff --git a/packages/cli/test/lib/init/verify-setup.test.ts b/packages/cli/test/lib/init/verify-setup.test.ts index fb9d29bcf..a4d76b7bc 100644 --- a/packages/cli/test/lib/init/verify-setup.test.ts +++ b/packages/cli/test/lib/init/verify-setup.test.ts @@ -86,7 +86,7 @@ async function writeProcessTreeFixture(ignoreSigterm = false): Promise { // matching scripts that use concurrently or chained commands. dev: 'node verify-parent.mjs && node -e "process.exit(0)"', }, - }) + }), ); const sigtermHandler = ignoreSigterm ? 'process.on("SIGTERM", () => {});' @@ -116,7 +116,7 @@ async function writeProcessTreeFixture(ignoreSigterm = false): Promise { ); process.stderr.write("SyntaxError: verification fixture\\n"); setInterval(() => {}, 1000); - ` + `, ); } @@ -146,7 +146,7 @@ describe("verifySetup", () => { const result = await verifySetup( { status: "success", result: { platform: "cocoa" } }, ui, - tmpDir + tmpDir, ); expect(result).toEqual({ verified: false, kind: "skipped" }); @@ -166,14 +166,14 @@ describe("verifySetup", () => { test("does not fail init when the detected command cannot be spawned", async () => { await writeFile( join(tmpDir, "package.json"), - JSON.stringify({ scripts: { dev: "missing-sentry-test-command" } }) + JSON.stringify({ scripts: { dev: "missing-sentry-test-command" } }), ); const { ui, calls } = createMockUI(); const result = await verifySetup( { status: "success", result: { platform: "javascript-nextjs" } }, ui, - tmpDir + tmpDir, ); // The SDK never phoned home, so nothing to verify or deep-link. expect(result.verified).toBe(false); @@ -201,7 +201,7 @@ describe("verifySetup", () => { const verification = verifySetup( { status: "success", result: { platform: "javascript-nextjs" } }, ui, - tmpDir + tmpDir, ); fixtureProcesses = await readFixtureProcesses(); await verification; @@ -215,7 +215,7 @@ describe("verifySetup", () => { message: "Could not verify — startup error: SyntaxError: verification fixture", }); - } + }, ); test.skipIf(process.platform === "win32")( @@ -227,7 +227,7 @@ describe("verifySetup", () => { const verification = verifySetup( { status: "success", result: { platform: "javascript-nextjs" } }, ui, - tmpDir + tmpDir, ); fixtureProcesses = await readFixtureProcesses(); await verification; @@ -237,6 +237,6 @@ describe("verifySetup", () => { expect(await waitForProcessExit(fixtureProcesses.childPid)).toBe(true); fixtureProcesses = undefined; }, - 10_000 + 10_000, ); }); diff --git a/packages/cli/test/lib/init/wizard-runner.test.ts b/packages/cli/test/lib/init/wizard-runner.test.ts index 5e2c150a2..1ae9fff1d 100644 --- a/packages/cli/test/lib/init/wizard-runner.test.ts +++ b/packages/cli/test/lib/init/wizard-runner.test.ts @@ -1,5 +1,5 @@ import { MastraClient } from "@mastra/client-js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as Sentry from "@sentry/node-core/light"; import { afterEach, @@ -10,7 +10,7 @@ import { test, vi, } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as banner from "../../../src/lib/banner.js"; import { clearAuth, setAuthToken } from "../../../src/lib/db/auth.js"; import { ENV_VAR_AGENTS } from "../../../src/lib/detect-agent.js"; @@ -26,17 +26,17 @@ import { CHECKLIST_VISIBLE_STEPS, WizardCancelledError, } from "../../../src/lib/init/clack-utils.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as fmt from "../../../src/lib/init/formatters.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as git from "../../../src/lib/init/git.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as inter from "../../../src/lib/init/interactive.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as preflight from "../../../src/lib/init/preflight.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as readiness from "../../../src/lib/init/readiness.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as registry from "../../../src/lib/init/tools/registry.js"; import type { ResolvedInitContext, @@ -45,7 +45,7 @@ import type { WizardOptions, WorkflowRunResult, } from "../../../src/lib/init/types.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as uiFactory from "../../../src/lib/init/ui/factory.js"; import { CANCELLED, @@ -53,7 +53,7 @@ import { type WizardUI, } from "../../../src/lib/init/ui/types.js"; import { runWizard } from "../../../src/lib/init/wizard-runner.js"; -// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference import * as workflowInputs from "../../../src/lib/init/workflow-inputs.js"; import { createMockUI, type MockCall } from "./ui/mock-ui.js"; @@ -88,7 +88,7 @@ function makeOptions(overrides?: Partial): WizardOptions { } function makeContext( - overrides?: Partial + overrides?: Partial, ): ResolvedInitContext { return { directory: "/tmp/test", @@ -174,7 +174,7 @@ function withV1SuspendEnvelopes(result: WorkflowRunResult): WorkflowRunResult { ...step, suspendPayload: withV1SuspendEnvelope(step.suspendPayload), }, - ]) + ]), ) : undefined; return { @@ -189,7 +189,7 @@ function withV1SuspendEnvelopes(result: WorkflowRunResult): WorkflowRunResult { function forceStdinTty(action: () => Promise): Promise { const originalDescriptor = Object.getOwnPropertyDescriptor( process.stdin, - "isTTY" + "isTTY", ); Object.defineProperty(process.stdin, "isTTY", { value: true, @@ -286,12 +286,12 @@ beforeEach(() => { .mockImplementation(() => true as any); startAsyncMock = vi.fn(() => - Promise.resolve(withV1SuspendEnvelopes(mockStartResult)) + Promise.resolve(withV1SuspendEnvelopes(mockStartResult)), ); runByIdMock = vi.fn(() => mockRunByIdResult instanceof Error ? Promise.reject(mockRunByIdResult) - : Promise.resolve(withV1SuspendEnvelopes(mockRunByIdResult)) + : Promise.resolve(withV1SuspendEnvelopes(mockRunByIdResult)), ); sharedResumeAsyncMock = vi.fn(() => { const result = mockResumeResults[resumeCallCount] ?? { @@ -321,7 +321,7 @@ beforeEach(() => { this as unknown as { options: { abortSignal?: AbortSignal; retries?: number }; } - ).options + ).options, ); return workflow as any; }); @@ -399,7 +399,7 @@ function lastError(): string | undefined { function initService401Error(): Error { return new Error( - 'HTTP error! status: 401 - {"error":"Unauthorized: invalid token"}' + 'HTTP error! status: 401 - {"error":"Unauthorized: invalid token"}', ); } @@ -435,11 +435,11 @@ function hasExpectedInitServiceAuthPolicy(err: unknown): boolean { describe("runWizard", () => { test("rejects a malformed bearer before constructing the service client", async () => { resolveInitContextSpy.mockResolvedValue( - makeContext({ authToken: "synthetic-prefix\nsynthetic-secret-tail" }) + makeContext({ authToken: "synthetic-prefix\nsynthetic-secret-tail" }), ); const error = await runWizard(makeOptions()).catch( - (caught: unknown) => caught + (caught: unknown) => caught, ); expect(error).toMatchObject({ reason: "invalid", @@ -461,7 +461,7 @@ describe("runWizard", () => { test("throws when stdin is not a TTY without --yes", async () => { const originalDescriptor = Object.getOwnPropertyDescriptor( process.stdin, - "isTTY" + "isTTY", ); Object.defineProperty(process.stdin, "isTTY", { value: false, @@ -471,7 +471,7 @@ describe("runWizard", () => { try { await expect(runWizard(makeOptions({ yes: false }))).rejects.toThrow( - WizardError + WizardError, ); } finally { if (originalDescriptor) { @@ -487,7 +487,7 @@ describe("runWizard", () => { expect(resolveInitContextSpy).toHaveBeenCalledWith( expect.objectContaining({ dryRun: true, yes: true }), - expect.anything() + expect.anything(), ); expect(lastWarn()).toContain("Dry-run"); }); @@ -504,8 +504,8 @@ describe("runWizard", () => { features: ["errorMonitoring", "performanceMonitoring"], org: "bete-dev", project: "nextjs", - }) - ) + }), + ), ); const welcome = calls.find((call) => call.kind === "welcome"); @@ -515,7 +515,7 @@ describe("runWizard", () => { } expect(welcome.options.title).toBe("Sentry Init"); expect(welcome.options.body).toContain( - "We'll use AI to inspect this project and configure Sentry." + "We'll use AI to inspect this project and configure Sentry.", ); expect(welcome.options.punchline).toContain("use AI for setup"); expect(getUISpy.mock.calls[0]?.[0]).toEqual( @@ -523,16 +523,16 @@ describe("runWizard", () => { initialWelcome: expect.objectContaining({ title: "Sentry Init", }), - }) + }), ); expect( - calls.some((call) => call.kind === "select" || call.kind === "confirm") + calls.some((call) => call.kind === "select" || call.kind === "confirm"), ).toBe(false); const introOn = calls.findIndex( - (call) => call.kind === "setIntroMode" && call.enabled + (call) => call.kind === "setIntroMode" && call.enabled, ); const introOff = calls.findIndex( - (call) => call.kind === "setIntroMode" && !call.enabled + (call) => call.kind === "setIntroMode" && !call.enabled, ); expect(introOn).toBeGreaterThanOrEqual(0); expect(introOff).toBeGreaterThanOrEqual(0); @@ -554,18 +554,18 @@ describe("runWizard", () => { features: ["errorMonitoring"], org: "bete-dev", project: "nextjs", - }) - ) + }), + ), ); const infoMessages = calls .filter((call) => call.kind === "log.info") .map((call) => call.message); expect( - infoMessages.some((message) => message.includes("This wizard uses AI")) + infoMessages.some((message) => message.includes("This wizard uses AI")), ).toBe(false); expect( - infoMessages.some((message) => message.includes("For manual setup")) + infoMessages.some((message) => message.includes("For manual setup")), ).toBe(false); }); @@ -646,7 +646,7 @@ describe("runWizard", () => { expect(formatBannerSpy).not.toHaveBeenCalled(); expect(stderrSpy).not.toHaveBeenCalledWith( - expect.stringContaining("BANNER") + expect.stringContaining("BANNER"), ); }); @@ -662,7 +662,7 @@ describe("runWizard", () => { ...ENV_VAR_AGENTS.keys(), ]); const cleanEnv = Object.fromEntries( - Object.entries(process.env).filter(([k]) => !agentKeys.has(k)) + Object.entries(process.env).filter(([k]) => !agentKeys.has(k)), ); setEnv(cleanEnv as NodeJS.ProcessEnv); try { @@ -730,7 +730,7 @@ describe("runWizard", () => { protocolVersion: 1, requestId, }), - }) + }), ); }); @@ -762,7 +762,7 @@ describe("runWizard", () => { prompt: "Continue?", }, makeContext(), - expect.anything() + expect.anything(), ); expect(sharedResumeAsyncMock).toHaveBeenCalledWith( expect.objectContaining({ @@ -770,7 +770,7 @@ describe("runWizard", () => { protocolVersion: 1, requestId, }), - }) + }), ); }); @@ -826,7 +826,7 @@ describe("runWizard", () => { protocolVersion: 1, requestId, }), - }) + }), ); }); @@ -872,7 +872,7 @@ describe("runWizard", () => { }); await expect(runWizard(makeOptions())).rejects.toThrow( - "Invalid init protocol envelope" + "Invalid init protocol envelope", ); expect(executeToolSpy).not.toHaveBeenCalled(); }); @@ -933,7 +933,7 @@ describe("runWizard", () => { expect(spinnerMock.stop).toHaveBeenCalledWith( "Sentry API request denied", - 1 + 1, ); expect(lastCancelMessage()).toBe("Sentry API request denied"); }); @@ -987,7 +987,7 @@ describe("runWizard", () => { }, }; executeToolSpy.mockRejectedValue( - new WizardError("tool rejected by server") + new WizardError("tool rejected by server"), ); await expect(runWizard(makeOptions())).rejects.toThrow(WizardError); @@ -1020,7 +1020,7 @@ describe("runWizard", () => { await runWizard(makeOptions()); const messages = spinnerMock.message.mock.calls.map( - (call: string[]) => call[0] + (call: string[]) => call[0], ); expect(messages).toContain("Reading 2 files..."); expect(messages).toContain("Analyzing 2 files..."); @@ -1132,14 +1132,13 @@ describe("runWizard", () => { expect.anything(), expect.anything(), expect.anything(), - identity + identity, ); }); test("shows --yes hint when LoggingUI prompt fails", async () => { - const { LoggingUIPromptError } = await import( - "../../../src/lib/init/ui/logging-ui.js" - ); + const { LoggingUIPromptError } = + await import("../../../src/lib/init/ui/logging-ui.js"); const { ui } = createMockUI(); const failingUI: WizardUI = { ...ui, @@ -1148,14 +1147,14 @@ describe("runWizard", () => { Promise.reject( new LoggingUIPromptError( "select", - "This is experimental and will modify files" - ) + "This is experimental and will modify files", + ), ), }; getUISpy.mockResolvedValue(failingUI); await expect( - forceStdinTty(() => runWizard(makeOptions({ yes: false }))) + forceStdinTty(() => runWizard(makeOptions({ yes: false }))), ).rejects.toThrow("Run with --yes for non-interactive mode."); }); }); @@ -1241,9 +1240,9 @@ describe("runWizard — MastraClient lifecycle", () => { Promise.resolve({ startAsync: startAsyncMock, resumeAsync: vi.fn(() => - Promise.resolve({ status: "success", result: { exitCode: 0 } }) + Promise.resolve({ status: "success", result: { exitCode: 0 } }), ), - }) + }), ), } as any; }); @@ -1279,17 +1278,20 @@ describe("runWizard — workflow exit codes", () => { [50, EXIT.WIZARD_VERIFY], // 999 is an unknown code; also exercises the default branch of mapWorkflowExitCode [999, EXIT.WIZARD], - ])("maps workflow exit code %s to the expected EXIT constant", async (workflowCode, expectedExitCode) => { - mockStartResult = { - status: "success", - result: { exitCode: workflowCode }, - }; + ])( + "maps workflow exit code %s to the expected EXIT constant", + async (workflowCode, expectedExitCode) => { + mockStartResult = { + status: "success", + result: { exitCode: workflowCode }, + }; - const err = await runWizard(makeOptions()).catch((e) => e); + const err = await runWizard(makeOptions()).catch((e) => e); - expect(err).toBeInstanceOf(WizardError); - expect((err as WizardError).exitCode).toBe(expectedExitCode); - }); + expect(err).toBeInstanceOf(WizardError); + expect((err as WizardError).exitCode).toBe(expectedExitCode); + }, + ); }); describe("runWizard — resumeWithRetry stale-step recovery", () => { @@ -1302,8 +1304,8 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { function makeStaleStepRun( resumeAsyncImpl: ( - args: Record - ) => Promise + args: Record, + ) => Promise, ) { let runByIdRef: ReturnType; getWorkflowSpy.mockImplementation(function (this: MastraClient) { @@ -1312,7 +1314,7 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { this as unknown as { options: { abortSignal?: AbortSignal; retries?: number }; } - ).options + ).options, ); runByIdRef = runByIdMock; return { @@ -1321,9 +1323,9 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { runId: "test-run-id", startAsync: startAsyncMock, resumeAsync: vi.fn(async (args) => - withV1SuspendEnvelopes(await resumeAsyncImpl(args)) + withV1SuspendEnvelopes(await resumeAsyncImpl(args)), ), - }) + }), ), runById: runByIdRef, } as any; @@ -1332,11 +1334,11 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { function httpError( status: number, - body: unknown + body: unknown, ): Error & { body: unknown; status: number } { return Object.assign( new Error(`HTTP error! status: ${status} - ${JSON.stringify(body)}`), - { body, status } + { body, status }, ); } @@ -1365,7 +1367,7 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { function selectWorkflowFields( result: WorkflowRunResult, - fields: string[] | undefined + fields: string[] | undefined, ): Record { const source = result as unknown as Record; const selected: Record = {}; @@ -1433,7 +1435,7 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { expect(executeToolSpy).toHaveBeenCalledWith( expect.objectContaining({ operation: "agent-checkpoint" }), - expect.anything() + expect.anything(), ); expect(capturedResume).toMatchObject({ data: { acknowledged: true }, @@ -1492,7 +1494,7 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { }; runByIdMock.mockImplementation( (_runId: string, opts?: { fields?: string[] }) => - Promise.resolve(selectWorkflowFields(currentRunState, opts?.fields)) + Promise.resolve(selectWorkflowFields(currentRunState, opts?.fields)), ); let resumeCount = 0; @@ -1515,7 +1517,7 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { "suspended", "activeStepsPath", ]), - }) + }), ); // Recovery succeeded on the first attempt — resumeAsync was not called again. expect(resumeCount).toBe(1); @@ -1629,7 +1631,7 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { expect(executeToolSpy).toHaveBeenCalledWith(activePayload, makeContext()); expect(executeToolSpy).not.toHaveBeenCalledWith( stalePayload, - makeContext() + makeContext(), ); expect(resumeCount).toBe(2); }); @@ -1671,7 +1673,7 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { const err = await runWizard(makeOptions()).catch((error) => error); expect(err).toMatchObject( - initServiceApiErrorShape("/api/workflows/sentry-wizard/resume-async") + initServiceApiErrorShape("/api/workflows/sentry-wizard/resume-async"), ); expect(hasExpectedInitServiceAuthPolicy(err)).toBe(true); expect(err).not.toBeInstanceOf(WizardError); @@ -1807,7 +1809,7 @@ describe("runWizard — resumeWithRetry stale-step recovery", () => { }, ]); expect(JSON.stringify(secondResumeData?._prevPhases)).not.toContain( - largeContent + largeContent, ); }); }); @@ -1831,7 +1833,7 @@ describe("runWizard — additional coverage", () => { const err = await runWizard(makeOptions()).catch((error) => error); expect(err).toMatchObject( - initServiceApiErrorShape("/api/workflows/sentry-wizard/start-async") + initServiceApiErrorShape("/api/workflows/sentry-wizard/start-async"), ); expect(hasExpectedInitServiceAuthPolicy(err)).toBe(true); expect(err.format()).toContain("sentry auth login"); @@ -1854,7 +1856,7 @@ describe("runWizard — additional coverage", () => { this as unknown as { options: { abortSignal?: AbortSignal; retries?: number }; } - ).options + ).options, ); return { createRun: createRunMock, @@ -1866,7 +1868,7 @@ describe("runWizard — additional coverage", () => { const err = await runWizard(makeOptions()).catch((error) => error); expect(err).toMatchObject( - initServiceApiErrorShape("/api/workflows/sentry-wizard/create-run") + initServiceApiErrorShape("/api/workflows/sentry-wizard/create-run"), ); expect(hasExpectedInitServiceAuthPolicy(err)).toBe(true); expect(createRunMock).toHaveBeenCalledTimes(1); @@ -1890,7 +1892,7 @@ describe("runWizard — additional coverage", () => { delete env.SENTRY_TOKEN; setEnv(env); resolveInitContextSpy.mockResolvedValue( - makeContext({ authToken: "env-token" }) + makeContext({ authToken: "env-token" }), ); startAsyncMock.mockRejectedValue(initService401Error()); @@ -1898,7 +1900,7 @@ describe("runWizard — additional coverage", () => { const err = await runWizard(makeOptions()).catch((error) => error); expect(err).toMatchObject( - initServiceApiErrorShape("/api/workflows/sentry-wizard/start-async") + initServiceApiErrorShape("/api/workflows/sentry-wizard/start-async"), ); expect(hasExpectedInitServiceAuthPolicy(err)).toBe(true); expect(err.format()).toContain("SENTRY_AUTH_TOKEN"); @@ -1917,7 +1919,7 @@ describe("runWizard — additional coverage", () => { host: "https://sentry.internal.example.com", }); resolveInitContextSpy.mockResolvedValue( - makeContext({ authToken: "self-hosted-token" }) + makeContext({ authToken: "self-hosted-token" }), ); try { @@ -1929,7 +1931,7 @@ describe("runWizard — additional coverage", () => { expect(capturedClientOptions).toHaveLength(0); expect(err.format()).toContain("sentry.internal.example.com"); expect(err.format()).toContain( - "sentry auth login --url https://sentry.io" + "sentry auth login --url https://sentry.io", ); } finally { await clearAuth(); @@ -1940,7 +1942,7 @@ describe("runWizard — additional coverage", () => { startAsyncMock.mockResolvedValue({ status: "bailed" }); await expect(runWizard(makeOptions())).rejects.toThrow( - /Unexpected workflow status/ + /Unexpected workflow status/, ); }); @@ -2054,13 +2056,13 @@ describe("runWizard — additional coverage", () => { (c) => c.kind === "setStep" && c.stepId === "discover-context" && - c.status === "in_progress" + c.status === "in_progress", ); const completedIdx = stepCalls.findIndex( (c) => c.kind === "setStep" && c.stepId === "discover-context" && - c.status === "completed" + c.status === "completed", ); expect(inProgressIdx).toBeLessThan(completedIdx); }); @@ -2109,20 +2111,20 @@ describe("runWizard — additional coverage", () => { }); } expect(stepCalls).not.toContainEqual( - expect.objectContaining({ stepId: "install-deps" }) + expect.objectContaining({ stepId: "install-deps" }), ); const discoverCompletedIndex = stepCalls.findIndex( (call) => call.kind === "setStep" && call.stepId === "discover-context" && - call.status === "completed" + call.status === "completed", ); const detectStartedIndex = stepCalls.findIndex( (call) => call.kind === "setStep" && call.stepId === "detect-platform" && - call.status === "in_progress" + call.status === "in_progress", ); expect(discoverCompletedIndex).toBeLessThan(detectStartedIndex); }); @@ -2138,7 +2140,7 @@ describe("runWizard — additional coverage", () => { }; await expect(runWizard(makeOptions())).rejects.toThrow( - "Framework detection failed" + "Framework detection failed", ); expect(mockUICalls).toContainEqual({ @@ -2155,7 +2157,7 @@ describe("runWizard — additional coverage", () => { test("uses existing platform name in detect-platform spinner label", async () => { resolveInitContextSpy.mockResolvedValue( - makeContext({ existingProject: { platform: "javascript-nextjs" } }) + makeContext({ existingProject: { platform: "javascript-nextjs" } }), ); mockStartResult = { status: "suspended", @@ -2176,7 +2178,7 @@ describe("runWizard — additional coverage", () => { await runWizard(makeOptions()); const messages = spinnerMock.message.mock.calls.map( - (c: unknown[]) => c[0] as string + (c: unknown[]) => c[0] as string, ); expect(messages.some((m) => m.includes("javascript-nextjs"))).toBe(true); }); @@ -2209,7 +2211,7 @@ describe("runWizard — progress rotation for long-running steps", () => { this as unknown as { options: { abortSignal?: AbortSignal; retries?: number }; } - ).options + ).options, ); return { createRun: vi.fn(() => @@ -2217,7 +2219,7 @@ describe("runWizard — progress rotation for long-running steps", () => { runId: "test-run-id", startAsync: startAsyncMock, resumeAsync: resumeAsyncMock, - }) + }), ), runById: runByIdMock, } as any; @@ -2233,24 +2235,24 @@ describe("runWizard — progress rotation for long-running steps", () => { // Check that the spinner received a rotating message const messagesAfterFirstRotation = spinnerMock.message.mock.calls.map( - (c: unknown[]) => c[0] as string + (c: unknown[]) => c[0] as string, ); expect( messagesAfterFirstRotation.some((m) => - m.includes("Fetching SDK documentation") - ) + m.includes("Fetching SDK documentation"), + ), ).toBe(true); // Advance past another rotation interval await vi.advanceTimersByTimeAsync(12_000); const messagesAfterSecondRotation = spinnerMock.message.mock.calls.map( - (c: unknown[]) => c[0] as string + (c: unknown[]) => c[0] as string, ); expect( messagesAfterSecondRotation.some((m) => - m.includes("Analyzing integration requirements") - ) + m.includes("Analyzing integration requirements"), + ), ).toBe(true); // Resolve the resume and let the wizard finish @@ -2284,7 +2286,7 @@ describe("runWizard — progress rotation for long-running steps", () => { this as unknown as { options: { abortSignal?: AbortSignal; retries?: number }; } - ).options + ).options, ); return { createRun: vi.fn(() => @@ -2292,7 +2294,7 @@ describe("runWizard — progress rotation for long-running steps", () => { runId: "test-run-id", startAsync: startAsyncMock, resumeAsync: resumeAsyncMock, - }) + }), ), runById: runByIdMock, } as any; @@ -2306,7 +2308,7 @@ describe("runWizard — progress rotation for long-running steps", () => { await vi.advanceTimersByTimeAsync(72_000); const messages = spinnerMock.message.mock.calls.map( - (c: unknown[]) => c[0] as string + (c: unknown[]) => c[0] as string, ); // After exhausting messages, should show elapsed time expect(messages.some((m) => /\(\d+s\)/.test(m))).toBe(true); @@ -2341,7 +2343,7 @@ describe("runWizard — progress rotation for long-running steps", () => { this as unknown as { options: { abortSignal?: AbortSignal; retries?: number }; } - ).options + ).options, ); return { createRun: vi.fn(() => @@ -2349,7 +2351,7 @@ describe("runWizard — progress rotation for long-running steps", () => { runId: "test-run-id", startAsync: startAsyncMock, resumeAsync: resumeAsyncMock, - }) + }), ), runById: runByIdMock, } as any; diff --git a/packages/cli/test/lib/input-validation.property.test.ts b/packages/cli/test/lib/input-validation.property.test.ts index cea0a6dde..8e5bb4e28 100644 --- a/packages/cli/test/lib/input-validation.property.test.ts +++ b/packages/cli/test/lib/input-validation.property.test.ts @@ -36,7 +36,7 @@ const validSuffixArb = stringMatching(/^[A-Z0-9]{1,10}$/); /** Valid API endpoint paths: segments of alphanum + hyphens separated by slashes */ const validEndpointArb = stringMatching( - /^\/?(api\/0\/)?[a-z][a-z0-9-]{0,30}(\/[a-z][a-z0-9-]{0,30}){0,5}\/?$/ + /^\/?(api\/0\/)?[a-z][a-z0-9-]{0,30}(\/[a-z][a-z0-9-]{0,30}){0,5}\/?$/, ); /** Characters that should be rejected in resource IDs */ @@ -52,7 +52,7 @@ const preEncodedArb = constantFrom( "%00", "%0A", "%7E", - "%41" + "%41", ); /** Control characters (ASCII 0x00-0x1F) as strings */ @@ -68,7 +68,7 @@ const controlCharArb = constantFrom( "\x0e", "\x0f", "\x1b", - "\x1f" + "\x1f", ); describe("rejectControlChars properties", () => { @@ -78,7 +78,7 @@ describe("rejectControlChars properties", () => { // Should not throw rejectControlChars(input, "test"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -89,9 +89,9 @@ describe("rejectControlChars properties", () => { ([prefix, ctrl, suffix]) => { const input = `${prefix}${ctrl}${suffix}`; expect(() => rejectControlChars(input, "test")).toThrow(/Invalid/); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -106,13 +106,13 @@ describe("rejectControlChars properties", () => { expect((e as Error).message).toContain("organization slug"); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); test("carriage return produces specific error description", () => { expect(() => rejectControlChars("abc\rdef", "test")).toThrow( - /carriage return/ + /carriage return/, ); }); @@ -124,7 +124,7 @@ describe("rejectControlChars properties", () => { // Should not throw — all printable ASCII is valid rejectControlChars(input, "test"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -135,7 +135,7 @@ describe("rejectPreEncoded properties", () => { property(validSlugArb, (input) => { rejectPreEncoded(input, "test"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -144,10 +144,10 @@ describe("rejectPreEncoded properties", () => { property(tuple(validSlugArb, preEncodedArb), ([prefix, encoded]) => { const input = `${prefix}${encoded}`; expect(() => rejectPreEncoded(input, "test")).toThrow( - /URL-encoded sequence/ + /URL-encoded sequence/, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -164,7 +164,7 @@ describe("validateResourceId properties", () => { property(validSlugArb, (input) => { validateResourceId(input, "test"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -173,7 +173,7 @@ describe("validateResourceId properties", () => { property(validSuffixArb, (input) => { validateResourceId(input, "test"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -183,7 +183,7 @@ describe("validateResourceId properties", () => { const input = `${slug}${injection}`; expect(() => validateResourceId(input, "test")).toThrow(/Invalid/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -193,34 +193,34 @@ describe("validateResourceId properties", () => { const input = `${slug}${ctrl}`; expect(() => validateResourceId(input, "test")).toThrow(/Invalid/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); test("specific hallucination patterns are caught", () => { // Query injection expect(() => - validateResourceId("my-org?query=foo", "organization slug") + validateResourceId("my-org?query=foo", "organization slug"), ).toThrow(/\?/); // Fragment injection expect(() => - validateResourceId("my-project#anchor", "project slug") + validateResourceId("my-project#anchor", "project slug"), ).toThrow(/#/); // Pre-encoded space expect(() => - validateResourceId("CLI-G%20extra", "issue identifier") + validateResourceId("CLI-G%20extra", "issue identifier"), ).toThrow(/%/); // Tab injection expect(() => - validateResourceId("my-org\tother", "organization slug") + validateResourceId("my-org\tother", "organization slug"), ).toThrow(/tab/); // Non-breaking space (U+00A0) — exotic whitespace matched by \s expect(() => - validateResourceId("my-org\u00a0other", "organization slug") + validateResourceId("my-org\u00a0other", "organization slug"), ).toThrow(/whitespace/); }); }); @@ -231,7 +231,7 @@ describe("validateEndpoint properties", () => { property(validEndpointArb, (input) => { validateEndpoint(input); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -241,14 +241,14 @@ describe("validateEndpoint properties", () => { validSlugArb.map((s) => `../${s}/`), validSlugArb.map((s) => `${s}/../admin/`), validSlugArb.map((s) => `${s}/../../admin/`), - constantFrom("..", "../admin", "../../admin/settings/") + constantFrom("..", "../admin", "../../admin/settings/"), ); await fcAssert( property(traversalArb, (input) => { expect(() => validateEndpoint(input)).toThrow(/path traversal/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -258,7 +258,7 @@ describe("validateEndpoint properties", () => { const input = `${endpoint}${ctrl}`; expect(() => validateEndpoint(input)).toThrow(/Invalid/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -283,7 +283,7 @@ describe("cross-validator consistency", () => { expect(() => rejectControlChars(input, "test")).toThrow(); expect(() => validateResourceId(input, "test")).toThrow(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/lib/install-script.test.ts b/packages/cli/test/lib/install-script.test.ts index e3b9eb7b0..62f20c550 100644 --- a/packages/cli/test/lib/install-script.test.ts +++ b/packages/cli/test/lib/install-script.test.ts @@ -120,7 +120,7 @@ case "$url" in ${downloadedExecutable}SCRIPT ;; esac -` +`, ); chmodSync(join(binDir, "curl"), 0o755); writeFileSync(join(binDir, "gunzip"), "#!/usr/bin/env bash\ncat\n"); @@ -137,25 +137,28 @@ esac JSON.stringify( { version: "0.45.0", description: "x".repeat(96 * 1024) }, null, - 2 + 2, ), - ])("installs the latest stable release when GitHub API access is blocked", (metadata) => { - env.SENTRY_TEST_REGISTRY_RESPONSE = metadata; - const result = spawnSync("bash", [installScript], { - env, - encoding: "utf8", - timeout: 10_000, - }); - - expect(result.status, result.stdout + result.stderr).toBe(0); - expect(recorded("download-urls")).toEqual([ - "https://release-registry.services.sentry.io/apps/sentry/latest", - expect.stringMatching( - /^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/ - ), - ]); - expect(existsSync(join(installDir, "sentry"))).toBe(true); - }); + ])( + "installs the latest stable release when GitHub API access is blocked", + (metadata) => { + env.SENTRY_TEST_REGISTRY_RESPONSE = metadata; + const result = spawnSync("bash", [installScript], { + env, + encoding: "utf8", + timeout: 10_000, + }); + + expect(result.status, result.stdout + result.stderr).toBe(0); + expect(recorded("download-urls")).toEqual([ + "https://release-registry.services.sentry.io/apps/sentry/latest", + expect.stringMatching( + /^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/, + ), + ]); + expect(existsSync(join(installDir, "sentry"))).toBe(true); + }, + ); test.each([ { name: "HTTP error", metadata: "", failure: "22" }, @@ -180,7 +183,7 @@ esac "https://release-registry.services.sentry.io/apps/sentry/latest", "https://api.github.com/repos/getsentry/cli/releases/latest", expect.stringMatching( - /^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/ + /^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/, ), ]); expect(existsSync(join(installDir, "sentry"))).toBe(true); @@ -193,7 +196,7 @@ esac metadata: JSON.stringify( { tag_name: "0.45.0", body: "x".repeat(96 * 1024) }, null, - 2 + 2, ), }, ])("accepts a GitHub release with $name", ({ metadata }) => { @@ -208,7 +211,7 @@ esac expect(result.status, result.stdout + result.stderr).toBe(0); expect(recorded("download-urls").at(-1)).toMatch( - /^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/ + /^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/, ); expect(existsSync(join(installDir, "sentry"))).toBe(true); }); @@ -223,32 +226,32 @@ esac failure: "0", }, { name: "nonstable tag", metadata: '{"tag_name":"nightly"}', failure: "0" }, - ])("stops when the registry and GitHub fail: $name", ({ - metadata, - failure, - }) => { - env.SENTRY_TEST_REGISTRY_FAIL = "22"; - env.SENTRY_TEST_GITHUB_RESPONSE = metadata; - env.SENTRY_TEST_GITHUB_FAIL = failure; - const result = spawnSync("bash", [installScript], { - env, - encoding: "utf8", - timeout: 10_000, - }); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - "Failed to fetch latest stable version from Sentry's release registry and GitHub" - ); - expect(result.stderr).toContain("--version "); - expect(result.stderr).not.toContain("Unexpected failure at line"); - expect(recorded("download-urls")).toEqual([ - "https://release-registry.services.sentry.io/apps/sentry/latest", - "https://api.github.com/repos/getsentry/cli/releases/latest", - ]); - expect(recorded("setup-args")).toEqual([]); - expect(existsSync(join(installDir, "sentry"))).toBe(false); - }); + ])( + "stops when the registry and GitHub fail: $name", + ({ metadata, failure }) => { + env.SENTRY_TEST_REGISTRY_FAIL = "22"; + env.SENTRY_TEST_GITHUB_RESPONSE = metadata; + env.SENTRY_TEST_GITHUB_FAIL = failure; + const result = spawnSync("bash", [installScript], { + env, + encoding: "utf8", + timeout: 10_000, + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain( + "Failed to fetch latest stable version from Sentry's release registry and GitHub", + ); + expect(result.stderr).toContain("--version "); + expect(result.stderr).not.toContain("Unexpected failure at line"); + expect(recorded("download-urls")).toEqual([ + "https://release-registry.services.sentry.io/apps/sentry/latest", + "https://api.github.com/repos/getsentry/cli/releases/latest", + ]); + expect(recorded("setup-args")).toEqual([]); + expect(existsSync(join(installDir, "sentry"))).toBe(false); + }, + ); function recorded(name: string): string[] { const path = join(testDir, name); @@ -259,7 +262,7 @@ esac function installerTempFiles(): string[] { return readdirSync(testDir).filter((name) => - name.startsWith("sentry-install-") + name.startsWith("sentry-install-"), ); } @@ -335,7 +338,7 @@ JSON exit 91 ;; esac -` +`, ); chmodSync(join(binDir, "curl"), 0o755); writeFileSync( @@ -346,14 +349,14 @@ if [[ $# -gt 0 && "$1" != "-c" ]]; then exit 64 fi exec /usr/bin/gunzip "$@" -` +`, ); chmodSync(join(binDir, "gunzip"), 0o755); } /** Run curl-style piped installation in a real controlling terminal. */ function runInTerminal( - options: { redirect?: string; detached?: boolean } = {} + options: { redirect?: string; detached?: boolean } = {}, ) { const launcher = join(testDir, "piped-install.cjs"); const command = @@ -366,7 +369,7 @@ const result = spawnSync("bash", ["-c", ${JSON.stringify(command)}], { stdio: "inherit", detached: ${options.detached ?? false} }); process.exitCode = result.status ?? 1; -` +`, ); // script(1) has different argument syntax on BSD and util-linux. A // detached shell retains its PTY output but cannot open /dev/tty. @@ -403,7 +406,7 @@ process.exitCode = result.status ?? 1; "--no-completions", "--no-agent-skills", ], - { env, encoding: "utf8", timeout: 10_000 } + { env, encoding: "utf8", timeout: 10_000 }, ); expect(result.status, result.stdout + result.stderr).toBe(0); expect(recorded("setup-args")).toEqual([ @@ -428,7 +431,7 @@ process.exitCode = result.status ?? 1; expect(installerTempFiles()).toEqual([]); expect(recorded("download-urls")).toEqual([ expect.stringMatching( - /^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.31\.0\/sentry-.+\.gz$/ + /^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.31\.0\/sentry-.+\.gz$/, ), ]); }); @@ -441,7 +444,7 @@ process.exitCode = result.status ?? 1; const result = spawnSync( "bash", [installScript, "--version", "nightly", "--no-modify-path"], - { env, encoding: "utf8", timeout: 10_000 } + { env, encoding: "utf8", timeout: 10_000 }, ); expect(result.status, result.stdout + result.stderr).toBe(0); @@ -467,42 +470,47 @@ process.exitCode = result.status ?? 1; test.each([ { failure: "token", flag: "SENTRY_TEST_NIGHTLY_TOKEN_FAIL" }, { failure: "manifest", flag: "SENTRY_TEST_NIGHTLY_MANIFEST_FAIL" }, - ])("does not fall back to legacy GHCR when Toolkit $failure fails", ({ - flag, - }) => { - configureNightlyDownload(false); - env[flag] = "1"; - const result = spawnSync("bash", [installScript, "--version", "nightly"], { - env, - encoding: "utf8", - timeout: 10_000, - }); - - expect(result.status).not.toBe(0); - expect(result.stderr).toContain( - flag === "SENTRY_TEST_NIGHTLY_TOKEN_FAIL" - ? "Failed to get GHCR token" - : "Failed to fetch nightly manifest from GHCR" - ); - expect(result.stderr).not.toContain("Unexpected failure at line"); - expect(recorded("curl-urls")).toEqual( - flag === "SENTRY_TEST_NIGHTLY_TOKEN_FAIL" - ? ["https://ghcr.io/token?scope=repository:getsentry/toolkit:pull"] - : [ - "https://ghcr.io/token?scope=repository:getsentry/toolkit:pull", - "https://ghcr.io/v2/getsentry/toolkit/manifests/nightly", - ] - ); - expect(recorded("setup-args")).toEqual([]); - expect(existsSync(join(installDir, "sentry"))).toBe(false); - }); + ])( + "does not fall back to legacy GHCR when Toolkit $failure fails", + ({ flag }) => { + configureNightlyDownload(false); + env[flag] = "1"; + const result = spawnSync( + "bash", + [installScript, "--version", "nightly"], + { + env, + encoding: "utf8", + timeout: 10_000, + }, + ); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain( + flag === "SENTRY_TEST_NIGHTLY_TOKEN_FAIL" + ? "Failed to get GHCR token" + : "Failed to fetch nightly manifest from GHCR", + ); + expect(result.stderr).not.toContain("Unexpected failure at line"); + expect(recorded("curl-urls")).toEqual( + flag === "SENTRY_TEST_NIGHTLY_TOKEN_FAIL" + ? ["https://ghcr.io/token?scope=repository:getsentry/toolkit:pull"] + : [ + "https://ghcr.io/token?scope=repository:getsentry/toolkit:pull", + "https://ghcr.io/v2/getsentry/toolkit/manifests/nightly", + ], + ); + expect(recorded("setup-args")).toEqual([]); + expect(existsSync(join(installDir, "sentry"))).toBe(false); + }, + ); test("uses the legacy release only after a Toolkit tag returns HTTP 404", () => { env.SENTRY_TEST_TOOLKIT_STATUS = "404"; const result = spawnSync( "bash", [installScript, "--version", "0.42.2", "--no-modify-path"], - { env, encoding: "utf8", timeout: 10_000 } + { env, encoding: "utf8", timeout: 10_000 }, ); expect(result.status, result.stdout + result.stderr).toBe(0); @@ -534,11 +542,11 @@ process.exitCode = result.status ?? 1; const secondPage = join(testDir, "releases-2.json"); writeFileSync( firstPage, - '[\n {\n "tag_name": "mcp@1.0.0",\n "prerelease": false\n }\n]\n' + '[\n {\n "tag_name": "mcp@1.0.0",\n "prerelease": false\n }\n]\n', ); writeFileSync( secondPage, - '[\n {\n "tag_name": "cli@0.46.0",\n "prerelease": false\n }\n]\n' + '[\n {\n "tag_name": "cli@0.46.0",\n "prerelease": false\n }\n]\n', ); env.SENTRY_TEST_RELEASES_PAGE_1 = firstPage; env.SENTRY_TEST_RELEASES_PAGE_2 = secondPage; @@ -556,10 +564,10 @@ process.exitCode = result.status ?? 1; "https://api.github.com/repos/getsentry/toolkit/releases?per_page=100&page=2", ]); expect(recorded("curl-urls")[3]).toBe( - "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.46.0" + "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.46.0", ); expect(recorded("curl-urls")[4]).toContain( - "/getsentry/toolkit/releases/download/cli@0.46.0/" + "/getsentry/toolkit/releases/download/cli@0.46.0/", ); }); @@ -567,7 +575,7 @@ process.exitCode = result.status ?? 1; const firstPage = join(testDir, "releases-1.json"); writeFileSync( firstPage, - '[\n {\n "tag_name": "cli@0.47.0-dev.1",\n "prerelease": false\n },\n {\n "tag_name": "cli@0.46.0",\n "prerelease": false\n }\n]\n' + '[\n {\n "tag_name": "cli@0.47.0-dev.1",\n "prerelease": false\n },\n {\n "tag_name": "cli@0.46.0",\n "prerelease": false\n }\n]\n', ); env.SENTRY_TEST_RELEASES_PAGE_1 = firstPage; env.SENTRY_TEST_TOOLKIT_STATUS = "200"; @@ -579,10 +587,10 @@ process.exitCode = result.status ?? 1; expect(result.status, result.stdout + result.stderr).toBe(0); expect(recorded("curl-urls")[2]).toBe( - "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.46.0" + "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.46.0", ); expect(recorded("curl-urls")[3]).toContain( - "/getsentry/toolkit/releases/download/cli@0.46.0/" + "/getsentry/toolkit/releases/download/cli@0.46.0/", ); expect(recorded("setup-args")).toContain("stable"); }); @@ -606,7 +614,7 @@ process.exitCode = result.status ?? 1; assets: [{ tag_name: "cli@99.0.0", prerelease: false }], tag_name: "cli@0.46.0", }, - ]) + ]), ); env.SENTRY_TEST_RELEASES_PAGE_1 = firstPage; env.SENTRY_TEST_TOOLKIT_STATUS = "200"; @@ -618,10 +626,10 @@ process.exitCode = result.status ?? 1; expect(result.status, result.stdout + result.stderr).toBe(0); expect(recorded("curl-urls")[2]).toBe( - "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.46.0" + "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.46.0", ); expect(recorded("curl-urls")[3]).toContain( - "/getsentry/toolkit/releases/download/cli@0.46.0/" + "/getsentry/toolkit/releases/download/cli@0.46.0/", ); }); @@ -639,7 +647,7 @@ process.exitCode = result.status ?? 1; "prerelease": false, "body": "${"x".repeat(256 * 1024)}" } -]` +]`, ); env.SENTRY_TEST_RELEASES_PAGE_1 = firstPage; env.SENTRY_TEST_TOOLKIT_STATUS = "200"; @@ -651,7 +659,7 @@ process.exitCode = result.status ?? 1; expect(result.status, result.stdout + result.stderr).toBe(0); expect(recorded("curl-urls")).toContain( - "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.46.0" + "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.46.0", ); expect(existsSync(join(installDir, "sentry"))).toBe(true); }); @@ -662,7 +670,7 @@ process.exitCode = result.status ?? 1; "#!/bin/sh\nprintf 'musl libc (x86_64)\\n'\n", { mode: 0o700, - } + }, ); const result = spawnSync("bash", [installScript, "--no-modify-path"], { env, @@ -692,7 +700,7 @@ process.exitCode = result.status ?? 1; "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.42.2", ]); expect(recorded("curl-urls")[4]).toBe( - "https://api.github.com/repos/getsentry/cli/releases/tags/0.42.2" + "https://api.github.com/repos/getsentry/cli/releases/tags/0.42.2", ); }); @@ -771,15 +779,16 @@ process.exitCode = result.status ?? 1; expect(recorded("post-binary")).toEqual([join(currentDir, "sentry")]); }); - test.each([ - 1, 130, - ])("preserves setup failure or interruption exit %i", (exitCode) => { - env.SENTRY_TEST_SETUP_EXIT = String(exitCode); - const result = runInTerminal(); - expect(result.status, result.stdout + result.stderr).toBe(exitCode); - expect(recorded("post-args")).toEqual([]); - expect(existsSync(join(installDir, "sentry"))).toBe(true); - }); + test.each([1, 130])( + "preserves setup failure or interruption exit %i", + (exitCode) => { + env.SENTRY_TEST_SETUP_EXIT = String(exitCode); + const result = runInTerminal(); + expect(result.status, result.stdout + result.stderr).toBe(exitCode); + expect(recorded("post-args")).toEqual([]); + expect(existsSync(join(installDir, "sentry"))).toBe(true); + }, + ); test.each([1, 130])("preserves non-interactive setup exit %i", (exitCode) => { env.SENTRY_TEST_SETUP_EXIT = String(exitCode); diff --git a/packages/cli/test/lib/interactive-login.test.ts b/packages/cli/test/lib/interactive-login.test.ts index f8eabf644..bd7668ccd 100644 --- a/packages/cli/test/lib/interactive-login.test.ts +++ b/packages/cli/test/lib/interactive-login.test.ts @@ -18,21 +18,21 @@ import { test, vi, } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as browser from "../../src/lib/browser.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as clipboard from "../../src/lib/clipboard.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbInstance from "../../src/lib/db/index.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as dbUser from "../../src/lib/db/user.js"; import { buildDeviceFlowDisplay, runInteractiveLogin, } from "../../src/lib/interactive-login.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as oauth from "../../src/lib/oauth.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as qrcode from "../../src/lib/qrcode.js"; import type { TokenResponse } from "../../src/types/index.js"; @@ -68,7 +68,7 @@ describe("buildDeviceFlowDisplay", () => { "AB_CD", urlWithUnderscores, true, - false + false, ); const joined = lines.join("\n"); // URL must not be escaped — underscores stay as-is for copy-paste @@ -169,7 +169,7 @@ describe("runInteractiveLogin", () => { await callbacks.onUserCode( "ABCD", "https://sentry.io/auth/device/", - "https://sentry.io/auth/device/?user_code=ABCD" + "https://sentry.io/auth/device/?user_code=ABCD", ); return makeTokenResponse({ id: "48168", @@ -201,7 +201,7 @@ describe("runInteractiveLogin", () => { await callbacks.onUserCode( "EFGH", "https://sentry.io/auth/device/", - "https://sentry.io/auth/device/?user_code=EFGH" + "https://sentry.io/auth/device/?user_code=EFGH", ); return makeTokenResponse({ id: "123", @@ -231,7 +231,7 @@ describe("runInteractiveLogin", () => { await callbacks.onUserCode( "WXYZ", "https://sentry.io/auth/device/", - "https://sentry.io/auth/device/?user_code=WXYZ" + "https://sentry.io/auth/device/?user_code=WXYZ", ); return makeTokenResponse(); // no user }); @@ -250,7 +250,7 @@ describe("runInteractiveLogin", () => { await callbacks.onUserCode( "SCOP", "https://sentry.io/auth/device/", - "https://sentry.io/auth/device/?user_code=SCOP" + "https://sentry.io/auth/device/?user_code=SCOP", ); return makeTokenResponse(); }); @@ -263,7 +263,7 @@ describe("runInteractiveLogin", () => { expect(performDeviceFlowSpy).toHaveBeenCalledWith( expect.any(Object), 1000, - "project:read org:read" + "project:read org:read", ); }); @@ -274,7 +274,7 @@ describe("runInteractiveLogin", () => { await callbacks.onUserCode( "NONE", "https://sentry.io/auth/device/", - "https://sentry.io/auth/device/?user_code=NONE" + "https://sentry.io/auth/device/?user_code=NONE", ); return makeTokenResponse(); }); @@ -284,7 +284,7 @@ describe("runInteractiveLogin", () => { expect(performDeviceFlowSpy).toHaveBeenCalledWith( expect.any(Object), 1000, - undefined + undefined, ); }); }); diff --git a/packages/cli/test/lib/introspect.property.test.ts b/packages/cli/test/lib/introspect.property.test.ts index 9c46a8bb2..a67652fc5 100644 --- a/packages/cli/test/lib/introspect.property.test.ts +++ b/packages/cli/test/lib/introspect.property.test.ts @@ -63,12 +63,10 @@ const flagsMapArb = array(tuple(nameArb, flagDefArb), { const commandArb = record({ brief: string(), flags: flagsMapArb, -}).map( - ({ brief, flags }): Command => ({ - brief, - parameters: { flags, aliases: {} }, - }) -); +}).map(({ brief, flags }): Command => ({ + brief, + parameters: { flags, aliases: {} }, +})); /** Generate a RouteMapEntry with a command */ const commandEntryArb = tuple(nameArb, commandArb, boolean()).map( @@ -76,19 +74,17 @@ const commandEntryArb = tuple(nameArb, commandArb, boolean()).map( name: { original: name }, target: cmd, hidden, - }) + }), ); /** Generate a simple RouteMap (one level) */ const routeMapArb = tuple( string(), - array(commandEntryArb, { minLength: 0, maxLength: 5 }) -).map( - ([brief, entries]): RouteMap => ({ - brief, - getAllEntries: () => entries, - }) -); + array(commandEntryArb, { minLength: 0, maxLength: 5 }), +).map(([brief, entries]): RouteMap => ({ + brief, + getAllEntries: () => entries, +})); // --------------------------------------------------------------------------- // Properties @@ -101,7 +97,7 @@ describe("property: extractFlags", () => { const result = extractFlags(flags); expect(result).toHaveLength(Object.keys(flags).length); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -114,7 +110,7 @@ describe("property: extractFlags", () => { expect(resultNames.has(name)).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -131,7 +127,7 @@ describe("property: extractFlags", () => { } } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -141,7 +137,7 @@ describe("property: getPositionalString", () => { fcAssert( property( array( - record({ placeholder: oneof(string(), constantFrom(undefined)) }) + record({ placeholder: oneof(string(), constantFrom(undefined)) }), ), (params) => { const result = getPositionalString({ @@ -152,9 +148,9 @@ describe("property: getPositionalString", () => { expect(result).toContain("<"); expect(result).toContain(">"); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -167,7 +163,7 @@ describe("property: getPositionalString", () => { }); expect(result).toContain("..."); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -181,7 +177,7 @@ describe("property: extractAllRoutes", () => { // Build a set of names that are ONLY hidden (no visible entry with same name) const visibleNames = new Set( - allEntries.filter((e) => !e.hidden).map((e) => e.name.original) + allEntries.filter((e) => !e.hidden).map((e) => e.name.original), ); const onlyHiddenNames = allEntries .filter((e) => e.hidden && !visibleNames.has(e.name.original)) @@ -191,7 +187,7 @@ describe("property: extractAllRoutes", () => { expect(onlyHiddenNames).not.toContain(route.name); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -204,7 +200,7 @@ describe("property: extractAllRoutes", () => { .filter((e) => !e.hidden).length; expect(routes.length).toBeLessThanOrEqual(visibleCount); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -215,7 +211,7 @@ describe("property: resolveCommandPath", () => { property(routeMapArb, (routeMap) => { expect(resolveCommandPath(routeMap, [])).toBeNull(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -227,7 +223,7 @@ describe("property: resolveCommandPath", () => { expect(result.info.path.startsWith("sentry ")).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/introspect.test.ts b/packages/cli/test/lib/introspect.test.ts index 5636f0e85..0de1ddb83 100644 --- a/packages/cli/test/lib/introspect.test.ts +++ b/packages/cli/test/lib/introspect.test.ts @@ -40,7 +40,7 @@ function makeCommand(overrides: Partial = {}): Command { function makeRouteMap( entries: RouteMapEntry[], - overrides: Partial = {} + overrides: Partial = {}, ): RouteMap { return { brief: "Test route", @@ -52,7 +52,7 @@ function makeRouteMap( function makeEntry( name: string, target: RouteMap | Command, - hidden = false + hidden = false, ): RouteMapEntry { return { name: { original: name }, @@ -299,7 +299,7 @@ describe("extractAllRoutes", () => { const issueRoute = makeRouteMap( [makeEntry("list", listCmd), makeEntry("view", viewCmd)], - { brief: "Manage issues" } + { brief: "Manage issues" }, ); const topLevel = makeRouteMap([ @@ -347,7 +347,7 @@ describe("resolveCommandPath", () => { const issueRoute = makeRouteMap( [makeEntry("list", listCmd), makeEntry("view", viewCmd)], - { brief: "Manage issues" } + { brief: "Manage issues" }, ); const topLevel = makeRouteMap([ @@ -413,7 +413,7 @@ describe("resolveCommandPath", () => { test("returns null for extra path segments beyond 2 levels", () => { expect(resolveCommandPath(topLevel, ["issue", "list", "extra"])).toBeNull(); expect( - resolveCommandPath(topLevel, ["issue", "list", "extra", "more"]) + resolveCommandPath(topLevel, ["issue", "list", "extra", "more"]), ).toBeNull(); }); diff --git a/packages/cli/test/lib/issue-collapse.property.test.ts b/packages/cli/test/lib/issue-collapse.property.test.ts index d0842f5d0..404e9413c 100644 --- a/packages/cli/test/lib/issue-collapse.property.test.ts +++ b/packages/cli/test/lib/issue-collapse.property.test.ts @@ -26,9 +26,9 @@ describe("property: buildIssueListCollapse", () => { }); expect(result).toContain("filtered"); expect(result).toContain("unhandled"); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -42,9 +42,9 @@ describe("property: buildIssueListCollapse", () => { shouldCollapseLifetime: collapseLifetime, }); expect(result.includes("stats")).toBe(collapseStats); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -58,9 +58,9 @@ describe("property: buildIssueListCollapse", () => { shouldCollapseLifetime: collapseLifetime, }); expect(result.includes("lifetime")).toBe(collapseLifetime); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -72,7 +72,7 @@ describe("property: buildIssueListCollapse", () => { }); expect(result).not.toContain("lifetime"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -86,9 +86,9 @@ describe("property: buildIssueListCollapse", () => { shouldCollapseLifetime: collapseLifetime, }); expect(result).not.toContain("base"); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -102,9 +102,9 @@ describe("property: buildIssueListCollapse", () => { shouldCollapseLifetime: collapseLifetime, }); expect(new Set(result).size).toBe(result.length); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -120,9 +120,9 @@ describe("property: buildIssueListCollapse", () => { const expected = 2 + (collapseStats ? 1 : 0) + (collapseLifetime ? 1 : 0); expect(result.length).toBe(expected); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/issue-id.property.test.ts b/packages/cli/test/lib/issue-id.property.test.ts index 8200e7cd3..1f454e729 100644 --- a/packages/cli/test/lib/issue-id.property.test.ts +++ b/packages/cli/test/lib/issue-id.property.test.ts @@ -36,14 +36,17 @@ const numericArb = array(constantFrom(..."0123456789".split("")), { const alphanumericWithLetterArb = tuple( array( constantFrom( - ..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split("") + ..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""), ), { minLength: 1, maxLength: 10, - } + }, ), - array(constantFrom(..."0123456789".split("")), { minLength: 0, maxLength: 5 }) + array(constantFrom(..."0123456789".split("")), { + minLength: 0, + maxLength: 5, + }), ).map(([letters, digits]) => [...letters, ...digits].join("")); /** Generate valid project slugs (lowercase alphanumeric with hyphens) */ @@ -52,7 +55,7 @@ const projectSlugArb = array( { minLength: 1, maxLength: 30, - } + }, ) .map((chars) => chars.join("")) .filter((s) => !(s.startsWith("-") || s.endsWith("-")) && s.length > 0); @@ -61,10 +64,10 @@ const projectSlugArb = array( const shortSuffixArb = array( constantFrom( ..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split( - "" - ) + "", + ), ), - { minLength: 1, maxLength: 10 } + { minLength: 1, maxLength: 10 }, ).map((chars) => chars.join("")); /** Generate alias-suffix format strings */ @@ -73,7 +76,7 @@ const aliasSuffixFormatArb = tuple( minLength: 1, maxLength: 10, }), - shortSuffixArb + shortSuffixArb, ).map(([aliasChars, suffix]) => `${aliasChars.join("")}-${suffix}`); // Properties for isAllDigits @@ -84,7 +87,7 @@ describe("property: isAllDigits", () => { property(numericArb, (digits) => { expect(isAllDigits(digits)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -93,7 +96,7 @@ describe("property: isAllDigits", () => { property(alphanumericWithLetterArb, (str) => { expect(isAllDigits(str)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -103,7 +106,7 @@ describe("property: isAllDigits", () => { const withHyphen = `${a}-${b}`; expect(isAllDigits(withHyphen)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -116,7 +119,7 @@ describe("property: isAllDigits", () => { property(nat(999_999_999), (n) => { expect(isAllDigits(String(n))).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -129,7 +132,7 @@ describe("property: isShortSuffix", () => { property(shortSuffixArb, (suffix) => { expect(isShortSuffix(suffix)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -139,7 +142,7 @@ describe("property: isShortSuffix", () => { const withHyphen = `${a}-${b}`; expect(isShortSuffix(withHyphen)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -148,7 +151,7 @@ describe("property: isShortSuffix", () => { property(numericArb, (digits) => { expect(isShortSuffix(digits)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -159,9 +162,9 @@ describe("property: isShortSuffix", () => { tuple(shortSuffixArb, constantFrom(...specialChars.split(""))), ([suffix, special]) => { expect(isShortSuffix(suffix + special)).toBe(false); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -174,7 +177,7 @@ describe("property: isShortId", () => { property(alphanumericWithLetterArb, (str) => { expect(isShortId(str)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -183,7 +186,7 @@ describe("property: isShortId", () => { property(numericArb, (digits) => { expect(isShortId(digits)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -200,9 +203,9 @@ describe("property: isShortId", () => { const isShort = isShortId(str); const isNumeric = isAllDigits(str); expect(isShort && isNumeric).toBe(false); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -218,7 +221,7 @@ describe("property: parseAliasSuffix", () => { expect(result?.alias).toBeDefined(); expect(result?.suffix).toBeDefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -230,7 +233,7 @@ describe("property: parseAliasSuffix", () => { expect(result.alias).toBe(result.alias.toLowerCase()); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -242,7 +245,7 @@ describe("property: parseAliasSuffix", () => { expect(result.suffix).toBe(result.suffix.toUpperCase()); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -253,7 +256,7 @@ describe("property: parseAliasSuffix", () => { const result = parseAliasSuffix(input); expect(result).toBeNull(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -267,7 +270,7 @@ describe("property: parseAliasSuffix", () => { expect(reconstructed.toLowerCase()).toBe(input.toLowerCase()); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -282,9 +285,9 @@ describe("property: expandToFullShortId", () => { ([suffix, projectSlug]) => { const result = expandToFullShortId(suffix, projectSlug); expect(result).toBe(result.toUpperCase()); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -295,9 +298,9 @@ describe("property: expandToFullShortId", () => { ([suffix, projectSlug]) => { const result = expandToFullShortId(suffix, projectSlug); expect(result).toContain("-"); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -308,9 +311,9 @@ describe("property: expandToFullShortId", () => { ([suffix, projectSlug]) => { const result = expandToFullShortId(suffix, projectSlug); expect(result.endsWith(suffix.toUpperCase())).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -321,9 +324,9 @@ describe("property: expandToFullShortId", () => { ([suffix, projectSlug]) => { const result = expandToFullShortId(suffix, projectSlug); expect(result.startsWith(projectSlug.toUpperCase())).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -335,9 +338,9 @@ describe("property: expandToFullShortId", () => { const result = expandToFullShortId(suffix, projectSlug); const expected = `${projectSlug.toUpperCase()}-${suffix.toUpperCase()}`; expect(result).toBe(expected); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -349,12 +352,12 @@ describe("property: expandToFullShortId", () => { const result1 = expandToFullShortId(suffix, projectSlug); const result2 = expandToFullShortId( suffix.toUpperCase(), - projectSlug.toUpperCase() + projectSlug.toUpperCase(), ); expect(result1).toBe(result2); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -372,9 +375,9 @@ describe("property: cross-function invariants", () => { const expanded = expandToFullShortId(suffix, projectSlug); expect(isShortId(expanded)).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -386,11 +389,11 @@ describe("property: cross-function invariants", () => { // Use alias as project slug for expansion const expanded = expandToFullShortId(parsed.suffix, parsed.alias); expect(expanded).toBe( - `${parsed.alias.toUpperCase()}-${parsed.suffix}` + `${parsed.alias.toUpperCase()}-${parsed.suffix}`, ); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -414,7 +417,7 @@ describe("property: cross-function invariants", () => { expect(numeric || short).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/issue-id.test.ts b/packages/cli/test/lib/issue-id.test.ts index 4576c6153..be20eb254 100644 --- a/packages/cli/test/lib/issue-id.test.ts +++ b/packages/cli/test/lib/issue-id.test.ts @@ -61,7 +61,7 @@ describe("expandToFullShortId representative examples", () => { test("expands suffix with project slug", () => { expect(expandToFullShortId("G", "craft")).toBe("CRAFT-G"); expect(expandToFullShortId("4y", "spotlight-electron")).toBe( - "SPOTLIGHT-ELECTRON-4Y" + "SPOTLIGHT-ELECTRON-4Y", ); }); @@ -89,7 +89,7 @@ describe("short ID resolution flow", () => { function resolveIssueId( input: string, - defaultProject?: string + defaultProject?: string, ): string | null { // Check alias-suffix pattern first const aliasSuffix = parseAliasSuffix(input); @@ -120,7 +120,7 @@ describe("short ID resolution flow", () => { test("resolves short suffix with default project", () => { expect(resolveIssueId("4y", "spotlight-electron")).toBe( - "SPOTLIGHT-ELECTRON-4Y" + "SPOTLIGHT-ELECTRON-4Y", ); expect(resolveIssueId("G", "craft")).toBe("CRAFT-G"); // Pure numeric suffix works when project context is available @@ -129,7 +129,7 @@ describe("short ID resolution flow", () => { test("passes through full short ID", () => { expect(resolveIssueId("SPOTLIGHT-ELECTRON-4Y")).toBe( - "SPOTLIGHT-ELECTRON-4Y" + "SPOTLIGHT-ELECTRON-4Y", ); expect(resolveIssueId("craft-g")).toBe("CRAFT-G"); }); diff --git a/packages/cli/test/lib/issue-links.test.ts b/packages/cli/test/lib/issue-links.test.ts index 33cba6d8f..9bf8406cf 100644 --- a/packages/cli/test/lib/issue-links.test.ts +++ b/packages/cli/test/lib/issue-links.test.ts @@ -91,10 +91,10 @@ describe("external issue associations", () => { }); expect(resolveAppIssueLink).not.toHaveBeenCalled(); expect(invalidateCachedResponsesMatching).toHaveBeenCalledWith( - "https://de.sentry.io/api/0/organizations/example/issues/123/" + "https://de.sentry.io/api/0/organizations/example/issues/123/", ); expect(invalidateCachedResponsesMatching).toHaveBeenCalledWith( - "https://sentry.io/api/0/issues/123/" + "https://sentry.io/api/0/issues/123/", ); }); @@ -120,25 +120,25 @@ describe("external issue associations", () => { expect.objectContaining({ appSlug: "custom-tracker", url: nativeLink.url, - }) + }), ); expect(resolveNativeIssueLink).not.toHaveBeenCalled(); }); - test.each([ - nativeLink.url, - appLink.webUrl, - ])("dry-run link submits no mutation: %s", async (url) => { - const result = await linkExternalIssue({ ...options, url, dryRun: true }); - expect(result).toMatchObject({ - linked: false, - changed: false, - dryRun: true, - }); - expect(linkNativeIssue).not.toHaveBeenCalled(); - expect(linkAppIssue).not.toHaveBeenCalled(); - expect(invalidateCachedResponsesMatching).not.toHaveBeenCalled(); - }); + test.each([nativeLink.url, appLink.webUrl])( + "dry-run link submits no mutation: %s", + async (url) => { + const result = await linkExternalIssue({ ...options, url, dryRun: true }); + expect(result).toMatchObject({ + linked: false, + changed: false, + dryRun: true, + }); + expect(linkNativeIssue).not.toHaveBeenCalled(); + expect(linkAppIssue).not.toHaveBeenCalled(); + expect(invalidateCachedResponsesMatching).not.toHaveBeenCalled(); + }, + ); test("dry-run link describes an existing app association", async () => { vi.mocked(resolveAppIssueLink).mockResolvedValue({ @@ -179,7 +179,7 @@ describe("external issue associations", () => { expect(unlinkNativeIssueLink).toHaveBeenCalledWith( "example", "123", - nativeLink + nativeLink, ); expect(resolveNativeIssueLink).not.toHaveBeenCalled(); expect(resolveAppIssueLink).not.toHaveBeenCalled(); @@ -197,31 +197,35 @@ describe("external issue associations", () => { expect(result).toMatchObject({ linked: false, changed: true }); }); - test.each([ - nativeLink.url, - appLink.webUrl, - ])("dry-run unlink preserves the link: %s", async (url) => { - const result = await unlinkExternalIssue({ ...options, url, dryRun: true }); - expect(result).toMatchObject({ - linked: true, - changed: false, - dryRun: true, - }); - expect(unlinkNativeIssueLink).not.toHaveBeenCalled(); - expect(unlinkAppIssueLink).not.toHaveBeenCalled(); - }); + test.each([nativeLink.url, appLink.webUrl])( + "dry-run unlink preserves the link: %s", + async (url) => { + const result = await unlinkExternalIssue({ + ...options, + url, + dryRun: true, + }); + expect(result).toMatchObject({ + linked: true, + changed: false, + dryRun: true, + }); + expect(unlinkNativeIssueLink).not.toHaveBeenCalled(); + expect(unlinkAppIssueLink).not.toHaveBeenCalled(); + }, + ); - test.each([ - nativeLink.url, - appLink.webUrl, - ])("missing association is already unlinked: %s", async (url) => { - vi.mocked(findNativeIssueLink).mockReturnValue(undefined); - vi.mocked(findAppIssueLink).mockReturnValue(undefined); - const result = await unlinkExternalIssue({ ...options, url }); - expect(result).toMatchObject({ linked: false, changed: false }); - expect(unlinkNativeIssueLink).not.toHaveBeenCalled(); - expect(unlinkAppIssueLink).not.toHaveBeenCalled(); - }); + test.each([nativeLink.url, appLink.webUrl])( + "missing association is already unlinked: %s", + async (url) => { + vi.mocked(findNativeIssueLink).mockReturnValue(undefined); + vi.mocked(findAppIssueLink).mockReturnValue(undefined); + const result = await unlinkExternalIssue({ ...options, url }); + expect(result).toMatchObject({ linked: false, changed: false }); + expect(unlinkNativeIssueLink).not.toHaveBeenCalled(); + expect(unlinkAppIssueLink).not.toHaveBeenCalled(); + }, + ); test("a failed link read is not treated as an empty list", async () => { const error = new ApiError("Forbidden", 403); @@ -255,7 +259,7 @@ describe("external issue associations", () => { ...options, appSlug: "linear", integrationId: "20", - }) + }), ).rejects.toThrow("--integration"); expect(resolveAppIssueLink).not.toHaveBeenCalled(); }); diff --git a/packages/cli/test/lib/jvm-bundle.test.ts b/packages/cli/test/lib/jvm-bundle.test.ts index b7924ac3d..ea61fb51f 100644 --- a/packages/cli/test/lib/jvm-bundle.test.ts +++ b/packages/cli/test/lib/jvm-bundle.test.ts @@ -28,7 +28,7 @@ describe("isInAmbiguousBuildDir", () => { test("keeps build/ under src/", () => { expect( - _isInAmbiguousBuildDir("src/main/java/com/example/build/Builder.java") + _isInAmbiguousBuildDir("src/main/java/com/example/build/Builder.java"), ).toBe(false); }); @@ -51,8 +51,8 @@ describe("isInAmbiguousBuildDir", () => { test("keeps deeply nested build/ under src/", () => { expect( _isInAmbiguousBuildDir( - "module/src/main/java/com/build/target/out/Foo.java" - ) + "module/src/main/java/com/build/target/out/Foo.java", + ), ).toBe(false); }); }); @@ -60,37 +60,37 @@ describe("isInAmbiguousBuildDir", () => { describe("stripSourceSetPrefix", () => { test("strips src/main/java/", () => { expect(_stripSourceSetPrefix("src/main/java/io/sentry/core/Foo.java")).toBe( - "io/sentry/core/Foo.java" + "io/sentry/core/Foo.java", ); }); test("strips module/src/main/kotlin/", () => { expect( - _stripSourceSetPrefix("sentry-core/src/main/kotlin/io/sentry/Foo.kt") + _stripSourceSetPrefix("sentry-core/src/main/kotlin/io/sentry/Foo.kt"), ).toBe("io/sentry/Foo.kt"); }); test("strips src/test/java/", () => { expect( - _stripSourceSetPrefix("src/test/java/com/example/FooTest.java") + _stripSourceSetPrefix("src/test/java/com/example/FooTest.java"), ).toBe("com/example/FooTest.java"); }); test("strips src/main/scala/", () => { expect(_stripSourceSetPrefix("src/main/scala/com/example/App.scala")).toBe( - "com/example/App.scala" + "com/example/App.scala", ); }); test("strips src/main/groovy/", () => { expect( - _stripSourceSetPrefix("src/main/groovy/com/example/Script.groovy") + _stripSourceSetPrefix("src/main/groovy/com/example/Script.groovy"), ).toBe("com/example/Script.groovy"); }); test("strips src/main/clojure/", () => { expect(_stripSourceSetPrefix("src/main/clojure/com/example/core.clj")).toBe( - "com/example/core.clj" + "com/example/core.clj", ); }); @@ -100,7 +100,7 @@ describe("stripSourceSetPrefix", () => { test("normalizes backslashes", () => { expect( - _stripSourceSetPrefix("src\\main\\java\\com\\example\\Foo.java") + _stripSourceSetPrefix("src\\main\\java\\com\\example\\Foo.java"), ).toBe("com/example/Foo.java"); }); }); @@ -108,7 +108,7 @@ describe("stripSourceSetPrefix", () => { describe("buildSourceUrl", () => { test("adds ~/ prefix and .jvm extension", () => { expect(_buildSourceUrl("io/sentry/core/Foo.java")).toBe( - "~/io/sentry/core/Foo.jvm" + "~/io/sentry/core/Foo.jvm", ); }); @@ -118,7 +118,7 @@ describe("buildSourceUrl", () => { test("replaces .scala extension", () => { expect(_buildSourceUrl("com/example/App.scala")).toBe( - "~/com/example/App.jvm" + "~/com/example/App.jvm", ); }); }); @@ -143,7 +143,7 @@ describe("buildJvmBundle", () => { }); await writeFile( join(tempDir, "src", "main", "java", "com", "example", "Main.java"), - "public class Main {}" + "public class Main {}", ); const result = await buildJvmBundle({ @@ -163,7 +163,7 @@ describe("buildJvmBundle", () => { }); await writeFile( join(tempDir, "src", "main", "kotlin", "com", "example", "App.kt"), - "fun main() {}" + "fun main() {}", ); const result = await buildJvmBundle({ @@ -180,14 +180,14 @@ describe("buildJvmBundle", () => { await mkdir(join(tempDir, "build", "generated"), { recursive: true }); await writeFile( join(tempDir, "build", "generated", "R.java"), - "// generated" + "// generated", ); // File in src/ should be included await mkdir(join(tempDir, "src", "main", "java"), { recursive: true }); await writeFile( join(tempDir, "src", "main", "java", "App.java"), - "class App {}" + "class App {}", ); const result = await buildJvmBundle({ @@ -205,7 +205,7 @@ describe("buildJvmBundle", () => { await mkdir(join(tempDir, "src", "main", "java"), { recursive: true }); await writeFile( join(tempDir, "src", "main", "java", "App.java"), - "class App {}" + "class App {}", ); const result = await buildJvmBundle({ @@ -235,7 +235,7 @@ describe("buildJvmBundle", () => { }); await writeFile( join(srcDir, "main", "java", "com", "example", "App.java"), - "class App {}" + "class App {}", ); const result = await buildJvmBundle({ @@ -254,7 +254,7 @@ describe("buildJvmBundle", () => { await mkdir(join(tempDir, "src", "main", "java"), { recursive: true }); await writeFile( join(tempDir, "src", "main", "java", "App.java"), - "class App {}" + "class App {}", ); const debugId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; diff --git a/packages/cli/test/lib/kitty-image.test.ts b/packages/cli/test/lib/kitty-image.test.ts index 76888126b..1d4ee78b5 100644 --- a/packages/cli/test/lib/kitty-image.test.ts +++ b/packages/cli/test/lib/kitty-image.test.ts @@ -21,7 +21,7 @@ const ESC = "\x1b"; function solidImage( width: number, height: number, - rgba: [number, number, number, number] + rgba: [number, number, number, number], ): DecodedImage { const data = new Uint8Array(width * height * 4); for (let i = 0; i < width * height; i++) { @@ -64,7 +64,7 @@ describe("encodeImageToKitty", () => { test("chunks large payloads with m=1 on all but the last chunk", () => { // 64×64 RGBA ≈ 16 KiB → ~21 KiB base64, well over the 4096-byte chunk cap. const out = encodeImageToKitty( - solidImage(64, 64, [10, 20, 30, 255]) + solidImage(64, 64, [10, 20, 30, 255]), ) as string; // Each chunk is an APC sequence terminated by ESC \; split on the introducer. const chunks = out @@ -84,7 +84,7 @@ describe("encodeImageToKitty", () => { test("downscales wide images to the max width", () => { const out = encodeImageToKitty( - solidImage(2000, 10, [0, 0, 0, 255]) + solidImage(2000, 10, [0, 0, 0, 255]), ) as string; expect(out).toContain("s=800"); }); @@ -100,7 +100,7 @@ describe("imageBytesToKitty", () => { test("returns undefined for an unsupported format", () => { expect( - imageBytesToKitty(new Uint8Array([1, 2, 3]), "text/plain") + imageBytesToKitty(new Uint8Array([1, 2, 3]), "text/plain"), ).toBeUndefined(); }); diff --git a/packages/cli/test/lib/list-command.test.ts b/packages/cli/test/lib/list-command.test.ts index 0d1f780d4..6b603ad33 100644 --- a/packages/cli/test/lib/list-command.test.ts +++ b/packages/cli/test/lib/list-command.test.ts @@ -12,7 +12,7 @@ import { parseCursorFlag, } from "../../src/lib/list-command.js"; import type { OrgListConfig } from "../../src/lib/org-list.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as orgListModule from "../../src/lib/org-list.js"; describe("parseCursorFlag", () => { @@ -45,7 +45,7 @@ type FakeEntity = { id: string; name: string }; type FakeWithOrg = FakeEntity & { orgSlug: string }; function makeFakeConfig( - overrides?: Partial> + overrides?: Partial>, ): OrgListConfig { return { paginationKey: "fake-list", @@ -53,7 +53,7 @@ function makeFakeConfig( commandPrefix: "sentry widget list", listForOrg: vi.fn(() => Promise.resolve([])), listPaginated: vi.fn(() => - Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }) + Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }), ), withOrg: (entity, orgSlug) => ({ ...entity, orgSlug }), displayTable: vi.fn(() => ""), @@ -125,7 +125,7 @@ describe("buildOrgListCommand", () => { const cmd = buildOrgListCommand( config, { brief: "List widgets" }, - "widget" + "widget", ); const func = await cmd.loader(); const { context } = createContext(); @@ -145,7 +145,7 @@ describe("buildOrgListCommand", () => { const cmd = buildOrgListCommand( config, { brief: "List widgets" }, - "widget" + "widget", ); const func = await cmd.loader(); const { context } = createContext(); diff --git a/packages/cli/test/lib/logger.test.ts b/packages/cli/test/lib/logger.test.ts index 03ce6b6aa..6a7044f02 100644 --- a/packages/cli/test/lib/logger.test.ts +++ b/packages/cli/test/lib/logger.test.ts @@ -243,11 +243,10 @@ describe("printLine", () => { describe("printJsonLine", () => { test("writes an NDJSON record only to stdout", async () => { - const loggerModule = (await import( - "../../src/lib/logger.js" - )) as typeof import("../../src/lib/logger.js") & { - printJsonLine?: (line: string) => void; - }; + const loggerModule = + (await import("../../src/lib/logger.js")) as typeof import("../../src/lib/logger.js") & { + printJsonLine?: (line: string) => void; + }; const stdout = vi .spyOn(process.stdout, "write") .mockImplementation(() => true); @@ -258,7 +257,7 @@ describe("printJsonLine", () => { expect(loggerModule.printJsonLine).toBeTypeOf("function"); loggerModule.printJsonLine?.('{"schema_version":1,"type":"error"}'); expect(stdout).toHaveBeenCalledWith( - '{"schema_version":1,"type":"error"}\n' + '{"schema_version":1,"type":"error"}\n', ); expect(stderr).not.toHaveBeenCalled(); } finally { diff --git a/packages/cli/test/lib/metrics-transform.test.ts b/packages/cli/test/lib/metrics-transform.test.ts index 2380c645e..c71ca592f 100644 --- a/packages/cli/test/lib/metrics-transform.test.ts +++ b/packages/cli/test/lib/metrics-transform.test.ts @@ -20,8 +20,8 @@ describe("makeTracemetricsAggregate", () => { "sum", "llm.token_usage", "distribution", - "none" - ) + "none", + ), ).toBe("sum(value,llm.token_usage,distribution,none)"); }); @@ -31,14 +31,19 @@ describe("makeTracemetricsAggregate", () => { "avg", "http.response_time", "distribution", - "millisecond" - ) + "millisecond", + ), ).toBe("avg(value,http.response_time,distribution,millisecond)"); }); test("works with p50 aggregation", () => { expect( - makeTracemetricsAggregate("p50", "cache.hit_rate", "distribution", "none") + makeTracemetricsAggregate( + "p50", + "cache.hit_rate", + "distribution", + "none", + ), ).toBe("p50(value,cache.hit_rate,distribution,none)"); }); }); @@ -46,25 +51,25 @@ describe("makeTracemetricsAggregate", () => { describe("resolveMetricField", () => { test("resolves known metric with default agg", () => { expect(resolveMetricField("llm.token_usage", "sum", SAMPLE_METRICS)).toBe( - "sum(value,llm.token_usage,distribution,none)" + "sum(value,llm.token_usage,distribution,none)", ); }); test("resolves with custom agg", () => { expect(resolveMetricField("cache.hit_rate", "avg", SAMPLE_METRICS)).toBe( - "avg(value,cache.hit_rate,distribution,none)" + "avg(value,cache.hit_rate,distribution,none)", ); }); test("preserves metric unit from metadata", () => { expect( - resolveMetricField("http.response_time", "p95", SAMPLE_METRICS) + resolveMetricField("http.response_time", "p95", SAMPLE_METRICS), ).toBe("p95(value,http.response_time,distribution,millisecond)"); }); test("throws ResolutionError for unknown metric", () => { expect(() => - resolveMetricField("nonexistent.metric", "sum", SAMPLE_METRICS) + resolveMetricField("nonexistent.metric", "sum", SAMPLE_METRICS), ).toThrow(ResolutionError); }); @@ -99,13 +104,13 @@ describe("resolveMetricField", () => { test("throws ResolutionError for invalid aggregation", () => { expect(() => - resolveMetricField("llm.token_usage", "invalid_agg", SAMPLE_METRICS) + resolveMetricField("llm.token_usage", "invalid_agg", SAMPLE_METRICS), ).toThrow(ResolutionError); }); test("resolves counter-type metric", () => { expect(resolveMetricField("request.count", "sum", SAMPLE_METRICS)).toBe( - "sum(value,request.count,counter,none)" + "sum(value,request.count,counter,none)", ); }); }); diff --git a/packages/cli/test/lib/mutate-command.test.ts b/packages/cli/test/lib/mutate-command.test.ts index a92d3dbd5..d53951319 100644 --- a/packages/cli/test/lib/mutate-command.test.ts +++ b/packages/cli/test/lib/mutate-command.test.ts @@ -100,7 +100,7 @@ describe("guardNonInteractive", () => { yes: false, force: false, "dry-run": true, - }) + }), ).not.toThrow(); }); @@ -110,7 +110,7 @@ describe("guardNonInteractive", () => { yes: true, force: false, "dry-run": false, - }) + }), ).not.toThrow(); }); @@ -120,7 +120,7 @@ describe("guardNonInteractive", () => { yes: false, force: true, "dry-run": false, - }) + }), ).not.toThrow(); }); }); @@ -135,8 +135,8 @@ describe("requireExplicitTarget", () => { requireExplicitTarget( { type: "auto-detect" }, "Project target", - "sentry project delete /" - ) + "sentry project delete /", + ), ).toThrow("Project target"); }); @@ -145,8 +145,8 @@ describe("requireExplicitTarget", () => { requireExplicitTarget( { type: "explicit", org: "acme", project: "my-app" }, "Project target", - "sentry project delete /" - ) + "sentry project delete /", + ), ).not.toThrow(); }); @@ -155,8 +155,8 @@ describe("requireExplicitTarget", () => { requireExplicitTarget( { type: "project-search", projectSlug: "my-app" }, "Project target", - "sentry project delete /" - ) + "sentry project delete /", + ), ).not.toThrow(); }); @@ -165,8 +165,8 @@ describe("requireExplicitTarget", () => { requireExplicitTarget( { type: "org-all", org: "acme" }, "Project target", - "sentry project delete /" - ) + "sentry project delete /", + ), ).not.toThrow(); }); @@ -175,12 +175,12 @@ describe("requireExplicitTarget", () => { requireExplicitTarget( { type: "auto-detect" }, "Project target", - "sentry project delete /" + "sentry project delete /", ); expect.unreachable("should have thrown"); } catch (error) { expect((error as Error).message).toContain( - "Auto-detection is disabled for destructive operations" + "Auto-detection is disabled for destructive operations", ); } }); diff --git a/packages/cli/test/lib/oauth.test.ts b/packages/cli/test/lib/oauth.test.ts index 86985d977..a4ab6b8c4 100644 --- a/packages/cli/test/lib/oauth.test.ts +++ b/packages/cli/test/lib/oauth.test.ts @@ -50,13 +50,13 @@ describe("performDeviceFlow polling", () => { verification_uri: "https://sentry.example/oauth/device/", interval: 1, expires_in: 30, - }) + }), ) .mockResolvedValueOnce( - Response.json({ error: "authorization_pending" }, { status: 400 }) + Response.json({ error: "authorization_pending" }, { status: 400 }), ) .mockResolvedValueOnce( - Response.json({ error: "slow_down" }, { status: 400 }) + Response.json({ error: "slow_down" }, { status: 400 }), ) .mockResolvedValueOnce(Response.json(token)); vi.stubGlobal("fetch", fetchMock); @@ -104,13 +104,13 @@ describe("resolveOAuthScopeString", () => { test("readOnly is ignored when explicit scopes are provided", () => { // `scopes` takes precedence over `readOnly`. expect( - resolveOAuthScopeString({ readOnly: true, scopes: ["project:write"] }) + resolveOAuthScopeString({ readOnly: true, scopes: ["project:write"] }), ).toBe("project:write"); }); test("explicit scopes preserve first-seen order and lowercase", () => { expect( - resolveOAuthScopeString({ scopes: ["ORG:READ", "project:read"] }) + resolveOAuthScopeString({ scopes: ["ORG:READ", "project:read"] }), ).toBe("org:read project:read"); }); @@ -118,13 +118,13 @@ describe("resolveOAuthScopeString", () => { expect( resolveOAuthScopeString({ scopes: ["org:read", "project:read", "org:read"], - }) + }), ).toBe("org:read project:read"); }); test("blank entries are skipped", () => { expect(resolveOAuthScopeString({ scopes: [" ", "org:read", ""] })).toBe( - "org:read" + "org:read", ); }); @@ -141,10 +141,10 @@ describe("resolveOAuthScopeString", () => { test("throws ValidationError when scopes resolve to empty", () => { expect(() => resolveOAuthScopeString({ scopes: [] })).toThrow( - ValidationError + ValidationError, ); expect(() => resolveOAuthScopeString({ scopes: ["", " "] })).toThrow( - ValidationError + ValidationError, ); }); }); @@ -157,7 +157,7 @@ describe("property: resolveOAuthScopeString", () => { // Order preserved, lowercase, space-joined. expect(result).toBe(scopes.map((s) => s.toLowerCase()).join(" ")); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -166,7 +166,7 @@ describe("property: resolveOAuthScopeString", () => { property(knownScopeArb, (scope) => { expect(resolveOAuthScopeString({ scopes: [scope] })).toBe(scope); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/objectstore.test.ts b/packages/cli/test/lib/objectstore.test.ts index b401eb783..71cfb48a2 100644 --- a/packages/cli/test/lib/objectstore.test.ts +++ b/packages/cli/test/lib/objectstore.test.ts @@ -39,9 +39,9 @@ afterEach(() => { describe("buildObjectUrl", () => { test("joins usecase, scope, and key (stripping a trailing slash)", () => { expect( - buildObjectUrl({ ...config, usecase: "preprod" }, "123/456/abc") + buildObjectUrl({ ...config, usecase: "preprod" }, "123/456/abc"), ).toBe( - "https://objectstore.example.com/v1/objects/preprod/org=123;project=456/123/456/abc" + "https://objectstore.example.com/v1/objects/preprod/org=123;project=456/123/456/abc", ); }); }); @@ -52,7 +52,7 @@ describe("objectExists", () => { expect(await objectExists(config, "123/456/abc")).toBe(true); const [url, init] = customFetchMock.mock.calls[0] ?? []; expect(url).toContain( - "/v1/objects/preprod_snapshots/org=123;project=456/123/456/abc" + "/v1/objects/preprod_snapshots/org=123;project=456/123/456/abc", ); expect(init.method).toBe("HEAD"); expect(init.headers["x-os-auth"]).toBe("Bearer jwt-token"); @@ -89,7 +89,7 @@ describe("putObject", () => { const [url, init] = customFetchMock.mock.calls[0] ?? []; expect(url).toBe( - "https://objectstore.example.com/v1/objects/preprod_snapshots/org=123;project=456/123/456/abc" + "https://objectstore.example.com/v1/objects/preprod_snapshots/org=123;project=456/123/456/abc", ); expect(init.method).toBe("PUT"); expect(init.headers["x-os-auth"]).toBe("Bearer jwt-token"); @@ -105,7 +105,7 @@ describe("putObject", () => { statusText: "too large", }); await expect(putObject(config, "k", new Uint8Array([0]))).rejects.toThrow( - ApiError + ApiError, ); }); }); diff --git a/packages/cli/test/lib/org-list-budget.test.ts b/packages/cli/test/lib/org-list-budget.test.ts index 5e422f4fc..4cb9cada2 100644 --- a/packages/cli/test/lib/org-list-budget.test.ts +++ b/packages/cli/test/lib/org-list-budget.test.ts @@ -29,65 +29,65 @@ const adapters = { }; describe("fetchGroupsWithBudget", () => { - test.each([ - "empty", - "failed", - ] as const)("redistributes the %s group's unused slots while preserving group order", async (firstGroup) => { - const first: FetchResult = - firstGroup === "empty" - ? page([]) - : { success: false, error: new Error("Project access denied") }; - const fetchGroup = vi.fn( - async (group: string, options: GroupFetchOptions) => { - if (options.startCursor) { + test.each(["empty", "failed"] as const)( + "redistributes the %s group's unused slots while preserving group order", + async (firstGroup) => { + const first: FetchResult = + firstGroup === "empty" + ? page([]) + : { success: false, error: new Error("Project access denied") }; + const fetchGroup = vi.fn( + async (group: string, options: GroupFetchOptions) => { + if (options.startCursor) { + return page( + Array.from( + { length: options.limit }, + (_, index) => `${group}-extra-${index}`, + ), + ); + } + if (group === "a") { + return first; + } + // Short pages with a cursor remain expandable, as in alert lists. return page( - Array.from( - { length: options.limit }, - (_, index) => `${group}-extra-${index}` - ) + group === "b" ? ["b-first"] : ["c-0", "c-1", "c-2"], + `${group}-next`, ); - } - if (group === "a") { - return first; - } - // Short pages with a cursor remain expandable, as in alert lists. - return page( - group === "b" ? ["b-first"] : ["c-0", "c-1", "c-2"], - `${group}-next` - ); - } - ); - const onProgress = vi.fn(); - const result = await fetchGroupsWithBudget(["a", "b", "c"], { - ...adapters, - limit: 9, - fetchGroup, - onProgress, - }); + }, + ); + const onProgress = vi.fn(); + const result = await fetchGroupsWithBudget(["a", "b", "c"], { + ...adapters, + limit: 9, + fetchGroup, + onProgress, + }); - expect(fetchGroup.mock.calls).toEqual([ - ["a", { limit: 3, startCursor: undefined }], - ["b", { limit: 3, startCursor: undefined }], - ["c", { limit: 3, startCursor: undefined }], - ["b", { limit: 3, startCursor: "b-next" }], - ["c", { limit: 2, startCursor: "c-next" }], - ]); - expect(result).toEqual({ - results: [ - first, - page(["b-first", "b-extra-0", "b-extra-1", "b-extra-2"]), - page(["c-0", "c-1", "c-2", "c-extra-0", "c-extra-1"]), - ], - hasMore: false, - }); - expect(onProgress.mock.calls).toEqual([[4], [9]]); - }); + expect(fetchGroup.mock.calls).toEqual([ + ["a", { limit: 3, startCursor: undefined }], + ["b", { limit: 3, startCursor: undefined }], + ["c", { limit: 3, startCursor: undefined }], + ["b", { limit: 3, startCursor: "b-next" }], + ["c", { limit: 2, startCursor: "c-next" }], + ]); + expect(result).toEqual({ + results: [ + first, + page(["b-first", "b-extra-0", "b-extra-1", "b-extra-2"]), + page(["c-0", "c-1", "c-2", "c-extra-0", "c-extra-1"]), + ], + hasMore: false, + }); + expect(onProgress.mock.calls).toEqual([[4], [9]]); + }, + ); test("retains fetched items and their resume cursor if a surplus fetch fails", async () => { const fetchGroup = vi.fn( async ( group: string, - options: GroupFetchOptions + options: GroupFetchOptions, ): Promise> => { if (options.startCursor) { return { success: false, error: new Error("Surplus fetch failed") }; @@ -95,7 +95,7 @@ describe("fetchGroupsWithBudget", () => { return group === "a" ? page([]) : page(["b-first", "b-second"], "b-retry"); - } + }, ); const onProgress = vi.fn(); const result = await fetchGroupsWithBudget(["a", "b"], { diff --git a/packages/cli/test/lib/org-list.test.ts b/packages/cli/test/lib/org-list.test.ts index da36c408b..e8a16b636 100644 --- a/packages/cli/test/lib/org-list.test.ts +++ b/packages/cli/test/lib/org-list.test.ts @@ -15,11 +15,11 @@ vi.mock("../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../src/lib/api-client.js"; vi.mock("../../src/lib/db/defaults.js", async (importOriginal) => { @@ -29,16 +29,16 @@ vi.mock("../../src/lib/db/defaults.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as defaults from "../../src/lib/db/defaults.js"; vi.mock("../../src/lib/db/pagination.js"); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.mocked access +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.mocked access import * as paginationDb from "../../src/lib/db/pagination.js"; vi.mock("../../src/lib/db/regions.js", async (importOriginal) => { @@ -48,7 +48,7 @@ vi.mock("../../src/lib/db/regions.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); @@ -78,11 +78,11 @@ vi.mock("../../src/lib/polling.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as polling from "../../src/lib/polling.js"; vi.mock("../../src/lib/region.js", async (importOriginal) => { @@ -92,11 +92,11 @@ vi.mock("../../src/lib/region.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as region from "../../src/lib/region.js"; vi.mock("../../src/lib/resolve-target.js", async (importOriginal) => { @@ -106,11 +106,11 @@ vi.mock("../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../src/lib/resolve-target.js"; /** @@ -124,7 +124,7 @@ beforeEach(() => { .mockImplementation((_opts, fn) => fn(() => { /* no-op setMessage */ - }) + }), ); }); afterEach(() => { @@ -135,7 +135,7 @@ type FakeEntity = { id: string; name: string }; type FakeWithOrg = FakeEntity & { orgSlug: string }; function makeConfig( - overrides?: Partial> + overrides?: Partial>, ): OrgListConfig { return { paginationKey: "test-list", @@ -143,7 +143,7 @@ function makeConfig( commandPrefix: "sentry widget list", listForOrg: vi.fn(() => Promise.resolve([])), listPaginated: vi.fn(() => - Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }) + Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }), ), withOrg: (entity, orgSlug) => ({ ...entity, orgSlug }), displayTable: vi.fn(() => ""), @@ -207,7 +207,7 @@ describe("fetchOrgSafe", () => { test("rethrows AuthError", async () => { const config = makeConfig({ listForOrg: vi.fn(() => - Promise.reject(new AuthError("not_authenticated")) + Promise.reject(new AuthError("not_authenticated")), ), }); await expect(fetchOrgSafe(config, "my-org")).rejects.toThrow(AuthError); @@ -273,7 +273,7 @@ describe("fetchAllOrgs", () => { const config = makeConfig({ listForOrg: vi.fn(() => - Promise.reject(new AuthError("not_authenticated")) + Promise.reject(new AuthError("not_authenticated")), ), }); @@ -287,7 +287,7 @@ describe("fetchAllOrgs", () => { describe("handleOrgAll", () => { const advancePaginationStateSpy = vi.mocked( - paginationDb.advancePaginationState + paginationDb.advancePaginationState, ); const hasPreviousPageSpy = vi.mocked(paginationDb.hasPreviousPage); @@ -305,7 +305,7 @@ describe("handleOrgAll", () => { const items: FakeEntity[] = [{ id: "1", name: "A" }]; const config = makeConfig({ listPaginated: vi.fn(() => - Promise.resolve({ data: items, nextCursor: "next:123" }) + Promise.resolve({ data: items, nextCursor: "next:123" }), ), }); @@ -326,7 +326,7 @@ describe("handleOrgAll", () => { test("caps perPage at API_MAX_PER_PAGE when limit exceeds it", async () => { const listPaginated = vi.fn(() => - Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }) + Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }), ); const config = makeConfig({ listPaginated }); @@ -341,13 +341,13 @@ describe("handleOrgAll", () => { expect(listPaginated).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ perPage: 100 }) + expect.objectContaining({ perPage: 100 }), ); }); test("passes limit through as perPage when below API_MAX_PER_PAGE", async () => { const listPaginated = vi.fn(() => - Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }) + Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }), ); const config = makeConfig({ listPaginated }); @@ -362,7 +362,7 @@ describe("handleOrgAll", () => { expect(listPaginated).toHaveBeenCalledWith( "my-org", - expect.objectContaining({ perPage: 25 }) + expect.objectContaining({ perPage: 25 }), ); }); @@ -372,7 +372,7 @@ describe("handleOrgAll", () => { Promise.resolve({ data: [{ id: "1", name: "A" }], nextCursor: undefined, - }) + }), ), }); @@ -393,7 +393,7 @@ describe("handleOrgAll", () => { test("returns hint with 'no entities found' when empty", async () => { const config = makeConfig({ listPaginated: vi.fn(() => - Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }) + Promise.resolve({ data: [] as FakeEntity[], nextCursor: undefined }), ), }); @@ -413,7 +413,7 @@ describe("handleOrgAll", () => { test("returns hint with next page info when more available", async () => { const config = makeConfig({ listPaginated: vi.fn(() => - Promise.resolve({ data: [{ id: "1", name: "A" }], nextCursor: "x" }) + Promise.resolve({ data: [{ id: "1", name: "A" }], nextCursor: "x" }), ), }); @@ -436,7 +436,7 @@ describe("handleOrgAll", () => { Promise.resolve({ data: [{ id: "1", name: "A" }], nextCursor: "cursor:abc", - }) + }), ), }); @@ -453,7 +453,7 @@ describe("handleOrgAll", () => { "test-list", "ctx", "next", - "cursor:abc" + "cursor:abc", ); }); @@ -463,7 +463,7 @@ describe("handleOrgAll", () => { Promise.resolve({ data: [{ id: "1", name: "A" }], nextCursor: undefined, - }) + }), ), }); @@ -480,7 +480,7 @@ describe("handleOrgAll", () => { "test-list", "ctx", "next", - undefined + undefined, ); }); @@ -497,7 +497,7 @@ describe("handleOrgAll", () => { data, nextCursor: String(offset + opts.perPage), }); - } + }, ); const config = makeConfig({ listPaginated }); @@ -599,7 +599,7 @@ describe("handleExplicitOrg", () => { describe("handleExplicitProject", () => { test("fetches and returns project-scoped entities", async () => { const listForProject = vi.fn(() => - Promise.resolve([{ id: "1", name: "Team A" }]) + Promise.resolve([{ id: "1", name: "Team A" }]), ); const config = makeConfig({ listForProject }); @@ -624,7 +624,7 @@ describe("handleExplicitProject", () => { org: "my-org", project: "my-proj", flags: { limit: 10, json: false }, - }) + }), ).rejects.toThrow("listForProject is not defined"); }); @@ -668,7 +668,7 @@ describe("handleProjectSearch", () => { await expect( handleProjectSearch(config, "no-such-project", { flags: { limit: 10, json: false }, - }) + }), ).rejects.toThrow(ResolutionError); }); @@ -691,7 +691,7 @@ describe("handleProjectSearch", () => { orgs: [], }); const listForProject = vi.fn(() => - Promise.resolve([{ id: "1", name: "Team A" }]) + Promise.resolve([{ id: "1", name: "Team A" }]), ); const config = makeConfig({ listForProject }); @@ -737,7 +737,7 @@ describe("handleProjectSearch", () => { orgs: [], }); const listForOrg = vi.fn(() => - Promise.resolve([{ id: "1", name: "Repo A" }]) + Promise.resolve([{ id: "1", name: "Repo A" }]), ); const config = makeConfig({ listForOrg }); @@ -758,7 +758,7 @@ describe("handleProjectSearch", () => { orgs: [], }); const listForOrg = vi.fn(() => - Promise.resolve([{ id: "1", name: "Repo A" }]) + Promise.resolve([{ id: "1", name: "Repo A" }]), ); const config = makeConfig({ listForOrg }); @@ -777,7 +777,7 @@ describe("handleProjectSearch", () => { }); const config = makeConfig(); const fallback = vi.fn(() => - Promise.resolve({ items: [] } as ListResult) + Promise.resolve({ items: [] } as ListResult), ); await handleProjectSearch(config, "acme-corp", { @@ -798,7 +798,7 @@ describe("handleProjectSearch", () => { await expect( handleProjectSearch(config, "acme-corp", { flags: { limit: 10, json: false }, - }) + }), ).rejects.toThrow(ResolutionError); }); @@ -836,7 +836,7 @@ describe("handleProjectSearch", () => { ], }); const listForOrg = vi.fn(() => - Promise.resolve([{ id: "1", name: "Widget" }]) + Promise.resolve([{ id: "1", name: "Widget" }]), ); const config = makeConfig({ listForOrg }); @@ -906,7 +906,7 @@ describe("dispatchOrgScopedList", () => { cwd: "/tmp", flags: { limit: 10, json: false, cursor: "some-cursor" }, parsed: { type: "explicit", org: "my-org", project: "my-proj" }, - }) + }), ).rejects.toThrow(ValidationError); }); @@ -933,7 +933,7 @@ describe("dispatchOrgScopedList", () => { const items: FakeEntity[] = [{ id: "1", name: "A" }]; const config = makeConfig({ listPaginated: vi.fn(() => - Promise.resolve({ data: items, nextCursor: undefined }) + Promise.resolve({ data: items, nextCursor: undefined }), ), }); const { writer } = createStdout(); @@ -952,7 +952,7 @@ describe("dispatchOrgScopedList", () => { test("explicit mode uses listForProject when available", async () => { const listForProject = vi.fn(() => - Promise.resolve([{ id: "1", name: "T" }]) + Promise.resolve([{ id: "1", name: "T" }]), ); const config = makeConfig({ listForProject }); const { writer } = createStdout(); @@ -991,7 +991,7 @@ describe("dispatchOrgScopedList", () => { const config = makeConfig(); const { writer } = createStdout(); const overrideCalled = vi.fn(() => - Promise.resolve({ items: [] } as ListResult) + Promise.resolve({ items: [] } as ListResult), ); await dispatchOrgScopedList({ @@ -1012,12 +1012,12 @@ describe("dispatchOrgScopedList", () => { const items: FakeEntity[] = [{ id: "1", name: "A" }]; const config = makeConfig({ listPaginated: vi.fn(() => - Promise.resolve({ data: items, nextCursor: undefined }) + Promise.resolve({ data: items, nextCursor: undefined }), ), }); const { writer } = createStdout(); const autoDetectOverride = vi.fn(() => - Promise.resolve({ items: [] } as ListResult) + Promise.resolve({ items: [] } as ListResult), ); const result = await dispatchOrgScopedList({ @@ -1039,7 +1039,7 @@ describe("dispatchOrgScopedList", () => { test("metadata-only config with full overrides dispatches correctly", async () => { const { writer } = createStdout(); const handler = vi.fn(() => - Promise.resolve({ items: [] } as ListResult) + Promise.resolve({ items: [] } as ListResult), ); await dispatchOrgScopedList({ @@ -1072,10 +1072,10 @@ describe("dispatchOrgScopedList", () => { overrides: { // missing auto-detect override — should throw explicit: vi.fn(() => - Promise.resolve({ items: [] } as ListResult) + Promise.resolve({ items: [] } as ListResult), ), }, - }) + }), ).rejects.toThrow("No handler for 'auto-detect' mode"); }); @@ -1103,7 +1103,7 @@ describe("dispatchOrgScopedList", () => { const items: FakeEntity[] = [{ id: "1", name: "Widget A" }]; const config = makeConfig({ listPaginated: vi.fn(() => - Promise.resolve({ data: items, nextCursor: undefined }) + Promise.resolve({ data: items, nextCursor: undefined }), ), }); @@ -1137,10 +1137,10 @@ describe("dispatchOrgScopedList", () => { }); const projectHandler = vi.fn(() => - Promise.resolve({ items: [] } as ListResult) + Promise.resolve({ items: [] } as ListResult), ); const orgHandler = vi.fn(() => - Promise.resolve({ items: [] } as ListResult) + Promise.resolve({ items: [] } as ListResult), ); await dispatchOrgScopedList({ @@ -1170,7 +1170,7 @@ describe("dispatchOrgScopedList", () => { }), ], }), - }) + }), ); }); @@ -1188,7 +1188,7 @@ describe("dispatchOrgScopedList", () => { cwd: "/tmp", flags: { limit: 10, json: false, cursor: "next" }, parsed: { type: "project-search", projectSlug: "missing" }, - }) + }), ).rejects.toThrow(ValidationError); expect(findProjectsBySlugMock).toHaveBeenCalledTimes(1); @@ -1202,7 +1202,7 @@ describe("dispatchOrgScopedList", () => { }); const handler = vi.fn(() => - Promise.resolve({ items: [] } as ListResult) + Promise.resolve({ items: [] } as ListResult), ); await dispatchOrgScopedList({ diff --git a/packages/cli/test/lib/parse-bool.property.test.ts b/packages/cli/test/lib/parse-bool.property.test.ts index 515fcce42..7e28041cd 100644 --- a/packages/cli/test/lib/parse-bool.property.test.ts +++ b/packages/cli/test/lib/parse-bool.property.test.ts @@ -50,7 +50,7 @@ describe("property: parseBoolValue", () => { property(truthyArb, (input) => { expect(parseBoolValue(input)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -59,7 +59,7 @@ describe("property: parseBoolValue", () => { property(falsyArb, (input) => { expect(parseBoolValue(input)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -69,7 +69,7 @@ describe("property: parseBoolValue", () => { const cased = randomCase(input); expect(parseBoolValue(cased)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -79,7 +79,7 @@ describe("property: parseBoolValue", () => { const cased = randomCase(input); expect(parseBoolValue(cased)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -89,9 +89,9 @@ describe("property: parseBoolValue", () => { tuple(whitespaceArb, truthyArb, whitespaceArb), ([pre, val, post]) => { expect(parseBoolValue(`${pre}${val}${post}`)).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -101,9 +101,9 @@ describe("property: parseBoolValue", () => { tuple(whitespaceArb, falsyArb, whitespaceArb), ([pre, val, post]) => { expect(parseBoolValue(`${pre}${val}${post}`)).toBe(false); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -131,7 +131,7 @@ describe("property: parseBoolValue", () => { expect(recognized).toContain(normalized); } }), - { numRuns: DEFAULT_NUM_RUNS * 2 } + { numRuns: DEFAULT_NUM_RUNS * 2 }, ); }); @@ -141,10 +141,10 @@ describe("property: parseBoolValue", () => { // Should never throw, always returns boolean | null const result = parseBoolValue(input); expect(result === true || result === false || result === null).toBe( - true + true, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/patch-cache.test.ts b/packages/cli/test/lib/patch-cache.test.ts index 65ee055af..1fc31032b 100644 --- a/packages/cli/test/lib/patch-cache.test.ts +++ b/packages/cli/test/lib/patch-cache.test.ts @@ -35,7 +35,7 @@ function getCacheDir(): string { /** Build a simple patch chain for testing */ function makeChain( patchDataList: Uint8Array[], - expectedSha256: string + expectedSha256: string, ): { patches: { data: Uint8Array; size: number }[]; expectedSha256: string; @@ -48,7 +48,7 @@ function makeChain( /** Build version steps for a sequential chain */ function makeSteps( - versions: string[] + versions: string[], ): { fromVersion: string; toVersion: string }[] { const steps: { fromVersion: string; toVersion: string }[] = []; for (let i = 0; i < versions.length - 1; i++) { @@ -72,13 +72,13 @@ describe("patchFileName", () => { test("sanitizes special characters in version strings", () => { expect(patchFileName("0.14.0-dev.100", "0.14.0-dev.101")).toBe( - "0.14.0-dev.100-0.14.0-dev.101.patch" + "0.14.0-dev.100-0.14.0-dev.101.patch", ); }); test("replaces non-safe characters with underscore", () => { expect(patchFileName("1.0.0+build", "1.0.1+build")).toBe( - "1.0.0_build-1.0.1_build.patch" + "1.0.0_build-1.0.1_build.patch", ); }); }); @@ -90,7 +90,7 @@ describe("chainFileName", () => { test("handles nightly versions", () => { expect(chainFileName("0.14.0-dev.100", "0.14.0-dev.101")).toBe( - "chain-0.14.0-dev.100-0.14.0-dev.101.json" + "chain-0.14.0-dev.100-0.14.0-dev.101.json", ); }); }); @@ -115,8 +115,8 @@ describe("savePatchesToCache", () => { expect( await access(patchFilePath).then( () => true, - () => false - ) + () => false, + ), ).toBe(true); expect(new Uint8Array(await readFile(patchFilePath))).toEqual(patchData); @@ -125,8 +125,8 @@ describe("savePatchesToCache", () => { expect( await access(metaFilePath).then( () => true, - () => false - ) + () => false, + ), ).toBe(true); const meta = JSON.parse(await readFile(metaFilePath, "utf-8")) as ChainMeta; expect(meta.fromVersion).toBe("0.13.0"); @@ -152,26 +152,26 @@ describe("savePatchesToCache", () => { expect( await access(join(cacheDir, patchFileName("0.12.0", "0.13.0"))).then( () => true, - () => false - ) + () => false, + ), ).toBe(true); expect( await access(join(cacheDir, patchFileName("0.13.0", "0.14.0"))).then( () => true, - () => false - ) + () => false, + ), ).toBe(true); expect( await access(join(cacheDir, patchFileName("0.14.0", "0.15.0"))).then( () => true, - () => false - ) + () => false, + ), ).toBe(true); // Chain metadata spans 0.12.0 → 0.15.0 const metaFilePath2 = join(cacheDir, chainFileName("0.12.0", "0.15.0")); const meta = JSON.parse( - await readFile(metaFilePath2, "utf-8") + await readFile(metaFilePath2, "utf-8"), ) as ChainMeta; expect(meta.patches).toHaveLength(3); expect(meta.expectedSha256).toBe("target-hash"); @@ -254,7 +254,7 @@ describe("loadCachedChain", () => { mkdirSync(cacheDir, { recursive: true }); await writeFile( join(cacheDir, patchFileName("0.13.0", "0.14.0")), - new Uint8Array([1, 2, 3]) + new Uint8Array([1, 2, 3]), ); const result = await loadCachedChain("0.13.0", "0.14.0"); @@ -274,7 +274,7 @@ describe("loadCachedChain", () => { }; await writeFile( join(cacheDir, chainFileName("0.13.0", "0.14.0")), - JSON.stringify(meta) + JSON.stringify(meta), ); const result = await loadCachedChain("0.13.0", "0.14.0"); @@ -309,11 +309,11 @@ describe("loadCachedChain", () => { }; await writeFile( join(cacheDir, chainFileName("0.13.0", "0.14.0")), - JSON.stringify(meta) + JSON.stringify(meta), ); await writeFile( join(cacheDir, patchFileName("0.13.0", "0.14.0")), - new Uint8Array([1, 2, 3]) + new Uint8Array([1, 2, 3]), ); const result = await loadCachedChain("0.13.0", "0.14.0"); @@ -325,7 +325,7 @@ describe("loadCachedChain", () => { mkdirSync(cacheDir, { recursive: true }); writeFileSync( join(cacheDir, "chain-0.13.0-0.14.0.json"), - "not valid json!!!" + "not valid json!!!", ); const result = await loadCachedChain("0.13.0", "0.14.0"); @@ -373,21 +373,21 @@ describe("cleanupPatchCache", () => { }; await writeFile( join(cacheDir, chainFileName("0.13.0", "0.14.0")), - JSON.stringify(meta) + JSON.stringify(meta), ); await writeFile( join(cacheDir, patchFileName("0.13.0", "0.14.0")), - new Uint8Array([1, 2, 3]) + new Uint8Array([1, 2, 3]), ); await cleanupPatchCache(); // Both metadata and patch file should be removed expect(existsSync(join(cacheDir, chainFileName("0.13.0", "0.14.0")))).toBe( - false + false, ); expect(existsSync(join(cacheDir, patchFileName("0.13.0", "0.14.0")))).toBe( - false + false, ); }); @@ -406,21 +406,21 @@ describe("cleanupPatchCache", () => { }; await writeFile( join(cacheDir, chainFileName("0.13.0", "0.14.0")), - JSON.stringify(meta) + JSON.stringify(meta), ); await writeFile( join(cacheDir, patchFileName("0.13.0", "0.14.0")), - new Uint8Array([1, 2, 3]) + new Uint8Array([1, 2, 3]), ); await cleanupPatchCache(); // Both files should still exist expect(existsSync(join(cacheDir, chainFileName("0.13.0", "0.14.0")))).toBe( - true + true, ); expect(existsSync(join(cacheDir, patchFileName("0.13.0", "0.14.0")))).toBe( - true + true, ); }); @@ -450,11 +450,11 @@ describe("cleanupPatchCache", () => { }; await writeFile( join(cacheDir, chainFileName("0.12.0", "0.13.0")), - JSON.stringify(oldMeta) + JSON.stringify(oldMeta), ); await writeFile( join(cacheDir, patchFileName("0.12.0", "0.13.0")), - new Uint8Array([1]) + new Uint8Array([1]), ); // Fresh entry @@ -467,29 +467,29 @@ describe("cleanupPatchCache", () => { }; await writeFile( join(cacheDir, chainFileName("0.13.0", "0.14.0")), - JSON.stringify(freshMeta) + JSON.stringify(freshMeta), ); await writeFile( join(cacheDir, patchFileName("0.13.0", "0.14.0")), - new Uint8Array([2]) + new Uint8Array([2]), ); await cleanupPatchCache(); // Old entry removed expect(existsSync(join(cacheDir, chainFileName("0.12.0", "0.13.0")))).toBe( - false + false, ); expect(existsSync(join(cacheDir, patchFileName("0.12.0", "0.13.0")))).toBe( - false + false, ); // Fresh entry preserved expect(existsSync(join(cacheDir, chainFileName("0.13.0", "0.14.0")))).toBe( - true + true, ); expect(existsSync(join(cacheDir, patchFileName("0.13.0", "0.14.0")))).toBe( - true + true, ); }); @@ -510,11 +510,11 @@ describe("cleanupPatchCache", () => { }; await writeFile( join(cacheDir, chainFileName("0.12.0", "0.13.0")), - JSON.stringify(oldMeta) + JSON.stringify(oldMeta), ); await writeFile( join(cacheDir, patchFileName("0.12.0", "0.13.0")), - new Uint8Array([1]) + new Uint8Array([1]), ); // Fresh chain: A→B→C (alive) — shares the A→B patch file @@ -530,31 +530,31 @@ describe("cleanupPatchCache", () => { }; await writeFile( join(cacheDir, chainFileName("0.12.0", "0.14.0")), - JSON.stringify(freshMeta) + JSON.stringify(freshMeta), ); await writeFile( join(cacheDir, patchFileName("0.13.0", "0.14.0")), - new Uint8Array([2]) + new Uint8Array([2]), ); await cleanupPatchCache(); // Old chain metadata removed expect(existsSync(join(cacheDir, chainFileName("0.12.0", "0.13.0")))).toBe( - false + false, ); // Shared A→B patch file preserved (still used by fresh chain) expect(existsSync(join(cacheDir, patchFileName("0.12.0", "0.13.0")))).toBe( - true + true, ); // Fresh chain fully intact expect(existsSync(join(cacheDir, chainFileName("0.12.0", "0.14.0")))).toBe( - true + true, ); expect(existsSync(join(cacheDir, patchFileName("0.13.0", "0.14.0")))).toBe( - true + true, ); }); }); @@ -579,31 +579,31 @@ describe("clearPatchCache", () => { [ { fromVersion: "0.10.0", toVersion: "0.11.0" }, { fromVersion: "0.11.0", toVersion: "0.12.0" }, - ] + ], ); // Verify files exist expect(existsSync(join(cacheDir, patchFileName("0.10.0", "0.11.0")))).toBe( - true + true, ); expect(existsSync(join(cacheDir, patchFileName("0.11.0", "0.12.0")))).toBe( - true + true, ); expect(existsSync(join(cacheDir, chainFileName("0.10.0", "0.12.0")))).toBe( - true + true, ); await clearPatchCache(); // All files gone expect(existsSync(join(cacheDir, patchFileName("0.10.0", "0.11.0")))).toBe( - false + false, ); expect(existsSync(join(cacheDir, patchFileName("0.11.0", "0.12.0")))).toBe( - false + false, ); expect(existsSync(join(cacheDir, chainFileName("0.10.0", "0.12.0")))).toBe( - false + false, ); }); diff --git a/packages/cli/test/lib/polling.property.test.ts b/packages/cli/test/lib/polling.property.test.ts index 6b033f45a..0d9d09e5a 100644 --- a/packages/cli/test/lib/polling.property.test.ts +++ b/packages/cli/test/lib/polling.property.test.ts @@ -38,7 +38,7 @@ describe("poll properties", () => { expect(result.value).toBe(stateValue); expect(fetchCount).toBe(1); // Only called once }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -65,9 +65,9 @@ describe("poll properties", () => { // Result should satisfy the stop condition expect(result.count).toBeGreaterThanOrEqual(stopAfter); expect(result.value).toBe(stateValue); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -94,7 +94,7 @@ describe("poll properties", () => { expect((error as TimeoutError).message).toBe(customMessage); } }), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) } // Fewer runs since timeout tests are slow + { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) }, // Fewer runs since timeout tests are slow ); }); @@ -152,9 +152,9 @@ describe("poll properties", () => { Math.ceil(actualTimeout / pollIntervalMs) + 2; expect(fetchCount).toBeLessThanOrEqual(maxExpectedCalls); expect(fetchCount).toBeGreaterThanOrEqual(1); // At least one call - } + }, ), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) }, ); }); @@ -184,9 +184,9 @@ describe("poll properties", () => { expect(result.value).toBe(stateValue); expect(fetchCount).toBe(nullCount + 1); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -219,9 +219,9 @@ describe("poll properties", () => { // Messages should have been generated for each non-null state expect(messages.length).toBeGreaterThanOrEqual(1); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -237,7 +237,7 @@ describe("poll edge cases", () => { json: true, pollIntervalMs: 10, timeoutMs: 0, - }) + }), ).rejects.toBeInstanceOf(TimeoutError); }); @@ -252,7 +252,7 @@ describe("poll edge cases", () => { json: true, pollIntervalMs: 10, timeoutMs: 1000, - }) + }), ).rejects.toThrow("Fetch failed"); }); }); diff --git a/packages/cli/test/lib/polling.test.ts b/packages/cli/test/lib/polling.test.ts index a66afe2a5..017baa11f 100644 --- a/packages/cli/test/lib/polling.test.ts +++ b/packages/cli/test/lib/polling.test.ts @@ -45,13 +45,13 @@ describe("withProgress", () => { rotatingMessages: ["Finding the relevant bits…"], rotationIntervalMs: 4000, }, - async () => deferred.promise + async () => deferred.promise, ); await vi.advanceTimersByTimeAsync(4000); expect(stdoutWrite.mock.calls.flat().join("")).toContain( - "Finding the relevant bits…" + "Finding the relevant bits…", ); deferred.resolve("done"); @@ -72,12 +72,12 @@ describe("withProgress", () => { rotatingMessages: ["It’s getting there…"], rotationIntervalMs: 4000, }, - async () => deferred.promise + async () => deferred.promise, ); await vi.advanceTimersByTimeAsync(4000); expect(stdoutWrite.mock.calls.flat().join("")).toContain( - "It’s getting there…" + "It’s getting there…", ); deferred.reject(new Error("docs unavailable")); @@ -99,8 +99,8 @@ describe("withProgress", () => { rotatingMessages: ["It’s getting there…"], rotationIntervalMs: 4000, }, - async () => "done" - ) + async () => "done", + ), ).resolves.toBe("done"); expect(stdoutWrite).not.toHaveBeenCalled(); @@ -121,8 +121,8 @@ describe("withProgress", () => { rotatingMessages: ["It’s getting there…"], rotationIntervalMs: 4000, }, - async () => "done" - ) + async () => "done", + ), ).resolves.toBe("done"); expect(stdoutWrite).not.toHaveBeenCalled(); @@ -144,8 +144,8 @@ describe("withProgress", () => { rotatingMessages: ["It’s getting there…"], rotationIntervalMs: 4000, }, - async () => "done" - ) + async () => "done", + ), ).resolves.toBe("done"); expect(stdoutWrite).not.toHaveBeenCalled(); diff --git a/packages/cli/test/lib/progress.test.ts b/packages/cli/test/lib/progress.test.ts index 7bb935e0d..2c4cccf5f 100644 --- a/packages/cli/test/lib/progress.test.ts +++ b/packages/cli/test/lib/progress.test.ts @@ -30,7 +30,7 @@ describe("makeByteProgress", () => { (m) => msgs.push(m), { nowMs: () => now, - } + }, ); p.onProgress(500); // first call: now=0, lastEmit=0 → throttled (0-0 < 100) now = 200; @@ -110,7 +110,7 @@ describe("makeByteProgress", () => { () => { throw new Error("boom"); }, - { nowMs: () => 1000 } + { nowMs: () => 1000 }, ); expect(() => { p.onProgress(100); @@ -130,7 +130,7 @@ describe("makeByteProgress", () => { "Applying 3 patch(es)", 930 * 1024 * 1024, (m) => msgs.push(m), - { format: "pct", nowMs: () => now } + { format: "pct", nowMs: () => now }, ); now = 200; p.onProgress(310 * 1024 * 1024); // 33% @@ -174,7 +174,7 @@ describe("makeByteProgress", () => { // helper itself still defaults to "bytes". const src = readFileSync( join(__dirname, "../../src/lib/delta-upgrade.ts"), - "utf8" + "utf8", ); // Find the makeByteProgress call inside the apply bar branch by scanning // for balanced parentheses, so nested calls like diff --git a/packages/cli/test/lib/proguard.property.test.ts b/packages/cli/test/lib/proguard.property.test.ts index 82adcdae9..94ceaef86 100644 --- a/packages/cli/test/lib/proguard.property.test.ts +++ b/packages/cli/test/lib/proguard.property.test.ts @@ -51,7 +51,7 @@ describe("proguard: computeProguardUuid", () => { // Variant nibble (first char of 4th group) is RFC 4122 (8/9/a/b). expect(["8", "9", "a", "b"]).toContain(uuid[19]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -62,7 +62,7 @@ describe("proguard: computeProguardUuid", () => { const b = computeProguardUuid(Buffer.from(bytes)); expect(a).toBe(b); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -74,10 +74,10 @@ describe("proguard: computeProguardUuid", () => { // Flip the first byte to guarantee different content. mutated[0] = (mutated[0]! + 1) % 256; expect(computeProguardUuid(original)).not.toBe( - computeProguardUuid(mutated) + computeProguardUuid(mutated), ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/promises.property.test.ts b/packages/cli/test/lib/promises.property.test.ts index 1c8eff8ca..f3b69a1a8 100644 --- a/packages/cli/test/lib/promises.property.test.ts +++ b/packages/cli/test/lib/promises.property.test.ts @@ -30,9 +30,9 @@ describe("anyTrue properties", () => { const actualResult = await anyTrue(items, async (i) => results[i]); expect(actualResult).toBe(expectedResult); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -42,7 +42,7 @@ describe("anyTrue properties", () => { const result = await anyTrue([], async () => predicateResult); expect(result).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -52,7 +52,7 @@ describe("anyTrue properties", () => { const result = await anyTrue([1], async () => predicateResult); expect(result).toBe(predicateResult); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -76,9 +76,9 @@ describe("anyTrue properties", () => { const expectedTrue = trueIndex % arrayLength !== errorIndex % arrayLength; expect(result).toBe(expectedTrue); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -93,7 +93,7 @@ describe("anyTrue properties", () => { expect(result).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -113,9 +113,9 @@ describe("anyTrue properties", () => { }); expect(result).toBe(true); - } + }, ), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) } // Fewer runs since we use delays + { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) }, // Fewer runs since we use delays ); }); @@ -132,9 +132,9 @@ describe("anyTrue properties", () => { }); expect(result).toBe(false); - } + }, ), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) }, ); }); @@ -152,7 +152,7 @@ describe("anyTrue properties", () => { // All items should have been checked expect(called.size).toBe(arrayLength); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/react-native/wrap-call.test.ts b/packages/cli/test/lib/react-native/wrap-call.test.ts index f7e49da5f..8519a3501 100644 --- a/packages/cli/test/lib/react-native/wrap-call.test.ts +++ b/packages/cli/test/lib/react-native/wrap-call.test.ts @@ -91,7 +91,7 @@ describe("wrapCall", () => { // Seed the report with the packager sourcemap path (from a prior bundle call). writeFileSync( reportPath, - JSON.stringify({ packager_sourcemap_path: pkgMap }) + JSON.stringify({ packager_sourcemap_path: pkgMap }), ); setArgs(["/tools/compose-source-maps.js", "-o", hermesMap]); @@ -130,7 +130,7 @@ describe("wrapCall", () => { writeFileSync(hermesMap, JSON.stringify({ version: 3 })); writeFileSync( reportPath, - JSON.stringify({ packager_sourcemap_path: pkgMap }) + JSON.stringify({ packager_sourcemap_path: pkgMap }), ); setArgs(["/tools/compose-source-maps.js", "-o", hermesMap]); @@ -141,7 +141,7 @@ describe("wrapCall", () => { }); expect( - JSON.parse(readFileSync(hermesMap, "utf-8")).debugId + JSON.parse(readFileSync(hermesMap, "utf-8")).debugId, ).toBeUndefined(); }); }); diff --git a/packages/cli/test/lib/react-native/xcode-env.test.ts b/packages/cli/test/lib/react-native/xcode-env.test.ts index 991de7119..fdab952c6 100644 --- a/packages/cli/test/lib/react-native/xcode-env.test.ts +++ b/packages/cli/test/lib/react-native/xcode-env.test.ts @@ -50,7 +50,7 @@ describe("findNode / findHermesc", () => { expect(findNode({})).toBe("node"); expect(findHermesc({ HERMES_CLI_PATH: "/h" })).toBe("/h"); expect(findHermesc({ PODS_ROOT: "/pods" })).toBe( - "/pods/hermes-engine/destroot/bin/hermesc" + "/pods/hermes-engine/destroot/bin/hermesc", ); }); }); @@ -59,7 +59,7 @@ describe("expandXcodeVars", () => { test("expands parenthesized and braced references", () => { const vars = { FOO: "hello world" }; expect(expandXcodeVars("A$(FOO)B", vars)).toBe("Ahello worldB"); - // biome-ignore lint/suspicious/noTemplateCurlyInString: literal Xcode ${VAR} syntax under test + // literal Xcode ${VAR} syntax under test expect(expandXcodeVars("A${FOO}B", vars)).toBe("Ahello worldB"); expect(expandXcodeVars("$(MISSING)", vars)).toBe(""); }); @@ -102,7 +102,7 @@ describe("discoverInfoPlist", () => { CFBundleIdentifiercom.example.$(SUFFIX) CFBundleShortVersionString2.0.0 CFBundleVersion42 - ` + `, ); const plist = await discoverInfoPlist( { @@ -110,7 +110,7 @@ describe("discoverInfoPlist", () => { INFOPLIST_FILE: "Info.plist", SUFFIX: "app", }, - dir + dir, ); expect(plist).toEqual({ name: "MyApp", @@ -129,7 +129,7 @@ describe("discoverInfoPlist", () => { MARKETING_VERSION: "1.0.0", CURRENT_PROJECT_VERSION: "7", }, - "/tmp" + "/tmp", ); expect(plist).toMatchObject({ bundleId: "com.example.app", build: "7" }); }); @@ -140,7 +140,7 @@ describe("resolveReleaseAndDist", () => { const result = await resolveReleaseAndDist( { SENTRY_RELEASE: "app@1.0", SENTRY_DIST: "100" }, "/tmp", - false + false, ); expect(result).toEqual({ release: "app@1.0", dist: "100" }); }); @@ -159,7 +159,7 @@ describe("resolveReleaseAndDist", () => { CURRENT_PROJECT_VERSION: "55", }, "/tmp", - false + false, ); expect(result).toEqual({ dist: "55", @@ -169,7 +169,7 @@ describe("resolveReleaseAndDist", () => { test("throws when the identity cannot be determined", async () => { await expect(resolveReleaseAndDist({}, "/tmp", false)).rejects.toThrow( - /Could not determine release/ + /Could not determine release/, ); }); }); @@ -180,7 +180,7 @@ describe("discoverInfoPlist: INFOPLIST_PREPROCESS", () => { PLIST_XML.replace("NAME", "Pre") .replace("BUNDLE", "com.pre.app") .replace("VERSION", "9.9") - .replace("BUILD", "99") + .replace("BUILD", "99"), ); const plist = await discoverInfoPlist( { @@ -190,7 +190,7 @@ describe("discoverInfoPlist: INFOPLIST_PREPROCESS", () => { INFOPLIST_PREPROCESSOR_DEFINITIONS: "DEBUG=1", }, "/tmp", - true + true, ); expect(plist).toEqual({ name: "Pre", @@ -201,7 +201,7 @@ describe("discoverInfoPlist: INFOPLIST_PREPROCESS", () => { expect(execMock).toHaveBeenCalledWith( "cc", expect.arrayContaining(["-xc", "-P", "-E", "-DDEBUG=1"]), - expect.anything() + expect.anything(), ); }); @@ -218,7 +218,7 @@ describe("discoverInfoPlist: INFOPLIST_PREPROCESS", () => { CURRENT_PROJECT_VERSION: "1", }, "/does-not-exist", - false + false, ); expect(execMock).not.toHaveBeenCalled(); expect(plist).toMatchObject({ bundleId: "com.fallback.app" }); @@ -236,7 +236,7 @@ describe("discoverInfoPlist: xcodebuild discovery (outside Xcode)", () => { PLIST_XML.replace("NAME", "MyApp") .replace("BUNDLE", "com.xcbuild.app") .replace("VERSION", "4.2.0") - .replace("BUILD", "7") + .replace("BUILD", "7"), ); execMock.mockImplementation((_cmd: string, args?: readonly string[]) => { @@ -263,7 +263,7 @@ describe("discoverInfoPlist: xcodebuild discovery (outside Xcode)", () => { expect(execMock).toHaveBeenCalledWith( "xcodebuild", expect.arrayContaining(["-configuration", "Release"]), - expect.anything() + expect.anything(), ); }); diff --git a/packages/cli/test/lib/region.test.ts b/packages/cli/test/lib/region.test.ts index dcbd1f67b..1ba365192 100644 --- a/packages/cli/test/lib/region.test.ts +++ b/packages/cli/test/lib/region.test.ts @@ -147,7 +147,7 @@ describe("resolveOrgRegion", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -185,7 +185,7 @@ describe("resolveOrgRegion", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -222,7 +222,7 @@ describe("resolveOrgRegion", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -258,7 +258,7 @@ describe("resolveOrgRegion", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { @@ -362,7 +362,7 @@ describe("resolveOrgRegion", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); } return new Response(JSON.stringify({ detail: "Not found" }), { diff --git a/packages/cli/test/lib/release-notes.property.test.ts b/packages/cli/test/lib/release-notes.property.test.ts index e9c4078aa..f93e5241f 100644 --- a/packages/cli/test/lib/release-notes.property.test.ts +++ b/packages/cli/test/lib/release-notes.property.test.ts @@ -50,7 +50,7 @@ const FILTERED_HEADERS = [ /** Simple description text */ const descriptionArb = array( constantFrom(..."abcdefghijklmnopqrstuvwxyz ".split("")), - { minLength: 3, maxLength: 30 } + { minLength: 3, maxLength: 30 }, ).map((chars) => chars.join("").trim() || "change"); /** Generate a section body with list items */ @@ -70,7 +70,7 @@ const releaseBodyArb = tuple( array(tuple(constantFrom(...FILTERED_HEADERS), sectionBodyArb), { minLength: 0, maxLength: 2, - }) + }), ).map(([kept, filtered]) => { // Interleave kept and filtered sections const all = [...kept, ...filtered]; @@ -87,13 +87,13 @@ const commitPrefixArb = constantFrom( "chore", "test", "ci", - "meta" + "meta", ); const commitScopeArb = constantFrom("", "(dashboard)", "(issue)", "(api)"); const commitMessageArb = tuple( commitPrefixArb, commitScopeArb, - descriptionArb + descriptionArb, ).map(([prefix, scope, desc]) => `${prefix}${scope}: ${desc}`); // ─────────────────────────── Tests ───────────────────────────────────────── @@ -107,7 +107,7 @@ describe("property: extractSections", () => { expect(VALID_CATEGORIES).toContain(section.category); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -122,7 +122,7 @@ describe("property: extractSections", () => { expect(section.category).not.toBe("documentation"); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -143,9 +143,9 @@ describe("property: extractSections", () => { .map(([header, items]) => `${header}\n\n${items}`) .join("\n\n"); expect(extractSections(body)).toEqual([]); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -163,9 +163,9 @@ describe("property: parseCommitMessages", () => { for (const section of sections) { expect(VALID_CATEGORIES).toContain(section.category); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -181,7 +181,7 @@ describe("property: parseCommitMessages", () => { // All commits have #skip-changelog, so no sections should be produced expect(sections).toEqual([]); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -204,9 +204,9 @@ describe("property: extractNightlyTimestamp", () => { ([major, minor, patch], ts) => { const version = `${major}.${minor}.${patch}-dev.${ts}`; expect(extractNightlyTimestamp(version)).toBe(ts); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/lib/release-notes.test.ts b/packages/cli/test/lib/release-notes.test.ts index ffccf4901..de6c23003 100644 --- a/packages/cli/test/lib/release-notes.test.ts +++ b/packages/cli/test/lib/release-notes.test.ts @@ -119,11 +119,11 @@ describe("buildChangelogSummary", () => { makeRelease("0.21.0", SAMPLE_RELEASE_BODY), makeRelease( "0.20.0", - "### New Features ✨\n\n- Feature from 0.20 by @user in [#100](url)\n\n### Bug Fixes 🐛\n\n- Fix from 0.20 by @user in [#101](url)" + "### New Features ✨\n\n- Feature from 0.20 by @user in [#100](url)\n\n### Bug Fixes 🐛\n\n- Fix from 0.20 by @user in [#101](url)", ), makeRelease( "0.19.0", - "### New Features ✨\n\n- Feature from 0.19 by @user in [#99](url)" + "### New Features ✨\n\n- Feature from 0.19 by @user in [#99](url)", ), makeRelease("0.18.0", "### Internal Changes 🔧\n\n- Only internal stuff"), ]; @@ -268,7 +268,7 @@ describe("parseCommitMessages", () => { describe("extractNightlyTimestamp", () => { test("extracts timestamp from standard nightly format", () => { expect(extractNightlyTimestamp("0.22.0-dev.1772661724")).toBe( - 1_772_661_724 + 1_772_661_724, ); }); @@ -321,18 +321,18 @@ describe("fetchChangelog source affinity", () => { JSON.stringify([ makeRelease( "mcp@99.0.0", - "### New Features ✨\n\n- Unrelated Toolkit package release" + "### New Features ✨\n\n- Unrelated Toolkit package release", ), makeRelease( "0.21.0", - "### Bug Fixes 🐛\n\n- Unprefixed Toolkit release" + "### Bug Fixes 🐛\n\n- Unprefixed Toolkit release", ), makeRelease( "cli@0.21.0", - "### Bug Fixes 🐛\n\n- Keep release stages source-affine" + "### Bug Fixes 🐛\n\n- Keep release stages source-affine", ), ]), - { status: 200 } + { status: 200 }, ); }); @@ -345,16 +345,16 @@ describe("fetchChangelog source affinity", () => { expect(changelog?.totalItems).toBe(1); expect(changelog?.sections[0]?.markdown).not.toContain( - "Unrelated Toolkit package release" + "Unrelated Toolkit package release", ); expect(changelog?.sections[0]?.markdown).not.toContain( - "Unprefixed Toolkit release" + "Unprefixed Toolkit release", ); expect(requestedUrls).toEqual([ "https://api.github.com/repos/getsentry/toolkit/releases?per_page=30", ]); expect(requestedUrls.some((url) => url.includes("getsentry/cli"))).toBe( - false + false, ); }); @@ -365,11 +365,11 @@ describe("fetchChangelog source affinity", () => { JSON.stringify([ makeRelease( "cli@0.21.0", - "### Bug Fixes 🐛\n\n- Reuse prefetched releases" + "### Bug Fixes 🐛\n\n- Reuse prefetched releases", ), ]), - { status: 200 } - ) + { status: 200 }, + ), ); const releases = await fetchRecentReleases(undefined, toolkitSource); @@ -383,7 +383,7 @@ describe("fetchChangelog source affinity", () => { expect(changelog?.totalItems).toBe(1); expect(changelog?.sections[0]?.markdown).toContain( - "Reuse prefetched releases" + "Reuse prefetched releases", ); }); @@ -416,7 +416,7 @@ describe("fetchChangelog source affinity", () => { makeRelease("mcp@0.21.0", "- Unrelated MCP release"), makeRelease( "cli@0.21.0", - "### Bug Fixes 🐛\n\n- Raw prefetched release" + "### Bug Fixes 🐛\n\n- Raw prefetched release", ), ] as never, }); @@ -462,10 +462,10 @@ describe("fetchChangelog source affinity", () => { JSON.stringify([ makeRelease( "0.21.0", - "### Bug Fixes 🐛\n\n- Keep the legacy bridge working" + "### Bug Fixes 🐛\n\n- Keep the legacy bridge working", ), ]), - { status: 200 } + { status: 200 }, ); }); @@ -490,7 +490,7 @@ describe("fetchChangelog source affinity", () => { JSON.stringify([ { commit: { message: "fix: keep nightly stages affine" } }, ]), - { status: 200 } + { status: 200 }, ); }); @@ -506,7 +506,7 @@ describe("fetchChangelog source affinity", () => { "https://api.github.com/repos/getsentry/toolkit/commits?sha=main&since=1970-01-01T00:01:41.000Z&until=1970-01-01T00:03:21.000Z&per_page=100", ]); expect(requestedUrls.some((url) => url.includes("getsentry/cli"))).toBe( - false + false, ); }); }); diff --git a/packages/cli/test/lib/release-parse.property.test.ts b/packages/cli/test/lib/release-parse.property.test.ts index f43198be1..0aba30d7c 100644 --- a/packages/cli/test/lib/release-parse.property.test.ts +++ b/packages/cli/test/lib/release-parse.property.test.ts @@ -14,11 +14,11 @@ vi.mock("../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTarget from "../../src/lib/resolve-target.js"; const slugChars = "abcdefghijklmnopqrstuvwxyz0123456789"; @@ -37,7 +37,7 @@ const slugWithHyphensArb = array(constantFrom(...`${slugChars}-`.split("")), { const versionArb = array( constantFrom(..."0123456789.abcdefghijklmnopqrstuvwxyz-+@".split("")), - { minLength: 1, maxLength: 20 } + { minLength: 1, maxLength: 20 }, ).map((chars) => chars.join("")); describe("property: parseReleaseArg", () => { @@ -52,7 +52,7 @@ describe("property: parseReleaseArg", () => { expect(result.version).toBe(input); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -66,9 +66,9 @@ describe("property: parseReleaseArg", () => { // so the whole string should be the version expect(result.version).toBe(version); expect(result.orgSlug).toBeUndefined(); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -82,9 +82,9 @@ describe("property: parseReleaseArg", () => { const result = parseReleaseArg(input, "test"); expect(result.orgSlug).toBe(slug); expect(result.version).toBe(version); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -96,9 +96,9 @@ describe("property: parseReleaseArg", () => { const result = parseReleaseArg(version, "test"); expect(result.orgSlug).toBeUndefined(); expect(result.version).toBe(version); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -161,20 +161,20 @@ describe("unit: resolveReleaseTarget", () => { test("throws ContextError when target is undefined", async () => { await expect( - resolveReleaseTarget(undefined, USAGE, "/tmp") + resolveReleaseTarget(undefined, USAGE, "/tmp"), ).rejects.toThrow(ContextError); }); test("throws ContextError when target is whitespace", async () => { await expect(resolveReleaseTarget(" ", USAGE, "/tmp")).rejects.toThrow( - "Release version" + "Release version", ); }); test("throws ContextError when org cannot be resolved", async () => { resolveOrgSpy.mockResolvedValue(null); await expect(resolveReleaseTarget("1.0.0", USAGE, "/tmp")).rejects.toThrow( - ContextError + ContextError, ); }); }); diff --git a/packages/cli/test/lib/replay-rrweb.test.ts b/packages/cli/test/lib/replay-rrweb.test.ts index fa3c4e866..ed97bd308 100644 --- a/packages/cli/test/lib/replay-rrweb.test.ts +++ b/packages/cli/test/lib/replay-rrweb.test.ts @@ -110,7 +110,7 @@ describe("hasFullSnapshot", () => { describe("rrwebDurationMs", () => { test("spans first to last event", () => { expect(rrwebDurationMs([fullSnapshot(T0), mouseMove(T0 + 2500, 1)])).toBe( - 2500 + 2500, ); expect(rrwebDurationMs([])).toBe(0); }); diff --git a/packages/cli/test/lib/replay-search.test.ts b/packages/cli/test/lib/replay-search.test.ts index 743f39515..73eb819c9 100644 --- a/packages/cli/test/lib/replay-search.test.ts +++ b/packages/cli/test/lib/replay-search.test.ts @@ -21,7 +21,7 @@ describe("getReplayRequestFields", () => { "release", "screen", "warning_id", - ]) + ]), ).toEqual([ "id", "error_ids", diff --git a/packages/cli/test/lib/resolve-effective-org.test.ts b/packages/cli/test/lib/resolve-effective-org.test.ts index 467710be5..152bb364f 100644 --- a/packages/cli/test/lib/resolve-effective-org.test.ts +++ b/packages/cli/test/lib/resolve-effective-org.test.ts @@ -188,7 +188,7 @@ describe("resolveEffectiveOrg with API refresh", () => { JSON.stringify({ regions: [{ name: "us", url: regionUrl }], }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } @@ -206,12 +206,12 @@ describe("resolveEffectiveOrg with API refresh", () => { }, }, ]), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); } return new Response("Not found", { status: 404 }); - } + }, ); } @@ -249,7 +249,7 @@ describe("resolveEffectiveOrg with API refresh", () => { async () => new Response(JSON.stringify({ detail: "Unauthorized" }), { status: 401, - }) + }), ); const result = await resolveEffectiveOrg("o1081365"); @@ -271,7 +271,7 @@ describe("resolveEffectiveOrg with API refresh", () => { expect(result).toBe("missing-org"); expect(debugSpy).toHaveBeenCalledWith( "resolveOrgRegion failed for 'missing-org', using raw slug", - expect.anything() + expect.anything(), ); } finally { debugSpy.mockRestore(); diff --git a/packages/cli/test/lib/resolve-target-listing.test.ts b/packages/cli/test/lib/resolve-target-listing.test.ts index ac066eb67..30cccba51 100644 --- a/packages/cli/test/lib/resolve-target-listing.test.ts +++ b/packages/cli/test/lib/resolve-target-listing.test.ts @@ -15,11 +15,11 @@ vi.mock("../../src/lib/api-client.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../src/lib/api-client.js"; import { DEFAULT_SENTRY_URL } from "../../src/lib/constants.js"; @@ -30,11 +30,11 @@ vi.mock("../../src/lib/db/defaults.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as defaults from "../../src/lib/db/defaults.js"; vi.mock("../../src/lib/db/auth.js", async (importOriginal) => { @@ -44,11 +44,11 @@ vi.mock("../../src/lib/db/auth.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as auth from "../../src/lib/db/auth.js"; import { setOrgRegion, setOrgRegions } from "../../src/lib/db/regions.js"; import { ContextError, ResolutionError } from "../../src/lib/errors.js"; @@ -60,11 +60,11 @@ vi.mock("../../src/lib/resolve-target.js", async (importOriginal) => { Object.entries(actual).map(([k, v]) => [ k, typeof v === "function" ? vi.fn(v) : v, - ]) + ]), ); }); -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as resolveTargetModule from "../../src/lib/resolve-target.js"; import { classifyProjectSearchTarget, @@ -201,7 +201,7 @@ describe("projectSearchNotFoundSuggestions", () => { displaySlug: "missing", scopedOrg, suggestions, - }) + }), ).toEqual(expected); }); }); @@ -244,7 +244,7 @@ describe("resolveOrgsForListing", () => { // Skip: resolveOrgsForListing calls resolveAllTargets internally (same-file). // vi.spyOn on the export doesn't intercept same-file calls in vitest. // These tests are covered by the Bun test suite. - // biome-ignore lint/suspicious/noSkippedTests: vitest can't intercept same-file internal calls + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest can't intercept same-file internal calls test.skip("returns unique orgs from DSN detection when no default", async () => { resolveAllTargetsSpy.mockResolvedValue({ targets: [ @@ -266,7 +266,7 @@ describe("resolveOrgsForListing", () => { expect(result.orgs).toEqual([]); }); - // biome-ignore lint/suspicious/noSkippedTests: vitest can't intercept same-file internal calls + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest can't intercept same-file internal calls test.skip("propagates footer from DSN detection", async () => { resolveAllTargetsSpy.mockResolvedValue({ targets: [ @@ -280,7 +280,7 @@ describe("resolveOrgsForListing", () => { expect(result.footer).toBe("Found 2 projects"); }); - // biome-ignore lint/suspicious/noSkippedTests: vitest can't intercept same-file internal calls + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest can't intercept same-file internal calls test.skip("propagates skippedSelfHosted from DSN detection", async () => { resolveAllTargetsSpy.mockResolvedValue({ targets: [{ org: "org-a", project: "proj-1" }], @@ -291,7 +291,7 @@ describe("resolveOrgsForListing", () => { expect(result.skippedSelfHosted).toBe(2); }); - // biome-ignore lint/suspicious/noSkippedTests: vitest can't intercept same-file internal calls + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest can't intercept same-file internal calls test.skip("returns empty orgs and propagates skippedSelfHosted when targets empty but DSNs found", async () => { resolveAllTargetsSpy.mockResolvedValue({ targets: [], @@ -317,7 +317,7 @@ describe("resolveProjectBoundTarget", () => { findProjectsBySlugSpy = vi.spyOn(apiClient, "findProjectsBySlug"); resolveOrgAndProjectSpy = vi.spyOn( resolveTargetModule, - "resolveOrgAndProject" + "resolveOrgAndProject", ); // No accessible orgs by default so the authenticated account fallback in // resolveOrgProjectOrGuide can't resolve and makes no real HTTP calls. @@ -350,7 +350,7 @@ describe("resolveProjectBoundTarget", () => { const parsed = { type: "org-all" as const, org: "my-org" }; await expect( - resolveProjectBoundTarget(parsed, CWD, "trace list") + resolveProjectBoundTarget(parsed, CWD, "trace list"), ).rejects.toThrow(ContextError); }); @@ -376,7 +376,7 @@ describe("resolveProjectBoundTarget", () => { }; await expect( - resolveProjectBoundTarget(parsed, CWD, "trace list") + resolveProjectBoundTarget(parsed, CWD, "trace list"), ).rejects.toThrow(ResolutionError); }); @@ -392,12 +392,12 @@ describe("resolveProjectBoundTarget", () => { const parsed = { type: "project-search" as const, projectSlug: "my-proj" }; await expect( - resolveProjectBoundTarget(parsed, CWD, "trace list") + resolveProjectBoundTarget(parsed, CWD, "trace list"), ).rejects.toThrow(ResolutionError); }); // Skip: same-file internal call — resolveProjectBoundTarget → resolveAllTargets - // biome-ignore lint/suspicious/noSkippedTests: vitest can't intercept same-file internal calls + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest can't intercept same-file internal calls test.skip("resolves auto-detect when DSN detection succeeds", async () => { resolveOrgAndProjectSpy.mockResolvedValue({ org: "detected-org", @@ -418,7 +418,7 @@ describe("resolveProjectBoundTarget", () => { const parsed = { type: "auto-detect" as const }; await expect( - resolveProjectBoundTarget(parsed, CWD, "log list") + resolveProjectBoundTarget(parsed, CWD, "log list"), ).rejects.toThrow(ContextError); }); @@ -472,7 +472,7 @@ describe("resolveProjectBoundFromArg", () => { findProjectsBySlugSpy = vi.spyOn(apiClient, "findProjectsBySlug"); resolveOrgAndProjectSpy = vi.spyOn( resolveTargetModule, - "resolveOrgAndProject" + "resolveOrgAndProject", ); setOrgRegion("my-org", DEFAULT_SENTRY_URL); }); @@ -486,7 +486,7 @@ describe("resolveProjectBoundFromArg", () => { const result = await resolveProjectBoundFromArg( "my-org/my-proj", CWD, - "trace list" + "trace list", ); expect(result).toEqual({ org: "my-org", project: "my-proj" }); }); @@ -504,12 +504,12 @@ describe("resolveProjectBoundFromArg", () => { test("throws ContextError for 'org/' (org-all) string", async () => { await expect( - resolveProjectBoundFromArg("sentry/", CWD, "trace list") + resolveProjectBoundFromArg("sentry/", CWD, "trace list"), ).rejects.toThrow(ContextError); }); // Skip: same-file internal call — resolveProjectBoundFromArg → resolveAllTargets - // biome-ignore lint/suspicious/noSkippedTests: vitest can't intercept same-file internal calls + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- vitest can't intercept same-file internal calls test.skip("resolves undefined to auto-detect", async () => { resolveOrgAndProjectSpy.mockResolvedValue({ org: "auto-org", @@ -521,7 +521,7 @@ describe("resolveProjectBoundFromArg", () => { const result = await resolveProjectBoundFromArg( undefined, CWD, - "trace list" + "trace list", ); expect(result).toEqual({ org: "auto-org", project: "auto-proj" }); }); @@ -561,7 +561,7 @@ describe("resolveProjectBoundTargets", () => { const result = await resolveProjectBoundTargets( { type: "explicit", org: "o1081365", project: "my-proj" }, - OPTS + OPTS, ); expect(result.targets).toHaveLength(1); @@ -582,7 +582,7 @@ describe("resolveProjectBoundTargets", () => { const result = await resolveProjectBoundTargets( { type: "org-all", org: "o1081365" }, - OPTS + OPTS, ); expect(listProjectsSpy).toHaveBeenCalledWith("real-org"); @@ -609,7 +609,7 @@ describe("resolveProjectBoundTargets", () => { const result = await resolveProjectBoundTargets( { type: "project-search", projectSlug: "my-proj", org: "my-org" }, - OPTS + OPTS, ); expect(result.targets).toHaveLength(1); @@ -625,7 +625,7 @@ describe("resolveProjectBoundTargets", () => { try { await resolveProjectBoundTargets( { type: "project-search", projectSlug: "missing" }, - OPTS + OPTS, ); expect.unreachable("should have thrown"); } catch (error) { @@ -715,7 +715,7 @@ describe("resolveOrgProjectOrGuide", () => { cwd: CWD, usageHint: "sentry issue list /", interactive: false, - }) + }), ).rejects.toBeInstanceOf(ContextError); // Unauthenticated path must not attempt to list orgs. expect(listOrganizationsSpy).not.toHaveBeenCalled(); @@ -742,7 +742,7 @@ describe("resolveOrgOptionalTarget bare org slug", () => { const result = await resolveOrgOptionalTarget( { type: "project-search", projectSlug: "acme-corp" }, CWD, - "explore" + "explore", ); expect(result).toEqual({ org: "acme-corp" }); @@ -759,7 +759,7 @@ describe("resolveOrgOptionalTarget bare org slug", () => { { type: "project-search", projectSlug: "missing" }, CWD, "alert metrics create", - usageHint + usageHint, ); expect.unreachable("should have thrown"); } catch (error) { diff --git a/packages/cli/test/lib/resolve-target.mocked.test.ts b/packages/cli/test/lib/resolve-target.mocked.test.ts index 1f2adf56c..8b2eb5bb1 100644 --- a/packages/cli/test/lib/resolve-target.mocked.test.ts +++ b/packages/cli/test/lib/resolve-target.mocked.test.ts @@ -53,13 +53,13 @@ const { all: [], hasMultiple: false, fingerprint: "", - }) + }), ), mockFindProjectRoot: vi.fn(() => Promise.resolve({ projectRoot: "/test/project", detectedFrom: "package.json", - }) + }), ), mockGetDsnSourceDescription: vi.fn(() => "SENTRY_DSN environment variable"), mockGetCachedProject: vi.fn(() => null), @@ -79,7 +79,7 @@ const { mockFindProjectsByPattern: vi.fn(() => Promise.resolve([])), mockListOrganizationsUncached: vi.fn(() => Promise.resolve([])), mockGetOrgByNumericId: vi.fn( - () => undefined as { slug: string; regionUrl: string } | undefined + () => undefined as { slug: string; regionUrl: string } | undefined, ), })); @@ -197,7 +197,7 @@ function resetAllMocks() { detectedFrom: "package.json", }); mockGetDsnSourceDescription.mockReturnValue( - "SENTRY_DSN environment variable" + "SENTRY_DSN environment variable", ); mockGetCachedProject.mockReturnValue(null); mockGetCachedProjectByDsnKey.mockReturnValue(null); @@ -580,13 +580,13 @@ describe("resolveOrgAndProject", () => { test("throws ContextError when only org provided", async () => { await expect( - resolveOrgAndProject({ org: "my-org", cwd: "/test" }) + resolveOrgAndProject({ org: "my-org", cwd: "/test" }), ).rejects.toThrow(ContextError); }); test("throws ContextError when only project provided", async () => { await expect( - resolveOrgAndProject({ project: "my-project", cwd: "/test" }) + resolveOrgAndProject({ project: "my-project", cwd: "/test" }), ).rejects.toThrow(ContextError); }); @@ -694,7 +694,7 @@ describe("resolveAllTargets", () => { test("throws ContextError when only org provided", async () => { await expect( - resolveAllTargets({ org: "my-org", cwd: "/test" }) + resolveAllTargets({ org: "my-org", cwd: "/test" }), ).rejects.toThrow(ContextError); }); diff --git a/packages/cli/test/lib/resolve-target.test.ts b/packages/cli/test/lib/resolve-target.test.ts index c1021c0f7..ec8abc2f5 100644 --- a/packages/cli/test/lib/resolve-target.test.ts +++ b/packages/cli/test/lib/resolve-target.test.ts @@ -76,7 +76,7 @@ describe("property: isValidDirNameForInference", () => { property(singleCharArb, (char) => { expect(isValidDirNameForInference(char)).toBe(false); }), - { numRuns: 50 } + { numRuns: 50 }, ); }); @@ -87,7 +87,7 @@ describe("property: isValidDirNameForInference", () => { const name = `.${suffix}`; expect(isValidDirNameForInference(name)).toBe(false); }), - { numRuns: 100 } + { numRuns: 100 }, ); }); @@ -97,7 +97,7 @@ describe("property: isValidDirNameForInference", () => { // Valid names with 2+ chars that don't start with dot should be accepted expect(isValidDirNameForInference(name)).toBe(true); }), - { numRuns: 100 } + { numRuns: 100 }, ); }); }); @@ -210,7 +210,7 @@ describe("Environment variable resolution (SENTRY_ORG / SENTRY_PROJECT)", () => originalFetch = globalThis.fetch; globalThis.fetch = mockFetch( async () => - new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }) + new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }), ); delete process.env.SENTRY_ORG; delete process.env.SENTRY_PROJECT; @@ -398,11 +398,11 @@ describe("fetchProjectId", () => { async () => new Response(JSON.stringify({ detail: "Not found" }), { status: 404, - }) + }), ); await expect(fetchProjectId("test-org", "test-project")).rejects.toThrow( - ResolutionError + ResolutionError, ); }); @@ -454,7 +454,7 @@ describe("fetchProjectId", () => { async () => new Response(JSON.stringify({ detail: "Not found" }), { status: 404, - }) + }), ); try { @@ -478,7 +478,7 @@ describe("fetchProjectId", () => { async () => new Response(JSON.stringify({ detail: "Not found" }), { status: 404, - }) + }), ); try { @@ -502,7 +502,7 @@ describe("fetchProjectId", () => { try { await expect(fetchProjectId("test-org", "test-project")).rejects.toThrow( - AuthError + AuthError, ); } finally { if (saved !== undefined) { @@ -518,7 +518,7 @@ describe("fetchProjectId", () => { async () => new Response(JSON.stringify({ detail: "Internal error" }), { status: 500, - }) + }), ); const result = await fetchProjectId("test-org", "test-project"); @@ -670,19 +670,19 @@ describe("resolveTargetSlugs", () => { test("keeps the same canonical target before and after the cache warms", async () => { for (const org of ["1", "1", "test-org"]) { expect(await resolveTargetSlugs(makeTarget(org, "42"))).toMatchObject( - canonical + canonical, ); } expect(requests).toEqual(["/api/0/projects/1/42/"]); expect(getCachedProjectBySlug("test-org", "frontend")?.projectId).toBe( - "42" + "42", ); }); test("reuses the metadata lookup performed by explicit target resolution", async () => { const projectId = await fetchProjectId("test-org", "42"); expect( - await resolveTargetSlugs({ ...makeTarget("test-org", "42"), projectId }) + await resolveTargetSlugs({ ...makeTarget("test-org", "42"), projectId }), ).toEqual({ ...canonical, projectId: 42 }); expect(requests).toEqual(["/api/0/projects/test-org/42/"]); }); @@ -696,7 +696,7 @@ describe("resolveTargetSlugs", () => { END; `); expect(await resolveTargetSlugs(makeTarget("1", "42"))).toMatchObject( - canonical + canonical, ); }); @@ -705,26 +705,26 @@ describe("resolveTargetSlugs", () => { { slug: "test-org", orgId: "1", regionUrl: DEFAULT_SENTRY_URL }, ]); expect(await resolveTargetSlugs(makeTarget("1", "frontend"))).toEqual( - makeTarget("test-org", "frontend") + makeTarget("test-org", "frontend"), ); expect(requests).toEqual([]); }); test("propagates lookup errors instead of returning unresolved IDs", async () => { globalThis.fetch = mockFetch(async () => - Response.json({ detail: "Permission denied" }, { status: 403 }) + Response.json({ detail: "Permission denied" }, { status: 403 }), ); await expect( - resolveTargetSlugs(makeTarget("test-org", "42")) + resolveTargetSlugs(makeTarget("test-org", "42")), ).rejects.toBeInstanceOf(ApiError); }); test("does not return a numeric organization when metadata cannot resolve it", async () => { globalThis.fetch = mockFetch(async () => - Response.json({ ...projectData, organization: undefined }) + Response.json({ ...projectData, organization: undefined }), ); await expect( - resolveTargetSlugs(makeTarget("1", "42")) + resolveTargetSlugs(makeTarget("1", "42")), ).rejects.toBeInstanceOf(ResolutionError); }); }); @@ -785,7 +785,7 @@ describe("resolveLogProjectId", () => { async () => new Response(JSON.stringify({ detail: "Internal error" }), { status: 500, - }) + }), ); const result = await resolveLogProjectId("test-org", "my-project"); @@ -799,7 +799,7 @@ describe("resolveLogProjectId", () => { try { await expect( - resolveLogProjectId("test-org", "my-project") + resolveLogProjectId("test-org", "my-project"), ).rejects.toThrow(AuthError); } finally { if (saved !== undefined) { @@ -814,11 +814,11 @@ describe("resolveLogProjectId", () => { clearProjectCache(); globalThis.fetch = mockFetch( async () => - new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }) + new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }), ); await expect( - resolveLogProjectId("test-org", "missing-project") + resolveLogProjectId("test-org", "missing-project"), ).rejects.toThrow(ResolutionError); }); }); @@ -850,7 +850,7 @@ describe("tryFuzzyProjectRecovery", () => { * @param projectsByOrg - Map of org slug → list of project slugs */ function mockListProjects( - projectsByOrg: Record + projectsByOrg: Record, ): typeof fetch { return mockFetch(async (input, init) => { const req = new Request(input, init); @@ -921,7 +921,7 @@ describe("tryFuzzyProjectRecovery", () => { setOrgRegion("test-org", DEFAULT_SENTRY_URL); globalThis.fetch = mockFetch( async () => - new Response(JSON.stringify({ detail: "Error" }), { status: 500 }) + new Response(JSON.stringify({ detail: "Error" }), { status: 500 }), ); const result = await tryFuzzyProjectRecovery("some-slug", [ @@ -1015,7 +1015,7 @@ describe("resolveOrgOptionalTarget", () => { // returns the original slug, so a 404 is sufficient. globalThis.fetch = mockFetch( async () => - new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }) + new Response(JSON.stringify({ detail: "Not found" }), { status: 404 }), ); delete process.env.SENTRY_ORG; delete process.env.SENTRY_PROJECT; @@ -1034,7 +1034,7 @@ describe("resolveOrgOptionalTarget", () => { const result = await resolveOrgOptionalTarget( parsed, getConfigDir(), - "explore" + "explore", ); expect(result.org).toBe("myorg"); expect(result.project).toBeUndefined(); @@ -1047,7 +1047,7 @@ describe("resolveOrgOptionalTarget", () => { const result = await resolveOrgOptionalTarget( parsed, getConfigDir(), - "explore" + "explore", ); expect(result.org).toBe("myorg"); expect(result.project).toBe("myproject"); @@ -1061,7 +1061,7 @@ describe("resolveOrgOptionalTarget", () => { const result = await resolveOrgOptionalTarget( parsed, getConfigDir(), - "explore" + "explore", ); expect(result.org).toBe("env-org"); expect(result.project).toBeUndefined(); @@ -1072,7 +1072,7 @@ describe("resolveOrgOptionalTarget", () => { const parsed = parseOrgProjectArg(undefined); await expect( - resolveOrgOptionalTarget(parsed, getConfigDir(), "explore") + resolveOrgOptionalTarget(parsed, getConfigDir(), "explore"), ).rejects.toThrow(ContextError); }); diff --git a/packages/cli/test/lib/resolve-team.test.ts b/packages/cli/test/lib/resolve-team.test.ts index 5c98c9415..5f4db6786 100644 --- a/packages/cli/test/lib/resolve-team.test.ts +++ b/packages/cli/test/lib/resolve-team.test.ts @@ -8,7 +8,7 @@ import { afterEach, describe, expect, test, vi } from "vitest"; vi.mock("../../src/lib/api/teams.js"); vi.mock("../../src/lib/api/organizations.js"); -// biome-ignore lint/performance/noNamespaceImport: needed for vi.spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for vi.spyOn mocking import * as teamsApi from "../../src/lib/api/teams.js"; import { ApiError, ResolutionError } from "../../src/lib/errors.js"; import { resolveOrCreateTeam } from "../../src/lib/resolve-team.js"; @@ -26,7 +26,7 @@ describe("resolveOrCreateTeam", () => { const apiError = new ApiError( "Failed to list teams", 401, - "Your account is disabled in this organization because it is over its member limit." + "Your account is disabled in this organization because it is over its member limit.", ); listTeamsSpy.mockRejectedValueOnce(apiError); diff --git a/packages/cli/test/lib/response-cache.property.test.ts b/packages/cli/test/lib/response-cache.property.test.ts index 74776715e..6f605c63c 100644 --- a/packages/cli/test/lib/response-cache.property.test.ts +++ b/packages/cli/test/lib/response-cache.property.test.ts @@ -30,7 +30,7 @@ const methodArb = constantFrom("GET", "POST", "PUT", "DELETE", "PATCH"); /** Generate simple path segments */ const pathSegmentArb = string({ minLength: 1, maxLength: 20 }).filter((s) => - /^[a-zA-Z0-9_-]+$/.test(s) + /^[a-zA-Z0-9_-]+$/.test(s), ); /** Generate URL-like strings with paths and query params */ @@ -38,20 +38,20 @@ const sentryUrlArb = tuple( constantFrom( "https://us.sentry.io", "https://de.sentry.io", - "https://sentry.io" + "https://sentry.io", ), array(pathSegmentArb, { minLength: 1, maxLength: 5 }), array( tuple( string({ minLength: 1, maxLength: 10 }).filter((s) => - /^[a-zA-Z]+$/.test(s) + /^[a-zA-Z]+$/.test(s), ), string({ minLength: 1, maxLength: 20 }).filter((s) => - /^[a-zA-Z0-9]+$/.test(s) - ) + /^[a-zA-Z0-9]+$/.test(s), + ), ), - { minLength: 0, maxLength: 4 } - ) + { minLength: 0, maxLength: 4 }, + ), ).map(([base, paths, params]) => { const pathStr = `/api/0/${paths.join("/")}`; const query = @@ -71,7 +71,7 @@ describe("property: buildCacheKey", () => { property(methodArb, sentryUrlArb, (method, url) => { expect(buildCacheKey(method, url)).toMatch(/^[0-9a-f]{64}$/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -80,7 +80,7 @@ describe("property: buildCacheKey", () => { property(methodArb, sentryUrlArb, (method, url) => { expect(buildCacheKey(method, url)).toBe(buildCacheKey(method, url)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -89,7 +89,7 @@ describe("property: buildCacheKey", () => { property(sentryUrlArb, (url) => { expect(buildCacheKey("GET", url)).not.toBe(buildCacheKey("POST", url)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -102,9 +102,9 @@ describe("property: buildCacheKey", () => { const url1 = `${base}/api/0/${path}?a=1&b=2&c=3`; const url2 = `${base}/api/0/${path}?c=3&a=1&b=2`; expect(buildCacheKey("GET", url1)).toBe(buildCacheKey("GET", url2)); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -113,7 +113,7 @@ describe("property: buildCacheKey", () => { property(sentryUrlArb, (url) => { expect(buildCacheKey("get", url)).toBe(buildCacheKey("GET", url)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -136,9 +136,9 @@ describe("property: normalizeUrl", () => { (method, url) => { const normalized = normalizeUrl(method, url); expect(normalized.startsWith(method.toUpperCase())).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -150,7 +150,7 @@ describe("property: normalizeUrl", () => { const [m] = normalized.split("|", 1); expect(m).toBe(method.toUpperCase()); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -166,7 +166,7 @@ describe("property: classifyUrl", () => { const tier = classifyUrl(url); expect(["immutable", "stable", "volatile", "no-cache"]).toContain(tier); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); diff --git a/packages/cli/test/lib/response-cache.test.ts b/packages/cli/test/lib/response-cache.test.ts index 26f347a1c..24c0a8369 100644 --- a/packages/cli/test/lib/response-cache.test.ts +++ b/packages/cli/test/lib/response-cache.test.ts @@ -49,7 +49,7 @@ afterEach(() => { function mockResponse( body: unknown, status = 200, - headers: Record = {} + headers: Record = {}, ): Response { return new Response(JSON.stringify(body), { status, @@ -96,7 +96,7 @@ describe("store and retrieve", () => { const cached = await getCachedResponse( TEST_METHOD, "https://us.sentry.io/api/0/organizations/nonexistent/projects/", - {} + {}, ); expect(cached).toBeUndefined(); }); @@ -165,7 +165,7 @@ describe("non-2xx responses", () => { TEST_METHOD, TEST_URL, {}, - mockResponse({ detail: "not found" }, 404) + mockResponse({ detail: "not found" }, 404), ); const cached = await getCachedResponse(TEST_METHOD, TEST_URL, {}); @@ -177,7 +177,7 @@ describe("non-2xx responses", () => { TEST_METHOD, TEST_URL, {}, - mockResponse({ detail: "server error" }, 500) + mockResponse({ detail: "server error" }, 500), ); const cached = await getCachedResponse(TEST_METHOD, TEST_URL, {}); @@ -240,7 +240,7 @@ describe("cache bypass", () => { TEST_METHOD, TEST_URL, {}, - mockResponse(TEST_BODY) + mockResponse(TEST_BODY), ); process.env.SENTRY_NO_CACHE = "1"; @@ -256,7 +256,7 @@ describe("cache bypass", () => { TEST_METHOD, TEST_URL, {}, - mockResponse(TEST_BODY) + mockResponse(TEST_BODY), ); // Remove the bypass to verify nothing was written @@ -271,7 +271,7 @@ describe("cache bypass", () => { TEST_METHOD, TEST_URL, {}, - mockResponse(TEST_BODY) + mockResponse(TEST_BODY), ); disableResponseCache(); @@ -288,7 +288,7 @@ describe("cache bypass", () => { TEST_METHOD, TEST_URL, {}, - mockResponse(freshBody) + mockResponse(freshBody), ); // Re-enable cache reads to verify the write succeeded @@ -308,7 +308,7 @@ describe("cache bypass", () => { TEST_METHOD, TEST_URL, {}, - mockResponse(staleBody) + mockResponse(staleBody), ); // Activate --fresh: reads are bypassed, but writes still go through @@ -323,7 +323,7 @@ describe("cache bypass", () => { TEST_METHOD, TEST_URL, {}, - mockResponse(freshBody) + mockResponse(freshBody), ); // Re-enable cache reads (simulates next invocation without --fresh) @@ -344,7 +344,7 @@ describe("normalizeUrl", () => { test("sorts query params alphabetically", () => { const result = normalizeUrl( "GET", - "https://sentry.io/api/0/issues/?b=2&a=1" + "https://sentry.io/api/0/issues/?b=2&a=1", ); expect(result).toBe("GET|https://sentry.io/api/0/issues/?a=1&b=2"); }); @@ -361,7 +361,7 @@ describe("normalizeUrl", () => { test("handles self-hosted URLs with unusual schemes", () => { const result = normalizeUrl( "GET", - "https://sentry.mycompany.internal/api/0/issues/" + "https://sentry.mycompany.internal/api/0/issues/", ); expect(result).toBe("GET|https://sentry.mycompany.internal/api/0/issues/"); }); @@ -382,7 +382,7 @@ describe("buildCacheKey", () => { test("different methods produce different keys", () => { expect(buildCacheKey("GET", TEST_URL)).not.toBe( - buildCacheKey("POST", TEST_URL) + buildCacheKey("POST", TEST_URL), ); }); @@ -413,7 +413,7 @@ describe("invalid URL handling", () => { "GET", "not-a-valid-url", {}, - mockResponse({ ok: true }) + mockResponse({ ok: true }), ); }); }); @@ -430,7 +430,7 @@ describe("no-cache tier", () => { TEST_METHOD, autofixUrl, {}, - mockResponse({ autofix: { status: "PROCESSING" } }) + mockResponse({ autofix: { status: "PROCESSING" } }), ); const cached = await getCachedResponse(TEST_METHOD, autofixUrl, {}); @@ -444,7 +444,7 @@ describe("no-cache tier", () => { TEST_METHOD, rootCauseUrl, {}, - mockResponse({ cause: "something" }) + mockResponse({ cause: "something" }), ); const cached = await getCachedResponse(TEST_METHOD, rootCauseUrl, {}); @@ -462,7 +462,7 @@ describe("file structure", () => { TEST_METHOD, TEST_URL, {}, - mockResponse(TEST_BODY) + mockResponse(TEST_BODY), ); const cacheDir = join(getConfigDir(), "cache", "responses"); @@ -476,7 +476,7 @@ describe("file structure", () => { TEST_METHOD, TEST_URL, {}, - mockResponse(TEST_BODY) + mockResponse(TEST_BODY), ); const cacheDir = join(getConfigDir(), "cache", "responses"); @@ -505,14 +505,14 @@ describe("atomic write regression", () => { TEST_METHOD, TEST_URL, {}, - mockResponse({ data: `stale-${i}` }) + mockResponse({ data: `stale-${i}` }), ); const freshBody = { data: `fresh-${i}` }; await storeCachedResponse( TEST_METHOD, TEST_URL, {}, - mockResponse(freshBody) + mockResponse(freshBody), ); const cached = await getCachedResponse(TEST_METHOD, TEST_URL, {}); @@ -537,13 +537,13 @@ describe("invalidateCachedResponsesMatching", () => { "GET", ORG_LIST_URL, {}, - mockResponse({ matched: true }) + mockResponse({ matched: true }), ); await storeCachedResponse( "GET", `${OTHER_PREFIX}?cursor=def`, {}, - mockResponse({ matched: false }) + mockResponse({ matched: false }), ); await invalidateCachedResponsesMatching(ORG_PREFIX); @@ -553,7 +553,7 @@ describe("invalidateCachedResponsesMatching", () => { const survivor = await getCachedResponse( "GET", `${OTHER_PREFIX}?cursor=def`, - {} + {}, ); expect(survivor).toBeDefined(); }); @@ -566,7 +566,7 @@ describe("invalidateCachedResponsesMatching", () => { "GET", ORG_LIST_URL, {}, - mockResponse({ owner: "a" }) + mockResponse({ owner: "a" }), ); expect(await getCachedResponse("GET", ORG_LIST_URL, {})).toBeDefined(); @@ -598,14 +598,14 @@ describe("invalidateCachedResponsesMatching with query params", () => { "GET", `${DETAIL_BASE}?collapse=stats&collapse=lifetime`, {}, - mockResponse({ id: "12345" }) + mockResponse({ id: "12345" }), ); expect( await getCachedResponse( "GET", `${DETAIL_BASE}?collapse=stats&collapse=lifetime`, - {} - ) + {}, + ), ).toBeDefined(); // Mutation-side invalidator uses the base URL (no params). @@ -615,8 +615,8 @@ describe("invalidateCachedResponsesMatching with query params", () => { await getCachedResponse( "GET", `${DETAIL_BASE}?collapse=stats&collapse=lifetime`, - {} - ) + {}, + ), ).toBeUndefined(); }); }); diff --git a/packages/cli/test/lib/route-subcommands.test.ts b/packages/cli/test/lib/route-subcommands.test.ts index 2d9f4960c..65f5c2005 100644 --- a/packages/cli/test/lib/route-subcommands.test.ts +++ b/packages/cli/test/lib/route-subcommands.test.ts @@ -22,7 +22,7 @@ describe("interceptSubcommand", () => { // Stricli route map, which fails under vitest because Node's CJS require // can't resolve .js→.ts for transitive ESM imports. The try-catch in // getSubcommandsForRoute gracefully degrades to empty sets in test. - // biome-ignore lint/suspicious/noSkippedTests: require("../app.js") fails in vitest — CJS can't resolve .js→.ts + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- require("../app.js") fails in vitest — CJS can't resolve .js→.ts test.skip("returns undefined and writes hint for known subcommand", () => { const stderr = makeStderr(); const result = interceptSubcommand("list", stderr, "project"); @@ -55,7 +55,7 @@ describe("interceptSubcommand", () => { }); // Skip: same reason as above — require("../app.js") fails in vitest - // biome-ignore lint/suspicious/noSkippedTests: require("../app.js") fails in vitest — CJS can't resolve .js→.ts + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- require("../app.js") fails in vitest — CJS can't resolve .js→.ts test.skip("hint includes the route name and subcommand", () => { const stderr = makeStderr(); interceptSubcommand("view", stderr, "issue"); @@ -63,7 +63,7 @@ describe("interceptSubcommand", () => { }); // Skip: same reason as above — require("../app.js") fails in vitest - // biome-ignore lint/suspicious/noSkippedTests: require("../app.js") fails in vitest — CJS can't resolve .js→.ts + // oxlint-disable-next-line sentry-cli/no-skipped-tests -- require("../app.js") fails in vitest — CJS can't resolve .js→.ts test.skip("handles 'explain' and 'plan' subcommands for issue route", () => { const stderr1 = makeStderr(); expect(interceptSubcommand("explain", stderr1, "issue")).toBeUndefined(); diff --git a/packages/cli/test/lib/run-commands.mocked.test.ts b/packages/cli/test/lib/run-commands.mocked.test.ts index d119f31ee..bc17c5411 100644 --- a/packages/cli/test/lib/run-commands.mocked.test.ts +++ b/packages/cli/test/lib/run-commands.mocked.test.ts @@ -50,7 +50,7 @@ describe("runCommands breadcrumb on failure", () => { // requiring shell built-ins. const result = await runCommands( makePayload(["ls /nonexistent-sentry-test-path-xyz"]), - { dryRun: false } + { dryRun: false }, ); expect(result.ok).toBe(false); @@ -77,7 +77,7 @@ describe("runCommands breadcrumb on failure", () => { test("does not emit a breadcrumb in dry-run mode", async () => { const result = await runCommands( makePayload(["ls /nonexistent-sentry-test-path-xyz"]), - { dryRun: true } + { dryRun: true }, ); expect(result.ok).toBe(true); diff --git a/packages/cli/test/lib/safe-read.test.ts b/packages/cli/test/lib/safe-read.test.ts index eecc77578..3686e76af 100644 --- a/packages/cli/test/lib/safe-read.test.ts +++ b/packages/cli/test/lib/safe-read.test.ts @@ -32,7 +32,7 @@ describe("safeReadFile", () => { beforeEach(() => { dir = join( tmpdir(), - `safe-read-${Date.now()}-${Math.random().toString(36).slice(2)}` + `safe-read-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(dir, { recursive: true }); }); @@ -86,7 +86,7 @@ describe("sentryclirc FIFO safety", () => { clearSentryCliRcCache(); dir = join( tmpdir(), - `sentryclirc-fifo-${Date.now()}-${Math.random().toString(36).slice(2)}` + `sentryclirc-fifo-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(dir, { recursive: true }); // Point both global fallback locations into `dir` so the loader @@ -127,7 +127,7 @@ describe("init read-files FIFO safety", () => { beforeEach(() => { dir = join( tmpdir(), - `readfiles-fifo-${Date.now()}-${Math.random().toString(36).slice(2)}` + `readfiles-fifo-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(dir, { recursive: true }); }); @@ -191,7 +191,7 @@ describe("init apply-patchset FIFO safety", () => { beforeEach(() => { dir = join( tmpdir(), - `applypatch-fifo-${Date.now()}-${Math.random().toString(36).slice(2)}` + `applypatch-fifo-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(dir, { recursive: true }); }); @@ -218,7 +218,7 @@ describe("init apply-patchset FIFO safety", () => { ], }, }, - { dryRun: false, authToken: undefined } + { dryRun: false, authToken: undefined }, ); expect(result.ok).toBe(false); @@ -246,7 +246,7 @@ describe("init apply-patchset FIFO safety", () => { ], }, }, - { dryRun: false, authToken: undefined } + { dryRun: false, authToken: undefined }, ); expect(result.ok).toBe(false); @@ -260,7 +260,7 @@ describe("workflow-inputs preReadCommonFiles FIFO safety", () => { beforeEach(() => { dir = join( tmpdir(), - `preread-fifo-${Date.now()}-${Math.random().toString(36).slice(2)}` + `preread-fifo-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(dir, { recursive: true }); }); @@ -287,7 +287,7 @@ describe("workflow-inputs preReadCommonFiles FIFO safety", () => { test("rejects a common-config alias to sensitive metadata", async () => { writeFileSync( join(dir, ".netrc"), - "machine example.test login user password secret\n" + "machine example.test login user password secret\n", ); symlinkSync(".netrc", join(dir, "package.json")); @@ -319,8 +319,8 @@ describe("workflow-inputs preReadCommonFiles FIFO safety", () => { buffer: Buffer, offset: number, length: number, - position: number | null - ) => actualHandle.read(buffer, offset, Math.min(length, 3), position) + position: number | null, + ) => actualHandle.read(buffer, offset, Math.min(length, 3), position), ); const openSpy = vi.spyOn(fs.promises, "open").mockResolvedValue({ close: vi.fn().mockResolvedValue(undefined), diff --git a/packages/cli/test/lib/scan/binary.property.test.ts b/packages/cli/test/lib/scan/binary.property.test.ts index 3ffa04b2f..7e8a61984 100644 --- a/packages/cli/test/lib/scan/binary.property.test.ts +++ b/packages/cli/test/lib/scan/binary.property.test.ts @@ -29,7 +29,7 @@ describe("property: isLikelyBinary", () => { property(uint8Array({ minLength: 0, maxLength: 9000 }), (buf) => { expect(isLikelyBinary(buf)).toBe(referenceHasNul(buf)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -40,7 +40,7 @@ describe("property: isLikelyBinary", () => { const b = isLikelyBinary(buf); expect(a).toBe(b); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -54,9 +54,9 @@ describe("property: isLikelyBinary", () => { const idx = offsetSeed % buf2.length; buf2[idx] = 0; expect(isLikelyBinary(buf2)).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -68,9 +68,9 @@ describe("property: isLikelyBinary", () => { // Strip NULs by flipping each to 1. const clean = buf.map((b) => (b === 0 ? 1 : b)); expect(isLikelyBinary(clean)).toBe(false); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/scan/binary.test.ts b/packages/cli/test/lib/scan/binary.test.ts index e1cea3f5a..0254bdbf6 100644 --- a/packages/cli/test/lib/scan/binary.test.ts +++ b/packages/cli/test/lib/scan/binary.test.ts @@ -117,7 +117,7 @@ describe("classifyByExtension", () => { test("no-extension files return null", () => { expect(classifyByExtension("/a/b/Makefile", TEXT_EXTENSIONS)).toBeNull(); expect( - classifyByExtension("/a/b/.sentryclirc", TEXT_EXTENSIONS) + classifyByExtension("/a/b/.sentryclirc", TEXT_EXTENSIONS), ).toBeNull(); }); }); diff --git a/packages/cli/test/lib/scan/concurrent.test.ts b/packages/cli/test/lib/scan/concurrent.test.ts index ce3164975..7e95b4077 100644 --- a/packages/cli/test/lib/scan/concurrent.test.ts +++ b/packages/cli/test/lib/scan/concurrent.test.ts @@ -31,7 +31,7 @@ describe("mapFilesConcurrent — gather variant", () => { test("null return values are filtered out", async () => { const out = await mapFilesConcurrent( emitRange(5), - async (i) => (i % 2 === 0 ? i : null) + async (i) => (i % 2 === 0 ? i : null), ); expect(out.sort((a, b) => a - b)).toEqual([0, 2, 4]); }); @@ -48,7 +48,7 @@ describe("mapFilesConcurrent — gather variant", () => { inFlight -= 1; return 1; }, - { concurrency: 3 } + { concurrency: 3 }, ); expect(maxSeen).toBeLessThanOrEqual(3); expect(maxSeen).toBeGreaterThanOrEqual(1); @@ -64,7 +64,7 @@ describe("mapFilesConcurrent — gather variant", () => { }, { onResult: (result) => ({ done: result === 3 }), - } + }, ); // With concurrency 50 and `done` on hit 4, we expect well under // all 100 items to have been processed before the flag stopped @@ -95,7 +95,7 @@ describe("mapFilesConcurrentStream — streaming variant", () => { const collected: number[] = []; for await (const item of mapFilesConcurrentStream( emitRange(5), - async (i) => [i, i + 10] + async (i) => [i, i + 10], )) { collected.push(item); } @@ -110,7 +110,7 @@ describe("mapFilesConcurrentStream — streaming variant", () => { const collected: number[] = []; for await (const item of mapFilesConcurrentStream( emitRange(5), - async (i) => (i % 2 === 0 ? [i] : null) + async (i) => (i % 2 === 0 ? [i] : null), )) { collected.push(item); } @@ -126,7 +126,7 @@ describe("mapFilesConcurrentStream — streaming variant", () => { produced += 1; return [i]; }, - { concurrency: 4 } + { concurrency: 4 }, )) { consumed.push(item); if (consumed.length === 2) { @@ -149,7 +149,7 @@ describe("mapFilesConcurrentStream — streaming variant", () => { await new Promise((r) => setTimeout(r, 20)); return [i]; }, - { signal: controller.signal, concurrency: 2 } + { signal: controller.signal, concurrency: 2 }, ); let threw: unknown = null; try { @@ -181,7 +181,7 @@ describe("mapFilesConcurrentStream — streaming variant", () => { for await (const _ of mapFilesConcurrentStream( erroringSource, async (i) => [i * 2], - { concurrency: 1 } + { concurrency: 1 }, )) { // break immediately — the error hasn't been observed yet. break; diff --git a/packages/cli/test/lib/scan/constants.property.test.ts b/packages/cli/test/lib/scan/constants.property.test.ts index b9d3f1409..a599234f7 100644 --- a/packages/cli/test/lib/scan/constants.property.test.ts +++ b/packages/cli/test/lib/scan/constants.property.test.ts @@ -31,7 +31,7 @@ describe("property: normalizePath", () => { const twice = normalizePath(once); expect(twice).toBe(once); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -45,7 +45,7 @@ describe("property: normalizePath", () => { property(string(), (input) => { expect(normalizePath(input).includes("\\")).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -57,14 +57,14 @@ describe("property: normalizePath", () => { property(string(), (input) => { expect(normalizePath(input)).toBe(input); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); describe("property: isMonorepoPackageDir", () => { const segmentArb = string({ minLength: 1, maxLength: 15 }).filter( - (s) => !s.includes("/") && s.length > 0 + (s) => !s.includes("/") && s.length > 0, ); test("any 2-segment path with MONOREPO_ROOTS first is a package dir", () => { @@ -73,7 +73,7 @@ describe("property: isMonorepoPackageDir", () => { const rel = `${root}/${pkg}`; expect(isMonorepoPackageDir(rel)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -82,7 +82,7 @@ describe("property: isMonorepoPackageDir", () => { property(segmentArb, (seg) => { expect(isMonorepoPackageDir(seg)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -93,9 +93,9 @@ describe("property: isMonorepoPackageDir", () => { tuple(segmentArb, segmentArb), (root, [b, c]) => { expect(isMonorepoPackageDir(`${root}/${b}/${c}`)).toBe(false); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -109,7 +109,7 @@ describe("property: isMonorepoPackageDir", () => { } expect(isMonorepoPackageDir(`${first}/${second}`)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -120,9 +120,8 @@ describe("DEFAULT_SKIP_DIRS vs DSN_ADDITIONAL_SKIP_DIRS", () => { // DEFAULT_SKIP_DIRS (and DSN_ADDITIONAL_SKIP_DIRS drops them) or // they're DSN-specific (and shouldn't be in both). const fn = async () => { - const { DEFAULT_SKIP_DIRS, DSN_ADDITIONAL_SKIP_DIRS } = await import( - "../../../src/lib/scan/constants.js" - ); + const { DEFAULT_SKIP_DIRS, DSN_ADDITIONAL_SKIP_DIRS } = + await import("../../../src/lib/scan/constants.js"); const base = new Set(DEFAULT_SKIP_DIRS); for (const extra of DSN_ADDITIONAL_SKIP_DIRS) { expect(base.has(extra)).toBe(false); diff --git a/packages/cli/test/lib/scan/grep.property.test.ts b/packages/cli/test/lib/scan/grep.property.test.ts index 8982a5d22..744fa280f 100644 --- a/packages/cli/test/lib/scan/grep.property.test.ts +++ b/packages/cli/test/lib/scan/grep.property.test.ts @@ -44,19 +44,19 @@ const filenameArb = array(constantFrom(...SAFE_CHARS), { */ const fileContentArb = array( array(constantFrom(...SAFE_CHARS), { minLength: 0, maxLength: 20 }).map( - (chars) => chars.join("") + (chars) => chars.join(""), ), - { minLength: 1, maxLength: 8 } + { minLength: 1, maxLength: 8 }, ).map((lines) => lines.join("\n")); /** Small tree: 1-5 filename/content pairs. */ const treeArb = array( constantFrom(null).chain(() => filenameArb.chain((name) => - fileContentArb.map((content) => [name, content] as [string, string]) - ) + fileContentArb.map((content) => [name, content] as [string, string]), + ), ), - { minLength: 1, maxLength: 5 } + { minLength: 1, maxLength: 5 }, ); /** Pattern: 1-3 chars from the same alphabet — likely to match. */ @@ -74,7 +74,7 @@ const patternArb = array(constantFrom(...SAFE_CHARS), { type NaiveMatch = { path: string; lineNum: number; line: string }; function naiveGrep( layout: readonly [string, string][], - pattern: string + pattern: string, ): NaiveMatch[] { const out: NaiveMatch[] = []; const regex = new RegExp(pattern); @@ -128,7 +128,7 @@ describe("property: collectGrep matches naive reference", () => { }), // Filesystem-heavy property; keep run count modest so CI stays // fast. 20 runs still explores plenty of pattern/corpus shapes. - { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) }, ); }); }); @@ -157,7 +157,7 @@ describe("property: idempotence", () => { rmSync(cwd, { recursive: true, force: true }); } }), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 15) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 15) }, ); }); }); diff --git a/packages/cli/test/lib/scan/grep.test.ts b/packages/cli/test/lib/scan/grep.test.ts index f24308bd8..88e069300 100644 --- a/packages/cli/test/lib/scan/grep.test.ts +++ b/packages/cli/test/lib/scan/grep.test.ts @@ -499,7 +499,7 @@ describe("collectGrep — regex errors", () => { const { cwd, cleanup } = makeSandbox({ "a.txt": "foo" }); try { await expect(collectGrep({ cwd, pattern: "[unclosed" })).rejects.toThrow( - ValidationError + ValidationError, ); } finally { cleanup(); @@ -685,7 +685,7 @@ describe("collectGrep — literal prefilter fast path", () => { test("case-insensitive literal prefilter finds all casings", async () => { const { cwd, cleanup } = makeSandbox({ "a.ts": ["IMPORT X FROM Y", "import y from z", "Import Z From W"].join( - "\n" + "\n", ), }); try { diff --git a/packages/cli/test/lib/scan/ignore.property.test.ts b/packages/cli/test/lib/scan/ignore.property.test.ts index 70c5ea13d..e5c59d665 100644 --- a/packages/cli/test/lib/scan/ignore.property.test.ts +++ b/packages/cli/test/lib/scan/ignore.property.test.ts @@ -43,7 +43,7 @@ const segmentArb = array(constantFrom(...SEGMENT_CHARS), { maxLength: 10, }).map((chars) => chars.join("")); const relPathArb = array(segmentArb, { minLength: 1, maxLength: 4 }).map((xs) => - xs.join("/") + xs.join("/"), ); /** * Pattern alphabet kept small and safe — the `ignore` package treats @@ -59,7 +59,7 @@ const patternArb = constantFrom( "*.bak", "src/**/*.generated.ts", "coverage", - ".env" + ".env", ); describe("property: IgnoreStack root-only == plain ignore instance", () => { @@ -75,7 +75,7 @@ describe("property: IgnoreStack root-only == plain ignore instance", () => { writeFileSync( join(cwd, ".gitignore"), `${patterns.join("\n")}\n`, - "utf8" + "utf8", ); } const stack = await IgnoreStack.create({ @@ -84,16 +84,16 @@ describe("property: IgnoreStack root-only == plain ignore instance", () => { }); const plain = ignore().add(patterns); expect(stack.isIgnored(relPath, false)).toBe( - plain.ignores(relPath) + plain.ignores(relPath), ); } finally { rmSync(cwd, { recursive: true, force: true }); } - } + }, ), // Fewer runs: each run writes fs state. 25 is enough to catch // regressions without blowing up CI time. - { numRuns: Math.min(DEFAULT_NUM_RUNS, 25) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 25) }, ); }); }); @@ -117,9 +117,9 @@ describe("property: alwaysSkipDirs always ignore their basename", () => { } finally { rmSync(cwd, { recursive: true, force: true }); } - } + }, ), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 25) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 25) }, ); }); }); diff --git a/packages/cli/test/lib/scan/ignore.test.ts b/packages/cli/test/lib/scan/ignore.test.ts index 140caaab6..4ca3e8d7c 100644 --- a/packages/cli/test/lib/scan/ignore.test.ts +++ b/packages/cli/test/lib/scan/ignore.test.ts @@ -185,7 +185,7 @@ describe("IgnoreStack — input validation", () => { try { const stack = await IgnoreStack.create({ cwd, alwaysSkipDirs: [] }); expect(() => stack.isIgnored("/etc/passwd", false)).toThrow( - /relative path/ + /relative path/, ); } finally { cleanup(); diff --git a/packages/cli/test/lib/scan/literal-extract.test.ts b/packages/cli/test/lib/scan/literal-extract.test.ts index d432df4d1..fef96f980 100644 --- a/packages/cli/test/lib/scan/literal-extract.test.ts +++ b/packages/cli/test/lib/scan/literal-extract.test.ts @@ -95,7 +95,7 @@ describe("extractInnerLiteral — quantifier handling", () => { test("longest run wins when multiple exist", () => { // `short.*longer_run.*short` — `longer_run` wins over `short`. expect(extractInnerLiteral("short.*longer_run.*short", "")).toBe( - "longer_run" + "longer_run", ); }); }); @@ -119,9 +119,12 @@ describe("extractInnerLiteral — character-class opaqueness (Cursor bug #1)", ( ["[[]foo|bar", null], // [ inside class (nested-looking but flat) ["[|]foo|bar", null], // | inside class is literal; second | is top-level ["[(|)]foo|bar", null], // class containing (, |, ) - ])("returns null for %p — alternation hidden by class corrupting paren depth", (pattern, expected) => { - expect(extractInnerLiteral(pattern, "")).toBe(expected); - }); + ])( + "returns null for %p — alternation hidden by class corrupting paren depth", + (pattern, expected) => { + expect(extractInnerLiteral(pattern, "")).toBe(expected); + }, + ); test.each([ ["foo[(]bar", "foo"], // ( inside class, no alternation → longest run "foo" @@ -155,9 +158,12 @@ describe("extractInnerLiteral — class-in-group opaqueness (Cursor bug #1 follo ["(foo[(]bar)?baz", "baz"], ["(A[[]B)?CDE", "CDE"], ["(A[\\)]B)?CDE", "CDE"], - ])("extracts the post-group literal when group contains a class with ( or )", (pattern, expected) => { - expect(extractInnerLiteral(pattern, "")).toBe(expected); - }); + ])( + "extracts the post-group literal when group contains a class with ( or )", + (pattern, expected) => { + expect(extractInnerLiteral(pattern, "")).toBe(expected); + }, + ); test.each([ ["(foo)bar", "bar"], // regular group + literal diff --git a/packages/cli/test/lib/scan/regex.test.ts b/packages/cli/test/lib/scan/regex.test.ts index a65f52f0b..47f27cdb9 100644 --- a/packages/cli/test/lib/scan/regex.test.ts +++ b/packages/cli/test/lib/scan/regex.test.ts @@ -80,19 +80,19 @@ describe("compilePattern", () => { test("caseSensitive: false adds i flag", () => { expect(compilePattern("foo", { caseSensitive: false }).toString()).toBe( - "/foo/i" + "/foo/i", ); }); test("inline + caseSensitive merge cleanly", () => { expect( - compilePattern("(?m)^foo", { caseSensitive: false }).toString() + compilePattern("(?m)^foo", { caseSensitive: false }).toString(), ).toBe("/^foo/im"); }); test("multiline: true adds m flag", () => { expect(compilePattern("foo", { multiline: true }).toString()).toBe( - "/foo/m" + "/foo/m", ); }); @@ -108,7 +108,7 @@ describe("compilePattern", () => { } catch (error) { expect((error as ValidationError).field).toBe("pattern"); expect((error as ValidationError).message).toMatch( - /Invalid grep pattern:/ + /Invalid grep pattern:/, ); } }); diff --git a/packages/cli/test/lib/scan/walker.property.test.ts b/packages/cli/test/lib/scan/walker.property.test.ts index 66a03f4cb..b840c190f 100644 --- a/packages/cli/test/lib/scan/walker.property.test.ts +++ b/packages/cli/test/lib/scan/walker.property.test.ts @@ -74,9 +74,9 @@ describe("property: walkFiles — invariants", () => { } finally { rmSync(cwd, { recursive: true, force: true }); } - } + }, ), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) }, ); }); @@ -123,10 +123,10 @@ describe("property: walkFiles — invariants", () => { } finally { rmSync(cwd, { recursive: true, force: true }); } - } + }, ), - { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) } + { numRuns: Math.min(DEFAULT_NUM_RUNS, 20) }, ); - } + }, ); }); diff --git a/packages/cli/test/lib/scan/walker.test.ts b/packages/cli/test/lib/scan/walker.test.ts index 9b93f43ab..6040a04fd 100644 --- a/packages/cli/test/lib/scan/walker.test.ts +++ b/packages/cli/test/lib/scan/walker.test.ts @@ -45,7 +45,7 @@ function makeSandbox(layout: Record): { /** Collect every relativePath a walk yields into a sorted array. */ async function collect( - opts: Parameters[0] + opts: Parameters[0], ): Promise { const out: WalkEntry[] = []; for await (const entry of walkFiles(opts)) { @@ -90,7 +90,7 @@ describe("walkFiles — basic traversal", () => { for await (const _ of walkFiles({ cwd: "./relative" })) { break; } - })() + })(), ).rejects.toThrow(/absolute/); }); }); @@ -289,7 +289,7 @@ describe("walkFiles — binary detection", () => { writeFileSync(join(cwd, "blob.bin"), bin); const findBlob = async ( - opts: Parameters[0] + opts: Parameters[0], ): Promise => { for await (const e of walkFiles(opts)) { if (e.relativePath === "blob.bin") { @@ -304,7 +304,7 @@ describe("walkFiles — binary detection", () => { // is skipped entirely). expect((await findBlob({ cwd }))?.isBinary).toBe(true); expect((await findBlob({ cwd, classifyBinary: false }))?.isBinary).toBe( - false + false, ); } finally { cleanup(); @@ -446,7 +446,7 @@ describe("walkFiles — descentHook", () => { entries.push(e); } const deep = entries.find( - (e) => e.relativePath === "packages/foo/src/deep/a.ts" + (e) => e.relativePath === "packages/foo/src/deep/a.ts", ); // 5 path segments → file depth 5. expect(deep?.depth).toBe(5); @@ -478,7 +478,7 @@ describe("walkFiles — descentHook", () => { // deep/ → depth 2, a.ts (file) → depth 3. Without the reset // a.ts would be depth 5 from the repo root. const deep = entries.find( - (e) => e.relativePath === "packages/foo/src/deep/a.ts" + (e) => e.relativePath === "packages/foo/src/deep/a.ts", ); expect(deep?.depth).toBe(3); const root = entries.find((e) => e.relativePath === "root.ts"); @@ -845,7 +845,7 @@ describe("walkFiles — parallel walker (concurrency > 1)", () => { // 2s ceiling — the bug manifested as an indefinite hang. With // the fix, the throw propagates in ~10ms. const watchdog = new Promise((_, rej) => - setTimeout(() => rej(new Error("timed out")), 2000) + setTimeout(() => rej(new Error("timed out")), 2000), ); await expect(Promise.race([drain(), watchdog])).rejects.toThrow(/abort/i); } finally { diff --git a/packages/cli/test/lib/scanner-flags.integration.test.ts b/packages/cli/test/lib/scanner-flags.integration.test.ts index 5a4cc908f..9e1b04ba9 100644 --- a/packages/cli/test/lib/scanner-flags.integration.test.ts +++ b/packages/cli/test/lib/scanner-flags.integration.test.ts @@ -36,7 +36,7 @@ const emptyCwd = mkdtempSync(join(tmpdir(), "scanner-flags-cwd-")); /** Run the real app with a mock context, capturing stdout and stderr. */ async function runApp( - args: string[] + args: string[], ): Promise<{ stdout: string; stderr: string; exitCode: number }> { let stdout = ""; let stderr = ""; @@ -183,13 +183,13 @@ describe("a value flag does not swallow --help", () => { expect(stdout).toContain("USAGE"); }); - test.each([ - "--json", - "-v", - ])("%s after a missing value flag still reaches the leaf command", async (flag) => { - const { stderr } = await runApp(["--org", flag, "issue", "list"]); - expect(stderr).not.toContain(NO_COMMAND_REGISTERED); - }); + test.each(["--json", "-v"])( + "%s after a missing value flag still reaches the leaf command", + async (flag) => { + const { stderr } = await runApp(["--org", flag, "issue", "list"]); + expect(stderr).not.toContain(NO_COMMAND_REGISTERED); + }, + ); }); describe("escape sequence is still respected", () => { diff --git a/packages/cli/test/lib/scope-recovery.test.ts b/packages/cli/test/lib/scope-recovery.test.ts index 11ff67e1e..d7676564a 100644 --- a/packages/cli/test/lib/scope-recovery.test.ts +++ b/packages/cli/test/lib/scope-recovery.test.ts @@ -9,7 +9,7 @@ import { } from "../../src/lib/scope-recovery.js"; function runtime( - overrides: Partial = {} + overrides: Partial = {}, ): ScopeRecoveryRuntime { return { assertTrustedHost: vi.fn(), @@ -34,7 +34,7 @@ describe("runWithScopeRecovery", () => { getAuthScopes: vi .fn() .mockResolvedValue( - OAUTH_SCOPES.filter((scope) => scope !== "team:admin") + OAUTH_SCOPES.filter((scope) => scope !== "team:admin"), ), }); @@ -42,7 +42,7 @@ describe("runWithScopeRecovery", () => { proceed, ["project", "create"], login, - testRuntime + testRuntime, ); expect(proceed).toHaveBeenCalledTimes(2); @@ -50,7 +50,7 @@ describe("runWithScopeRecovery", () => { expect(testRuntime.assertTrustedHost).toHaveBeenCalledOnce(); expect(login).toHaveBeenCalledWith(); expect(testRuntime.write).toHaveBeenCalledWith( - expect.stringContaining("team:admin") + expect.stringContaining("team:admin"), ); }); @@ -60,7 +60,7 @@ describe("runWithScopeRecovery", () => { const login = vi.fn(); await expect( - runWithScopeRecovery(proceed, [], login, runtime()) + runWithScopeRecovery(proceed, [], login, runtime()), ).rejects.toBe(error); expect(login).not.toHaveBeenCalled(); @@ -78,7 +78,7 @@ describe("runWithScopeRecovery", () => { proceed, [], login, - runtime({ getAuthScopes: vi.fn().mockResolvedValue(null) }) + runtime({ getAuthScopes: vi.fn().mockResolvedValue(null) }), ); expect(login).toHaveBeenCalledOnce(); @@ -101,7 +101,7 @@ describe("runWithScopeRecovery", () => { getAuthScopes: vi .fn() .mockRejectedValue(new ApiError("Invalid token", 401)), - }) + }), ); expect(login).toHaveBeenCalledOnce(); @@ -125,7 +125,7 @@ describe("runWithScopeRecovery", () => { proceed, [], login, - runtime({ getAuthSource, getAuthScopes }) + runtime({ getAuthSource, getAuthScopes }), ); expect(login).toHaveBeenCalledOnce(); @@ -145,8 +145,8 @@ describe("runWithScopeRecovery", () => { runtime({ getAuthSource: () => "env:SENTRY_AUTH_TOKEN", getAuthScopes, - }) - ) + }), + ), ).rejects.toBe(error); expect(login).not.toHaveBeenCalled(); @@ -163,8 +163,8 @@ describe("runWithScopeRecovery", () => { vi.fn().mockRejectedValue(error), [], login, - runtime({ getAuthScopes, inputIsTty: () => false }) - ) + runtime({ getAuthScopes, inputIsTty: () => false }), + ), ).rejects.toBe(error); expect(getAuthScopes).toHaveBeenCalledOnce(); @@ -181,8 +181,8 @@ describe("runWithScopeRecovery", () => { vi.fn().mockRejectedValue(error), ["--json"], login, - runtime({ getAuthScopes, promptsAllowed: () => false }) - ) + runtime({ getAuthScopes, promptsAllowed: () => false }), + ), ).rejects.toBe(error); expect(getAuthScopes).toHaveBeenCalledOnce(); @@ -200,8 +200,8 @@ describe("runWithScopeRecovery", () => { login, runtime({ getAuthScopes: vi.fn().mockRejectedValue(new Error("offline")), - }) - ) + }), + ), ).rejects.toBe(error); expect(login).not.toHaveBeenCalled(); }); @@ -220,8 +220,8 @@ describe("runWithScopeRecovery", () => { proceed, [], login, - runtime({ getAuthScopes: vi.fn().mockResolvedValue([]) }) - ) + runtime({ getAuthScopes: vi.fn().mockResolvedValue([]) }), + ), ).rejects.toBe(second); expect(login).toHaveBeenCalledOnce(); expect(proceed).toHaveBeenCalledTimes(2); @@ -237,8 +237,8 @@ describe("scope recovery availability", () => { expect( await captureOAuthScopeRecoveryGate(availableRuntime).shouldDelegate( error, - { unattended: false } - ) + { unattended: false }, + ), ).toBe(true); const blockedCases = [ @@ -261,8 +261,8 @@ describe("scope recovery availability", () => { expect( await captureOAuthScopeRecoveryGate(blocked.runtime).shouldDelegate( error, - { unattended: blocked.unattended } - ) + { unattended: blocked.unattended }, + ), ).toBe(false); expect(getAuthScopes).not.toHaveBeenCalled(); } @@ -280,7 +280,7 @@ describe("scope recovery availability", () => { expect( await scopeRecovery.shouldDelegate(new ApiError("Unauthorized", 401), { unattended: false, - }) + }), ).toBe(true); expect(getAuthScopes).not.toHaveBeenCalled(); }); @@ -291,7 +291,7 @@ describe("upgrade scope check", () => { const login = vi.fn().mockResolvedValue({ method: "oauth" }); const refreshed = await ensureCurrentOAuthScopes( login, - runtime({ getAuthScopes: vi.fn().mockResolvedValue(["org:read"]) }) + runtime({ getAuthScopes: vi.fn().mockResolvedValue(["org:read"]) }), ); expect(refreshed).toBe(true); @@ -306,7 +306,7 @@ describe("upgrade scope check", () => { getAuthScopes: vi .fn() .mockRejectedValue(new ApiError("Invalid token", 401)), - }) + }), ); expect(refreshed).toBe(true); @@ -319,7 +319,7 @@ describe("upgrade scope check", () => { login, runtime({ getAuthScopes: vi.fn().mockRejectedValue(new AuthError("expired")), - }) + }), ); expect(refreshed).toBe(true); diff --git a/packages/cli/test/lib/sdk-flag-types.test.ts b/packages/cli/test/lib/sdk-flag-types.test.ts index 9986ad9ef..279618931 100644 --- a/packages/cli/test/lib/sdk-flag-types.test.ts +++ b/packages/cli/test/lib/sdk-flag-types.test.ts @@ -13,7 +13,7 @@ import { describe, expect, test } from "vitest"; const declarations = readFileSync( fileURLToPath(new URL("../../src/sdk.generated.d.cts", import.meta.url)), - "utf-8" + "utf-8", ); function paramsType(name: string): string { diff --git a/packages/cli/test/lib/sdk-invoke-isolation.focused.test.ts b/packages/cli/test/lib/sdk-invoke-isolation.focused.test.ts index 9dea3f4f2..744c1a73c 100644 --- a/packages/cli/test/lib/sdk-invoke-isolation.focused.test.ts +++ b/packages/cli/test/lib/sdk-invoke-isolation.focused.test.ts @@ -6,7 +6,7 @@ const invocationState = vi.hoisted(() => ({ handler: undefined as | (( context: HandlerContext, - flags: Record + flags: Record, ) => Promise) | undefined, })); @@ -17,7 +17,7 @@ vi.mock("../../src/app.js", () => { loader: async () => async function focusedCommand( this: HandlerContext, - flags: Record + flags: Record, ): Promise { if (!invocationState.handler) { throw new Error("Missing invocation handler"); @@ -39,7 +39,7 @@ vi.mock("../../src/app.js", () => { vi.mock("../../src/lib/telemetry.js", () => ({ setCommandSpanName: vi.fn(), withTelemetry: async ( - callback: (span: undefined) => Promise + callback: (span: undefined) => Promise, ): Promise => await callback(undefined), })); vi.mock("@sentry/node-core/light", () => ({ getClient: () => null })); @@ -74,7 +74,7 @@ describe("overlapping SDK invocation isolation", () => { await release.get(id)?.promise; context.stdout.captureObject?.({ header: getCustomHeaders().find( - ([name]) => name === "X-Invocation" + ([name]) => name === "X-Invocation", )?.[1], host: getEnv().SENTRY_HOST, token: getEnv().SENTRY_AUTH_TOKEN, diff --git a/packages/cli/test/lib/sdk-invoke.test.ts b/packages/cli/test/lib/sdk-invoke.test.ts index 7cfc32897..7d2c8b764 100644 --- a/packages/cli/test/lib/sdk-invoke.test.ts +++ b/packages/cli/test/lib/sdk-invoke.test.ts @@ -141,7 +141,7 @@ describe("applyFlagDefaults: undefined stripping", () => { const flagDefs: Record = {}; const result = applyFlagDefaults( { a: undefined, b: null, c: 0, d: "", e: false }, - flagDefs + flagDefs, ); expect("a" in result).toBe(false); expect(result.b).toBeNull(); diff --git a/packages/cli/test/lib/sea-assets.test.ts b/packages/cli/test/lib/sea-assets.test.ts index b718ea286..4e8083124 100644 --- a/packages/cli/test/lib/sea-assets.test.ts +++ b/packages/cli/test/lib/sea-assets.test.ts @@ -25,7 +25,7 @@ describe("getSeaRawAsset", () => { }); expect(() => getSeaRawAsset("dist-build/missing.bin")).toThrow( - "embedded asset is missing" + "embedded asset is missing", ); }); }); diff --git a/packages/cli/test/lib/search-query.property.test.ts b/packages/cli/test/lib/search-query.property.test.ts index 9b9c87632..bfeaea651 100644 --- a/packages/cli/test/lib/search-query.property.test.ts +++ b/packages/cli/test/lib/search-query.property.test.ts @@ -24,7 +24,7 @@ const validInListKeyArb = constantFrom( "message", "platform", "os.name", - "transaction" + "transaction", ); /** Keys that are NOT valid for in-list syntax. */ @@ -42,7 +42,7 @@ const simpleValueArb = constantFrom( "Chrome", "Firefox", "unresolved", - "resolved" + "resolved", ); /** Free-text terms (no colon → not a qualifier). */ @@ -52,7 +52,7 @@ const freeTextArb = constantFrom( "sandbox", "order", "android", - "poolexhaustion" + "poolexhaustion", ); /** Safe terms that contain neither OR nor AND as standalone tokens. */ @@ -65,7 +65,7 @@ const safeTermArb = constantFrom( "assigned:me", "sandbox", "order", - "android" + "android", ); describe("property: sanitizeQuery", () => { @@ -79,9 +79,9 @@ describe("property: sanitizeQuery", () => { const query = `${key}:${val1} OR ${key}:${val2}`; const result = sanitizeQuery(query); expect(result).toBe(`${key}:[${val1},${val2}]`); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -96,9 +96,9 @@ describe("property: sanitizeQuery", () => { const query = `${key}:${v1} OR ${key}:${v2} OR ${key}:${v3}`; const result = sanitizeQuery(query); expect(result).toBe(`${key}:[${v1},${v2},${v3}]`); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -108,9 +108,9 @@ describe("property: sanitizeQuery", () => { tuple(freeTextArb, freeTextArb).map(([a, b]) => `${a} OR ${b}`), (query) => { expect(() => sanitizeQuery(query)).toThrow(ValidationError); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -128,9 +128,9 @@ describe("property: sanitizeQuery", () => { } const query = `${key1}:${val1} OR ${key2}:${val2}`; expect(() => sanitizeQuery(query)).toThrow(ValidationError); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -143,9 +143,9 @@ describe("property: sanitizeQuery", () => { (key, val1, val2) => { const query = `${key}:${val1} OR ${key}:${val2}`; expect(() => sanitizeQuery(query)).toThrow(ValidationError); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -161,9 +161,9 @@ describe("property: sanitizeQuery", () => { } // AND must not be present as standalone token expect(result).not.toMatch(/\bAND\b/); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -172,7 +172,7 @@ describe("property: sanitizeQuery", () => { property(safeTermArb, (term) => { expect(sanitizeQuery(term)).toBe(term); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/search-query.test.ts b/packages/cli/test/lib/search-query.test.ts index 6bf9d7abf..39ecd0825 100644 --- a/packages/cli/test/lib/search-query.test.ts +++ b/packages/cli/test/lib/search-query.test.ts @@ -23,7 +23,7 @@ const { normalizeQuery, transformUnquoted } = __testing; describe("sanitizeQuery: passthrough", () => { test("passes through a simple qualifier query unchanged", () => { expect(sanitizeQuery("is:unresolved level:error")).toBe( - "is:unresolved level:error" + "is:unresolved level:error", ); }); @@ -46,19 +46,19 @@ describe("sanitizeQuery: passthrough", () => { test("does not match OR inside quoted strings", () => { expect(sanitizeQuery('message:"error OR timeout"')).toBe( - 'message:"error OR timeout"' + 'message:"error OR timeout"', ); }); test("does not match AND inside quoted strings", () => { expect(sanitizeQuery('title:"error AND timeout"')).toBe( - 'title:"error AND timeout"' + 'title:"error AND timeout"', ); }); test("does not match OR in qualifier values with more context", () => { expect(sanitizeQuery("is:unresolved tag:OR_something")).toBe( - "is:unresolved tag:OR_something" + "is:unresolved tag:OR_something", ); }); }); @@ -74,7 +74,7 @@ describe("sanitizeQuery: AND", () => { test("strips multiple AND operators", () => { expect(sanitizeQuery("error AND timeout AND crash")).toBe( - "error timeout crash" + "error timeout crash", ); }); @@ -85,7 +85,7 @@ describe("sanitizeQuery: AND", () => { test("strips AND with qualifiers", () => { expect(sanitizeQuery("is:unresolved AND level:error")).toBe( - "is:unresolved level:error" + "is:unresolved level:error", ); }); @@ -105,58 +105,58 @@ describe("sanitizeQuery: AND", () => { describe("sanitizeQuery: numeric project:", () => { test("rewrites a numeric project: filter to project_id", () => { expect( - sanitizeQuery("project:4511730126487632 environment:vercel-production") + sanitizeQuery("project:4511730126487632 environment:vercel-production"), ).toBe("project_id:4511730126487632 environment:vercel-production"); }); test("rewrites a numeric project: in-list", () => { expect( - sanitizeQuery("is:unresolved project:[4505521413357568,6442225]") + sanitizeQuery("is:unresolved project:[4505521413357568,6442225]"), ).toBe("is:unresolved project_id:[4505521413357568,6442225]"); }); test("rewrites a negated numeric project: filter", () => { expect(sanitizeQuery("!project:1423462 lastSeen:-1h")).toBe( - "!project_id:1423462 lastSeen:-1h" + "!project_id:1423462 lastSeen:-1h", ); }); test("leaves project slugs alone", () => { expect(sanitizeQuery("project:frontend is:unresolved")).toBe( - "project:frontend is:unresolved" + "project:frontend is:unresolved", ); }); test("leaves project_id numeric filters alone", () => { expect(sanitizeQuery("project_id:4511730126487632")).toBe( - "project_id:4511730126487632" + "project_id:4511730126487632", ); }); test("leaves namespaced project keys alone", () => { expect(sanitizeQuery("bolt.project_id:70054175")).toBe( - "bolt.project_id:70054175" + "bolt.project_id:70054175", ); expect(sanitizeQuery("bolt.project:70054175")).toBe( - "bolt.project:70054175" + "bolt.project:70054175", ); }); test("does not rewrite a numeric id inside a quoted value", () => { expect(sanitizeQuery('message:"project:4511730126487632"')).toBe( - 'message:"project:4511730126487632"' + 'message:"project:4511730126487632"', ); }); test("does not rewrite mixed slug/numeric in-lists", () => { expect(sanitizeQuery("project:[frontend,6442225]")).toBe( - "project:[frontend,6442225]" + "project:[frontend,6442225]", ); }); test("rewrites numeric project: then OR in one step", () => { expect(sanitizeQuery("project:123 OR project:456")).toBe( - "project_id:[123,456]" + "project_id:[123,456]", ); }); }); @@ -168,84 +168,88 @@ describe("sanitizeQuery: numeric project:", () => { describe("sanitizeQuery: OR → in-list (success)", () => { test("rewrites same-key qualifier OR to in-list", () => { expect(sanitizeQuery("level:error OR level:warning")).toBe( - "level:[error,warning]" + "level:[error,warning]", ); }); test("rewrites OR chain of 3+ same-key qualifiers", () => { expect(sanitizeQuery("level:error OR level:warning OR level:fatal")).toBe( - "level:[error,warning,fatal]" + "level:[error,warning,fatal]", ); }); test("preserves surrounding tokens", () => { expect(sanitizeQuery("is:unresolved level:error OR level:warning")).toBe( - "is:unresolved level:[error,warning]" + "is:unresolved level:[error,warning]", ); }); test("preserves surrounding tokens on both sides", () => { expect( - sanitizeQuery("is:unresolved level:error OR level:warning firstSeen:-24h") + sanitizeQuery( + "is:unresolved level:error OR level:warning firstSeen:-24h", + ), ).toBe("is:unresolved level:[error,warning] firstSeen:-24h"); }); test("rewrites quoted values", () => { expect( - sanitizeQuery('message:"pool exhaustion" OR message:"connection timeout"') + sanitizeQuery( + 'message:"pool exhaustion" OR message:"connection timeout"', + ), ).toBe('message:["pool exhaustion","connection timeout"]'); }); test("merges existing in-list value with plain value", () => { expect(sanitizeQuery("level:[error,warning] OR level:fatal")).toBe( - "level:[error,warning,fatal]" + "level:[error,warning,fatal]", ); }); test("merges two in-list values", () => { expect(sanitizeQuery("level:[error] OR level:[warning,fatal]")).toBe( - "level:[error,warning,fatal]" + "level:[error,warning,fatal]", ); }); test("rewrites multiple independent OR groups", () => { expect( sanitizeQuery( - "level:error OR level:warning browser:Chrome OR browser:Firefox" - ) + "level:error OR level:warning browser:Chrome OR browser:Firefox", + ), ).toBe("level:[error,warning] browser:[Chrome,Firefox]"); }); test("handles case-insensitive OR", () => { expect(sanitizeQuery("level:error or level:warning")).toBe( - "level:[error,warning]" + "level:[error,warning]", ); expect(sanitizeQuery("level:error Or level:warning")).toBe( - "level:[error,warning]" + "level:[error,warning]", ); }); test("handles mixed AND and OR", () => { expect( - sanitizeQuery("is:unresolved AND level:error OR level:warning") + sanitizeQuery("is:unresolved AND level:error OR level:warning"), ).toBe("is:unresolved level:[error,warning]"); }); test("preserves key casing from first token", () => { expect(sanitizeQuery("Level:error OR level:warning")).toBe( - "Level:[error,warning]" + "Level:[error,warning]", ); }); test("handles leading OR (stray)", () => { expect(sanitizeQuery("OR level:error OR level:warning")).toBe( - "level:[error,warning]" + "level:[error,warning]", ); }); test("handles trailing OR (stray)", () => { expect(sanitizeQuery("level:error OR level:warning OR")).toBe( - "level:[error,warning]" + "level:[error,warning]", ); }); }); @@ -261,50 +265,50 @@ describe("sanitizeQuery: OR → throws", () => { test("throws for different keys across OR", () => { expect(() => sanitizeQuery("level:error OR assigned:me")).toThrow( - ValidationError + ValidationError, ); }); test("throws for is: qualifier (not supported with in-list)", () => { expect(() => sanitizeQuery("is:unresolved OR is:resolved")).toThrow( - ValidationError + ValidationError, ); }); test("throws for has: qualifier (not supported with in-list)", () => { expect(() => sanitizeQuery("has:user OR has:email")).toThrow( - ValidationError + ValidationError, ); }); test("throws for negated qualifiers", () => { expect(() => sanitizeQuery("!level:error OR !level:warning")).toThrow( - ValidationError + ValidationError, ); }); test("throws for wildcards in values", () => { expect(() => sanitizeQuery("message:*error* OR message:*timeout*")).toThrow( - ValidationError + ValidationError, ); }); test("throws for comparison operator values (not valid in in-list)", () => { expect(() => sanitizeQuery("age:>24h OR age:>7d")).toThrow(ValidationError); expect(() => sanitizeQuery("times_seen:>100 OR times_seen:>200")).toThrow( - ValidationError + ValidationError, ); expect(() => - sanitizeQuery("span.duration:>=1s OR span.duration:>=500ms") + sanitizeQuery("span.duration:>=1s OR span.duration:>=500ms"), ).toThrow(ValidationError); expect(() => - sanitizeQuery("firstSeen:<=2024-01-01 OR firstSeen:<=2024-06-01") + sanitizeQuery("firstSeen:<=2024-01-01 OR firstSeen:<=2024-06-01"), ).toThrow(ValidationError); }); test("throws for mixed free-text and qualifier OR", () => { expect(() => sanitizeQuery("is:unresolved error OR timeout")).toThrow( - ValidationError + ValidationError, ); }); @@ -326,14 +330,16 @@ describe("sanitizeQuery: OR → throws", () => { // From CLI-16J: AI agent tried free-text OR expect(() => sanitizeQuery( - "is:unresolved pool exhaustion OR connection timeout OR connection terminated" - ) + "is:unresolved pool exhaustion OR connection timeout OR connection terminated", + ), ).toThrow(ValidationError); }); test("throws for parenthesized groups across OR boundary", () => { expect(() => - sanitizeQuery("(level:error assigned:me) OR (level:warning assigned:bob)") + sanitizeQuery( + "(level:error assigned:me) OR (level:warning assigned:bob)", + ), ).toThrow(ValidationError); }); }); @@ -345,19 +351,19 @@ describe("sanitizeQuery: OR → throws", () => { describe("sanitizeQuery: paren groups", () => { test("throws for OR inside paren groups", () => { expect(() => sanitizeQuery("(level:error OR level:warning)")).toThrow( - ValidationError + ValidationError, ); }); test("throws for OR inside paren groups with surrounding filters", () => { expect(() => - sanitizeQuery("(level:error OR level:warning) assigned:me") + sanitizeQuery("(level:error OR level:warning) assigned:me"), ).toThrow(ValidationError); }); test("passes through paren groups without boolean operators", () => { expect(sanitizeQuery("(level:error) assigned:me")).toBe( - "(level:error) assigned:me" + "(level:error) assigned:me", ); }); }); @@ -379,7 +385,7 @@ describe("sanitizeQuery: edge cases", () => { test("throws for OR in paren group even with rewritable top-level OR", () => { expect(() => - sanitizeQuery("level:error OR level:warning (a:1 OR a:2)") + sanitizeQuery("level:error OR level:warning (a:1 OR a:2)"), ).toThrow(ValidationError); }); @@ -390,7 +396,7 @@ describe("sanitizeQuery: edge cases", () => { test("rejects wildcard values in existing in-list during merge", () => { expect(() => sanitizeQuery("key:[*err*] OR key:val")).toThrow( - ValidationError + ValidationError, ); }); }); @@ -399,21 +405,21 @@ describe("normalizeQuery: pre-parse text normalization", () => { describe("mismatched brackets", () => { test("fixes wrong closing delimiter ) → ]", () => { expect(normalizeQuery("status_code:[401,403,429,500,)")).toBe( - "status_code:[401,403,429,500]" + "status_code:[401,403,429,500]", ); }); test("fixes trailing comma + wrong delimiter combined", () => { expect(normalizeQuery("error.http.status_code:[401,403,429,500,)")).toBe( - "error.http.status_code:[401,403,429,500]" + "error.http.status_code:[401,403,429,500]", ); }); test("repairs within a longer query", () => { expect( normalizeQuery( - "is:unresolved error.http.status_code:[401,403,429,500,)" - ) + "is:unresolved error.http.status_code:[401,403,429,500,)", + ), ).toBe("is:unresolved error.http.status_code:[401,403,429,500]"); }); }); @@ -421,13 +427,13 @@ describe("normalizeQuery: pre-parse text normalization", () => { describe("trailing list commas", () => { test("strips trailing comma in in-list filter", () => { expect(normalizeQuery("level:[error,warning,]")).toBe( - "level:[error,warning]" + "level:[error,warning]", ); }); test("strips trailing comma with spaces", () => { expect(normalizeQuery("level:[error, warning, ]")).toBe( - "level:[error, warning]" + "level:[error, warning]", ); }); }); @@ -435,13 +441,13 @@ describe("normalizeQuery: pre-parse text normalization", () => { describe("passthrough", () => { test("leaves valid queries unchanged", () => { expect(normalizeQuery("level:[error,warning]")).toBe( - "level:[error,warning]" + "level:[error,warning]", ); }); test("leaves non-list queries unchanged", () => { expect(normalizeQuery("is:unresolved level:error")).toBe( - "is:unresolved level:error" + "is:unresolved level:error", ); }); @@ -458,25 +464,25 @@ describe("normalizeQuery: pre-parse text normalization", () => { describe("quote awareness", () => { test("does not modify bracket content inside double quotes", () => { expect(normalizeQuery('message:"error [500,] found"')).toBe( - 'message:"error [500,] found"' + 'message:"error [500,] found"', ); }); test("does not modify mismatched brackets inside quotes", () => { expect(normalizeQuery('message:"codes [401,403,)"')).toBe( - 'message:"codes [401,403,)"' + 'message:"codes [401,403,)"', ); }); test("repairs unquoted filter but preserves quoted content", () => { expect( - normalizeQuery('level:[error,warning,) message:"[trailing,]"') + normalizeQuery('level:[error,warning,) message:"[trailing,]"'), ).toBe('level:[error,warning] message:"[trailing,]"'); }); test("handles multiple quoted regions", () => { expect(normalizeQuery('a:"[1,]" level:[x,) b:"[2,]"')).toBe( - 'a:"[1,]" level:[x] b:"[2,]"' + 'a:"[1,]" level:[x] b:"[2,]"', ); }); @@ -499,7 +505,7 @@ describe("transformUnquoted", () => { test("preserves quoted segments", () => { expect(transformUnquoted('hello "world" foo', upper)).toBe( - 'HELLO "world" FOO' + 'HELLO "world" FOO', ); }); @@ -530,10 +536,10 @@ describe("sanitizeQuery: normalization integration", () => { test("normalizes complex filter in longer query", () => { const result = sanitizeQuery( - "is:unresolved error.http.status_code:[401,403,429,500,)" + "is:unresolved error.http.status_code:[401,403,429,500,)", ); expect(result).toBe( - "is:unresolved error.http.status_code:[401,403,429,500]" + "is:unresolved error.http.status_code:[401,403,429,500]", ); }); diff --git a/packages/cli/test/lib/search-query.warn.test.ts b/packages/cli/test/lib/search-query.warn.test.ts index 85d879bfc..7abb4089f 100644 --- a/packages/cli/test/lib/search-query.warn.test.ts +++ b/packages/cli/test/lib/search-query.warn.test.ts @@ -39,7 +39,7 @@ describe("sanitizeQuery: rewrite warnings", () => { test("numeric project: plus OR warns once with the final in-list", () => { expect(sanitizeQuery("project:123 OR project:456")).toBe( - "project_id:[123,456]" + "project_id:[123,456]", ); const warns = runningQueries(); expect(warns).toHaveLength(1); @@ -52,7 +52,7 @@ describe("sanitizeQuery: rewrite warnings", () => { test("numeric project: plus AND warns once with the stripped query", () => { expect(sanitizeQuery("project:123 AND is:unresolved")).toBe( - "project_id:123 is:unresolved" + "project_id:123 is:unresolved", ); const warns = runningQueries(); expect(warns).toHaveLength(1); @@ -64,7 +64,7 @@ describe("sanitizeQuery: rewrite warnings", () => { test("OR-only still warns once with the in-list", () => { expect(sanitizeQuery("level:error OR level:warning")).toBe( - "level:[error,warning]" + "level:[error,warning]", ); const warns = runningQueries(); expect(warns).toHaveLength(1); @@ -89,7 +89,7 @@ describe("sanitizeQuery: rewrite warnings", () => { // happened, and the API 400 will quote project_id, so the user must // be told. expect(sanitizeQuery("project:123 ((( broken")).toBe( - "project_id:123 ((( broken" + "project_id:123 ((( broken", ); const warns = runningQueries(); expect(warns).toHaveLength(1); diff --git a/packages/cli/test/lib/security/auto-login-host-guard.test.ts b/packages/cli/test/lib/security/auto-login-host-guard.test.ts index a26ae9f51..401468e69 100644 --- a/packages/cli/test/lib/security/auto-login-host-guard.test.ts +++ b/packages/cli/test/lib/security/auto-login-host-guard.test.ts @@ -154,7 +154,7 @@ describe("auto-login host guard", () => { expect(msg).toBe( "Refusing to log in against https://sentry.example.com — --url was not provided.\n\n" + "To authenticate against this self-hosted instance, confirm the host explicitly:\n" + - " sentry auth login --url https://sentry.example.com" + " sentry auth login --url https://sentry.example.com", ); }); @@ -164,10 +164,10 @@ describe("auto-login host guard", () => { rcSource: "/repo/.sentryclirc", }); expect(msg).toContain( - "this URL was read from .sentryclirc (/repo/.sentryclirc) but hasn't been confirmed as trusted yet" + "this URL was read from .sentryclirc (/repo/.sentryclirc) but hasn't been confirmed as trusted yet", ); expect(msg).toContain( - " sentry auth login --url https://sentry.example.com --token " + " sentry auth login --url https://sentry.example.com --token ", ); }); }); diff --git a/packages/cli/test/lib/security/custom-headers-leak.test.ts b/packages/cli/test/lib/security/custom-headers-leak.test.ts index 8fc146594..37aa6ec1b 100644 --- a/packages/cli/test/lib/security/custom-headers-leak.test.ts +++ b/packages/cli/test/lib/security/custom-headers-leak.test.ts @@ -56,10 +56,10 @@ describe("CVE: custom-headers leak (share URL + auth-login bypass)", () => { let capturedHeaders: Headers | undefined; globalThis.fetch = (async ( input: RequestInfo | URL, - init?: RequestInit + init?: RequestInit, ) => { capturedHeaders = new Headers( - init?.headers ?? (input instanceof Request ? input.headers : undefined) + init?.headers ?? (input instanceof Request ? input.headers : undefined), ); return new Response("{}", { status: 404 }); }) as typeof fetch; @@ -68,7 +68,7 @@ describe("CVE: custom-headers leak (share URL + auth-login bypass)", () => { await getSharedIssue( "https://evil.com", "test-org", - "deadbeef12345678" + "deadbeef12345678", ).catch(() => { /* we only care about headers, not the response */ }); @@ -88,9 +88,8 @@ describe("CVE: custom-headers leak (share URL + auth-login bypass)", () => { process.env.SENTRY_CUSTOM_HEADERS = "X-IAP-Token: legit"; // `applyLoginUrl` (from login command) registers the trust anchor - const { applyLoginUrl } = await import( - "../../../src/commands/auth/login.js" - ); + const { applyLoginUrl } = + await import("../../../src/commands/auth/login.js"); applyLoginUrl("https://sentry.acme.com"); const headers = new Headers(); @@ -99,9 +98,8 @@ describe("CVE: custom-headers leak (share URL + auth-login bypass)", () => { expect(headers.get("X-IAP-Token")).toBe("legit"); // Cleanup the anchor so subsequent tests aren't affected - const { resetLoginTrustAnchorForTesting } = await import( - "../../../src/lib/token-host.js" - ); + const { resetLoginTrustAnchorForTesting } = + await import("../../../src/lib/token-host.js"); resetLoginTrustAnchorForTesting(); }); @@ -117,9 +115,8 @@ describe("CVE: custom-headers leak (share URL + auth-login bypass)", () => { process.env.SENTRY_URL = "https://evil.com"; // simulating rc shim write // Intentionally NOT calling applyLoginUrl — attacker flow doesn't - const { resetLoginTrustAnchorForTesting } = await import( - "../../../src/lib/token-host.js" - ); + const { resetLoginTrustAnchorForTesting } = + await import("../../../src/lib/token-host.js"); resetLoginTrustAnchorForTesting(); const headers = new Headers(); diff --git a/packages/cli/test/lib/security/fetch-layer-guard.test.ts b/packages/cli/test/lib/security/fetch-layer-guard.test.ts index 887860231..330d2cc23 100644 --- a/packages/cli/test/lib/security/fetch-layer-guard.test.ts +++ b/packages/cli/test/lib/security/fetch-layer-guard.test.ts @@ -49,7 +49,7 @@ describe("CVE defense-in-depth: fetch layer refuses mismatched hosts", () => { const headers = new Headers(); applyCustomHeaders( headers, - "https://evil.com/api/0/shared/issues/deadbeef/" + "https://evil.com/api/0/shared/issues/deadbeef/", ); expect(headers.get("X-IAP-Token")).toBeNull(); @@ -64,7 +64,7 @@ describe("CVE defense-in-depth: fetch layer refuses mismatched hosts", () => { const headers = new Headers(); applyCustomHeaders( headers, - "https://sentry.example.com/api/0/organizations/" + "https://sentry.example.com/api/0/organizations/", ); expect(headers.get("X-IAP-Token")).toBe("sensitive-iap-value"); diff --git a/packages/cli/test/lib/security/login-token-rc-poison.test.ts b/packages/cli/test/lib/security/login-token-rc-poison.test.ts index 0168bfab4..325427ff8 100644 --- a/packages/cli/test/lib/security/login-token-rc-poison.test.ts +++ b/packages/cli/test/lib/security/login-token-rc-poison.test.ts @@ -95,10 +95,10 @@ describe("CVE: auth login --token with rc-poisoned env.SENTRY_URL", () => { originalFetch = globalThis.fetch; globalThis.fetch = (async ( input: RequestInfo | URL, - init?: RequestInit + init?: RequestInit, ) => { const headers = new Headers( - init?.headers ?? (input instanceof Request ? input.headers : undefined) + init?.headers ?? (input instanceof Request ? input.headers : undefined), ); fetchCalls.push({ url: extractFetchUrl(input), @@ -128,12 +128,12 @@ describe("CVE: auth login --token with rc-poisoned env.SENTRY_URL", () => { token: "user-saas-api-token-secret", force: false, timeout: 900, - }) + }), ).rejects.toBeInstanceOf(HostScopeError); // Critical: the user's token NEVER hit the wire. const leaked = fetchCalls.filter((c) => - c.authorization?.includes("user-saas-api-token-secret") + c.authorization?.includes("user-saas-api-token-secret"), ); expect(leaked).toEqual([]); // And no requests to the attacker at all @@ -155,7 +155,7 @@ describe("CVE: auth login --token with rc-poisoned env.SENTRY_URL", () => { const context = createContext(); await expect( - func.call(context, { force: false, timeout: 900 }) + func.call(context, { force: false, timeout: 900 }), ).rejects.toBeInstanceOf(HostScopeError); const toEvil = fetchCalls.filter((c) => urlHostnameIn(c.url, ["evil.com"])); @@ -182,13 +182,13 @@ describe("CVE: auth login --token with rc-poisoned env.SENTRY_URL", () => { url: "https://sentry.example.com", force: false, timeout: 900, - }) + }), ).rejects.toThrow(); // our fetch mock throws const toEvil = fetchCalls.filter((c) => urlHostnameIn(c.url, ["evil.com"])); expect(toEvil).toEqual([]); const toIntended = fetchCalls.filter((c) => - urlHostnameIn(c.url, ["sentry.example.com"]) + urlHostnameIn(c.url, ["sentry.example.com"]), ); expect(toIntended.length).toBeGreaterThan(0); }); @@ -209,7 +209,7 @@ describe("CVE: auth login --token with rc-poisoned env.SENTRY_URL", () => { token: "legit-token", force: false, timeout: 900, - }) + }), ).rejects.toBeInstanceOf(HostScopeError); // No network I/O attempted. @@ -223,9 +223,8 @@ describe("CVE: auth login --token with rc-poisoned env.SENTRY_URL", () => { // hostA's anchor as a free pass — the refusal guard checks anchor↔host // match, not anchor existence. captureEnvTokenHost(); - const { registerLoginTrustAnchor } = await import( - "../../../src/lib/token-host.js" - ); + const { registerLoginTrustAnchor } = + await import("../../../src/lib/token-host.js"); registerLoginTrustAnchor("https://sentry.hosta.com"); process.env.SENTRY_URL = "https://evil.com"; @@ -238,7 +237,7 @@ describe("CVE: auth login --token with rc-poisoned env.SENTRY_URL", () => { token: "user-saas-api-token-secret", force: false, timeout: 900, - }) + }), ).rejects.toBeInstanceOf(HostScopeError); const toEvil = fetchCalls.filter((c) => urlHostnameIn(c.url, ["evil.com"])); diff --git a/packages/cli/test/lib/security/refresh-token-poison.test.ts b/packages/cli/test/lib/security/refresh-token-poison.test.ts index d2203a1c7..2c03b2e77 100644 --- a/packages/cli/test/lib/security/refresh-token-poison.test.ts +++ b/packages/cli/test/lib/security/refresh-token-poison.test.ts @@ -59,7 +59,7 @@ describe("CVE defense-in-depth: refresh token", () => { process.env.SENTRY_URL = "https://evil.com"; await expect( - refreshAccessToken("fake-refresh-token", { credentialHost }) + refreshAccessToken("fake-refresh-token", { credentialHost }), ).rejects.toThrow(/unexpected fetch|Cannot connect|fetch failed/); // The captured host remains the only destination; evil.com sees nothing. @@ -80,7 +80,7 @@ describe("CVE defense-in-depth: refresh token", () => { await expect( refreshAccessToken("fake-refresh-token", { credentialHost: getEnvTokenHost(), - }) + }), ).rejects.toThrow(/unexpected fetch|Cannot connect|fetch failed/); // A request was attempted, and it went to the correct host @@ -95,18 +95,21 @@ describe("CVE defense-in-depth: refresh token", () => { new Error("unable to verify the first certificate"), "TLS certificate error connecting to", ], - ])("%s refresh failure names the credential host", async (_, failure, prefix) => { - delete process.env.SENTRY_HOST; - delete process.env.SENTRY_URL; - const credentialHost = "https://sentry.example.com:8443"; - globalThis.fetch = (async (input: RequestInfo | URL) => { - fetchCalls.push(extractFetchUrl(input)); - throw failure; - }) as typeof fetch; + ])( + "%s refresh failure names the credential host", + async (_, failure, prefix) => { + delete process.env.SENTRY_HOST; + delete process.env.SENTRY_URL; + const credentialHost = "https://sentry.example.com:8443"; + globalThis.fetch = (async (input: RequestInfo | URL) => { + fetchCalls.push(extractFetchUrl(input)); + throw failure; + }) as typeof fetch; - await expect( - refreshAccessToken("fake-refresh-token", { credentialHost }) - ).rejects.toThrow(`${prefix} ${credentialHost}`); - expect(fetchCalls).toEqual([`${credentialHost}/oauth/token/`]); - }); + await expect( + refreshAccessToken("fake-refresh-token", { credentialHost }), + ).rejects.toThrow(`${prefix} ${credentialHost}`); + expect(fetchCalls).toEqual([`${credentialHost}/oauth/token/`]); + }, + ); }); diff --git a/packages/cli/test/lib/security/sentryclirc-url-poison.test.ts b/packages/cli/test/lib/security/sentryclirc-url-poison.test.ts index 0a6e61e8b..558edcd4e 100644 --- a/packages/cli/test/lib/security/sentryclirc-url-poison.test.ts +++ b/packages/cli/test/lib/security/sentryclirc-url-poison.test.ts @@ -88,7 +88,7 @@ describe("CVE: .sentryclirc URL credential exfiltration", () => { // Critical: assertRcUrlTrusted must throw, blocking any credentialed // request via buildCommand's wrapper. await expect(assertRcUrlTrusted(testDir)).rejects.toThrow( - /does not match|sentry auth login --url/ + /does not match|sentry auth login --url/, ); }); @@ -105,7 +105,7 @@ describe("CVE: .sentryclirc URL credential exfiltration", () => { await applySentryCliRcEnvShim(testDir); await expect(assertRcUrlTrusted(testDir)).rejects.toThrow( - /does not match|sentry auth login --url/ + /does not match|sentry auth login --url/, ); }); diff --git a/packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts b/packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts index c602aaa30..50d76f63c 100644 --- a/packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts +++ b/packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts @@ -39,9 +39,8 @@ describe("CVE defense-in-depth: sntrys_ claim vs request mismatch", () => { // Clear the GET response cache between tests so a cached 200 from // a prior test's identical URL doesn't short-circuit the fetch // wrapper (which would leave `fetchCalls` empty). - const { clearResponseCache } = await import( - "../../../src/lib/response-cache.js" - ); + const { clearResponseCache } = + await import("../../../src/lib/response-cache.js"); resetAuthTokenCache(); resetAuthRowCache(); resetIdentityFingerprintCache(); @@ -51,10 +50,10 @@ describe("CVE defense-in-depth: sntrys_ claim vs request mismatch", () => { originalFetch = globalThis.fetch; globalThis.fetch = (async ( input: RequestInfo | URL, - init?: RequestInit + init?: RequestInit, ) => { const headers = new Headers( - init?.headers ?? (input instanceof Request ? input.headers : undefined) + init?.headers ?? (input instanceof Request ? input.headers : undefined), ); fetchCalls.push({ url: extractFetchUrl(input), @@ -78,24 +77,22 @@ describe("CVE defense-in-depth: sntrys_ claim vs request mismatch", () => { org: "x", }); process.env.SENTRY_HOST = "https://sentry.secondhost.com"; - const { captureEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); // Direct request to sentry.secondhost.com (matches env scope but NOT the claim). - const { apiRequestToRegion } = await import( - "../../../src/lib/api/infrastructure.js" - ); + const { apiRequestToRegion } = + await import("../../../src/lib/api/infrastructure.js"); await expect( apiRequestToRegion("https://sentry.secondhost.com", "/organizations/", { method: "GET", - }) + }), ).rejects.toThrow(/embedded claim|sentry\.firsthost\.com/i); // Token never hit the wire. const leaked = fetchCalls.filter((c) => - c.auth?.includes("secret-tail-for-test") + c.auth?.includes("secret-tail-for-test"), ); expect(leaked).toEqual([]); }); @@ -107,15 +104,13 @@ describe("CVE defense-in-depth: sntrys_ claim vs request mismatch", () => { org: "x", }); process.env.SENTRY_HOST = "https://sentry.acme.com"; - const { captureEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); // Request to the host the claim agrees with → bearer attaches. - const { apiRequestToRegion } = await import( - "../../../src/lib/api/infrastructure.js" - ); + const { apiRequestToRegion } = + await import("../../../src/lib/api/infrastructure.js"); await apiRequestToRegion("https://sentry.acme.com", "/organizations/", { method: "GET", }); @@ -137,23 +132,20 @@ describe("CVE defense-in-depth: sntrys_ claim vs request mismatch", () => { org: "x", }); process.env.SENTRY_HOST = "https://sentry.acme.com"; - const { captureEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); // Simulate: control silo's /users/me/regions/ told us about a // regional silo at https://us.sentry.acme.com. - const { registerTrustedRegionUrls } = await import( - "../../../src/lib/db/regions.js" - ); + const { registerTrustedRegionUrls } = + await import("../../../src/lib/db/regions.js"); registerTrustedRegionUrls(["https://us.sentry.acme.com"]); // Request to the regional silo (NOT the claim's url) must succeed // because the region URL is part of the same trust class. - const { apiRequestToRegion } = await import( - "../../../src/lib/api/infrastructure.js" - ); + const { apiRequestToRegion } = + await import("../../../src/lib/api/infrastructure.js"); await apiRequestToRegion("https://us.sentry.acme.com", "/organizations/", { method: "GET", }); @@ -169,14 +161,12 @@ describe("CVE defense-in-depth: sntrys_ claim vs request mismatch", () => { // no-op. The existing host-scoping check is the only enforcement. process.env.SENTRY_AUTH_TOKEN = "sntryu_opaqueusertoken1234567890abcdef"; process.env.SENTRY_HOST = "https://sentry.acme.com"; - const { captureEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); - const { apiRequestToRegion } = await import( - "../../../src/lib/api/infrastructure.js" - ); + const { apiRequestToRegion } = + await import("../../../src/lib/api/infrastructure.js"); await apiRequestToRegion("https://sentry.acme.com", "/organizations/", { method: "GET", }); @@ -207,9 +197,8 @@ describe("UX path: env-token-host falls back to sntrys_ claim url", () => { }); // No SENTRY_HOST, no SENTRY_URL set. - const { captureEnvTokenHost, getEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost, getEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); expect(getEnvTokenHost()).toBe("https://sentry.selfhosted.example.com"); @@ -227,9 +216,8 @@ describe("UX path: env-token-host falls back to sntrys_ claim url", () => { }); process.env.SENTRY_HOST = "https://sentry.secondhost.com"; - const { captureEnvTokenHost, getEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost, getEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); expect(getEnvTokenHost()).toBe("https://sentry.firsthost.com"); @@ -239,9 +227,8 @@ describe("UX path: env-token-host falls back to sntrys_ claim url", () => { process.env.SENTRY_AUTH_TOKEN = "sntryu_opaque-user-token"; // No SENTRY_HOST. - const { captureEnvTokenHost, getEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost, getEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); expect(getEnvTokenHost()).toBe("https://sentry.io"); @@ -260,9 +247,8 @@ describe("UX path: env-token-host falls back to sntrys_ claim url", () => { org: "victim", }); - const { captureEnvTokenHost, getEnvTokenHost } = await import( - "../../../src/lib/env-token-host.js" - ); + const { captureEnvTokenHost, getEnvTokenHost } = + await import("../../../src/lib/env-token-host.js"); captureEnvTokenHost(); // Yes, the snapshot is evil.com — because that's what the user's diff --git a/packages/cli/test/lib/security/url-arg-poison.test.ts b/packages/cli/test/lib/security/url-arg-poison.test.ts index c8f5d5046..3e117f56f 100644 --- a/packages/cli/test/lib/security/url-arg-poison.test.ts +++ b/packages/cli/test/lib/security/url-arg-poison.test.ts @@ -31,7 +31,7 @@ describe("CVE: URL argument credential exfiltration", () => { test("parseIssueArg with attacker URL throws before env is poisoned (SaaS-scoped token)", () => { // preload sets SENTRY_AUTH_TOKEN; env-token defaults to SaaS. expect(() => - parseIssueArg("https://evil.com/organizations/target-org/issues/12345/") + parseIssueArg("https://evil.com/organizations/target-org/issues/12345/"), ).toThrow(/does not match|sentry auth login --url/); // Env untouched — no routing poison. @@ -42,8 +42,8 @@ describe("CVE: URL argument credential exfiltration", () => { test("parseOrgProjectArg with attacker URL throws before env is poisoned", () => { expect(() => parseOrgProjectArg( - "https://evil.com/organizations/target-org/issues/12345/" - ) + "https://evil.com/organizations/target-org/issues/12345/", + ), ).toThrow(/does not match|sentry auth login --url/); expect(process.env.SENTRY_HOST).toBeUndefined(); @@ -54,7 +54,7 @@ describe("CVE: URL argument credential exfiltration", () => { expect(() => parsePositionalArgs([ "https://evil.com/organizations/acme/issues/999/events/deadbeef/", - ]) + ]), ).toThrow(/does not match|sentry auth login --url/); expect(process.env.SENTRY_HOST).toBeUndefined(); @@ -66,8 +66,8 @@ describe("CVE: URL argument credential exfiltration", () => { // applySentryUrlContext, which throws before getSharedIssue is invoked. expect(() => parseIssueArg( - "https://evil.com/share/issue/deadbeef12345678deadbeef12345678/" - ) + "https://evil.com/share/issue/deadbeef12345678deadbeef12345678/", + ), ).toThrow(/does not match|sentry auth login --url/); expect(process.env.SENTRY_HOST).toBeUndefined(); @@ -90,7 +90,7 @@ describe("CVE: URL argument credential exfiltration", () => { process.env.SENTRY_HOST = "https://sentry.example.com"; resetEnvTokenHostForTesting(); parseOrgProjectArg( - "https://sentry.example.com/organizations/acme/issues/1/" + "https://sentry.example.com/organizations/acme/issues/1/", ); expect(process.env.SENTRY_HOST).toBe("https://sentry.example.com"); expect(process.env.SENTRY_URL).toBe("https://sentry.example.com"); diff --git a/packages/cli/test/lib/seer-trial.test.ts b/packages/cli/test/lib/seer-trial.test.ts index 90f898769..29728c305 100644 --- a/packages/cli/test/lib/seer-trial.test.ts +++ b/packages/cli/test/lib/seer-trial.test.ts @@ -9,10 +9,10 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../src/lib/api-client.js"; import { SeerError } from "../../src/lib/errors.js"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as loggerModule from "../../src/lib/logger.js"; import { isTrialEligible, @@ -104,7 +104,7 @@ describe("promptAndStartTrial", () => { loggerWithTagSpy = vi .spyOn(loggerModule.logger, "withTag") .mockReturnValue( - mockLogInstance as ReturnType + mockLogInstance as ReturnType, ); }); @@ -125,10 +125,10 @@ describe("promptAndStartTrial", () => { expect(loggerPromptSpy).not.toHaveBeenCalled(); // Should show expired/upgrade message expect( - logInfoCalls.some((m) => m.includes("No Seer trial available")) + logInfoCalls.some((m) => m.includes("No Seer trial available")), ).toBe(true); expect(logInfoCalls.some((m) => m.includes("upgrading your plan"))).toBe( - true + true, ); }); @@ -171,7 +171,7 @@ describe("promptAndStartTrial", () => { expect(result).toBe(false); expect(logInfoCalls.some((m) => m.includes("run out of Seer quota"))).toBe( - true + true, ); expect(startProductTrialSpy).not.toHaveBeenCalled(); }); @@ -197,10 +197,10 @@ describe("promptAndStartTrial", () => { expect(result).toBe(true); expect(startProductTrialSpy).toHaveBeenCalledWith("test-org", "seerUsers"); expect(logInfoCalls.some((m) => m.includes("Starting Seer trial"))).toBe( - true + true, ); expect( - logSuccessCalls.some((m) => m.includes("Seer trial activated")) + logSuccessCalls.some((m) => m.includes("Seer trial activated")), ).toBe(true); }); @@ -230,7 +230,7 @@ describe("promptAndStartTrial", () => { expect(result).toBe(true); expect(startProductTrialSpy).toHaveBeenCalledWith( "test-org", - "seerAutofix" + "seerAutofix", ); }); @@ -243,11 +243,11 @@ describe("promptAndStartTrial", () => { expect(result).toBe(false); expect(logWarnCalls.some((m) => m.includes("Failed to start trial"))).toBe( - true + true, ); // Should include a link to billing/settings expect( - logWarnCalls.some((m) => m.includes("settings/billing/overview")) + logWarnCalls.some((m) => m.includes("settings/billing/overview")), ).toBe(true); // Should mention support contact expect(logWarnCalls.some((m) => m.includes("support@sentry"))).toBe(true); @@ -260,7 +260,7 @@ describe("promptAndStartTrial", () => { await promptAndStartTrial("test-org", "not_enabled"); expect( - logInfoCalls.some((m) => m.includes("not enabled for your organization")) + logInfoCalls.some((m) => m.includes("not enabled for your organization")), ).toBe(true); }); @@ -271,7 +271,7 @@ describe("promptAndStartTrial", () => { await promptAndStartTrial("test-org", "no_budget"); expect(logInfoCalls.some((m) => m.includes("run out of Seer quota"))).toBe( - true + true, ); }); diff --git a/packages/cli/test/lib/sentry-client-refresh-cache.focused.test.ts b/packages/cli/test/lib/sentry-client-refresh-cache.focused.test.ts index 34df4af64..e2e4fec43 100644 --- a/packages/cli/test/lib/sentry-client-refresh-cache.focused.test.ts +++ b/packages/cli/test/lib/sentry-client-refresh-cache.focused.test.ts @@ -65,9 +65,9 @@ test("401 refresh caches GET under the bearer actually sent on retry", async () "cache-control": "private, max-age=60", vary: "authorization", }, - } + }, ); - } + }, ); const response = await getSdkConfig("https://sentry.io").fetch(url); expect(await response.json()).toEqual({ id: "refresh-cache-focused" }); @@ -77,7 +77,7 @@ test("401 refresh caches GET under the bearer actually sent on retry", async () "GET", url, { authorization: "Bearer new-access" }, - getIdentityFingerprint() + getIdentityFingerprint(), ); expect(cached).toBeDefined(); }); diff --git a/packages/cli/test/lib/sentry-client.auth.test.ts b/packages/cli/test/lib/sentry-client.auth.test.ts index ad50c18e0..10f248283 100644 --- a/packages/cli/test/lib/sentry-client.auth.test.ts +++ b/packages/cli/test/lib/sentry-client.auth.test.ts @@ -39,10 +39,10 @@ const ORG_TOKEN = mintSntrysToken({ }); const MALFORMED_TOKEN = ORG_TOKEN.replace( "test-secret-tail", - "te\nst-secret-tail" + "te\nst-secret-tail", ); const EDGE_CONTROLS = `${Array.from({ length: 32 }, (_, code) => - String.fromCharCode(code) + String.fromCharCode(code), ).join("")}\x7f`; describe("authenticated fetch bearer validation", () => { @@ -60,7 +60,7 @@ describe("authenticated fetch bearer validation", () => { /** Mock responses while recording the actual request URL and Authorization. */ function mockResponses( - respond: (url: string, authorization: string | null) => Response + respond: (url: string, authorization: string | null) => Response, ): typeof fetch { return mockFetch((input, init) => { const url = extractFetchUrl(input); @@ -95,32 +95,32 @@ describe("authenticated fetch bearer validation", () => { getDatabase().query("UPDATE auth SET token = ? WHERE id = 1").run(token); } - test.each([ - ...ENV_TOKEN_KEYS, - "stored", - ])("rejects an internal LF from %s before any request or auth fallback", async (source) => { - if (source === "stored") { - storeLegacyToken(MALFORMED_TOKEN); - } else { - process.env[source] = MALFORMED_TOKEN; - } - - const error = await withAuthGuard(request).catch( - (caught: unknown) => caught - ); - expect(error).toBeInstanceOf(AuthError); - expect(error).toBeInstanceOf(MalformedAuthTokenError); - const authError = error as AuthError; - expect(authError.reason).toBe("invalid"); - expect(authError.exitCode).toBe(EXIT.AUTH_INVALID); - expect(authError.message).toContain("single line"); - expect(authError.cause).toBeUndefined(); - expect(`${authError.message}\n${authError.stack}`).not.toContain( - MALFORMED_TOKEN - ); - expect(shouldAutoAuth(authError, () => true)).toBe(false); - expect(requests).toEqual([]); - }); + test.each([...ENV_TOKEN_KEYS, "stored"])( + "rejects an internal LF from %s before any request or auth fallback", + async (source) => { + if (source === "stored") { + storeLegacyToken(MALFORMED_TOKEN); + } else { + process.env[source] = MALFORMED_TOKEN; + } + + const error = await withAuthGuard(request).catch( + (caught: unknown) => caught, + ); + expect(error).toBeInstanceOf(AuthError); + expect(error).toBeInstanceOf(MalformedAuthTokenError); + const authError = error as AuthError; + expect(authError.reason).toBe("invalid"); + expect(authError.exitCode).toBe(EXIT.AUTH_INVALID); + expect(authError.message).toContain("single line"); + expect(authError.cause).toBeUndefined(); + expect(`${authError.message}\n${authError.stack}`).not.toContain( + MALFORMED_TOKEN, + ); + expect(shouldAutoAuth(authError, () => true)).toBe(false); + expect(requests).toEqual([]); + }, + ); test.each([ ["CR", "\r"], @@ -165,33 +165,37 @@ describe("authenticated fetch bearer validation", () => { expect(requests[0]?.authorization).toBe("Bearer synthetic-token"); }); - test.each( - ENV_TOKEN_KEYS - )("trims surrounding C0, DEL and whitespace from SDK %s", async (key) => { - // An isolated SDK environment preserves NULs that process.env cannot. - setEnv({ - ...process.env, - [key]: `${EDGE_CONTROLS}\u00a0synthetic-token\ufeff${EDGE_CONTROLS}`, - }); + test.each(ENV_TOKEN_KEYS)( + "trims surrounding C0, DEL and whitespace from SDK %s", + async (key) => { + // An isolated SDK environment preserves NULs that process.env cannot. + setEnv({ + ...process.env, + [key]: `${EDGE_CONTROLS}\u00a0synthetic-token\ufeff${EDGE_CONTROLS}`, + }); - await request(); + await request(); - expect(requests).toEqual([ - { url: RESOURCE_URL, authorization: "Bearer synthetic-token" }, - ]); - }); + expect(requests).toEqual([ + { url: RESOURCE_URL, authorization: "Bearer synthetic-token" }, + ]); + }, + ); test.each([ " stored-token ", "\t\n\u00a0stored-token\r\n", "\x1f\u00a0stored-token\ufeff\x7f", - ])("trims surrounding controls and whitespace from legacy credentials %#", async (token) => { - storeLegacyToken(token); - await request(); - expect(requests).toEqual([ - { url: RESOURCE_URL, authorization: "Bearer stored-token" }, - ]); - }); + ])( + "trims surrounding controls and whitespace from legacy credentials %#", + async (token) => { + storeLegacyToken(token); + await request(); + expect(requests).toEqual([ + { url: RESOURCE_URL, authorization: "Bearer stored-token" }, + ]); + }, + ); test("rejects a whitespace-only stored credential", async () => { storeLegacyToken(" \t\r\n "); @@ -216,8 +220,8 @@ describe("authenticated fetch bearer validation", () => { "Cache-Control": "private, max-age=300", Vary: "Authorization", }, - } - ) + }, + ), ); expect(await (await request()).json()).toEqual({ source: "cache" }); @@ -234,8 +238,8 @@ describe("authenticated fetch bearer validation", () => { "Cache-Control": "private, max-age=300", Vary: "Authorization", }, - } - ) + }, + ), ); await request(); @@ -265,14 +269,15 @@ describe("authenticated fetch bearer validation", () => { expect(requests).toEqual([]); }); - test.each( - ENV_TOKEN_KEYS - )("ignores a malformed %s when stored OAuth takes precedence", async (key) => { - process.env[key] = MALFORMED_TOKEN; - setAuthToken("stored-token"); - await request(); - expect(requests[0]?.authorization).toBe("Bearer stored-token"); - }); + test.each(ENV_TOKEN_KEYS)( + "ignores a malformed %s when stored OAuth takes precedence", + async (key) => { + process.env[key] = MALFORMED_TOKEN; + setAuthToken("stored-token"); + await request(); + expect(requests[0]?.authorization).toBe("Bearer stored-token"); + }, + ); test("rejects forced malformed env credentials instead of using stored OAuth", async () => { process.env.SENTRY_AUTH_TOKEN = MALFORMED_TOKEN; @@ -289,28 +294,28 @@ describe("authenticated fetch bearer validation", () => { { key: "SENTRY_AUTH_TOKEN", forceEnv: false }, { key: "SENTRY_AUTH_TOKEN", forceEnv: true }, { key: "SENTRY_TOKEN", forceEnv: true }, - ])("rejects control-only $key without fallback (force-env=$forceEnv)", async ({ - key, - forceEnv, - }) => { - if (forceEnv) { - setAuthToken("stored-token"); - } - setEnv({ - ...process.env, - SENTRY_AUTH_TOKEN: undefined, - SENTRY_TOKEN: "alias-token", - SENTRY_FORCE_ENV_TOKEN: forceEnv ? "1" : undefined, - [key]: "\0\x01\x7f", - }); + ])( + "rejects control-only $key without fallback (force-env=$forceEnv)", + async ({ key, forceEnv }) => { + if (forceEnv) { + setAuthToken("stored-token"); + } + setEnv({ + ...process.env, + SENTRY_AUTH_TOKEN: undefined, + SENTRY_TOKEN: "alias-token", + SENTRY_FORCE_ENV_TOKEN: forceEnv ? "1" : undefined, + [key]: "\0\x01\x7f", + }); - await expect(request()).rejects.toMatchObject({ - name: "MalformedAuthTokenError", - reason: "invalid", - exitCode: EXIT.AUTH_INVALID, - }); - expect(requests).toEqual([]); - }); + await expect(request()).rejects.toMatchObject({ + name: "MalformedAuthTokenError", + reason: "invalid", + exitCode: EXIT.AUTH_INVALID, + }); + expect(requests).toEqual([]); + }, + ); test.each([ "refreshed-token", @@ -347,74 +352,77 @@ describe("authenticated fetch bearer validation", () => { " \t\n ", "opaque-\0-token", "opaque-\u0100-token", - ])("rejects malformed refreshed credentials without retrying the request %#", async (token) => { - process.env.SENTRY_CLIENT_ID = "synthetic-client-id"; - setAuthToken("stored-token", 3600, "synthetic-refresh-token"); - globalThis.fetch = mockResponses((url) => { - if (url.endsWith("/oauth/token/")) { - return Response.json({ - access_token: token, - token_type: "bearer", - expires_in: 3600, - refresh_token: "synthetic-refresh-token", + ])( + "rejects malformed refreshed credentials without retrying the request %#", + async (token) => { + process.env.SENTRY_CLIENT_ID = "synthetic-client-id"; + setAuthToken("stored-token", 3600, "synthetic-refresh-token"); + globalThis.fetch = mockResponses((url) => { + if (url.endsWith("/oauth/token/")) { + return Response.json({ + access_token: token, + token_type: "bearer", + expires_in: 3600, + refresh_token: "synthetic-refresh-token", + }); + } + return new Response("{}", { status: 401 }); + }); + + for (let attempt = 0; attempt < 2; attempt++) { + const error = await request().catch((caught: unknown) => caught); + expect(error).toBeInstanceOf(MalformedAuthTokenError); + expect(error).toMatchObject({ + reason: "invalid", + exitCode: EXIT.AUTH_INVALID, + }); + expect((error as Error).cause).toBeUndefined(); + if (token) { + expect(String(error)).not.toContain(token); + } + expect(getAuthConfig()).toMatchObject({ + token: "stored-token", + refreshToken: "synthetic-refresh-token", }); } - return new Response("{}", { status: 401 }); - }); - - for (let attempt = 0; attempt < 2; attempt++) { - const error = await request().catch((caught: unknown) => caught); - expect(error).toBeInstanceOf(MalformedAuthTokenError); - expect(error).toMatchObject({ - reason: "invalid", - exitCode: EXIT.AUTH_INVALID, - }); - expect((error as Error).cause).toBeUndefined(); - if (token) { - expect(String(error)).not.toContain(token); + const refreshAttempt = [ + { url: RESOURCE_URL, authorization: "Bearer stored-token" }, + { url: "https://sentry.io/oauth/token/", authorization: null }, + ]; + expect(requests).toEqual([...refreshAttempt, ...refreshAttempt]); + }, + ); + + test.each(["none", ...ENV_TOKEN_KEYS])( + "normalizes a proactive refresh with malformed env source %s", + async (source) => { + process.env.SENTRY_CLIENT_ID = "synthetic-client-id"; + if (source !== "none") { + process.env[source] = MALFORMED_TOKEN; } + setAuthToken("expired-token", -1, "synthetic-refresh-token"); + globalThis.fetch = mockResponses((url) => + url.endsWith("/oauth/token/") + ? Response.json({ + access_token: "\x1f \nrefreshed-token\r\t\x7f", + token_type: "bearer", + expires_in: 3600, + refresh_token: "replacement-refresh-token", + }) + : Response.json({}), + ); + + expect((await request()).status).toBe(200); expect(getAuthConfig()).toMatchObject({ - token: "stored-token", - refreshToken: "synthetic-refresh-token", + token: "refreshed-token", + refreshToken: "replacement-refresh-token", }); - } - const refreshAttempt = [ - { url: RESOURCE_URL, authorization: "Bearer stored-token" }, - { url: "https://sentry.io/oauth/token/", authorization: null }, - ]; - expect(requests).toEqual([...refreshAttempt, ...refreshAttempt]); - }); - - test.each([ - "none", - ...ENV_TOKEN_KEYS, - ])("normalizes a proactive refresh with malformed env source %s", async (source) => { - process.env.SENTRY_CLIENT_ID = "synthetic-client-id"; - if (source !== "none") { - process.env[source] = MALFORMED_TOKEN; - } - setAuthToken("expired-token", -1, "synthetic-refresh-token"); - globalThis.fetch = mockResponses((url) => - url.endsWith("/oauth/token/") - ? Response.json({ - access_token: "\x1f \nrefreshed-token\r\t\x7f", - token_type: "bearer", - expires_in: 3600, - refresh_token: "replacement-refresh-token", - }) - : Response.json({}) - ); - - expect((await request()).status).toBe(200); - expect(getAuthConfig()).toMatchObject({ - token: "refreshed-token", - refreshToken: "replacement-refresh-token", - }); - expect(requests).toEqual([ - { url: "https://sentry.io/oauth/token/", authorization: null }, - { url: RESOURCE_URL, authorization: "Bearer refreshed-token" }, - ]); - }); + expect(requests).toEqual([ + { url: "https://sentry.io/oauth/token/", authorization: null }, + { url: RESOURCE_URL, authorization: "Bearer refreshed-token" }, + ]); + }, + ); test("rejects malformed proactive refresh before storing or using the token", async () => { process.env.SENTRY_CLIENT_ID = "synthetic-client-id"; @@ -425,7 +433,7 @@ describe("authenticated fetch bearer validation", () => { token_type: "bearer", expires_in: 3600, refresh_token: "replacement-refresh-token", - }) + }), ); await expect(request()).rejects.toMatchObject({ diff --git a/packages/cli/test/lib/sentry-client.base-url.test.ts b/packages/cli/test/lib/sentry-client.base-url.test.ts index 37a34a733..3ce7894d8 100644 --- a/packages/cli/test/lib/sentry-client.base-url.test.ts +++ b/packages/cli/test/lib/sentry-client.base-url.test.ts @@ -56,20 +56,20 @@ describe("API base URL for org-auth credentials", () => { name: "Claim Host Region Org", links: { regionUrl: "/" }, }), - { status: 200, headers: { "Content-Type": "application/json" } } + { status: 200, headers: { "Content-Type": "application/json" } }, ); }); try { await expect(resolveOrgRegion("claim-host-region-org")).resolves.toBe( - claimHost + claimHost, ); expect(requests).toHaveLength(1); expect(requests[0]?.url).toBe( - `${claimHost}/api/0/organizations/claim-host-region-org/` + `${claimHost}/api/0/organizations/claim-host-region-org/`, ); expect(requests[0]?.headers.get("authorization")).toBe( - `Bearer ${orgAuthToken}` + `Bearer ${orgAuthToken}`, ); } finally { globalThis.fetch = originalFetch; @@ -104,13 +104,13 @@ describe("API base URL for org-auth credentials", () => { process.env.SENTRY_AUTH_TOKEN = orgAuthToken; const db = getDatabase(); db.query( - "INSERT OR REPLACE INTO auth (id, token, refresh_token, host, updated_at) VALUES (1, 'stored-token', 'refresh-token', NULL, ?)" + "INSERT OR REPLACE INTO auth (id, token, refresh_token, host, updated_at) VALUES (1, 'stored-token', 'refresh-token', NULL, ?)", ).run(Date.now()); expect(getApiBaseUrl()).toBe(DEFAULT_SENTRY_URL); expect( (db.query("SELECT host FROM auth WHERE id = 1").get() as { host: string }) - .host + .host, ).toBe(DEFAULT_SENTRY_URL); }); @@ -131,13 +131,13 @@ describe("API base URL for org-auth credentials", () => { }); const db = getDatabase(); db.query( - "INSERT OR REPLACE INTO auth (id, token, refresh_token, host, updated_at) VALUES (1, 'stored-token', 'refresh-token', NULL, ?)" + "INSERT OR REPLACE INTO auth (id, token, refresh_token, host, updated_at) VALUES (1, 'stored-token', 'refresh-token', NULL, ?)", ).run(Date.now()); expect(getApiBaseUrl()).toBe(DEFAULT_SENTRY_URL); expect( (db.query("SELECT host FROM auth WHERE id = 1").get() as { host: string }) - .host + .host, ).toBe(DEFAULT_SENTRY_URL); }); diff --git a/packages/cli/test/lib/sentry-client.invalidation.test.ts b/packages/cli/test/lib/sentry-client.invalidation.test.ts index 342bbad69..80da1f885 100644 --- a/packages/cli/test/lib/sentry-client.invalidation.test.ts +++ b/packages/cli/test/lib/sentry-client.invalidation.test.ts @@ -32,7 +32,7 @@ function makeResponse(body: unknown, status = 200): Response { let originalFetch: typeof globalThis.fetch; type FetchHandler = ( input: Request | string | URL, - init?: RequestInit + init?: RequestInit, ) => Promise; beforeEach(() => { @@ -71,13 +71,13 @@ describe("HTTP-layer auto-invalidation", () => { "GET", DETAIL_URL, {}, - makeResponse({ id: "12345" }) + makeResponse({ id: "12345" }), ); await storeCachedResponse( "GET", `${LIST_URL}?cursor=abc`, {}, - makeResponse({ data: [] }) + makeResponse({ data: [] }), ); installMockFetch(async (input, init) => { @@ -92,7 +92,7 @@ describe("HTTP-layer auto-invalidation", () => { // already cleared when the caller sees the response. expect(await getCachedResponse("GET", DETAIL_URL, {})).toBeUndefined(); expect( - await getCachedResponse("GET", `${LIST_URL}?cursor=abc`, {}) + await getCachedResponse("GET", `${LIST_URL}?cursor=abc`, {}), ).toBeUndefined(); }); @@ -101,7 +101,7 @@ describe("HTTP-layer auto-invalidation", () => { "GET", DETAIL_URL, {}, - makeResponse({ id: "12345" }) + makeResponse({ id: "12345" }), ); installMockFetch(async () => makeResponse({ error: "denied" }, 403)); @@ -115,13 +115,13 @@ describe("HTTP-layer auto-invalidation", () => { "GET", DETAIL_URL, {}, - makeResponse({ id: "12345" }) + makeResponse({ id: "12345" }), ); installMockFetch(async () => makeResponse({ id: "99999" })); await runAuthenticatedFetch( `${BASE}organizations/acme/issues/99999/`, - "GET" + "GET", ); expect(await getCachedResponse("GET", DETAIL_URL, {})).toBeDefined(); }); @@ -132,14 +132,14 @@ describe("HTTP-layer auto-invalidation", () => { "GET", `${orgListUrl}?cursor=xyz`, {}, - makeResponse({ data: [] }) + makeResponse({ data: [] }), ); installMockFetch(async () => new Response(null, { status: 204 })); await runAuthenticatedFetch(`${BASE}projects/acme/frontend/`, "DELETE"); expect( - await getCachedResponse("GET", `${orgListUrl}?cursor=xyz`, {}) + await getCachedResponse("GET", `${orgListUrl}?cursor=xyz`, {}), ).toBeUndefined(); }); @@ -149,7 +149,7 @@ describe("HTTP-layer auto-invalidation", () => { "GET", DETAIL_URL, {}, - makeResponse({ owner: "a" }) + makeResponse({ owner: "a" }), ); setAuthToken("identity-b", 3600, "refresh-b"); diff --git a/packages/cli/test/lib/sentry-client.redirect.focused.test.ts b/packages/cli/test/lib/sentry-client.redirect.focused.test.ts index 4be8141e7..cc215739c 100644 --- a/packages/cli/test/lib/sentry-client.redirect.focused.test.ts +++ b/packages/cli/test/lib/sentry-client.redirect.focused.test.ts @@ -37,7 +37,7 @@ describe("authenticated discovery redirects", () => { return requests.length === 1 ? Response.redirect("https://de.sentry.io/api/0/organizations/", 307) : Response.json([]); - } + }, ); const response = await getSdkConfig("https://sentry.io", { validatedRedirects: true, @@ -51,8 +51,8 @@ describe("authenticated discovery redirects", () => { requests.every( (request) => request.redirect === "manual" && - request.headers.get("authorization") === "Bearer redirect-token" - ) + request.headers.get("authorization") === "Bearer redirect-token", + ), ).toBe(true); }); @@ -65,12 +65,12 @@ describe("authenticated discovery redirects", () => { async (input: RequestInfo | URL, init?: RequestInit) => { destinations.push(new Request(input, init).url); return Response.redirect("https://evil.example.net/steal", 302); - } + }, ); await expect( getSdkConfig("https://control.example.com", { validatedRedirects: true, - }).fetch("https://control.example.com/api/0/organizations/") + }).fetch("https://control.example.com/api/0/organizations/"), ).rejects.toBeInstanceOf(HostScopeError); expect(destinations).toEqual([ "https://control.example.com/api/0/organizations/", @@ -85,13 +85,13 @@ describe("authenticated discovery redirects", () => { if (destinations.length === 1 || destinations.length === 3) { return Response.redirect( "https://de.sentry.io/api/0/organizations/", - 307 + 307, ); } return destinations.length === 2 ? new Response("retry", { status: 503 }) : Response.json([]); - } + }, ); const response = await getSdkConfig("https://sentry.io", { validatedRedirects: true, @@ -112,12 +112,12 @@ describe("authenticated discovery redirects", () => { const request = new Request(input, init); destinations.push(request.url); return Response.redirect(request.url, 307); - } + }, ); await expect( getSdkConfig("https://sentry.io", { validatedRedirects: true }).fetch( - "https://sentry.io/api/0/organizations/" - ) + "https://sentry.io/api/0/organizations/", + ), ).rejects.toBeInstanceOf(HostScopeError); expect(destinations).toEqual(["https://sentry.io/api/0/organizations/"]); }); @@ -125,43 +125,42 @@ describe("authenticated discovery redirects", () => { test.each([ { status: 303, expectedMethod: "GET", expectedBody: "" }, { status: 307, expectedMethod: "POST", expectedBody: "payload" }, - ])("preserves fetch redirect semantics for $status", async ({ - status, - expectedMethod, - expectedBody, - }) => { - const requests: Array<{ - method: string; - body: string; - contentType: string | null; - }> = []; - globalThis.fetch = vi.fn(async (input: RequestInfo | URL) => { - const request = new Request(input); - requests.push({ - method: request.method, - body: request.body ? await request.text() : "", - contentType: request.headers.get("content-type"), + ])( + "preserves fetch redirect semantics for $status", + async ({ status, expectedMethod, expectedBody }) => { + const requests: Array<{ + method: string; + body: string; + contentType: string | null; + }> = []; + globalThis.fetch = vi.fn(async (input: RequestInfo | URL) => { + const request = new Request(input); + requests.push({ + method: request.method, + body: request.body ? await request.text() : "", + contentType: request.headers.get("content-type"), + }); + return requests.length === 1 + ? Response.redirect("https://sentry.io/api/0/redirected/", status) + : Response.json({ ok: true }); }); - return requests.length === 1 - ? Response.redirect("https://sentry.io/api/0/redirected/", status) - : Response.json({ ok: true }); - }); - const response = await getSdkConfig("https://sentry.io", { - validatedRedirects: true, - }).fetch("https://sentry.io/api/0/organizations/", { - method: "POST", - headers: { "Content-Type": "text/plain" }, - body: "payload", - }); + const response = await getSdkConfig("https://sentry.io", { + validatedRedirects: true, + }).fetch("https://sentry.io/api/0/organizations/", { + method: "POST", + headers: { "Content-Type": "text/plain" }, + body: "payload", + }); - expect(response.status).toBe(200); - expect(requests[1]).toEqual({ - method: expectedMethod, - body: expectedBody, - contentType: status === 303 ? null : "text/plain", - }); - }); + expect(response.status).toBe(200); + expect(requests[1]).toEqual({ + method: expectedMethod, + body: expectedBody, + contentType: status === 303 ? null : "text/plain", + }); + }, + ); test("cancels an untrusted redirect body without masking the trust error", async () => { setAuthToken("self-hosted-token", undefined, undefined, { @@ -179,14 +178,14 @@ describe("authenticated discovery redirects", () => { { status: 302, headers: { Location: "https://evil.example.net/steal" }, - } - ) + }, + ), ); await expect( getSdkConfig("https://control.example.com", { validatedRedirects: true, - }).fetch("https://control.example.com/api/0/organizations/") + }).fetch("https://control.example.com/api/0/organizations/"), ).rejects.toBeInstanceOf(HostScopeError); expect(cancellation.observed).toBe(true); }); diff --git a/packages/cli/test/lib/sentry-client.test.ts b/packages/cli/test/lib/sentry-client.test.ts index 2caaf9e33..029c834a4 100644 --- a/packages/cli/test/lib/sentry-client.test.ts +++ b/packages/cli/test/lib/sentry-client.test.ts @@ -67,7 +67,7 @@ describe("fetchWithRetry / buildAttemptFactory", () => { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ slug: "acme" }), - } + }, ); expect(res.status).toBe(200); @@ -194,7 +194,7 @@ describe("fetchWithRetry / buildAttemptFactory", () => { new Blob([new Uint8Array([1, 2, 3, 4])], { type: "application/octet-stream", }), - "chunk.bin" + "chunk.bin", ); const authFetch = getAuthenticatedFetch(); @@ -248,7 +248,7 @@ describe("fetchWithTimeout internal timeout classification", () => { signal.addEventListener( "abort", () => reject(new DOMException("aborted", "AbortError")), - { once: true } + { once: true }, ); }); }); @@ -263,7 +263,7 @@ describe("fetchWithTimeout internal timeout classification", () => { expect(thrown).toBeInstanceOf(TimeoutError); expect((thrown as TimeoutError).message).toContain( - "Request timed out after" + "Request timed out after", ); // MAX_RETRIES = 2 → attempts 0, 1, 2. expect(calls).toBe(3); @@ -288,7 +288,7 @@ describe("user abort passthrough", () => { signal?.addEventListener( "abort", () => reject(new DOMException("aborted", "AbortError")), - { once: true } + { once: true }, ); }); }); @@ -319,37 +319,37 @@ describe("resolveTimeoutMs", () => { test("returns 120 000 ms for Seer /autofix/ POST paths", () => { expect( __resolveRequestTimeoutMsForTests( - "https://us.sentry.io/api/0/organizations/acme/issues/1/autofix/" - ) + "https://us.sentry.io/api/0/organizations/acme/issues/1/autofix/", + ), ).toBe(120_000); expect( __resolveRequestTimeoutMsForTests( - "https://us.sentry.io/api/0/organizations/acme/issues/1/autofix/?run_id=42" - ) + "https://us.sentry.io/api/0/organizations/acme/issues/1/autofix/?run_id=42", + ), ).toBe(120_000); expect( __resolveRequestTimeoutMsForTests( - "https://us.sentry.io/api/0/organizations/acme/issues/1/autofix" - ) + "https://us.sentry.io/api/0/organizations/acme/issues/1/autofix", + ), ).toBe(120_000); }); test("returns the 30 000 ms default for non-overridden paths", () => { expect( __resolveRequestTimeoutMsForTests( - "https://us.sentry.io/api/0/organizations/" - ) + "https://us.sentry.io/api/0/organizations/", + ), ).toBe(30_000); expect( __resolveRequestTimeoutMsForTests( - "https://us.sentry.io/api/0/issues/1/events/" - ) + "https://us.sentry.io/api/0/issues/1/events/", + ), ).toBe(30_000); // Substring-only matches must not inherit the override. expect( __resolveRequestTimeoutMsForTests( - "https://us.sentry.io/api/0/autofixation-report/" - ) + "https://us.sentry.io/api/0/autofixation-report/", + ), ).toBe(30_000); }); }); diff --git a/packages/cli/test/lib/sentry-url-parser.property.test.ts b/packages/cli/test/lib/sentry-url-parser.property.test.ts index 3f1198917..afeb8d02f 100644 --- a/packages/cli/test/lib/sentry-url-parser.property.test.ts +++ b/packages/cli/test/lib/sentry-url-parser.property.test.ts @@ -29,12 +29,12 @@ import { DEFAULT_NUM_RUNS } from "../model-based/helpers.js"; * `sentry.io` and dropping the org subdomain. */ const orgSlugArb = stringMatching(/^[a-z][a-z0-9-]{1,20}[a-z0-9]$/).filter( - (s) => !s.startsWith("xn--") + (s) => !s.startsWith("xn--"), ); /** Generates valid project slugs (lowercase, alphanumeric with hyphens) */ const projectSlugArb = stringMatching(/^[a-z][a-z0-9-]{1,20}[a-z0-9]$/).filter( - (s) => !s.startsWith("xn--") + (s) => !s.startsWith("xn--"), ); /** Generates valid 32-character hex trace IDs */ @@ -62,7 +62,7 @@ describe("parseSentryUrl round-trip properties", () => { expect(parsed?.project).toBeUndefined(); expect(parsed?.traceId).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -77,7 +77,7 @@ describe("parseSentryUrl round-trip properties", () => { expect(parsed?.project).toBe(project); expect(parsed?.issueId).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -92,7 +92,7 @@ describe("parseSentryUrl round-trip properties", () => { expect(parsed?.traceId).toBe(traceId); expect(parsed?.issueId).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -107,7 +107,7 @@ describe("parseSentryUrl round-trip properties", () => { expect(parsed?.replayId).toBe(replayId); expect(parsed?.issueId).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -123,7 +123,7 @@ describe("parseSentryUrl round-trip properties", () => { expect(parsed?.issueId).toBe(issueId); expect(parsed?.eventId).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -139,9 +139,9 @@ describe("parseSentryUrl round-trip properties", () => { expect(parsed?.org).toBe(org); expect(parsed?.issueId).toBe(issueId); expect(parsed?.eventId).toBe(eventId); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -151,7 +151,7 @@ describe("parseSentryUrl round-trip properties", () => { property(orgSlugArb, (org) => { expect(parseSentryUrl(org)).toBeNull(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -164,7 +164,7 @@ describe("parseSentryUrl round-trip properties", () => { expect(parsed).not.toBeNull(); expect(parsed?.baseUrl).toMatch(/^https?:\/\/.+/); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/sentry-url-parser.test.ts b/packages/cli/test/lib/sentry-url-parser.test.ts index 299582cfd..c57e08e67 100644 --- a/packages/cli/test/lib/sentry-url-parser.test.ts +++ b/packages/cli/test/lib/sentry-url-parser.test.ts @@ -50,7 +50,7 @@ describe("parseSentryUrl", () => { test("self-hosted /organizations/{org}/", () => { const result = parseSentryUrl( - "https://sentry.example.com/organizations/acme-corp/" + "https://sentry.example.com/organizations/acme-corp/", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -60,7 +60,7 @@ describe("parseSentryUrl", () => { test("self-hosted Feedback permalink supports legacy mixed-case project slugs", () => { const result = parseSentryUrl( - "https://sentry.example.com/organizations/acme-corp/feedback/?feedbackSlug=Legacy_Project%3A5146636313" + "https://sentry.example.com/organizations/acme-corp/feedback/?feedbackSlug=Legacy_Project%3A5146636313", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -81,7 +81,7 @@ describe("parseSentryUrl", () => { describe("issue URLs", () => { test("/organizations/{org}/issues/{numericId}/", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/issues/32886/" + "https://sentry.io/organizations/my-org/issues/32886/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -92,7 +92,7 @@ describe("parseSentryUrl", () => { test("/organizations/{org}/issues/{shortId}/", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/issues/CLI-G/" + "https://sentry.io/organizations/my-org/issues/CLI-G/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -103,7 +103,7 @@ describe("parseSentryUrl", () => { test("self-hosted issue URL with query params", () => { const result = parseSentryUrl( - "https://sentry.example.com/organizations/acme-corp/issues/32886/?project=2" + "https://sentry.example.com/organizations/acme-corp/issues/32886/?project=2", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -114,7 +114,7 @@ describe("parseSentryUrl", () => { test("self-hosted issue URL with port", () => { const result = parseSentryUrl( - "https://sentry.acme.internal:9000/organizations/devops/issues/100/" + "https://sentry.acme.internal:9000/organizations/devops/issues/100/", ); expect(result).toEqual({ baseUrl: "https://sentry.acme.internal:9000", @@ -125,7 +125,7 @@ describe("parseSentryUrl", () => { test("HTTP (non-HTTPS) self-hosted URL", () => { const result = parseSentryUrl( - "http://sentry.local:8080/organizations/dev/issues/42/" + "http://sentry.local:8080/organizations/dev/issues/42/", ); expect(result).toEqual({ baseUrl: "http://sentry.local:8080", @@ -138,7 +138,7 @@ describe("parseSentryUrl", () => { describe("event URLs", () => { test("/organizations/{org}/issues/{id}/events/{eventId}/", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/issues/32886/events/abc123def456/" + "https://sentry.io/organizations/my-org/issues/32886/events/abc123def456/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -150,7 +150,7 @@ describe("parseSentryUrl", () => { test("self-hosted event URL", () => { const result = parseSentryUrl( - "https://sentry.example.com/organizations/acme/issues/999/events/deadbeef/" + "https://sentry.example.com/organizations/acme/issues/999/events/deadbeef/", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -162,7 +162,7 @@ describe("parseSentryUrl", () => { test("event URL without trailing slash", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/issues/1/events/evt001" + "https://sentry.io/organizations/my-org/issues/1/events/evt001", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -176,7 +176,7 @@ describe("parseSentryUrl", () => { describe("trace URLs", () => { test("/organizations/{org}/explore/traces/trace/{traceId}/", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/explore/traces/trace/a4d1aae7216b47ff8117cf4e09ce9d0a/" + "https://sentry.io/organizations/my-org/explore/traces/trace/a4d1aae7216b47ff8117cf4e09ce9d0a/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -187,7 +187,7 @@ describe("parseSentryUrl", () => { test("org-domain /explore/traces/trace/{traceId}/", () => { const result = parseSentryUrl( - "https://my-org.sentry.io/explore/traces/trace/a4d1aae7216b47ff8117cf4e09ce9d0a/" + "https://my-org.sentry.io/explore/traces/trace/a4d1aae7216b47ff8117cf4e09ce9d0a/", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -198,7 +198,7 @@ describe("parseSentryUrl", () => { test("/organizations/{org}/traces/{traceId}/", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/traces/a4d1aae7216b47ff8117cf4e09ce9d0a/" + "https://sentry.io/organizations/my-org/traces/a4d1aae7216b47ff8117cf4e09ce9d0a/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -209,7 +209,7 @@ describe("parseSentryUrl", () => { test("self-hosted trace URL", () => { const result = parseSentryUrl( - "https://sentry.example.com/organizations/devops/traces/00112233445566778899aabbccddeeff/" + "https://sentry.example.com/organizations/devops/traces/00112233445566778899aabbccddeeff/", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -223,7 +223,7 @@ describe("parseSentryUrl", () => { // unrelated sub-route — the slug must not be captured as a trace ID. It // resolves to the org (like the replay list), not a trace detail. const result = parseSentryUrl( - "https://my-org.sentry.io/explore/traces/some-subroute/" + "https://my-org.sentry.io/explore/traces/some-subroute/", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -241,7 +241,7 @@ describe("parseSentryUrl", () => { test("organizations explore/traces without trace segment is org-only", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/explore/traces/some-subroute/" + "https://sentry.io/organizations/my-org/explore/traces/some-subroute/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -253,7 +253,7 @@ describe("parseSentryUrl", () => { describe("replay URLs", () => { test("/organizations/{org}/explore/replays/{replayId}/", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/explore/replays/346789a703f6454384f1de473b8b9fcc/" + "https://sentry.io/organizations/my-org/explore/replays/346789a703f6454384f1de473b8b9fcc/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -264,7 +264,7 @@ describe("parseSentryUrl", () => { test("legacy /organizations/{org}/replays/{replayId}/", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/replays/346789a703f6454384f1de473b8b9fcc/" + "https://sentry.io/organizations/my-org/replays/346789a703f6454384f1de473b8b9fcc/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -275,7 +275,7 @@ describe("parseSentryUrl", () => { test("self-hosted replay URL", () => { const result = parseSentryUrl( - "https://sentry.example.com/organizations/acme-corp/explore/replays/346789a703f6454384f1de473b8b9fcc/" + "https://sentry.example.com/organizations/acme-corp/explore/replays/346789a703f6454384f1de473b8b9fcc/", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -286,7 +286,7 @@ describe("parseSentryUrl", () => { test("normalizes uppercase replay IDs in replay URLs", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/explore/replays/346789A703F6454384F1DE473B8B9FCC/" + "https://sentry.io/organizations/my-org/explore/replays/346789A703F6454384F1DE473B8B9FCC/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -297,7 +297,7 @@ describe("parseSentryUrl", () => { test("falls back to org for replay listing URL", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/explore/replays/" + "https://sentry.io/organizations/my-org/explore/replays/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -307,7 +307,7 @@ describe("parseSentryUrl", () => { test("falls back to org for legacy replay listing URL", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/replays/" + "https://sentry.io/organizations/my-org/replays/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -318,30 +318,30 @@ describe("parseSentryUrl", () => { test("rejects non-hex replay ID on explore path", () => { expect( parseSentryUrl( - "https://sentry.io/organizations/my-org/explore/replays/some-random-page/" - ) + "https://sentry.io/organizations/my-org/explore/replays/some-random-page/", + ), ).toBeNull(); }); test("rejects non-hex replay ID on legacy path", () => { expect( parseSentryUrl( - "https://sentry.io/organizations/my-org/replays/some-random-page/" - ) + "https://sentry.io/organizations/my-org/replays/some-random-page/", + ), ).toBeNull(); }); test("rejects non-hex replay ID on subdomain explore path", () => { expect( parseSentryUrl( - "https://my-org.sentry.io/explore/replays/some-random-page/" - ) + "https://my-org.sentry.io/explore/replays/some-random-page/", + ), ).toBeNull(); }); test("falls back to org for subdomain replay listing URL", () => { const result = parseSentryUrl( - "https://my-org.sentry.io/explore/replays/" + "https://my-org.sentry.io/explore/replays/", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -353,7 +353,7 @@ describe("parseSentryUrl", () => { describe("dashboard URLs", () => { test("/organizations/{org}/dashboard/{id}/", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/dashboard/4326879/" + "https://sentry.io/organizations/my-org/dashboard/4326879/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -364,7 +364,7 @@ describe("parseSentryUrl", () => { test("self-hosted dashboard URL", () => { const result = parseSentryUrl( - "https://sentry.example.com/organizations/devops/dashboard/12345/" + "https://sentry.example.com/organizations/devops/dashboard/12345/", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -375,7 +375,7 @@ describe("parseSentryUrl", () => { test("dashboard URL without trailing slash", () => { const result = parseSentryUrl( - "https://sentry.io/organizations/my-org/dashboard/999" + "https://sentry.io/organizations/my-org/dashboard/999", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -388,7 +388,7 @@ describe("parseSentryUrl", () => { describe("project settings URLs", () => { test("/settings/{org}/projects/{project}/", () => { const result = parseSentryUrl( - "https://sentry.io/settings/my-org/projects/backend/" + "https://sentry.io/settings/my-org/projects/backend/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -399,7 +399,7 @@ describe("parseSentryUrl", () => { test("self-hosted project settings URL", () => { const result = parseSentryUrl( - "https://sentry.example.com/settings/acme/projects/web-frontend/" + "https://sentry.example.com/settings/acme/projects/web-frontend/", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -412,7 +412,7 @@ describe("parseSentryUrl", () => { describe("SaaS subdomain-style URLs (org in hostname)", () => { test("issue URL extracts org from subdomain", () => { const result = parseSentryUrl( - "https://my-org.sentry.io/issues/99124558/" + "https://my-org.sentry.io/issues/99124558/", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -423,7 +423,7 @@ describe("parseSentryUrl", () => { test("issue URL with event ID", () => { const result = parseSentryUrl( - "https://my-org.sentry.io/issues/99124558/events/abc123/" + "https://my-org.sentry.io/issues/99124558/events/abc123/", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -435,7 +435,7 @@ describe("parseSentryUrl", () => { test("trace URL extracts org from subdomain", () => { const result = parseSentryUrl( - "https://my-org.sentry.io/traces/a4d1aae7216b47ff8117cf4e09ce9d0a/" + "https://my-org.sentry.io/traces/a4d1aae7216b47ff8117cf4e09ce9d0a/", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -446,7 +446,7 @@ describe("parseSentryUrl", () => { test("replay URL extracts org from subdomain", () => { const result = parseSentryUrl( - "https://my-org.sentry.io/explore/replays/346789a703f6454384f1de473b8b9fcc/" + "https://my-org.sentry.io/explore/replays/346789a703f6454384f1de473b8b9fcc/", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -457,7 +457,7 @@ describe("parseSentryUrl", () => { test("legacy replay URL extracts org from subdomain", () => { const result = parseSentryUrl( - "https://my-org.sentry.io/replays/346789a703f6454384f1de473b8b9fcc/" + "https://my-org.sentry.io/replays/346789a703f6454384f1de473b8b9fcc/", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -468,7 +468,7 @@ describe("parseSentryUrl", () => { test("dashboard URL extracts org from subdomain", () => { const result = parseSentryUrl( - "https://sentry-sdks.sentry.io/dashboard/4326879/" + "https://sentry-sdks.sentry.io/dashboard/4326879/", ); expect(result).toEqual({ baseUrl: "https://sentry-sdks.sentry.io", @@ -487,7 +487,7 @@ describe("parseSentryUrl", () => { test("modern Feedback permalink returns its org", () => { const result = parseSentryUrl( - "https://my-org.sentry.io/feedback/?feedbackSlug=my-project%3A5146636313" + "https://my-org.sentry.io/feedback/?feedbackSlug=my-project%3A5146636313", ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -503,7 +503,7 @@ describe("parseSentryUrl", () => { "my-project%250A%3A5146636313", ])("does not extract malformed Feedback slug %s", (feedbackSlug) => { const result = parseSentryUrl( - `https://my-org.sentry.io/feedback/?feedbackSlug=${feedbackSlug}` + `https://my-org.sentry.io/feedback/?feedbackSlug=${feedbackSlug}`, ); expect(result).toEqual({ baseUrl: "https://my-org.sentry.io", @@ -513,7 +513,7 @@ describe("parseSentryUrl", () => { test("hyphenated org slug", () => { const result = parseSentryUrl( - "https://acme-corp.sentry.io/issues/12345/" + "https://acme-corp.sentry.io/issues/12345/", ); expect(result).toEqual({ baseUrl: "https://acme-corp.sentry.io", @@ -539,10 +539,10 @@ describe("parseSentryUrl", () => { // Self-hosted hostname doesn't end with .sentry.io — subdomain extraction must not apply. // The path /issues/123/ has no /organizations/ prefix, so no matcher handles it. expect( - parseSentryUrl("https://sentry.example.com/issues/123/") + parseSentryUrl("https://sentry.example.com/issues/123/"), ).toBeNull(); expect( - parseSentryUrl("https://sentry.acme.internal:9000/issues/456/") + parseSentryUrl("https://sentry.acme.internal:9000/issues/456/"), ).toBeNull(); }); }); @@ -550,7 +550,7 @@ describe("parseSentryUrl", () => { describe("share URLs", () => { test("SaaS subdomain share URL extracts org and shareId", () => { const result = parseSentryUrl( - "https://gibush-kq.sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/" + "https://gibush-kq.sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/", ); expect(result).toEqual({ baseUrl: "https://gibush-kq.sentry.io", @@ -561,7 +561,7 @@ describe("parseSentryUrl", () => { test("bare sentry.io share URL has no org", () => { const result = parseSentryUrl( - "https://sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/" + "https://sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -571,7 +571,7 @@ describe("parseSentryUrl", () => { test("self-hosted share URL", () => { const result = parseSentryUrl( - "https://sentry.example.com/share/issue/aabbccdd11223344aabbccdd11223344/" + "https://sentry.example.com/share/issue/aabbccdd11223344aabbccdd11223344/", ); expect(result).toEqual({ baseUrl: "https://sentry.example.com", @@ -581,7 +581,7 @@ describe("parseSentryUrl", () => { test("self-hosted share URL with port", () => { const result = parseSentryUrl( - "https://sentry.acme.internal:9000/share/issue/deadbeefdeadbeefdeadbeefdeadbeef/" + "https://sentry.acme.internal:9000/share/issue/deadbeefdeadbeefdeadbeefdeadbeef/", ); expect(result).toEqual({ baseUrl: "https://sentry.acme.internal:9000", @@ -592,7 +592,7 @@ describe("parseSentryUrl", () => { test("region subdomain share URL has no org", () => { // us.sentry.io is a region, not an org — share URL falls through to matchSharePath const result = parseSentryUrl( - "https://us.sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/" + "https://us.sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5/", ); expect(result).toEqual({ baseUrl: "https://us.sentry.io", @@ -602,7 +602,7 @@ describe("parseSentryUrl", () => { test("share URL without trailing slash", () => { const result = parseSentryUrl( - "https://sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5" + "https://sentry.io/share/issue/f1abd515c51346778384ff25dfb341e5", ); expect(result).toEqual({ baseUrl: "https://sentry.io", @@ -634,7 +634,7 @@ describe("parseSentryUrl", () => { test("/settings/{org}/projects/ without project slug", () => { expect( - parseSentryUrl("https://sentry.io/settings/my-org/projects/") + parseSentryUrl("https://sentry.io/settings/my-org/projects/"), ).toBeNull(); }); }); @@ -660,9 +660,8 @@ describe("applySentryUrlContext", () => { delete process.env.SENTRY_HOST; // Reset env-token-host capture so each test can re-pin based on the // SENTRY_HOST they set (or leave unset → SaaS default). - const { resetEnvTokenHostForTesting } = await import( - "../../src/lib/env-token-host.js" - ); + const { resetEnvTokenHostForTesting } = + await import("../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); }); @@ -677,9 +676,8 @@ describe("applySentryUrlContext", () => { } else { delete process.env.SENTRY_HOST; } - const { resetEnvTokenHostForTesting } = await import( - "../../src/lib/env-token-host.js" - ); + const { resetEnvTokenHostForTesting } = + await import("../../src/lib/env-token-host.js"); resetEnvTokenHostForTesting(); }); @@ -696,7 +694,7 @@ describe("applySentryUrlContext", () => { // Env-token defaults to SaaS (no SENTRY_HOST set), so a self-hosted URL // is a host-scope mismatch → CliError, env untouched. expect(() => applySentryUrlContext("https://sentry.example.com")).toThrow( - /does not match|sentry auth login --url/ + /does not match|sentry auth login --url/, ); expect(process.env.SENTRY_HOST).toBeUndefined(); expect(process.env.SENTRY_URL).toBeUndefined(); @@ -721,7 +719,7 @@ describe("applySentryUrlContext", () => { process.env.SENTRY_HOST = "https://existing.example.com"; process.env.SENTRY_URL = "https://existing.example.com"; expect(() => applySentryUrlContext("https://sentry.other.com")).toThrow( - /does not match|sentry auth login --url/ + /does not match|sentry auth login --url/, ); // Existing env left intact — throw happens before any write. expect(process.env.SENTRY_HOST).toBe("https://existing.example.com"); diff --git a/packages/cli/test/lib/sentry-urls.credential.test.ts b/packages/cli/test/lib/sentry-urls.credential.test.ts index b6f52dda2..9bef0e60f 100644 --- a/packages/cli/test/lib/sentry-urls.credential.test.ts +++ b/packages/cli/test/lib/sentry-urls.credential.test.ts @@ -35,10 +35,10 @@ describe("web URLs for active credential hosts", () => { expect(getSentryBaseUrl()).toBe("https://sentry.example.com"); expect(isSaaS()).toBe(false); expect(buildOrgUrl("acme")).toBe( - "https://sentry.example.com/organizations/acme/" + "https://sentry.example.com/organizations/acme/", ); expect(buildProjectUrl("acme", "site")).toBe( - "https://sentry.example.com/settings/acme/projects/site/" + "https://sentry.example.com/settings/acme/projects/site/", ); }); @@ -61,7 +61,7 @@ describe("web URLs for active credential hosts", () => { }); expect(getSentryBaseUrl()).toBe("https://configured.example.com/sentry"); expect(buildOrgUrl("acme")).toBe( - "https://configured.example.com/sentry/organizations/acme/" + "https://configured.example.com/sentry/organizations/acme/", ); }); }); diff --git a/packages/cli/test/lib/sentry-urls.property.test.ts b/packages/cli/test/lib/sentry-urls.property.test.ts index ec7bb8d81..c3ea30d0c 100644 --- a/packages/cli/test/lib/sentry-urls.property.test.ts +++ b/packages/cli/test/lib/sentry-urls.property.test.ts @@ -61,12 +61,12 @@ afterEach(() => { /** Valid subdomain parts (lowercase alphanumeric with hyphens, not starting/ending with hyphen) */ const subdomainPartArb = stringMatching( - /^[a-z][a-z0-9-]{0,10}[a-z0-9]$/ + /^[a-z][a-z0-9-]{0,10}[a-z0-9]$/, ).filter((s) => !s.includes("--") && s.length >= 2); /** Valid org/project slugs (exclude xn-- punycode prefix — invalid IDN breaks URL parser) */ const slugArb = stringMatching(/^[a-z][a-z0-9-]{1,30}[a-z0-9]$/).filter( - (s) => !s.startsWith("xn--") + (s) => !s.startsWith("xn--"), ); /** Valid event IDs (32-char hex) */ @@ -96,12 +96,12 @@ const nonSentryDomainArb = oneof( "sentry-fake.com", "notsentry.io", "sentry.io.evil.com", - "example.sentry.io.evil.com" + "example.sentry.io.evil.com", ), // Generate random domains tuple(subdomainPartArb, constantFrom(".com", ".org", ".net", ".io")).map( - ([sub, tld]) => `${sub}${tld}` - ) + ([sub, tld]) => `${sub}${tld}`, + ), ).filter((domain) => { // Ensure it's not actually sentry.io or a subdomain return domain !== "sentry.io" && !domain.endsWith(".sentry.io"); @@ -127,7 +127,7 @@ describe("isSentrySaasUrl properties", () => { const url = `https://${region}.sentry.io`; expect(isSentrySaasUrl(url)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -137,7 +137,7 @@ describe("isSentrySaasUrl properties", () => { const url = `https://${subdomain}.sentry.io`; expect(isSentrySaasUrl(url)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -147,7 +147,7 @@ describe("isSentrySaasUrl properties", () => { const url = `https://${domain}`; expect(isSentrySaasUrl(url)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -191,15 +191,15 @@ describe("isSentrySaasUrl properties", () => { property( oneof( constantFrom("https://sentry.io", "https://us.sentry.io"), - nonSentryDomainArb.map((d) => `https://${d}`) + nonSentryDomainArb.map((d) => `https://${d}`), ), (url) => { const result1 = isSentrySaasUrl(url); const result2 = isSentrySaasUrl(url); expect(result1).toBe(result2); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -211,7 +211,7 @@ describe("buildOrgUrl properties", () => { const result = buildOrgUrl(orgSlug); expect(result.startsWith(getOrgBaseUrl(orgSlug))).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -221,7 +221,7 @@ describe("buildOrgUrl properties", () => { const result = buildOrgUrl(orgSlug); expect(result).toContain(orgSlug); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -231,7 +231,7 @@ describe("buildOrgUrl properties", () => { const result = buildOrgUrl(orgSlug); expect(result).toBe(`${getOrgBaseUrl(orgSlug)}/`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -241,7 +241,7 @@ describe("buildOrgUrl properties", () => { const result = buildOrgUrl(orgSlug); expect(() => new URL(result)).not.toThrow(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -254,7 +254,7 @@ describe("buildProjectUrl properties", () => { expect(result).toContain(orgSlug); expect(result).toContain(projectSlug); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -263,10 +263,10 @@ describe("buildProjectUrl properties", () => { property(tuple(slugArb, slugArb), ([orgSlug, projectSlug]) => { const result = buildProjectUrl(orgSlug, projectSlug); expect(result).toBe( - `${getOrgBaseUrl(orgSlug)}/settings/projects/${projectSlug}/` + `${getOrgBaseUrl(orgSlug)}/settings/projects/${projectSlug}/`, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -276,7 +276,7 @@ describe("buildProjectUrl properties", () => { const result = buildProjectUrl(orgSlug, projectSlug); expect(() => new URL(result)).not.toThrow(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -288,7 +288,7 @@ describe("buildEventSearchUrl properties", () => { const result = buildEventSearchUrl(orgSlug, eventId); expect(result).toContain(`event.id:${eventId}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -299,7 +299,7 @@ describe("buildEventSearchUrl properties", () => { const url = new URL(result); expect(url.searchParams.get("query")).toContain(eventId); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -310,11 +310,11 @@ describe("buildProjectIssuesUrl properties", () => { property(tuple(slugArb, projectIdArb), ([orgSlug, projectId]) => { const result = buildProjectIssuesUrl(orgSlug, projectId); expect(result).toBe( - `${getOrgBaseUrl(orgSlug)}/issues/?project=${projectId}` + `${getOrgBaseUrl(orgSlug)}/issues/?project=${projectId}`, ); expect(new URL(result).searchParams.get("project")).toBe(projectId); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -324,7 +324,7 @@ describe("buildProjectIssuesUrl properties", () => { const result = buildProjectIssuesUrl(orgSlug); expect(result).toBe(`${getOrgBaseUrl(orgSlug)}/issues/`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -335,7 +335,7 @@ describe("buildProjectIssuesUrl properties", () => { expect(() => new URL(result)).not.toThrow(); expect(new URL(result).pathname).toBe("/issues/"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -350,7 +350,7 @@ describe("parseOrgProjectFromSettingsUrl properties", () => { projectSlug, }); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -364,7 +364,7 @@ describe("parseOrgProjectFromSettingsUrl properties", () => { projectSlug, }); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -380,7 +380,7 @@ describe("buildOrgSettingsUrl properties", () => { const result = buildOrgSettingsUrl(orgSlug); expect(result.endsWith("/")).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -390,7 +390,7 @@ describe("buildOrgSettingsUrl properties", () => { const result = buildOrgSettingsUrl(orgSlug, hash); expect(result).toContain(`#${hash}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -400,7 +400,7 @@ describe("buildOrgSettingsUrl properties", () => { const result = buildOrgSettingsUrl(orgSlug, hash); expect(result.endsWith(`#${hash}`)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -412,7 +412,7 @@ describe("buildSeerSettingsUrl properties", () => { const result = buildSeerSettingsUrl(orgSlug); expect(result).toContain("/seer/"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -422,7 +422,7 @@ describe("buildSeerSettingsUrl properties", () => { const result = buildSeerSettingsUrl(orgSlug); expect(result).toContain("/settings/seer/"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -434,7 +434,7 @@ describe("buildBillingUrl properties", () => { const result = buildBillingUrl(orgSlug); expect(result.includes("?")).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -444,7 +444,7 @@ describe("buildBillingUrl properties", () => { const result = buildBillingUrl(orgSlug, product); expect(result).toContain(`?product=${product}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -454,7 +454,7 @@ describe("buildBillingUrl properties", () => { const result = buildBillingUrl(orgSlug); expect(result).toContain("/billing/overview/"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -466,7 +466,7 @@ describe("buildLogsUrl properties", () => { const result = buildLogsUrl(orgSlug); expect(result.includes("?")).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -476,7 +476,7 @@ describe("buildLogsUrl properties", () => { const result = buildLogsUrl(orgSlug, logId); expect(result).toContain(`?query=sentry.item_id:${logId}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -486,7 +486,7 @@ describe("buildLogsUrl properties", () => { const result = buildLogsUrl(orgSlug); expect(result).toContain("/explore/logs/"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -496,7 +496,7 @@ describe("buildLogsUrl properties", () => { expect(() => new URL(buildLogsUrl(orgSlug))).not.toThrow(); expect(() => new URL(buildLogsUrl(orgSlug, logId))).not.toThrow(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -508,7 +508,7 @@ describe("buildTraceUrl properties", () => { const result = buildTraceUrl(orgSlug, traceId); expect(result).toContain(`/explore/traces/trace/${traceId}/`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -518,7 +518,7 @@ describe("buildTraceUrl properties", () => { const result = buildTraceUrl(orgSlug, traceId); expect(result).toContain(orgSlug); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -528,7 +528,7 @@ describe("buildTraceUrl properties", () => { const result = buildTraceUrl(orgSlug, traceId); expect(() => new URL(result)).not.toThrow(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -537,10 +537,10 @@ describe("buildTraceUrl properties", () => { property(tuple(slugArb, traceIdArb), ([orgSlug, traceId]) => { const result = buildTraceUrl(orgSlug, traceId); expect(result).toBe( - `${getOrgBaseUrl(orgSlug)}/explore/traces/trace/${traceId}/` + `${getOrgBaseUrl(orgSlug)}/explore/traces/trace/${traceId}/`, ); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -552,7 +552,7 @@ describe("buildDashboardsListUrl properties", () => { const result = buildDashboardsListUrl(orgSlug); expect(result).toContain("/dashboards/"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -562,7 +562,7 @@ describe("buildDashboardsListUrl properties", () => { const result = buildDashboardsListUrl(orgSlug); expect(result).toContain(orgSlug); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -572,7 +572,7 @@ describe("buildDashboardsListUrl properties", () => { const result = buildDashboardsListUrl(orgSlug); expect(() => new URL(result)).not.toThrow(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -584,7 +584,7 @@ describe("buildDashboardUrl properties", () => { const result = buildDashboardUrl(orgSlug, dashboardId); expect(result).toContain(`/dashboard/${dashboardId}/`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -595,7 +595,7 @@ describe("buildDashboardUrl properties", () => { expect(result).toContain(orgSlug); expect(result).toContain(dashboardId); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -605,7 +605,7 @@ describe("buildDashboardUrl properties", () => { const result = buildDashboardUrl(orgSlug, dashboardId); expect(() => new URL(result)).not.toThrow(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -622,7 +622,7 @@ describe("SENTRY_HOST precedence", () => { process.env.SENTRY_HOST = "https://sentry.company.com"; expect(getOrgBaseUrl("my-org")).toBe("https://sentry.company.com"); expect(buildOrgUrl("my-org")).toBe( - "https://sentry.company.com/organizations/my-org/" + "https://sentry.company.com/organizations/my-org/", ); }); }); @@ -640,63 +640,63 @@ describe("self-hosted URLs", () => { test("buildOrgUrl uses path-based pattern", () => { expect(buildOrgUrl("my-org")).toBe( - `${SELF_HOSTED_URL}/organizations/my-org/` + `${SELF_HOSTED_URL}/organizations/my-org/`, ); }); test("buildEventSearchUrl uses path-based pattern", () => { expect( - buildEventSearchUrl("my-org", "abc123def456abc123def456abc123de") + buildEventSearchUrl("my-org", "abc123def456abc123def456abc123de"), ).toBe( - `${SELF_HOSTED_URL}/organizations/my-org/issues/?query=event.id:abc123def456abc123def456abc123de` + `${SELF_HOSTED_URL}/organizations/my-org/issues/?query=event.id:abc123def456abc123def456abc123de`, ); }); test("buildLogsUrl uses path-based pattern", () => { expect(buildLogsUrl("my-org")).toBe( - `${SELF_HOSTED_URL}/organizations/my-org/explore/logs/` + `${SELF_HOSTED_URL}/organizations/my-org/explore/logs/`, ); }); test("buildTraceUrl uses path-based pattern", () => { expect(buildTraceUrl("my-org", "abc123def456abc123def456abc123de")).toBe( - `${SELF_HOSTED_URL}/organizations/my-org/explore/traces/trace/abc123def456abc123def456abc123de/` + `${SELF_HOSTED_URL}/organizations/my-org/explore/traces/trace/abc123def456abc123def456abc123de/`, ); }); test("buildDashboardsListUrl uses path-based pattern", () => { expect(buildDashboardsListUrl("my-org")).toBe( - `${SELF_HOSTED_URL}/organizations/my-org/dashboards/` + `${SELF_HOSTED_URL}/organizations/my-org/dashboards/`, ); }); test("buildDashboardUrl uses path-based pattern", () => { expect(buildDashboardUrl("my-org", "42")).toBe( - `${SELF_HOSTED_URL}/organizations/my-org/dashboard/42/` + `${SELF_HOSTED_URL}/organizations/my-org/dashboard/42/`, ); }); test("buildProjectUrl uses path-based pattern", () => { expect(buildProjectUrl("my-org", "my-project")).toBe( - `${SELF_HOSTED_URL}/settings/my-org/projects/my-project/` + `${SELF_HOSTED_URL}/settings/my-org/projects/my-project/`, ); }); test("buildOrgSettingsUrl uses path-based pattern", () => { expect(buildOrgSettingsUrl("my-org")).toBe( - `${SELF_HOSTED_URL}/settings/my-org/` + `${SELF_HOSTED_URL}/settings/my-org/`, ); }); test("buildSeerSettingsUrl uses path-based pattern", () => { expect(buildSeerSettingsUrl("my-org")).toBe( - `${SELF_HOSTED_URL}/settings/my-org/seer/` + `${SELF_HOSTED_URL}/settings/my-org/seer/`, ); }); test("buildBillingUrl uses path-based pattern", () => { expect(buildBillingUrl("my-org")).toBe( - `${SELF_HOSTED_URL}/settings/my-org/billing/overview/` + `${SELF_HOSTED_URL}/settings/my-org/billing/overview/`, ); }); @@ -722,9 +722,9 @@ describe("self-hosted URLs", () => { const parsed = new URL(url); expect(parsed.hostname).toBe("sentry.company.com"); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -751,9 +751,9 @@ describe("URL building cross-function properties", () => { for (const url of urls) { expect(() => new URL(url)).not.toThrow(); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -764,29 +764,29 @@ describe("URL building cross-function properties", () => { ([orgSlug, projectSlug, eventId, hash, product]) => { expect(buildOrgUrl(orgSlug)).toBe(buildOrgUrl(orgSlug)); expect(buildProjectUrl(orgSlug, projectSlug)).toBe( - buildProjectUrl(orgSlug, projectSlug) + buildProjectUrl(orgSlug, projectSlug), ); expect(buildEventSearchUrl(orgSlug, eventId)).toBe( - buildEventSearchUrl(orgSlug, eventId) + buildEventSearchUrl(orgSlug, eventId), ); expect(buildOrgSettingsUrl(orgSlug, hash)).toBe( - buildOrgSettingsUrl(orgSlug, hash) + buildOrgSettingsUrl(orgSlug, hash), ); expect(buildSeerSettingsUrl(orgSlug)).toBe( - buildSeerSettingsUrl(orgSlug) + buildSeerSettingsUrl(orgSlug), ); expect(buildBillingUrl(orgSlug, product)).toBe( - buildBillingUrl(orgSlug, product) + buildBillingUrl(orgSlug, product), ); expect(buildDashboardsListUrl(orgSlug)).toBe( - buildDashboardsListUrl(orgSlug) + buildDashboardsListUrl(orgSlug), ); expect(buildDashboardUrl(orgSlug, "42")).toBe( - buildDashboardUrl(orgSlug, "42") + buildDashboardUrl(orgSlug, "42"), ); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/sentryclirc-import.property.test.ts b/packages/cli/test/lib/sentryclirc-import.property.test.ts index 215ade271..018cbdb6f 100644 --- a/packages/cli/test/lib/sentryclirc-import.property.test.ts +++ b/packages/cli/test/lib/sentryclirc-import.property.test.ts @@ -34,7 +34,7 @@ import { DEFAULT_NUM_RUNS } from "../model-based/helpers.js"; const slugArb = array( constantFrom(..."abcdefghijklmnopqrstuvwxyz0123456789-".split("")), - { minLength: 1, maxLength: 12 } + { minLength: 1, maxLength: 12 }, ).map((chars) => chars.join("")); const tokenArb = string({ minLength: 8, maxLength: 40 }); @@ -63,7 +63,7 @@ describe("property: isSameFileOrigin", () => { }; expect(isSameFileOrigin(plan)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -90,9 +90,9 @@ describe("property: isSameFileOrigin", () => { warnings: [], }; expect(isSameFileOrigin(plan)).toBe(false); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -111,7 +111,7 @@ describe("property: isSameFileOrigin", () => { }; expect(isSameFileOrigin(plan)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -127,7 +127,7 @@ describe("property: maskToken", () => { const masked = maskToken(token); expect(masked).toContain("*"); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -137,14 +137,14 @@ describe("property: maskToken", () => { fcAssert( property( string({ minLength: 1, maxLength: 100 }).filter( - (t) => !/^\*+$/.test(t) + (t) => !/^\*+$/.test(t), ), (token) => { const masked = maskToken(token); expect(masked).not.toBe(token); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -155,7 +155,7 @@ describe("property: maskToken", () => { // Every character should be an asterisk expect(masked).toBe("*".repeat(token.length)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -187,7 +187,7 @@ describe("property: buildImportPlan merge order", () => { const plan = buildImportPlan([fileA, fileB]); expect(plan.effective.org).toBe(orgA); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -210,7 +210,7 @@ describe("property: buildImportPlan merge order", () => { expect(plan.effective.org).toBe(org); expect(plan.effective.token).toBe(token); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/sentryclirc-import.test.ts b/packages/cli/test/lib/sentryclirc-import.test.ts index 455eea057..38349b087 100644 --- a/packages/cli/test/lib/sentryclirc-import.test.ts +++ b/packages/cli/test/lib/sentryclirc-import.test.ts @@ -69,7 +69,7 @@ describe("classifyRcFileLocation", () => { test("arbitrary path returns 'project-local'", () => { expect(classifyRcFileLocation("/tmp/some/project/.sentryclirc")).toBe( - "project-local" + "project-local", ); }); }); @@ -131,7 +131,7 @@ describe("isSameFileOrigin", () => { describe("buildImportPlan", () => { function makeFile( - overrides: Partial = {} + overrides: Partial = {}, ): DiscoveredRcFile { return { path: "/test/.sentryclirc", @@ -403,7 +403,7 @@ describe("discoverRcFiles", () => { const rcPath = join(configDir, ".sentryclirc"); writeFileSync( rcPath, - "[auth]\ntoken = test-token\n\n[defaults]\norg = my-org\n" + "[auth]\ntoken = test-token\n\n[defaults]\norg = my-org\n", ); const files = await discoverRcFiles(configDir); @@ -479,7 +479,7 @@ describe("maskToken", () => { expect(maskToken("123456789ab")).not.toContain("123456789ab"); // Longer tokens show partial but not full expect(maskToken("my-secret-token-value")).not.toBe( - "my-secret-token-value" + "my-secret-token-value", ); }); }); @@ -556,7 +556,7 @@ describe("executeImport — token guard", () => { describe("buildImportPlan — URL handling", () => { function makeFile( - overrides: Partial = {} + overrides: Partial = {}, ): DiscoveredRcFile { return { path: "/test/.sentryclirc", @@ -787,7 +787,7 @@ describe("discoverRcFiles — additional", () => { const rcPath = join(configDir, ".sentryclirc"); writeFileSync( rcPath, - "[auth]\ntoken = my-token\n\n[defaults]\nurl = https://sentry.example.com\norg = my-org\nproject = my-proj\n" + "[auth]\ntoken = my-token\n\n[defaults]\nurl = https://sentry.example.com\norg = my-org\nproject = my-proj\n", ); const files = await discoverRcFiles(configDir); @@ -823,12 +823,12 @@ describe("discoverRcFiles — additional", () => { // Project-local file has project writeFileSync( join(projectDir, ".sentryclirc"), - "[defaults]\nproject = local-proj\n" + "[defaults]\nproject = local-proj\n", ); // Global file has token + org writeFileSync( join(configDir, ".sentryclirc"), - "[auth]\ntoken = global-token\n\n[defaults]\norg = global-org\n" + "[auth]\ntoken = global-token\n\n[defaults]\norg = global-org\n", ); const files = await discoverRcFiles(projectDir); diff --git a/packages/cli/test/lib/sentryclirc.property.test.ts b/packages/cli/test/lib/sentryclirc.property.test.ts index f436eff5d..e59584c40 100644 --- a/packages/cli/test/lib/sentryclirc.property.test.ts +++ b/packages/cli/test/lib/sentryclirc.property.test.ts @@ -57,7 +57,7 @@ const tokenArb = array( { minLength: 5, maxLength: 20, - } + }, ).map((chars) => `sntrys_${chars.join("")}`); /** Generate a partial config (each field may or may not be present) */ @@ -67,7 +67,7 @@ const partialConfigArb = record( project: slugArb, token: tokenArb, }, - { requiredKeys: [] } + { requiredKeys: [] }, ); type PartialConfig = { org?: string; project?: string; token?: string }; @@ -117,7 +117,7 @@ describe("property: loadSentryCliRc", () => { writeFileSync( join(childDir, CONFIG_FILENAME), serializeConfig(childConfig), - "utf-8" + "utf-8", ); // Load with child only @@ -133,7 +133,7 @@ describe("property: loadSentryCliRc", () => { writeFileSync( join(parentDir, CONFIG_FILENAME), serializeConfig(parentConfig), - "utf-8" + "utf-8", ); // Load again with parent also present @@ -143,9 +143,9 @@ describe("property: loadSentryCliRc", () => { for (const field of fieldsChildOnly) { expect(resultBoth[field as keyof typeof resultBoth]).toBeDefined(); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -162,14 +162,14 @@ describe("property: loadSentryCliRc", () => { writeFileSync( join(parentDir, CONFIG_FILENAME), serializeConfig(parentConfig), - "utf-8" + "utf-8", ); // Write child config writeFileSync( join(childDir, CONFIG_FILENAME), serializeConfig(childConfig), - "utf-8" + "utf-8", ); const result = await loadSentryCliRc(childDir); @@ -184,9 +184,9 @@ describe("property: loadSentryCliRc", () => { if (childConfig.token) { expect(result.token).toBe(childConfig.token); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -203,14 +203,14 @@ describe("property: loadSentryCliRc", () => { writeFileSync( join(parentDir, CONFIG_FILENAME), serializeConfig(parentConfig), - "utf-8" + "utf-8", ); // Write child config writeFileSync( join(childDir, CONFIG_FILENAME), serializeConfig(childConfig), - "utf-8" + "utf-8", ); const result = await loadSentryCliRc(childDir); @@ -225,9 +225,9 @@ describe("property: loadSentryCliRc", () => { if (!childConfig.token && parentConfig.token) { expect(result.token).toBe(parentConfig.token); } - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/sentryclirc.test.ts b/packages/cli/test/lib/sentryclirc.test.ts index 220c3ea0d..e541b77ea 100644 --- a/packages/cli/test/lib/sentryclirc.test.ts +++ b/packages/cli/test/lib/sentryclirc.test.ts @@ -120,7 +120,7 @@ url = https://sentry.io/ [auth] token = sntrys_test -` +`, ); const result = await loadSentryCliRc(testDir); @@ -161,7 +161,7 @@ token = sntrys_test test("closest file wins: token from child, org from parent", async () => { writeRcFile( testDir, - "[defaults]\norg = parent-org\n[auth]\ntoken = parent-token" + "[defaults]\norg = parent-org\n[auth]\ntoken = parent-token", ); const childDir = join(testDir, "sub"); @@ -324,7 +324,7 @@ describe("applySentryCliRcEnvShim", () => { expect(readEnv("SENTRY_URL")).toBe("https://evil.example.com"); await expect(assertRcUrlTrusted(testDir)).rejects.toThrow( - /does not match|sentry auth login --url/ + /does not match|sentry auth login --url/, ); }); diff --git a/packages/cli/test/lib/shell.property.test.ts b/packages/cli/test/lib/shell.property.test.ts index c7f3d07b2..cc1451fb8 100644 --- a/packages/cli/test/lib/shell.property.test.ts +++ b/packages/cli/test/lib/shell.property.test.ts @@ -52,7 +52,7 @@ const pathPrefixArb = constantFrom( "/home/user/.local/bin", "/opt/homebrew/bin", "/nix/store/abc123-bash-5.2/bin", - "/snap/bin" + "/snap/bin", ); /** Generate absolute directory paths */ @@ -62,7 +62,7 @@ const directoryArb = constantFrom( "/usr/local/bin", "/opt/sentry/bin", "/home/user/bin", - "/tmp/test/bin" + "/tmp/test/bin", ); /** Generate home directory paths */ @@ -70,7 +70,7 @@ const homeDirArb = constantFrom( "/home/user", "/home/alice", "/Users/bob", - "/root" + "/root", ); /** Generate PATH strings from a set of directories */ @@ -88,7 +88,7 @@ describe("property: detectShellType", () => { const result = detectShellType(`${prefix}/${shell}`); expect(result).toBe(shell); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -100,11 +100,11 @@ describe("property: detectShellType", () => { knownShellArb, (prefix1, prefix2, shell) => { expect(detectShellType(`${prefix1}/${shell}`)).toBe( - detectShellType(`${prefix2}/${shell}`) + detectShellType(`${prefix2}/${shell}`), ); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -115,7 +115,7 @@ describe("property: detectShellType", () => { const result = detectShellType(`${prefix}/xonsh`); expect(allShellTypes).toContain(result); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -131,7 +131,7 @@ describe("property: getConfigCandidates", () => { const candidates = getConfigCandidates(shellType, homeDir); expect(candidates.length).toBeGreaterThan(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -143,7 +143,7 @@ describe("property: getConfigCandidates", () => { expect(path.startsWith(homeDir)).toBe(true); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -155,7 +155,7 @@ describe("property: getPathCommand", () => { const cmd = getPathCommand(shellType, dir); expect(cmd).toContain(dir); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -165,7 +165,7 @@ describe("property: getPathCommand", () => { const cmd = getPathCommand(shellType, dir); expect(cmd).toContain(`"${dir}`); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -177,7 +177,7 @@ describe("property: getPathCommand", () => { expect(getPathCommand(shell, dir)).toContain("export PATH="); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -190,7 +190,7 @@ describe("property: isInPath", () => { const fullPath = `${pathStr}:${dir}`; expect(isInPath(dir, fullPath)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -201,7 +201,7 @@ describe("property: isInPath", () => { property(pathStringArb, (pathStr) => { expect(isInPath(absentDir, pathStr)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -210,7 +210,7 @@ describe("property: isInPath", () => { property(directoryArb, (dir) => { expect(isInPath(dir, undefined)).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -221,7 +221,7 @@ describe("property: addToPath", () => { beforeEach(() => { testDir = join( "/tmp", - `shell-prop-${Date.now()}-${Math.random().toString(36).slice(2)}` + `shell-prop-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -243,7 +243,7 @@ describe("property: addToPath", () => { const second = await addToPath(configFile, dir, shellType); expect(second.modified).toBe(false); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -260,7 +260,7 @@ describe("property: addToPath", () => { const expectedCmd = getPathCommand(shellType, dir); expect(content).toContain(expectedCmd); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/shell.test.ts b/packages/cli/test/lib/shell.test.ts index a5e0571db..2e31fde69 100644 --- a/packages/cli/test/lib/shell.test.ts +++ b/packages/cli/test/lib/shell.test.ts @@ -27,7 +27,7 @@ describe("shell utilities", () => { const candidates = getConfigCandidates( "unknown", "/home/user", - "/home/user/.config" + "/home/user/.config", ); expect(candidates).toContain("/home/user/.bashrc"); expect(candidates).toContain("/home/user/.bash_profile"); @@ -41,7 +41,7 @@ describe("shell utilities", () => { beforeEach(() => { testDir = join( "/tmp", - `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -74,7 +74,7 @@ describe("shell utilities", () => { beforeEach(() => { testDir = join( "/tmp", - `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -105,7 +105,7 @@ describe("shell utilities", () => { beforeEach(() => { testDir = join( "/tmp", - `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -119,7 +119,7 @@ describe("shell utilities", () => { const result = await addToPath( configFile, "/home/user/.sentry/bin", - "bash" + "bash", ); expect(result.modified).toBe(true); @@ -136,7 +136,7 @@ describe("shell utilities", () => { const result = await addToPath( configFile, "/home/user/.sentry/bin", - "bash" + "bash", ); expect(result.modified).toBe(true); @@ -151,13 +151,13 @@ describe("shell utilities", () => { const configFile = join(testDir, ".bashrc"); writeFileSync( configFile, - '# sentry\nexport PATH="/home/user/.sentry/bin:$PATH"\n' + '# sentry\nexport PATH="/home/user/.sentry/bin:$PATH"\n', ); const result = await addToPath( configFile, "/home/user/.sentry/bin", - "bash" + "bash", ); expect(result.modified).toBe(false); @@ -171,14 +171,14 @@ describe("shell utilities", () => { const result = await addToPath( configFile, "/home/user/.sentry/bin", - "bash" + "bash", ); expect(result.modified).toBe(true); const content = await readFile(configFile, "utf-8"); expect(content).toContain( - "# existing content without newline\n\n# sentry\n" + "# existing content without newline\n\n# sentry\n", ); }); @@ -187,12 +187,12 @@ describe("shell utilities", () => { const result = await addToPath( configFile, "/home/user/.sentry/bin", - "bash" + "bash", ); expect(result.modified).toBe(false); expect(result.manualCommand).toBe( - 'export PATH="/home/user/.sentry/bin:$PATH"' + 'export PATH="/home/user/.sentry/bin:$PATH"', ); }); }); @@ -203,7 +203,7 @@ describe("shell utilities", () => { beforeEach(() => { testDir = join( "/tmp", - `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -216,7 +216,7 @@ describe("shell utilities", () => { const configFile = join(testDir, ".zshrc"); const result = await addToFpath( configFile, - "/home/user/.local/share/zsh/site-functions" + "/home/user/.local/share/zsh/site-functions", ); expect(result.modified).toBe(true); @@ -224,7 +224,7 @@ describe("shell utilities", () => { const content = await readFile(configFile, "utf-8"); expect(content).toContain( - 'fpath=("/home/user/.local/share/zsh/site-functions" $fpath)' + 'fpath=("/home/user/.local/share/zsh/site-functions" $fpath)', ); }); @@ -234,7 +234,7 @@ describe("shell utilities", () => { const result = await addToFpath( configFile, - "/home/user/.local/share/zsh/site-functions" + "/home/user/.local/share/zsh/site-functions", ); expect(result.modified).toBe(true); @@ -243,7 +243,7 @@ describe("shell utilities", () => { expect(content).toContain("# existing content"); expect(content).toContain("# sentry"); expect(content).toContain( - 'fpath=("/home/user/.local/share/zsh/site-functions" $fpath)' + 'fpath=("/home/user/.local/share/zsh/site-functions" $fpath)', ); }); @@ -251,12 +251,12 @@ describe("shell utilities", () => { const configFile = join(testDir, ".zshrc"); writeFileSync( configFile, - '# sentry\nfpath=("/home/user/.local/share/zsh/site-functions" $fpath)\n' + '# sentry\nfpath=("/home/user/.local/share/zsh/site-functions" $fpath)\n', ); const result = await addToFpath( configFile, - "/home/user/.local/share/zsh/site-functions" + "/home/user/.local/share/zsh/site-functions", ); expect(result.modified).toBe(false); @@ -269,14 +269,14 @@ describe("shell utilities", () => { const result = await addToFpath( configFile, - "/home/user/.local/share/zsh/site-functions" + "/home/user/.local/share/zsh/site-functions", ); expect(result.modified).toBe(true); const content = await readFile(configFile, "utf-8"); expect(content).toContain( - "# existing content without newline\n\n# sentry\n" + "# existing content without newline\n\n# sentry\n", ); }); @@ -284,12 +284,12 @@ describe("shell utilities", () => { const configFile = "/dev/null/impossible/path/.zshrc"; const result = await addToFpath( configFile, - "/home/user/.local/share/zsh/site-functions" + "/home/user/.local/share/zsh/site-functions", ); expect(result.modified).toBe(false); expect(result.manualCommand).toBe( - 'fpath=("/home/user/.local/share/zsh/site-functions" $fpath)' + 'fpath=("/home/user/.local/share/zsh/site-functions" $fpath)', ); }); }); @@ -300,7 +300,7 @@ describe("shell utilities", () => { beforeEach(() => { testDir = join( "/tmp", - `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}` + `shell-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(testDir, { recursive: true }); }); @@ -365,7 +365,7 @@ describe("isInPath", () => { test("returns true for an exact match", () => { expect(isInPath("/usr/local/bin", `/usr/bin${sep}/usr/local/bin`)).toBe( - true + true, ); }); diff --git a/packages/cli/test/lib/sixel-image.test.ts b/packages/cli/test/lib/sixel-image.test.ts index 6b3e7b8f4..d61c4d959 100644 --- a/packages/cli/test/lib/sixel-image.test.ts +++ b/packages/cli/test/lib/sixel-image.test.ts @@ -27,7 +27,7 @@ const ESC = "\x1b"; function solidImage( width: number, height: number, - rgba: [number, number, number, number] + rgba: [number, number, number, number], ): DecodedImage { const data = new Uint8Array(width * height * 4); for (let i = 0; i < width * height; i++) { @@ -50,7 +50,7 @@ function toPngBytes(img: DecodedImage): Uint8Array { function toJpegBytes(img: DecodedImage): Uint8Array { const { data } = encodeJpeg( { data: Buffer.from(img.data), width: img.width, height: img.height }, - 90 + 90, ); return new Uint8Array(data); } @@ -169,7 +169,7 @@ describe("readImageDimensions", () => { test("returns undefined for a truncated header", () => { expect( - readImageDimensions(new Uint8Array([0x89, 0x50]), "png") + readImageDimensions(new Uint8Array([0x89, 0x50]), "png"), ).toBeUndefined(); }); }); diff --git a/packages/cli/test/lib/sixel.test.ts b/packages/cli/test/lib/sixel.test.ts index c9686f5f2..bb50d7843 100644 --- a/packages/cli/test/lib/sixel.test.ts +++ b/packages/cli/test/lib/sixel.test.ts @@ -78,9 +78,9 @@ describe("parseSixelCaps", () => { (attrs) => { const reply = `${ESC}[?${attrs.join(";")}c`; expect(parseSixelCaps(reply).supported).toBe(attrs.includes(4)); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -124,7 +124,7 @@ describe("sixelFits", () => { test("declines when unsupported", () => { expect(sixelFits({ supported: false, cellWidth: 10 }, 200, 640)).toBe( - false + false, ); }); }); @@ -159,7 +159,7 @@ describe("selectGraphicsFormat", () => { selectGraphicsFormatFromAvailability("sixel", { kitty: true, sixel: false, - }) + }), ).toBe("kitty"); }); }); diff --git a/packages/cli/test/lib/snapshots/archive.test.ts b/packages/cli/test/lib/snapshots/archive.test.ts index 500abc3cf..4d577d821 100644 --- a/packages/cli/test/lib/snapshots/archive.test.ts +++ b/packages/cli/test/lib/snapshots/archive.test.ts @@ -25,7 +25,7 @@ function tempDir(): string { /** Yield a buffer as an async stream, split into fixed-size chunks. */ async function* streamOf( buffer: Uint8Array, - chunkSize = 8 + chunkSize = 8, ): AsyncIterable { for (let offset = 0; offset < buffer.length; offset += chunkSize) { yield buffer.subarray(offset, offset + chunkSize); @@ -136,7 +136,7 @@ describe("extractZipStream", () => { process.on("unhandledRejection", onUnhandled); try { await expect(extractZipStream(faulty(), out)).rejects.toThrow( - "network boom" + "network boom", ); await new Promise((r) => setTimeout(r, 20)); expect(unhandled).toEqual([]); diff --git a/packages/cli/test/lib/snapshots/diff.test.ts b/packages/cli/test/lib/snapshots/diff.test.ts index 143f51430..9f5530834 100644 --- a/packages/cli/test/lib/snapshots/diff.test.ts +++ b/packages/cli/test/lib/snapshots/diff.test.ts @@ -20,7 +20,7 @@ import { function png( width: number, height: number, - rgb: [number, number, number] + rgb: [number, number, number], ): Buffer { const image = new PNG({ width, height }); for (let i = 0; i < width * height * 4; i += 4) { @@ -36,7 +36,7 @@ function png( function jpeg( width: number, height: number, - rgb: [number, number, number] + rgb: [number, number, number], ): Buffer { const data = Buffer.alloc(width * height * 4); for (let i = 0; i < data.length; i += 4) { @@ -56,7 +56,7 @@ describe("compareImages", () => { png(4, 4, [10, 20, 30]), png(4, 4, [10, 20, 30]), "a.png", - OPTS + OPTS, ); expect(result.kind).toBe("match"); }); @@ -66,7 +66,7 @@ describe("compareImages", () => { png(4, 4, [255, 0, 0]), png(4, 4, [0, 255, 0]), "a.png", - OPTS + OPTS, ); expect(result.kind).toBe("changed"); if (result.kind === "changed") { @@ -82,7 +82,7 @@ describe("compareImages", () => { png(4, 4, [0, 0, 0]), png(8, 8, [0, 0, 0]), "a.png", - OPTS + OPTS, ); expect(result.kind).toBe("layout"); }); @@ -92,7 +92,7 @@ describe("compareImages", () => { jpeg(4, 4, [20, 40, 60]), jpeg(4, 4, [20, 40, 60]), "a.jpg", - OPTS + OPTS, ); expect(result.kind).toBe("match"); }); @@ -103,8 +103,8 @@ describe("compareImages", () => { Buffer.from("not a real png"), png(4, 4, [0, 0, 0]), "a.png", - OPTS - ) + OPTS, + ), ).toThrow(); }); }); diff --git a/packages/cli/test/lib/snapshots/images.test.ts b/packages/cli/test/lib/snapshots/images.test.ts index 058704b08..300753816 100644 --- a/packages/cli/test/lib/snapshots/images.test.ts +++ b/packages/cli/test/lib/snapshots/images.test.ts @@ -103,10 +103,10 @@ describe("validateImageSizes", () => { test("throws listing images over the pixel limit", () => { expect(() => validateImageSizes([img(8001, 5000)])).toThrow( - ValidationError + ValidationError, ); expect(() => validateImageSizes([img(8001, 5000)])).toThrow( - String(MAX_PIXELS_PER_IMAGE) + String(MAX_PIXELS_PER_IMAGE), ); }); }); @@ -132,7 +132,7 @@ describe("splitAndTrim", () => { describe("normalizeImageNames", () => { test("strips a leading ./ and backslashes → forward slashes", () => { expect(normalizeImageNames(["./img/a.png", "img\\b.png", "c.png"])).toEqual( - ["img/a.png", "img/b.png", "c.png"] + ["img/a.png", "img/b.png", "c.png"], ); }); }); diff --git a/packages/cli/test/lib/sourcemap/directive.test.ts b/packages/cli/test/lib/sourcemap/directive.test.ts index a14360cdb..053495e53 100644 --- a/packages/cli/test/lib/sourcemap/directive.test.ts +++ b/packages/cli/test/lib/sourcemap/directive.test.ts @@ -24,7 +24,7 @@ describe("parseSourceMappingDirective", () => { test("classifies an inline data URL", () => { const result = parse( - "//# sourceMappingURL=data:application/json;base64,e30=" + "//# sourceMappingURL=data:application/json;base64,e30=", ); expect(result?.kind).toBe("inline"); expect(result?.value).toBe("data:application/json;base64,e30="); @@ -32,7 +32,7 @@ describe("parseSourceMappingDirective", () => { test("classifies a remote URL", () => { expect( - parse("//# sourceMappingURL=https://cdn.example.com/app.js.map")?.kind + parse("//# sourceMappingURL=https://cdn.example.com/app.js.map")?.kind, ).toBe("remote"); }); @@ -42,7 +42,7 @@ describe("parseSourceMappingDirective", () => { test("tolerates a trailing CR (CRLF line)", () => { expect(parse("//# sourceMappingURL=app.js.map\r")?.value).toBe( - "app.js.map" + "app.js.map", ); }); diff --git a/packages/cli/test/lib/sourcemap/inject.test.ts b/packages/cli/test/lib/sourcemap/inject.test.ts index be05615a6..3a17527b6 100644 --- a/packages/cli/test/lib/sourcemap/inject.test.ts +++ b/packages/cli/test/lib/sourcemap/inject.test.ts @@ -245,11 +245,11 @@ describe("injectDirectory — inline sourcemaps", () => { // The rewritten inline directive carries the injected map. const m = js.match( - /sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)/ + /sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)/, ); expect(m).not.toBeNull(); const rewritten = JSON.parse( - Buffer.from(m?.[1] ?? "", "base64").toString("utf-8") + Buffer.from(m?.[1] ?? "", "base64").toString("utf-8"), ); expect(rewritten.debug_id).toBe(debugId); expect(rewritten.debugId).toBe(debugId); @@ -257,7 +257,7 @@ describe("injectDirectory — inline sourcemaps", () => { // injectedMapContent matches the rewritten inline map. expect( - JSON.parse((injectedMapContent ?? Buffer.alloc(0)).toString()) + JSON.parse((injectedMapContent ?? Buffer.alloc(0)).toString()), ).toEqual(rewritten); }); @@ -289,7 +289,7 @@ describe("injectDirectory — inline sourcemaps", () => { const jsPath = join(dir, "banner.js"); writeFileSync( jsPath, - `console.log(1)\n//# sourceMappingURL=${toDataUrl(SAMPLE_MAP)}\n/*! some-lib v1.2.3 | MIT */\n` + `console.log(1)\n//# sourceMappingURL=${toDataUrl(SAMPLE_MAP)}\n/*! some-lib v1.2.3 | MIT */\n`, ); const results = await injectDirectory(dir, { dryRun: true }); expect(results).toHaveLength(1); @@ -300,14 +300,14 @@ describe("injectDirectory — inline sourcemaps", () => { const jsPath = writeInline( "cli.js", SAMPLE_MAP, - "#!/usr/bin/env node\nconsole.log(1)\n" + "#!/usr/bin/env node\nconsole.log(1)\n", ); await injectDirectory(dir); const js = readFileSync(jsPath, "utf-8"); expect(js.startsWith("#!/usr/bin/env node\n")).toBe(true); // Snippet must follow the hashbang, not precede it. expect(js.indexOf("sentry-dbid-")).toBeGreaterThan( - js.indexOf("#!/usr/bin/env node") + js.indexOf("#!/usr/bin/env node"), ); }); @@ -318,7 +318,7 @@ describe("injectDirectory — inline sourcemaps", () => { const jsPath = writeInline( "twin.js", SAMPLE_MAP, - `const s = "//# sourceMappingURL=${fake}";\nconsole.log(s)\n` + `const s = "//# sourceMappingURL=${fake}";\nconsole.log(s)\n`, ); const results = await injectDirectory(dir); expect(results[0]?.injected).toBe(true); @@ -331,7 +331,7 @@ describe("injectDirectory — inline sourcemaps", () => { .find((l) => l.startsWith("//# sourceMappingURL=data:")); const realB64 = realLine?.match(/base64,([A-Za-z0-9+/=]+)/)?.[1] ?? ""; const realMap = JSON.parse( - Buffer.from(realB64, "base64").toString("utf-8") + Buffer.from(realB64, "base64").toString("utf-8"), ); expect(realMap.debug_id).toBe(results[0]?.debugId); expect(realMap.version).toBe(3); // the real SAMPLE_MAP, not the fake @@ -344,7 +344,7 @@ describe("injectDirectory — inline sourcemaps", () => { const bad = join(dir, "bad.js"); writeFileSync( bad, - "console.log(2)\n//# sourceMappingURL=data:application/json;base64,@@@nope@@@\n" + "console.log(2)\n//# sourceMappingURL=data:application/json;base64,@@@nope@@@\n", ); const results = await injectDirectory(dir, { dryRun: true }); @@ -375,10 +375,10 @@ describe("injectDirectory — debug ID uniqueness (regression #3350)", () => { /** Map results back to a `basename → debugId` object. */ function debugIdsByName( - results: Awaited> + results: Awaited>, ): Record { return Object.fromEntries( - results.map((r) => [r.jsPath.slice(dir.length + 1), r.debugId]) + results.map((r) => [r.jsPath.slice(dir.length + 1), r.debugId]), ); } @@ -401,11 +401,11 @@ describe("injectDirectory — debug ID uniqueness (regression #3350)", () => { const emptyInline = JSON.parse(EMPTY_MAP); writeFileSync( join(dir, "a.js"), - `console.log(1)\n//# sourceMappingURL=${toDataUrl(emptyInline)}\n` + `console.log(1)\n//# sourceMappingURL=${toDataUrl(emptyInline)}\n`, ); writeFileSync( join(dir, "b.js"), - `console.log(22)\n//# sourceMappingURL=${toDataUrl(emptyInline)}\n` + `console.log(22)\n//# sourceMappingURL=${toDataUrl(emptyInline)}\n`, ); const ids = debugIdsByName(await injectDirectory(dir)); @@ -469,7 +469,7 @@ describe("injectDirectory — pre-existing sourcemap debug ID", () => { */ function writePluginPair( name: string, - mapExtra: Record + mapExtra: Record, ): { jsPath: string; mapPath: string; js: string; map: string } { const jsPath = join(dir, name); const mapPath = `${jsPath}.map`; @@ -524,7 +524,7 @@ describe("injectDirectory — pre-existing sourcemap debug ID", () => { expect(readFileSync(jsPath, "utf-8")).toBe(js); // The map is uploaded exactly as decoded — no snippet, so no line offset. const uploaded = JSON.parse( - (results[0]?.injectedMapContent ?? Buffer.alloc(0)).toString() + (results[0]?.injectedMapContent ?? Buffer.alloc(0)).toString(), ); expect(uploaded.debug_id).toBe(PLUGIN_ID); expect(uploaded.mappings).toBe(BASE_MAP.mappings); @@ -535,11 +535,11 @@ describe("injectDirectory — pre-existing sourcemap debug ID", () => { const jsId = "99999999-8888-7777-6666-555555555555"; writeFileSync( jsPath, - `console.log(1)\n//# sourceMappingURL=bundle.js.map\n//# debugId=${jsId}\n` + `console.log(1)\n//# sourceMappingURL=bundle.js.map\n//# debugId=${jsId}\n`, ); writeFileSync( `${jsPath}.map`, - JSON.stringify({ ...BASE_MAP, debug_id: PLUGIN_ID }) + JSON.stringify({ ...BASE_MAP, debug_id: PLUGIN_ID }), ); const results = await injectDirectory(dir); @@ -564,7 +564,7 @@ describe("injectDirectory — pre-existing sourcemap debug ID", () => { const js = readFileSync(pair.jsPath, "utf-8"); expect(js).toContain(`//# debugId=${results[0]?.debugId}`); expect(JSON.parse(readFileSync(pair.mapPath, "utf-8")).debug_id).toBe( - results[0]?.debugId + results[0]?.debugId, ); }); @@ -613,144 +613,144 @@ describe("injectDirectory — pre-existing sourcemap debug ID", () => { }); }); -describe.each([ - "external", - "inline", -] as const)("injectDirectory — %s map runtime registration", (kind) => { - let dir: string; - const debugId = "11111111-2222-5333-9444-555555555555"; +describe.each(["external", "inline"] as const)( + "injectDirectory — %s map runtime registration", + (kind) => { + let dir: string; + const debugId = "11111111-2222-5333-9444-555555555555"; - beforeEach(() => { - dir = mkdtempSync(join(tmpdir(), "sentry-inject-runtime-")); - }); + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), "sentry-inject-runtime-")); + }); - afterEach(() => { - rmSync(dir, { recursive: true, force: true }); - }); + afterEach(() => { + rmSync(dir, { recursive: true, force: true }); + }); - test.each([ - "legacy", - "formatted", - ])("preserves an existing %s registration without an identifier marker", async (format) => { - const jsPath = join(dir, "main.js"); - // Legacy CLI snippets have no _sentryDebugIdIdentifier marker. - const snippet = - format === "legacy" - ? `!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="${debugId}")}catch(e){}}();` - : `(() => { + test.each(["legacy", "formatted"])( + "preserves an existing %s registration without an identifier marker", + async (format) => { + const jsPath = join(dir, "main.js"); + // Legacy CLI snippets have no _sentryDebugIdIdentifier marker. + const snippet = + format === "legacy" + ? `!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="${debugId}")}catch(e){}}();` + : `(() => { var root = globalThis; var stack = new root.Error().stack; root['_sentryDebugIds'] = root['_sentryDebugIds'] || {}; root['_sentryDebugIds'][stack] = '${debugId}'; })();`; - const mapContent = JSON.stringify({ - version: 3, - sources: ["main.ts"], - names: [], - mappings: ";AAAA", - debugId, - }); - const mapUrl = - kind === "inline" - ? `data:application/json;base64,${Buffer.from(mapContent).toString("base64")}` - : "main.js.map"; - const js = `${snippet}\n//# debugId=${debugId}\n//# sourceMappingURL=${mapUrl}\n`; - writeFileSync(jsPath, js); - if (kind === "external") { - writeFileSync(`${jsPath}.map`, mapContent); - } - - for (const options of [{ dryRun: true }, {}]) { - expect((await injectDirectory(dir, options))[0]).toMatchObject({ - debugId, - injected: false, - }); - expect(readFileSync(jsPath, "utf-8")).toBe(js); - if (kind === "external") { - expect(readFileSync(`${jsPath}.map`, "utf-8")).toBe(mapContent); - } - } - const sandbox: { _sentryDebugIds?: Record } = {}; - runInNewContext(js, sandbox, { filename: "main.js" }); - expect(Object.values(sandbox._sentryDebugIds ?? {})).toEqual([debugId]); - }); - - test.each([ - "comment", - "debugId", - "debug_id", - "comment and map", - ])("registers an ID from %s exactly once", async (source) => { - const jsPath = join(dir, "main.js"); - const hasComment = source.startsWith("comment"); - const mapContent = JSON.stringify({ - version: 3, - sources: ["main.ts"], - sourcesContent: ["globalThis.answer = 42;"], - names: [], - mappings: "AAAA", - ...(source === "comment" - ? {} - : { [source === "debug_id" ? "debug_id" : "debugId"]: debugId }), - }); - const mapUrl = - kind === "inline" - ? `data:application/json;base64,${Buffer.from(mapContent).toString("base64")}` - : "main.js.map"; - // SDK readers mention the registry without registering a bundle ID. - const js = - "globalThis.answer = 42; globalThis.readIds = () => globalThis._sentryDebugIds;\n" + - (hasComment ? `//# debugId=${debugId}\n` : "") + - `//# sourceMappingURL=${mapUrl}\n`; - writeFileSync(jsPath, js); - if (kind === "external") { - writeFileSync(`${jsPath}.map`, mapContent); - } - - const preview = await injectDirectory(dir, { dryRun: true }); - expect(preview[0]).toMatchObject({ debugId, injected: true }); - expect(readFileSync(jsPath, "utf-8")).toBe(js); - if (kind === "external") { - expect(readFileSync(`${jsPath}.map`, "utf-8")).toBe(mapContent); - } - - const results = await injectDirectory(dir); - expect(results[0]).toMatchObject({ debugId, injected: true }); - const output = readFileSync(jsPath, "utf-8"); - const sandbox: { - answer?: number; - _sentryDebugIds?: Record; - } = {}; - runInNewContext(output, sandbox, { filename: "main.js" }); - expect(sandbox.answer).toBe(42); - expect(Object.values(sandbox._sentryDebugIds ?? {})).toEqual([debugId]); - expect(output.match(/\/\/# debugId=/g)).toHaveLength(1); - - const outputMap = - kind === "external" - ? readFileSync(`${jsPath}.map`, "utf-8") - : results[0]?.injectedMapContent?.toString("utf-8"); - expect(JSON.parse(outputMap ?? "{}")).toMatchObject({ - debugId, - debug_id: debugId, - mappings: ";AAAA", - }); - if (kind === "inline") { - const url = output.match(/\/\/# sourceMappingURL=(\S+)/)?.[1]; - expect(tryDecodeInlineSourcemap(url ?? "")?.json).toBe(outputMap); - } + const mapContent = JSON.stringify({ + version: 3, + sources: ["main.ts"], + names: [], + mappings: ";AAAA", + debugId, + }); + const mapUrl = + kind === "inline" + ? `data:application/json;base64,${Buffer.from(mapContent).toString("base64")}` + : "main.js.map"; + const js = `${snippet}\n//# debugId=${debugId}\n//# sourceMappingURL=${mapUrl}\n`; + writeFileSync(jsPath, js); + if (kind === "external") { + writeFileSync(`${jsPath}.map`, mapContent); + } + + for (const options of [{ dryRun: true }, {}]) { + expect((await injectDirectory(dir, options))[0]).toMatchObject({ + debugId, + injected: false, + }); + expect(readFileSync(jsPath, "utf-8")).toBe(js); + if (kind === "external") { + expect(readFileSync(`${jsPath}.map`, "utf-8")).toBe(mapContent); + } + } + const sandbox: { _sentryDebugIds?: Record } = {}; + runInNewContext(js, sandbox, { filename: "main.js" }); + expect(Object.values(sandbox._sentryDebugIds ?? {})).toEqual([debugId]); + }, + ); - const second = await injectDirectory(dir); - expect(second[0]).toMatchObject({ debugId, injected: false }); - expect(readFileSync(jsPath, "utf-8")).toBe(output); - expect( - kind === "external" - ? readFileSync(`${jsPath}.map`, "utf-8") - : second[0]?.injectedMapContent?.toString("utf-8") - ).toBe(outputMap); - expect((await injectDirectory(dir, { dryRun: true }))[0]).toMatchObject({ - debugId, - injected: false, - }); - }); -}); + test.each(["comment", "debugId", "debug_id", "comment and map"])( + "registers an ID from %s exactly once", + async (source) => { + const jsPath = join(dir, "main.js"); + const hasComment = source.startsWith("comment"); + const mapContent = JSON.stringify({ + version: 3, + sources: ["main.ts"], + sourcesContent: ["globalThis.answer = 42;"], + names: [], + mappings: "AAAA", + ...(source === "comment" + ? {} + : { [source === "debug_id" ? "debug_id" : "debugId"]: debugId }), + }); + const mapUrl = + kind === "inline" + ? `data:application/json;base64,${Buffer.from(mapContent).toString("base64")}` + : "main.js.map"; + // SDK readers mention the registry without registering a bundle ID. + const js = + "globalThis.answer = 42; globalThis.readIds = () => globalThis._sentryDebugIds;\n" + + (hasComment ? `//# debugId=${debugId}\n` : "") + + `//# sourceMappingURL=${mapUrl}\n`; + writeFileSync(jsPath, js); + if (kind === "external") { + writeFileSync(`${jsPath}.map`, mapContent); + } + + const preview = await injectDirectory(dir, { dryRun: true }); + expect(preview[0]).toMatchObject({ debugId, injected: true }); + expect(readFileSync(jsPath, "utf-8")).toBe(js); + if (kind === "external") { + expect(readFileSync(`${jsPath}.map`, "utf-8")).toBe(mapContent); + } + + const results = await injectDirectory(dir); + expect(results[0]).toMatchObject({ debugId, injected: true }); + const output = readFileSync(jsPath, "utf-8"); + const sandbox: { + answer?: number; + _sentryDebugIds?: Record; + } = {}; + runInNewContext(output, sandbox, { filename: "main.js" }); + expect(sandbox.answer).toBe(42); + expect(Object.values(sandbox._sentryDebugIds ?? {})).toEqual([debugId]); + expect(output.match(/\/\/# debugId=/g)).toHaveLength(1); + + const outputMap = + kind === "external" + ? readFileSync(`${jsPath}.map`, "utf-8") + : results[0]?.injectedMapContent?.toString("utf-8"); + expect(JSON.parse(outputMap ?? "{}")).toMatchObject({ + debugId, + debug_id: debugId, + mappings: ";AAAA", + }); + if (kind === "inline") { + const url = output.match(/\/\/# sourceMappingURL=(\S+)/)?.[1]; + expect(tryDecodeInlineSourcemap(url ?? "")?.json).toBe(outputMap); + } + + const second = await injectDirectory(dir); + expect(second[0]).toMatchObject({ debugId, injected: false }); + expect(readFileSync(jsPath, "utf-8")).toBe(output); + expect( + kind === "external" + ? readFileSync(`${jsPath}.map`, "utf-8") + : second[0]?.injectedMapContent?.toString("utf-8"), + ).toBe(outputMap); + expect((await injectDirectory(dir, { dryRun: true }))[0]).toMatchObject( + { + debugId, + injected: false, + }, + ); + }, + ); + }, +); diff --git a/packages/cli/test/lib/sourcemap/inline-sourcemap.test.ts b/packages/cli/test/lib/sourcemap/inline-sourcemap.test.ts index 1537075f1..a46bd0929 100644 --- a/packages/cli/test/lib/sourcemap/inline-sourcemap.test.ts +++ b/packages/cli/test/lib/sourcemap/inline-sourcemap.test.ts @@ -26,17 +26,17 @@ function toDataUrl(value: unknown, charset?: string): string { describe("isInlineSourcemapUrl", () => { test("true for data:application/json URLs", () => { expect(isInlineSourcemapUrl("data:application/json;base64,e30=")).toBe( - true + true, ); expect( - isInlineSourcemapUrl("data:application/json;charset=utf-8;base64,e30=") + isInlineSourcemapUrl("data:application/json;charset=utf-8;base64,e30="), ).toBe(true); }); test("false for external/remote URLs", () => { expect(isInlineSourcemapUrl("app.js.map")).toBe(false); expect(isInlineSourcemapUrl("https://cdn.example.com/app.js.map")).toBe( - false + false, ); }); }); @@ -52,13 +52,13 @@ describe("tryDecodeInlineSourcemap", () => { test("preserves the charset prefix", () => { const decoded = tryDecodeInlineSourcemap(toDataUrl({}, "utf-8")); expect(decoded?.dataUrlPrefix).toBe( - "data:application/json;charset=utf-8;base64," + "data:application/json;charset=utf-8;base64,", ); }); test("returns undefined for invalid base64 (non-fatal)", () => { expect( - tryDecodeInlineSourcemap("data:application/json;base64,@@@not-base64@@@") + tryDecodeInlineSourcemap("data:application/json;base64,@@@not-base64@@@"), ).toBeUndefined(); }); @@ -66,7 +66,7 @@ describe("tryDecodeInlineSourcemap", () => { // "not json" base64-encoded — decodes cleanly but is not JSON. const blob = Buffer.from("not json").toString("base64"); expect( - tryDecodeInlineSourcemap(`data:application/json;base64,${blob}`) + tryDecodeInlineSourcemap(`data:application/json;base64,${blob}`), ).toBeUndefined(); }); @@ -81,12 +81,12 @@ describe("property: inline sourcemap round-trip", () => { property(dictionary(string(), string()), (obj) => { const dataUrl = encodeInlineSourcemap( obj, - "data:application/json;base64," + "data:application/json;base64,", ); const decoded = tryDecodeInlineSourcemap(dataUrl); expect(decoded?.map).toEqual(obj); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -95,11 +95,11 @@ describe("property: inline sourcemap round-trip", () => { property(dictionary(string(), string()), (obj) => { const prefix = "data:application/json;charset=utf-8;base64,"; const decoded = tryDecodeInlineSourcemap( - encodeInlineSourcemap(obj, prefix) + encodeInlineSourcemap(obj, prefix), ); expect(decoded?.dataUrlPrefix).toBe(prefix); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/sourcemap/resolve.test.ts b/packages/cli/test/lib/sourcemap/resolve.test.ts index 987bad152..50ccfb379 100644 --- a/packages/cli/test/lib/sourcemap/resolve.test.ts +++ b/packages/cli/test/lib/sourcemap/resolve.test.ts @@ -51,7 +51,7 @@ describe("resolveDirectorySourcemaps", () => { test("classifies an inline data: sourceMappingURL", async () => { write( "inline.js", - "console.log(1)\n//# sourceMappingURL=data:application/json;base64,e30=\n" + "console.log(1)\n//# sourceMappingURL=data:application/json;base64,e30=\n", ); const results = await resolveDirectorySourcemaps(dir); @@ -63,7 +63,7 @@ describe("resolveDirectorySourcemaps", () => { test("classifies a remote sourceMappingURL", async () => { write( "remote.js", - "console.log(1)\n//# sourceMappingURL=https://cdn.example.com/remote.js.map\n" + "console.log(1)\n//# sourceMappingURL=https://cdn.example.com/remote.js.map\n", ); const results = await resolveDirectorySourcemaps(dir); diff --git a/packages/cli/test/lib/sourcemap/zip.test.ts b/packages/cli/test/lib/sourcemap/zip.test.ts index 77aac7c6e..4756598d6 100644 --- a/packages/cli/test/lib/sourcemap/zip.test.ts +++ b/packages/cli/test/lib/sourcemap/zip.test.ts @@ -110,62 +110,62 @@ describe("ZipWriter", () => { }); }); -describe.each([ - "deflate", - "stored", -] as const)("property: ZipWriter round-trip (%s)", (compression) => { - test("arbitrary string content survives write → extract", async () => { - await fcAssert( - asyncProperty( - string({ minLength: 0, maxLength: 10_000 }), - async (input) => { - const zipPath = join( - tmpDir, - `prop-${compression}-${Date.now()}-${Math.random()}.zip` - ); - const zip = await ZipWriter.create(zipPath, { compression }); - await zip.addEntry("data.txt", Buffer.from(input, "utf-8")); - await zip.finalize(); - - const proc = spawnSync("unzip", ["-p", zipPath, "data.txt"], { - stdio: ["pipe", "pipe", "pipe"], - maxBuffer: 50_000, - }); - expect(proc.status).toBe(0); - expect(proc.stdout.toString()).toBe(input); - } - ), - { numRuns: DEFAULT_NUM_RUNS } - ); - }); +describe.each(["deflate", "stored"] as const)( + "property: ZipWriter round-trip (%s)", + (compression) => { + test("arbitrary string content survives write → extract", async () => { + await fcAssert( + asyncProperty( + string({ minLength: 0, maxLength: 10_000 }), + async (input) => { + const zipPath = join( + tmpDir, + `prop-${compression}-${Date.now()}-${Math.random()}.zip`, + ); + const zip = await ZipWriter.create(zipPath, { compression }); + await zip.addEntry("data.txt", Buffer.from(input, "utf-8")); + await zip.finalize(); + + const proc = spawnSync("unzip", ["-p", zipPath, "data.txt"], { + stdio: ["pipe", "pipe", "pipe"], + maxBuffer: 50_000, + }); + expect(proc.status).toBe(0); + expect(proc.stdout.toString()).toBe(input); + }, + ), + { numRuns: DEFAULT_NUM_RUNS }, + ); + }); - test("arbitrary binary content survives write → extract", async () => { - await fcAssert( - asyncProperty( - // minLength: 1 — empty files have a separate unit test; - // unzip -p returns exit code 9 for empty entries on some systems - uint8Array({ minLength: 1, maxLength: 10_000 }), - async (input) => { - const zipPath = join( - tmpDir, - `prop-bin-${compression}-${Date.now()}-${Math.random()}.zip` - ); - const zip = await ZipWriter.create(zipPath, { compression }); - await zip.addEntry("data.bin", Buffer.from(input)); - await zip.finalize(); - - const proc = spawnSync("unzip", ["-p", zipPath, "data.bin"], { - stdio: ["pipe", "pipe", "pipe"], - maxBuffer: 50_000, - }); - expect(proc.status).toBe(0); - expect(Buffer.from(proc.stdout)).toEqual(Buffer.from(input)); - } - ), - { numRuns: DEFAULT_NUM_RUNS } - ); - }); -}); + test("arbitrary binary content survives write → extract", async () => { + await fcAssert( + asyncProperty( + // minLength: 1 — empty files have a separate unit test; + // unzip -p returns exit code 9 for empty entries on some systems + uint8Array({ minLength: 1, maxLength: 10_000 }), + async (input) => { + const zipPath = join( + tmpDir, + `prop-bin-${compression}-${Date.now()}-${Math.random()}.zip`, + ); + const zip = await ZipWriter.create(zipPath, { compression }); + await zip.addEntry("data.bin", Buffer.from(input)); + await zip.finalize(); + + const proc = spawnSync("unzip", ["-p", zipPath, "data.bin"], { + stdio: ["pipe", "pipe", "pipe"], + maxBuffer: 50_000, + }); + expect(proc.status).toBe(0); + expect(Buffer.from(proc.stdout)).toEqual(Buffer.from(input)); + }, + ), + { numRuns: DEFAULT_NUM_RUNS }, + ); + }); + }, +); describe("ZipWriter compression mode", () => { // The local file header records the entry's compression method at diff --git a/packages/cli/test/lib/span-tree.test.ts b/packages/cli/test/lib/span-tree.test.ts index e77c48045..4ec36d8a5 100644 --- a/packages/cli/test/lib/span-tree.test.ts +++ b/packages/cli/test/lib/span-tree.test.ts @@ -3,7 +3,7 @@ */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as apiClient from "../../src/lib/api-client.js"; import { getSpanTreeLines } from "../../src/lib/span-tree.js"; import type { SentryEvent } from "../../src/types/index.js"; diff --git a/packages/cli/test/lib/telemetry-credentials.test.ts b/packages/cli/test/lib/telemetry-credentials.test.ts index 191376e92..27f3d9423 100644 --- a/packages/cli/test/lib/telemetry-credentials.test.ts +++ b/packages/cli/test/lib/telemetry-credentials.test.ts @@ -17,7 +17,7 @@ import { import { afterEach, describe, expect, test, vi } from "vitest"; import { extractMessagePrefix } from "../../src/lib/error-reporting.js"; import { redactTelemetryEnvelope } from "../../src/lib/telemetry/credential-redaction.js"; -// biome-ignore lint/performance/noNamespaceImport: spy on the outbound transport factory +// oxlint-disable-next-line sentry-cli/no-namespace-import -- spy on the outbound transport factory import * as transportModule from "../../src/lib/telemetry/zstd-transport.js"; import { initSentry } from "../../src/lib/telemetry.js"; @@ -43,7 +43,7 @@ function captureTelemetry() { client.init(); function serializedEnvelope(type: string): string { const envelope = send.mock.calls.find(([entry]) => - entry[1].some(([header]) => header.type === type) + entry[1].some(([header]) => header.type === type), )?.[0]; if (!envelope) { throw new Error(`Expected a ${type} envelope`); @@ -90,7 +90,7 @@ describe("telemetry credential boundaries", () => { test("scrubs exception causes in the outgoing Sentry envelope", async () => { const capture = captureTelemetry(); captureException( - new TypeError(headerError, { cause: new Error(`Rejected ${token}`) }) + new TypeError(headerError, { cause: new Error(`Rejected ${token}`) }), ); await capture.client.flush(1000); @@ -104,7 +104,7 @@ describe("telemetry credential boundaries", () => { test("scrubs logs after the SDK adds scope attributes and fmt parameters", async () => { const capture = captureTelemetry(); const frozen = Object.freeze({ token }); - // biome-ignore lint/style/useConsistentBuiltinInstantiation: regression coverage for boxed log parameters + // regression coverage for boxed log parameters const boxed = new String(token); withScope((scope) => { scope.setAttribute("scope.token", token); @@ -161,7 +161,7 @@ describe("telemetry credential boundaries", () => { test("redacts before deriving a grouping key from the first line", () => { expect(extractMessagePrefix(headerError, 4)).toBe( - "Headers.set: is an invalid" + "Headers.set: is an invalid", ); }); diff --git a/packages/cli/test/lib/telemetry-session.test.ts b/packages/cli/test/lib/telemetry-session.test.ts index 7c10c7c98..36f146586 100644 --- a/packages/cli/test/lib/telemetry-session.test.ts +++ b/packages/cli/test/lib/telemetry-session.test.ts @@ -11,7 +11,7 @@ * with other telemetry tests. */ -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterEach, describe, expect, test, vi } from "vitest"; import { diff --git a/packages/cli/test/lib/telemetry.test.ts b/packages/cli/test/lib/telemetry.test.ts index fc5e1123d..719e8e03e 100644 --- a/packages/cli/test/lib/telemetry.test.ts +++ b/packages/cli/test/lib/telemetry.test.ts @@ -6,7 +6,7 @@ import { chmodSync, mkdirSync, rmSync } from "node:fs"; import { setTimeout as sleep } from "node:timers/promises"; -// biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking +// oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as Sentry from "@sentry/node-core/light"; import { afterAll, @@ -59,7 +59,7 @@ afterAll(() => { if (!preTestListeners.has(listener)) { process.removeListener( "beforeExit", - listener as (...args: unknown[]) => void + listener as (...args: unknown[]) => void, ); } } @@ -122,7 +122,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw new Error("test error"); - }) + }), ).rejects.toThrow("test error"); }); @@ -131,7 +131,7 @@ describe("withTelemetry", () => { withTelemetry(async () => { await sleep(1); throw new Error("async error"); - }) + }), ).rejects.toThrow("async error"); }); @@ -172,7 +172,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); }); @@ -193,7 +193,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); }); @@ -202,7 +202,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); }); @@ -210,7 +210,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw new Error("unexpected bug"); - }) + }), ).rejects.toThrow("unexpected bug"); }); @@ -225,7 +225,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); expect(captureSpy).not.toHaveBeenCalled(); captureSpy.mockRestore(); @@ -237,7 +237,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); expect(captureSpy).not.toHaveBeenCalled(); captureSpy.mockRestore(); @@ -250,18 +250,18 @@ describe("withTelemetry", () => { "Not found", 404, "detail", - "/api/0/organizations/foo/" + "/api/0/organizations/foo/", ); await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); // Silenced: no captureException expect(captureSpy).not.toHaveBeenCalled(); // Metric emitted with normalized endpoint attribute const silencedCall = metricSpy.mock.calls.find( - (c) => c[0] === "cli.error.silenced" + (c) => c[0] === "cli.error.silenced", ); expect(silencedCall).toBeDefined(); expect(silencedCall?.[2]).toMatchObject({ @@ -282,12 +282,12 @@ describe("withTelemetry", () => { "Server error", 500, "Internal", - "/api/0/organizations/foo/" + "/api/0/organizations/foo/", ); await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); // Captured via reportCliError → Sentry.withScope expect(withScopeSpy).toHaveBeenCalled(); @@ -317,18 +317,18 @@ describe("withTelemetry", () => { const { ContextError } = await import("../../src/lib/errors.js"); const error = new ContextError( "Organization and project", - "sentry issue view //" + "sentry issue view //", ); await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); // ContextError is no longer silenced — its volume drives auto-detection // and UX improvements, so it must be reported to Sentry. expect(captureSpy).toHaveBeenCalled(); const silencedCall = metricSpy.mock.calls.find( - (c) => c[0] === "cli.error.silenced" + (c) => c[0] === "cli.error.silenced", ); expect(silencedCall).toBeUndefined(); // ...but the session must NOT be marked crashed. @@ -355,13 +355,13 @@ describe("withTelemetry", () => { } as unknown as Sentry.Scope); try { await expect( - withTelemetry(() => formatAuthHeader("first-part\nsecond-part")) + withTelemetry(() => formatAuthHeader("first-part\nsecond-part")), ).rejects.toThrow(MalformedAuthTokenError); expect(captureSpy).toHaveBeenCalledExactlyOnceWith( - expect.any(MalformedAuthTokenError) + expect.any(MalformedAuthTokenError), ); expect( - metricSpy.mock.calls.find((c) => c[0] === "cli.error.silenced") + metricSpy.mock.calls.find((c) => c[0] === "cli.error.silenced"), ).toBeUndefined(); expect(session.status).toBe("ok"); } finally { @@ -390,7 +390,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); expect(session.status).toBe("crashed"); isolationScopeSpy.mockRestore(); @@ -416,7 +416,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); expect(session.status).toBe("crashed"); isolationScopeSpy.mockRestore(); @@ -445,7 +445,7 @@ describe("withTelemetry", () => { await expect( withTelemetry(() => { throw error; - }) + }), ).rejects.toThrow(error); expect(session.status).toBe("ok"); isolationScopeSpy.mockRestore(); @@ -465,8 +465,8 @@ describe("isUserApiError", () => { // request — a CLI bug, not a user API error. expect( isUserApiError( - new ApiError("bad", 400, "Error parsing search query: invalid status") - ) + new ApiError("bad", 400, "Error parsing search query: invalid status"), + ), ).toBe(false); }); @@ -476,13 +476,13 @@ describe("isUserApiError", () => { test("returns true for 403 Forbidden", () => { expect(isUserApiError(new ApiError("Forbidden", 403, "No access"))).toBe( - true + true, ); }); test("returns true for 404 Not Found", () => { expect( - isUserApiError(new ApiError("Not found", 404, "Issue not found")) + isUserApiError(new ApiError("Not found", 404, "Issue not found")), ).toBe(true); }); @@ -645,13 +645,13 @@ describe("setOrgProjectContext", () => { test("handles single org/project", () => { expect(() => - setOrgProjectContext(["my-org"], ["my-project"]) + setOrgProjectContext(["my-org"], ["my-project"]), ).not.toThrow(); }); test("handles multiple orgs/projects", () => { expect(() => - setOrgProjectContext(["org1", "org2"], ["proj1", "proj2"]) + setOrgProjectContext(["org1", "org2"], ["proj1", "proj2"]), ).not.toThrow(); }); }); @@ -747,7 +747,7 @@ describe("setFlagContext", () => { expect(setTagSpy).toHaveBeenCalledWith("flag.no-modify-path", "true"); expect(setTagSpy).toHaveBeenCalledWith( "flag.some-very-long-flag-name", - "value" + "value", ); }); @@ -824,7 +824,7 @@ describe("withHttpSpan", () => { await expect( withHttpSpan("POST", "/test", async () => { throw new Error("http error"); - }) + }), ).rejects.toThrow("http error"); }); }); @@ -839,7 +839,7 @@ describe("withDbSpan", () => { expect(() => withDbSpan("testOp", () => { throw new Error("db error"); - }) + }), ).toThrow("db error"); }); }); @@ -854,7 +854,7 @@ describe("withSerializeSpan", () => { expect(() => withSerializeSpan("format", () => { throw new Error("serialize error"); - }) + }), ).toThrow("serialize error"); }); }); @@ -877,7 +877,7 @@ describe("withTracing", () => { await expect( withTracing("test", "test.op", () => { throw new Error("sync error"); - }) + }), ).rejects.toThrow("sync error"); }); @@ -886,7 +886,7 @@ describe("withTracing", () => { withTracing("test", "test.op", async () => { await sleep(1); throw new Error("async error"); - }) + }), ).rejects.toThrow("async error"); }); @@ -926,7 +926,7 @@ describe("withFsSpan", () => { await expect( withFsSpan("readFile", () => { throw new Error("fs error"); - }) + }), ).rejects.toThrow("fs error"); }); }); @@ -953,7 +953,7 @@ describe("withTracingSpan", () => { await expect( withTracingSpan("test", "test.op", () => { throw new Error("test error"); - }) + }), ).rejects.toThrow("test error"); }); @@ -1013,14 +1013,14 @@ describe("createWizardPromptTelemetry", () => { "wizard.user_wait_ms", 25, "millisecond", - expect.anything() + expect.anything(), ); expect(setMeasurementSpy).toHaveBeenNthCalledWith( 2, "wizard.user_wait_ms", 40, "millisecond", - expect.anything() + expect.anything(), ); expect(metricSpy).toHaveBeenCalledWith("wizard.user_wait_ms", 25, { attributes: { @@ -1289,56 +1289,56 @@ describe("getSentryTracePropagationTargets", () => { test("matches SaaS regional URLs", () => { const targets = getSentryTracePropagationTargets(); const regexTargets = targets.filter( - (t): t is RegExp => t instanceof RegExp + (t): t is RegExp => t instanceof RegExp, ); expect( - regexTargets.some((r) => r.test("https://us.sentry.io/api/0/")) + regexTargets.some((r) => r.test("https://us.sentry.io/api/0/")), ).toBe(true); expect( - regexTargets.some((r) => r.test("https://de.sentry.io/api/0/")) + regexTargets.some((r) => r.test("https://de.sentry.io/api/0/")), ).toBe(true); expect( regexTargets.some((r) => - r.test("https://o1234.ingest.us.sentry.io/api/0/") - ) + r.test("https://o1234.ingest.us.sentry.io/api/0/"), + ), ).toBe(true); }); test("matches bare sentry.io", () => { const targets = getSentryTracePropagationTargets(); const regexTargets = targets.filter( - (t): t is RegExp => t instanceof RegExp + (t): t is RegExp => t instanceof RegExp, ); expect(regexTargets.some((r) => r.test("https://sentry.io/api/0/"))).toBe( - true + true, ); }); test("does not match non-sentry URLs", () => { const targets = getSentryTracePropagationTargets(); const regexTargets = targets.filter( - (t): t is RegExp => t instanceof RegExp + (t): t is RegExp => t instanceof RegExp, ); expect(regexTargets.some((r) => r.test("https://example.com/api/0/"))).toBe( - false + false, ); expect( - regexTargets.some((r) => r.test("https://not-sentry.io/api/0/")) + regexTargets.some((r) => r.test("https://not-sentry.io/api/0/")), ).toBe(false); }); test("does not match domains beyond sentry.io TLD boundary", () => { const targets = getSentryTracePropagationTargets(); const regexTargets = targets.filter( - (t): t is RegExp => t instanceof RegExp + (t): t is RegExp => t instanceof RegExp, ); // sentry.io.evil.com should NOT match expect( - regexTargets.some((r) => r.test("https://sentry.io.evil.com/api/0/")) + regexTargets.some((r) => r.test("https://sentry.io.evil.com/api/0/")), ).toBe(false); // us.sentry.io.evil.com should NOT match expect( - regexTargets.some((r) => r.test("https://us.sentry.io.evil.com/api/0/")) + regexTargets.some((r) => r.test("https://us.sentry.io.evil.com/api/0/")), ).toBe(false); }); @@ -1346,7 +1346,7 @@ describe("getSentryTracePropagationTargets", () => { process.env[SENTRY_URL_ENV] = "https://sentry.mycompany.com"; const targets = getSentryTracePropagationTargets(); const stringTargets = targets.filter( - (t): t is string => typeof t === "string" + (t): t is string => typeof t === "string", ); expect(stringTargets).toContain("https://sentry.mycompany.com"); }); @@ -1355,7 +1355,7 @@ describe("getSentryTracePropagationTargets", () => { // Default (no SENTRY_URL) → only RegExp targets, no string targets const targets = getSentryTracePropagationTargets(); const stringTargets = targets.filter( - (t): t is string => typeof t === "string" + (t): t is string => typeof t === "string", ); expect(stringTargets).toHaveLength(0); }); @@ -1365,7 +1365,7 @@ describe("getSentryTracePropagationTargets", () => { const targets = getSentryTracePropagationTargets(); // SaaS URLs are already covered by regex — no string target should be added const stringTargets = targets.filter( - (t): t is string => typeof t === "string" + (t): t is string => typeof t === "string", ); expect(stringTargets).toHaveLength(0); }); diff --git a/packages/cli/test/lib/telemetry/zstd-transport.e2e.test.ts b/packages/cli/test/lib/telemetry/zstd-transport.e2e.test.ts index a6e0ab9af..871e8704e 100644 --- a/packages/cli/test/lib/telemetry/zstd-transport.e2e.test.ts +++ b/packages/cli/test/lib/telemetry/zstd-transport.e2e.test.ts @@ -39,7 +39,7 @@ function startMockIngest( responder: (req: IncomingMessage) => { statusCode: number; headers?: Record; - } + }, ): Promise<{ url: string; captures: CapturedRequest[]; @@ -77,8 +77,8 @@ describe("makeCompressedTransport (e2e)", () => { (s) => new Promise((resolve) => { s.close(() => resolve()); - }) - ) + }), + ), ); }); @@ -112,7 +112,7 @@ describe("makeCompressedTransport (e2e)", () => { const text = Buffer.from( decompressed.buffer, decompressed.byteOffset, - decompressed.byteLength + decompressed.byteLength, ).toString("utf-8"); expect(text).toContain(message); expect(text).toContain('"type":"event"'); @@ -140,7 +140,7 @@ describe("makeCompressedTransport (e2e)", () => { expect(response.statusCode).toBe(429); expect(response.headers?.["retry-after"]).toBe("60"); expect(response.headers?.["x-sentry-rate-limits"]).toBe( - "60:error:organization" + "60:error:organization", ); expect(captures).toHaveLength(1); }); diff --git a/packages/cli/test/lib/telemetry/zstd-transport.property.test.ts b/packages/cli/test/lib/telemetry/zstd-transport.property.test.ts index 568295ac9..d1f0ae74d 100644 --- a/packages/cli/test/lib/telemetry/zstd-transport.property.test.ts +++ b/packages/cli/test/lib/telemetry/zstd-transport.property.test.ts @@ -46,9 +46,9 @@ describe("property: maybeCompress round-trip (zstd path)", () => { expect(result.encodingApplied).toBe("zstd"); const decompressed = await promisify(zstdDecompress)(result.payload); expect(Buffer.from(decompressed).equals(buf)).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -63,9 +63,9 @@ describe("property: maybeCompress round-trip (gzip path)", () => { const result = await maybeCompress(buf, "gzip"); expect(result.encodingApplied).toBe("gzip"); expect(gunzipSync(result.payload).equals(buf)).toBe(true); - } + }, ), - { numRuns: 25 } // gzip on 96 KiB is slower; fewer runs + { numRuns: 25 }, // gzip on 96 KiB is slower; fewer runs ); }); }); @@ -78,7 +78,7 @@ describe("property: normalizeBody string/Uint8Array equivalence", () => { const fromBytes = normalizeBody(new TextEncoder().encode(s)); expect(fromString.equals(fromBytes)).toBe(true); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -90,13 +90,13 @@ describe("property: normalizeBody string/Uint8Array equivalence", () => { const fromString = await maybeCompress(normalizeBody(s), "zstd"); const fromBytes = await maybeCompress( normalizeBody(new TextEncoder().encode(s)), - "zstd" + "zstd", ); expect(fromString.encodingApplied).toBe(fromBytes.encodingApplied); expect(fromString.payload.equals(fromBytes.payload)).toBe(true); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -111,9 +111,9 @@ describe("property: maybeCompress passthrough below threshold", () => { const result = await maybeCompress(buf, "zstd"); expect(result.encodingApplied).toBe("none"); expect(result.payload).toBe(buf); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -126,9 +126,9 @@ describe("property: maybeCompress passthrough below threshold", () => { const result = await maybeCompress(buf, "gzip"); expect(result.encodingApplied).toBe("none"); expect(result.payload).toBe(buf); - } + }, ), - { numRuns: 25 } // 32 KiB arbitrary alloc is slower; fewer runs + { numRuns: 25 }, // 32 KiB arbitrary alloc is slower; fewer runs ); }); }); diff --git a/packages/cli/test/lib/telemetry/zstd-transport.test.ts b/packages/cli/test/lib/telemetry/zstd-transport.test.ts index 78d164e7b..4ec12ba64 100644 --- a/packages/cli/test/lib/telemetry/zstd-transport.test.ts +++ b/packages/cli/test/lib/telemetry/zstd-transport.test.ts @@ -69,14 +69,14 @@ function buildMockHttpModule(response: MockResponseShape): { }; req.write = (chunk: Buffer | string) => { captured.chunks.push( - Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk) + Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), ); return true; }; req.end = (chunk?: Buffer | string) => { if (chunk !== undefined) { captured.chunks.push( - Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk) + Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), ); } // Fire the response on next tick so the caller has time to @@ -197,7 +197,7 @@ describe("makeCompressedTransport", () => { expect(response.statusCode).toBe(429); expect(response.headers?.["retry-after"]).toBe("60"); expect(response.headers?.["x-sentry-rate-limits"]).toBe( - "60:error:organization" + "60:error:organization", ); }); @@ -226,7 +226,7 @@ describe("makeCompressedTransport", () => { // Array collapsed to first element expect(response.headers?.["x-sentry-rate-limits"]).toBe( - "30:transaction:organization" + "30:transaction:organization", ); }); @@ -560,7 +560,7 @@ describe("shouldFallbackToDefault", () => { shouldFallbackToDefault(httpsUrl, { ...opts, proxy: "http://proxy.internal:3128", - }) + }), ).toBe(true); }); diff --git a/packages/cli/test/lib/time-range.property.test.ts b/packages/cli/test/lib/time-range.property.test.ts index 895ecfb3c..14476e27e 100644 --- a/packages/cli/test/lib/time-range.property.test.ts +++ b/packages/cli/test/lib/time-range.property.test.ts @@ -28,7 +28,7 @@ import { DEFAULT_NUM_RUNS } from "../model-based/helpers.js"; /** Generate a valid relative period string like "7d", "24h", "1m" */ const relativePeriodArb = integer({ min: 1, max: 999 }).chain((n) => - constantFrom("m", "h", "d", "w").map((u) => `${n}${u}`) + constantFrom("m", "h", "d", "w").map((u) => `${n}${u}`), ); /** Generate an ISO date string (YYYY-MM-DD) in a sensible range */ @@ -49,7 +49,7 @@ const sortedDatePairArb = array(isoDateArb, { minLength: 2, maxLength: 2 }).map( sorted[1] = d.toISOString().slice(0, 10); } return sorted as [string, string]; - } + }, ); // --------------------------------------------------------------------------- @@ -66,7 +66,7 @@ describe("property: parsePeriod relative", () => { expect(result.period).toBe(period); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -86,7 +86,7 @@ describe("property: parsePeriod range syntax", () => { expect(result.end).toBeDefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -100,7 +100,7 @@ describe("property: parsePeriod range syntax", () => { expect(result.end).toBeUndefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -114,7 +114,7 @@ describe("property: parsePeriod range syntax", () => { expect(result.end).toBeDefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -134,7 +134,7 @@ describe("property: parsePeriod operators", () => { expect(result.end).toBeUndefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -148,7 +148,7 @@ describe("property: parsePeriod operators", () => { expect(result.end).toBeUndefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -162,7 +162,7 @@ describe("property: parsePeriod operators", () => { expect(result.end).toBeDefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -176,7 +176,7 @@ describe("property: parsePeriod operators", () => { expect(result.end).toBeDefined(); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -191,7 +191,7 @@ describe("property: parsePeriod operators", () => { expect(exclTime).toBeGreaterThan(inclTime); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -206,7 +206,7 @@ describe("property: parsePeriod operators", () => { expect(exclTime).toBeLessThan(inclTime); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -222,7 +222,7 @@ describe("property: timeRangeToApiParams mutual exclusivity", () => { isoDateArb.map((d) => parsePeriod(`${d}..`)), isoDateArb.map((d) => parsePeriod(`..${d}`)), isoDateArb.map((d) => parsePeriod(`>=${d}`)), - isoDateArb.map((d) => parsePeriod(`<=${d}`)) + isoDateArb.map((d) => parsePeriod(`<=${d}`)), ); test("statsPeriod and start/end are never both set", () => { @@ -235,11 +235,11 @@ describe("property: timeRangeToApiParams mutual exclusivity", () => { } else { // At least one of start/end should be defined for absolute expect(params.start !== undefined || params.end !== undefined).toBe( - true + true, ); } }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -255,7 +255,7 @@ describe("property: serializeTimeRange", () => { const range = parsePeriod(period); expect(serializeTimeRange(range)).toBe(serializeTimeRange(range)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -266,7 +266,7 @@ describe("property: serializeTimeRange", () => { const fromRange = parsePeriod(`${d}..`); expect(serializeTimeRange(fromOp)).toBe(serializeTimeRange(fromRange)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -277,7 +277,7 @@ describe("property: serializeTimeRange", () => { const fromRange = parsePeriod(`..${d}`); expect(serializeTimeRange(fromOp)).toBe(serializeTimeRange(fromRange)); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -295,7 +295,7 @@ describe("property: timeRangeToSeconds", () => { expect(seconds).toBeDefined(); expect(seconds).toBeGreaterThan(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -307,7 +307,7 @@ describe("property: timeRangeToSeconds", () => { expect(seconds).toBeDefined(); expect(seconds).toBeGreaterThanOrEqual(0); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -317,7 +317,7 @@ describe("property: timeRangeToSeconds", () => { expect(timeRangeToSeconds(parsePeriod(`${d}..`))).toBeUndefined(); expect(timeRangeToSeconds(parsePeriod(`..${d}`))).toBeUndefined(); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/time-range.test.ts b/packages/cli/test/lib/time-range.test.ts index 37ec87a86..814bcb153 100644 --- a/packages/cli/test/lib/time-range.test.ts +++ b/packages/cli/test/lib/time-range.test.ts @@ -118,7 +118,7 @@ describe("parsePeriod: range syntax", () => { test("rejects end before start", () => { expect(() => parsePeriod("2024-06-01..2024-01-01")).toThrow( - "is after end date" + "is after end date", ); }); @@ -179,7 +179,7 @@ describe("parsePeriod: comparison operators", () => { if (exclusive.type === "absolute" && inclusive.type === "absolute") { // Exclusive start should be strictly later than inclusive start expect(new Date(exclusive.start!).getTime()).toBeGreaterThan( - new Date(inclusive.start!).getTime() + new Date(inclusive.start!).getTime(), ); } }); @@ -190,7 +190,7 @@ describe("parsePeriod: comparison operators", () => { if (exclusive.type === "absolute" && inclusive.type === "absolute") { // Exclusive end should be strictly earlier than inclusive end expect(new Date(exclusive.end!).getTime()).toBeLessThan( - new Date(inclusive.end!).getTime() + new Date(inclusive.end!).getTime(), ); } }); @@ -327,7 +327,7 @@ describe("timeRangeToApiParams", () => { describe("serializeTimeRange", () => { test("relative → 'rel:7d'", () => { expect(serializeTimeRange({ type: "relative", period: "7d" })).toBe( - "rel:7d" + "rel:7d", ); }); @@ -373,19 +373,19 @@ describe("serializeTimeRange", () => { describe("timeRangeToSeconds", () => { test("relative 7d → 604800", () => { expect(timeRangeToSeconds({ type: "relative", period: "7d" })).toBe( - 604_800 + 604_800, ); }); test("relative 24h → 86400", () => { expect(timeRangeToSeconds({ type: "relative", period: "24h" })).toBe( - 86_400 + 86_400, ); }); test("relative 1w → 604800", () => { expect(timeRangeToSeconds({ type: "relative", period: "1w" })).toBe( - 604_800 + 604_800, ); }); @@ -403,13 +403,13 @@ describe("timeRangeToSeconds", () => { timeRangeToSeconds({ type: "absolute", start: "2024-01-01T00:00:00Z", - }) + }), ).toBeUndefined(); expect( timeRangeToSeconds({ type: "absolute", end: "2024-02-01T00:00:00Z", - }) + }), ).toBeUndefined(); }); }); diff --git a/packages/cli/test/lib/timezone.mocked.test.ts b/packages/cli/test/lib/timezone.mocked.test.ts index ddf752d94..8e6282a9d 100644 --- a/packages/cli/test/lib/timezone.mocked.test.ts +++ b/packages/cli/test/lib/timezone.mocked.test.ts @@ -36,7 +36,7 @@ vi.mock("node:fs", async (importOriginal) => { } return (actual.readFileSync as (...a: unknown[]) => unknown)( path, - ...rest + ...rest, ); }), readlinkSync: vi.fn((path: string, ...rest: unknown[]) => { @@ -51,7 +51,7 @@ vi.mock("node:fs", async (importOriginal) => { } return (actual.readlinkSync as (...a: unknown[]) => unknown)( path, - ...rest + ...rest, ); }), }; @@ -73,7 +73,7 @@ vi.mock("node:child_process", async (importOriginal) => { } return (actual.execSync as (...a: unknown[]) => unknown)( command, - ...rest + ...rest, ); }), }; diff --git a/packages/cli/test/lib/token-claims.property.test.ts b/packages/cli/test/lib/token-claims.property.test.ts index 449176357..ab811264e 100644 --- a/packages/cli/test/lib/token-claims.property.test.ts +++ b/packages/cli/test/lib/token-claims.property.test.ts @@ -31,7 +31,7 @@ const mint = mintSntrysToken; /** Arbitrary URL-shaped string (https://) */ const httpsUrlArb = stringMatching( - /^[a-z][a-z0-9-]{0,30}(\.[a-z][a-z0-9-]{0,30}){0,4}$/ + /^[a-z][a-z0-9-]{0,30}(\.[a-z][a-z0-9-]{0,30}){0,4}$/, ).map((host) => `https://${host}`); describe("property: parseSntrysClaim never throws on adversarial input", () => { @@ -45,19 +45,19 @@ describe("property: parseSntrysClaim never throws on adversarial input", () => { expect(result.url.length).toBeGreaterThan(0); } }), - { numRuns: DEFAULT_NUM_RUNS * 4 } + { numRuns: DEFAULT_NUM_RUNS * 4 }, ); }); test("strings starting with sntrys_ but with weird content → never throws", () => { const sntrysPrefixed = string({ maxLength: 1000 }).map( - (rest) => `sntrys_${rest}` + (rest) => `sntrys_${rest}`, ); fcAssert( property(sntrysPrefixed, (input) => { expect(() => parseSntrysClaim(input)).not.toThrow(); }), - { numRuns: DEFAULT_NUM_RUNS * 4 } + { numRuns: DEFAULT_NUM_RUNS * 4 }, ); }); }); @@ -69,7 +69,7 @@ describe("property: round-trip — minted tokens parse back to the same url", () const token = mint({ iat: 1_700_000_000, url, org: "x" }); expect(parseSntrysClaim(token)?.url).toBe(url); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); @@ -80,9 +80,9 @@ describe("property: round-trip — minted tokens parse back to the same url", () ([iat, url]) => { const token = mint({ iat, url, org: "x" }); expect(parseSntrysClaim(token)).toBeDefined(); - } + }, ), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -101,7 +101,7 @@ describe("property: forged claims parse identically (NOT a security signal)", () }); expect(parseSntrysClaim(forged)?.url).toBe(forgedUrl); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); diff --git a/packages/cli/test/lib/token-claims.test.ts b/packages/cli/test/lib/token-claims.test.ts index 54abf90a5..a0ce7a5de 100644 --- a/packages/cli/test/lib/token-claims.test.ts +++ b/packages/cli/test/lib/token-claims.test.ts @@ -70,7 +70,7 @@ describe("parseSntrysClaim", () => { // `not_valid_base64` contains underscore which would actually break // the underscore count, so this is the format the test preload uses: expect( - parseSntrysClaim("sntrys_test-token-for-unit-tests_000000") + parseSntrysClaim("sntrys_test-token-for-unit-tests_000000"), ).toBeUndefined(); // Pure invalid base64 character set in middle (with right underscore count) expect(parseSntrysClaim("sntrys_!!!notbase64!!!_secret")).toBeUndefined(); diff --git a/packages/cli/test/lib/token-host.property.test.ts b/packages/cli/test/lib/token-host.property.test.ts index 734d355d9..2d55f3cec 100644 --- a/packages/cli/test/lib/token-host.property.test.ts +++ b/packages/cli/test/lib/token-host.property.test.ts @@ -29,12 +29,12 @@ const HOST_LABEL = stringMatching(/^[a-z0-9][a-z0-9-]{0,20}$/); /** Two-label lowercase host (e.g. "example.com") */ const simpleHostArb = tuple(HOST_LABEL, HOST_LABEL).map( - ([a, b]) => `${a}.${b}` + ([a, b]) => `${a}.${b}`, ); /** Non-SaaS host: never ends with `sentry.io` */ const nonSaasHostArb = simpleHostArb.filter( - (h) => h !== "sentry.io" && !h.endsWith(".sentry.io") + (h) => h !== "sentry.io" && !h.endsWith(".sentry.io"), ); /** SaaS host: any subdomain of sentry.io (including bare `sentry.io`) */ @@ -62,7 +62,7 @@ describe("property: normalizeOrigin is idempotent", () => { } expect(normalizeOrigin(once)).toBe(once); }), - { numRuns: DEFAULT_NUM_RUNS } + { numRuns: DEFAULT_NUM_RUNS }, ); }); }); @@ -73,14 +73,14 @@ describe("property: SaaS equivalence is reflexive + symmetric", () => { property(saasSubdomainArb, saasSubdomainArb, (a, b) => { // Both a and b are https://