Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/tests/pr-risk-labels.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ for (const risk of ["low", "medium", "high"]) {
},
async ({ run, labels }) => {
await run();
assert.deepEqual([...labels].sort(), ["area: auth", `risk: ${risk}`]);
assert.deepEqual(
[...labels].sort((a, b) => a.localeCompare(b)),
["area: auth", `risk: ${risk}`],
);
},
);
});
Expand Down
34 changes: 25 additions & 9 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,34 @@
"rules": {
"no-unused-vars": "off",
"typescript/return-await": ["error", "always"],
"typescript/await-thenable": "off",
"typescript/no-base-to-string": "off",
"typescript/no-duplicate-type-constituents": "off",
"typescript/no-floating-promises": "off",
"typescript/no-misused-spread": "off",
"typescript/no-redundant-type-constituents": "off",
"typescript/require-array-sort-compare": "off",
"typescript/restrict-template-expressions": "off",
"typescript/unbound-method": "off",
"typescript/no-floating-promises": [
"error",
{
"allowForKnownSafeCalls": [
{
"from": "package",
"package": "node:test",
"name": ["test", "it", "describe", "suite"]
}
]
}
],
"typescript/restrict-template-expressions": [
"error",
{
"allowNever": true,
"allow": ["URL", "URLSearchParams"]
}
],
"jsx-a11y/interactive-supports-focus": "off"
},
"overrides": [
{
"files": ["**/*.test.{ts,tsx,mjs}", "**/test/**/*.{ts,tsx}"],
"rules": {
"typescript/unbound-method": "off"
}
},
{
"files": ["packages/mcp-cloudflare/src/client/**/*.tsx"],
"rules": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/script/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ async function compileAllTargets(
successes += 1;
} catch (error) {
console.error(
` Post-processing ${getPackageName(target)} failed: ${error}`,
` Post-processing ${getPackageName(target)} failed: ${String(error)}`,
);
failures += 1;
}
Expand Down
12 changes: 8 additions & 4 deletions packages/cli/src/commands/alert/issues/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
} from "../../../lib/api-client.js";
import { parseOrgProjectArg } from "../../../lib/arg-parsing.js";
import { buildCommand, numberParser } from "../../../lib/command.js";
import { ContextError, ValidationError } from "../../../lib/errors.js";
import {
ContextError,
stringifyUnknown,
ValidationError,
} from "../../../lib/errors.js";
import { CommandOutput } from "../../../lib/formatters/output.js";
import { DRY_RUN_ALIASES, DRY_RUN_FLAG } from "../../../lib/mutate-command.js";
import { resolveProjectBoundTargets } from "../../../lib/resolve-target.js";
Expand Down Expand Up @@ -244,9 +248,9 @@ export const createCommand = buildCommand({
yield new CommandOutput({
org: target.org,
project: target.project,
id: String(created.id ?? ""),
name: String(created.name ?? flags.name),
status: String(
id: stringifyUnknown(created.id ?? ""),
name: stringifyUnknown(created.name ?? flags.name),
status: stringifyUnknown(
created.status ?? (created.enabled === false ? "disabled" : "active"),
),
} satisfies CreateResult);
Expand Down
10 changes: 7 additions & 3 deletions packages/cli/src/commands/alert/issues/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
} from "../../../lib/api-client.js";
import { parseOrgProjectArg } from "../../../lib/arg-parsing.js";
import { buildCommand, numberParser } from "../../../lib/command.js";
import { ContextError, ValidationError } from "../../../lib/errors.js";
import {
ContextError,
stringifyUnknown,
ValidationError,
} from "../../../lib/errors.js";
import { CommandOutput } from "../../../lib/formatters/output.js";
import { resolveProjectBoundTargets } from "../../../lib/resolve-target.js";
import {
Expand Down Expand Up @@ -284,10 +288,10 @@ export const editCommand = buildCommand({
...updated,
org: target.org,
project: target.project,
id: String(updated.id ?? rule.id),
id: stringifyUnknown(updated.id ?? rule.id),
// 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(
status: stringifyUnknown(
updated.status ?? (updated.enabled === false ? "disabled" : "active"),
),
} satisfies EditResult);
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/alert/issues/rule-resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { ResolutionError, ValidationError } from "../../../lib/errors.js";
import { fuzzyMatch } from "../../../lib/fuzzy.js";
import { logger } from "../../../lib/logger.js";
import type { ResolvedTarget } from "../../../lib/resolve-target.js";
import { isAllDigits } from "../../../lib/utils.js";
import { countChar, isAllDigits } from "../../../lib/utils.js";

const log = logger.withTag("alert.issues");

Expand Down Expand Up @@ -42,7 +42,7 @@ export function parseIssueRuleArg(
);
}

const slashCount = [...trimmed].filter((c) => c === "/").length;
const slashCount = countChar(trimmed, "/");
if (slashCount === 0) {
return { ref: trimmed, targetArg: undefined };
}
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/alert/metrics/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import type { SentryContext } from "../../../context.js";
import { createMetricAlertRule } from "../../../lib/api-client.js";
import { buildCommand, numberParser } from "../../../lib/command.js";
import { ValidationError } from "../../../lib/errors.js";
import { stringifyUnknown, ValidationError } from "../../../lib/errors.js";
import { warning } from "../../../lib/formatters/colors.js";
import { CommandOutput } from "../../../lib/formatters/output.js";
import { DRY_RUN_ALIASES, DRY_RUN_FLAG } from "../../../lib/mutate-command.js";
Expand Down Expand Up @@ -222,8 +222,8 @@ export const createCommand = buildCommand({
const created = await createMetricAlertRule(orgSlug, body);
yield new CommandOutput({
org: orgSlug,
id: String(created.id ?? ""),
name: String(created.name ?? flags.name),
id: stringifyUnknown(created.id ?? ""),
name: stringifyUnknown(created.name ?? flags.name),
status: metricAlertStatusLabel(created.status),
} satisfies CreateResult);
},
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/alert/metrics/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
putMetricAlertRule,
} from "../../../lib/api-client.js";
import { buildCommand, numberParser } from "../../../lib/command.js";
import { ValidationError } from "../../../lib/errors.js";
import { stringifyUnknown, ValidationError } from "../../../lib/errors.js";
import { warning } from "../../../lib/formatters/colors.js";
import { CommandOutput } from "../../../lib/formatters/output.js";
import { resolveOrgOnlyFromArg } from "../../../lib/resolve-target.js";
Expand Down Expand Up @@ -290,7 +290,7 @@ export const editCommand = buildCommand({
yield new CommandOutput({
...updated,
org: orgSlug,
id: String(updated.id ?? rule.id),
id: stringifyUnknown(updated.id ?? rule.id),
status: metricAlertStatusLabel(updated.status),
} satisfies EditResult);
},
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/alert/metrics/rule-resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { ResolutionError, ValidationError } from "../../../lib/errors.js";
import { fuzzyMatch } from "../../../lib/fuzzy.js";
import { logger } from "../../../lib/logger.js";
import { isAllDigits } from "../../../lib/utils.js";
import { countChar, isAllDigits } from "../../../lib/utils.js";

const log = logger.withTag("alert.metrics");

Expand Down Expand Up @@ -45,7 +45,7 @@ export function parseMetricRuleArg(
return { ref: trimmed, targetArg: undefined };
}

const slashCount = [...trimmed].filter((c) => c === "/").length;
const slashCount = countChar(trimmed, "/");
if (slashCount > 1) {
throw new ValidationError(
`Metric alerts are org-scoped — use '<org>/<rule-id-or-name>', not '<org>/<project>/<rule>'.\nUse: ${usageHint}`,
Expand Down
8 changes: 6 additions & 2 deletions packages/cli/src/commands/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import * as Sentry from "@sentry/node-core/light";
import type { SentryContext } from "../context.js";
import { appendSearchParams, rawApiRequest } from "../lib/api-client.js";
import { buildCommand } from "../lib/command.js";
import { OutputError, ValidationError } from "../lib/errors.js";
import {
OutputError,
stringifyUnknown,
ValidationError,
} from "../lib/errors.js";
import { filterFields } from "../lib/formatters/json.js";
import { CommandOutput } from "../lib/formatters/output.js";
import { validateEndpoint } from "../lib/input-validation.js";
Expand Down Expand Up @@ -1071,7 +1075,7 @@ export function formatApiResponse(data: unknown): string {
if (typeof data === "object") {
return JSON.stringify(data, null, 2);
}
return String(data);
return stringifyUnknown(data);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/auth/refresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ Examples:
}

// Standard token refresh path (no scope change).
const auth = await getAuthConfig();
const auth = getAuthConfig();
if (!auth?.refreshToken && auth?.token) {
throw new AuthError(
"invalid",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/issue/explain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import type { SentryContext } from "../../context.js";
import { buildCommand } from "../../lib/command.js";
import { ApiError, ContextError } from "../../lib/errors.js";
import { ApiError, ContextError, stringifyUnknown } from "../../lib/errors.js";
import { CommandOutput } from "../../lib/formatters/output.js";
import {
formatIssueExplain,
Expand Down Expand Up @@ -145,7 +145,9 @@ export const explainCommand = buildCommand({
issueArgs,
(issueArg) => analyzeIssue(issueArg, cwd, flags, flags.json || isBatch),
(issueArg, reason) => {
log.warn(`Failed to analyze issue ${issueArg}: ${reason}`);
log.warn(
`Failed to analyze issue ${issueArg}: ${stringifyUnknown(reason)}`,
);
},
);

Expand Down
8 changes: 5 additions & 3 deletions packages/cli/src/commands/issue/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getLatestEvent, listReplayIdsForIssue } from "../../lib/api-client.js";
import { spansFlag } from "../../lib/arg-parsing.js";
import { openInBrowser } from "../../lib/browser.js";
import { buildCommand } from "../../lib/command.js";
import { ContextError } from "../../lib/errors.js";
import { ContextError, stringifyUnknown } from "../../lib/errors.js";
import { plainSafeMuted } from "../../lib/formatters/human.js";
import {
formatIssueView,
Expand Down Expand Up @@ -187,7 +187,9 @@ export function fetchMultipleIssueViews(
issueArgs,
(issueArg) => buildSingleIssueViewData(issueArg, cwd, spans),
(issueArg, reason) => {
log.warn(`Failed to fetch issue ${issueArg}: ${reason}`);
log.warn(
`Failed to fetch issue ${issueArg}: ${stringifyUnknown(reason)}`,
);
},
);
}
Expand Down Expand Up @@ -222,7 +224,7 @@ async function openIssuesInBrowser(
return issue;
},
(issueArg, reason) => {
log.warn(`Failed to open issue ${issueArg}: ${reason}`);
log.warn(`Failed to open issue ${issueArg}: ${stringifyUnknown(reason)}`);
},
);

Expand Down
26 changes: 14 additions & 12 deletions packages/cli/src/commands/local/control-plane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ import { type Context, Hono } from "hono";
import { cors } from "hono/cors";
import { HTTPException } from "hono/http-exception";
import { streamSSE } from "hono/streaming";
import { stringifyUnknown } from "../../lib/errors.js";
import { SENTRY_CONTENT_TYPE } from "../../lib/formatters/local.js";
import { formatLocalServerUrl } from "./ui.js";
import { buildSSEHandler, isHostedUiOrigin, MAX_BODY_BYTES } from "./server.js";
import {
buildSSEHandler,
isHostedUiOrigin,
MAX_BODY_BYTES,
spotlightEnvelopeId,
} from "./server.js";
import { logger } from "../../lib/logger.js";
import {
createLocalSessionStore,
Expand Down Expand Up @@ -92,14 +98,11 @@ function eventSummary(parsed: {
return id ? [id] : [];
})[0];
return {
envelopeId: String(header.__spotlight_envelope_id ?? ""),
eventId:
eventIdFrom(header) ??
itemEventId ??
String(header.__spotlight_envelope_id ?? ""),
envelopeId: spotlightEnvelopeId(header),
eventId: eventIdFrom(header) ?? itemEventId ?? spotlightEnvelopeId(header),
itemTypes: items.map((item) =>
Array.isArray(item) && typeof item[0] === "object" && item[0]
? String((item[0] as { type?: unknown }).type ?? "unknown")
? stringifyUnknown((item[0] as { type?: unknown }).type ?? "unknown")
: "unknown",
),
};
Expand Down Expand Up @@ -441,17 +444,16 @@ export function createLocalControlPlane({
.read({ all: true })
.map((container) => {
const parsed = container.getParsedEnvelope();
const header = (parsed?.envelope[0] ?? {}) as {
__spotlight_envelope_id?: unknown;
};
return {
id: String(header.__spotlight_envelope_id ?? ""),
id: spotlightEnvelopeId(parsed?.envelope[0] ?? {}),
contentType: container.getContentType(),
itemTypes:
parsed?.envelope[1]
.map((item) =>
Array.isArray(item) && typeof item[0] === "object" && item[0]
? String((item[0] as { type?: unknown }).type ?? "unknown")
? stringifyUnknown(
(item[0] as { type?: unknown }).type ?? "unknown",
)
: "unknown",
)
.filter(Boolean) ?? [],
Expand Down
18 changes: 15 additions & 3 deletions packages/cli/src/commands/local/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@ const LOCAL_UI_PREVIEW_HOST_RE = /^sentry-local-git-[a-z0-9-]+\.sentry\.dev$/;

export { isLoopbackHost } from "./loopback.js";

/**
* Read Spotlight's `__spotlight_envelope_id` envelope header as a string.
* Spotlight stores a `uuidv7` UUID object there, which renders through its
* own `toString()`.
*/
export function spotlightEnvelopeId(header: Record<string, unknown>): string {
const id = header.__spotlight_envelope_id as
| { toString(): string }
| null
| undefined;
return id == null ? "" : id.toString();
}

type LocalReceiverOptions = {
/** Enables stateful UI routes only when the receiver itself is loopback-bound. */
uiActions?: boolean;
Expand Down Expand Up @@ -225,11 +238,10 @@ export function buildSSEHandler(
if (!parsed) {
return;
}
const header = parsed.envelope[0];
const envelopeId = header.__spotlight_envelope_id;
const envelopeId = spotlightEnvelopeId(parsed.envelope[0]);
stream
.writeSSE({
id: envelopeId ? String(envelopeId) : undefined,
id: envelopeId || undefined,
event: `${container.getContentType()}${base64Indicator}`,
data: JSON.stringify(parsed.envelope),
})
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/release/propose-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const proposeVersionCommand = buildCommand({
}

// Fall back to git HEAD SHA
const sha = await getHeadCommit(cwd);
const sha = getHeadCommit(cwd);
yield new CommandOutput({ version: sha, source: "git" });
return { hint: "Detected from git HEAD" };
},
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/lib/api/explore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import type { EventsTableResponse } from "../../types/dashboard.js";
import { EventsTableResponseSchema } from "../../types/dashboard.js";
import { stringifyUnknown } from "../errors.js";
import { resolveOrgRegion } from "../region.js";
import {
API_MAX_PER_PAGE,
Expand Down Expand Up @@ -156,9 +157,9 @@ export async function queryMetricsMeta(
}

return allRows.map((row) => ({
name: String(row["metric.name"] ?? ""),
type: String(row["metric.type"] ?? "distribution"),
unit: String(row["metric.unit"] ?? "none"),
name: stringifyUnknown(row["metric.name"] ?? ""),
type: stringifyUnknown(row["metric.type"] ?? "distribution"),
unit: stringifyUnknown(row["metric.unit"] ?? "none"),
}));
}

Expand Down
Loading
Loading