diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md index 0791b2921..3ef5f9452 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/alert.md @@ -197,7 +197,7 @@ Edit an issue alert rule | `triggers` | object \| null | Conditions on which the alert triggers | | `actionFilters` | array \| null | Filters to run before actions fire, each with the actions to fire | | `environment` | string \| null | Name of the environment the alert is limited to. Null for all environments | -| `config` | object | Configuration of the alert, such as frequency in minutes | +| `config` | unknown | Configuration of the alert, such as frequency in minutes | | `detectorIds` | array \| null | IDs of the monitors connected to the alert | | `enabled` | boolean | Whether the alert is enabled | | `lastTriggered` | string \| null | When the alert last fired (ISO 8601) | @@ -402,7 +402,7 @@ Edit a metric alert rule | `dateUpdated` | string | When the monitor was last updated (ISO 8601) | | `dataSources` | array \| null | Data sources the monitor evaluates, such as a query subscription | | `conditionGroup` | object \| null | Conditions that decide the monitor's status | -| `config` | object | Configuration of the monitor, which depends on its type | +| `config` | object \| unknown | | | `enabled` | boolean | Whether the monitor is enabled | | `org` | string | Organization slug | | `status` | string | Rule status (active, disabled) | diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md index 4cae2fe8c..3afc37c96 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md @@ -45,11 +45,11 @@ View details of one or more events | `packages` | object | Modules loaded when the event occurred, keyed by module name with their versions | | `type` | string | Event type, such as error, default or transaction | | `metadata` | object | Event metadata | -| `occurrence` | object \| null | | +| `occurrence` | object \| unknown \| null | | | `_meta` | object | Metadata about changes Sentry made to the event's fields, such as data scrubbing or trimming | | `user` | object \| null | User context of the event | | `tags` | array | Tags attached to the event | -| `contexts` | object \| null | Contexts of the event, such as device, os, browser and runtime | +| `contexts` | object \| null | Contexts of the event, such as device, os, browser and runtime, keyed by alias | | `sdk` | object \| null | SDK that sent the event | | `context` | object \| null | Additional data sent with the event in its extra field | | `errors` | array | Errors Sentry found while processing the event | diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/release.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/release.md index cc144b70d..17a449433 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/release.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/release.md @@ -38,7 +38,7 @@ List releases with adoption and health metrics | `version` | string | Release version | | `shortVersion` | string | Shortened release version | | `versionInfo` | object \| null | Parsed version information | -| `status` | string | Release status (open or archived) | +| `status` | string | Release status | | `ref` | string \| null | Version control reference, such as a commit SHA or tag | | `url` | string \| null | Release URL | | `dateCreated` | string \| null | When the release was created (ISO 8601) | @@ -53,7 +53,7 @@ List releases with adoption and health metrics | `authors` | array | Authors of the release's commits | | `owner` | object \| null | Release owner | | `projects` | array | Projects in the release | -| `adoptionStages` | object \| null | Adoption stage of the release in each project | +| `adoptionStages` | object \| null | Adoption stage of the release in each project, keyed by project slug | | `orgSlug` | string | Organization slug | | `targetProject` | string | Project slug, when listing several | @@ -106,7 +106,7 @@ View release details with health metrics | `version` | string | Release version | | `shortVersion` | string | Shortened release version | | `versionInfo` | object \| null | Parsed version information | -| `status` | string | Release status (open or archived) | +| `status` | string | Release status | | `ref` | string \| null | Version control reference, such as a commit SHA or tag | | `url` | string \| null | Release URL | | `dateCreated` | string \| null | When the release was created (ISO 8601) | @@ -121,7 +121,7 @@ View release details with health metrics | `authors` | array | Authors of the release's commits | | `owner` | object \| null | Release owner | | `projects` | array | Projects in the release | -| `adoptionStages` | object \| null | Adoption stage of the release in each project | +| `adoptionStages` | object \| null | Adoption stage of the release in each project, keyed by project slug | **Examples:** @@ -164,7 +164,7 @@ Create a release | `version` | string | Release version | | `shortVersion` | string | Shortened release version | | `versionInfo` | object \| null | Parsed version information | -| `status` | string | Release status (open or archived) | +| `status` | string | Release status | | `ref` | string \| null | Version control reference, such as a commit SHA or tag | | `url` | string \| null | Release URL | | `dateCreated` | string \| null | When the release was created (ISO 8601) | @@ -179,7 +179,7 @@ Create a release | `authors` | array | Authors of the release's commits | | `owner` | object \| null | Release owner | | `projects` | array | Projects in the release (only slug with --dry-run) | -| `adoptionStages` | object \| null | Adoption stage of the release in each project | +| `adoptionStages` | object \| null | Adoption stage of the release in each project, keyed by project slug | | `dryRun` | boolean | True when --dry-run made no change | **Examples:** @@ -236,7 +236,7 @@ Finalize a release | `version` | string | Release version | | `shortVersion` | string | Shortened release version | | `versionInfo` | object \| null | Parsed version information | -| `status` | string | Release status (open or archived) | +| `status` | string | Release status | | `ref` | string \| null | Version control reference, such as a commit SHA or tag | | `url` | string \| null | Release URL | | `dateCreated` | string \| null | When the release was created (ISO 8601) | @@ -251,7 +251,7 @@ Finalize a release | `authors` | array | Authors of the release's commits | | `owner` | object \| null | Release owner | | `projects` | array | Projects in the release | -| `adoptionStages` | object \| null | Adoption stage of the release in each project | +| `adoptionStages` | object \| null | Adoption stage of the release in each project, keyed by project slug | | `dryRun` | boolean | True when --dry-run made no change | | `org` | string | Organization slug (--dry-run) | | `released` | string | Release date to set (--dry-run) | @@ -332,7 +332,7 @@ Archive a release | `version` | string | Release version | | `shortVersion` | string | Shortened release version | | `versionInfo` | object \| null | Parsed version information | -| `status` | string | Release status (open or archived) | +| `status` | string | Release status | | `ref` | string \| null | Version control reference, such as a commit SHA or tag | | `url` | string \| null | Release URL | | `dateCreated` | string \| null | When the release was created (ISO 8601) | @@ -347,7 +347,7 @@ Archive a release | `authors` | array | Authors of the release's commits | | `owner` | object \| null | Release owner | | `projects` | array | Projects in the release | -| `adoptionStages` | object \| null | Adoption stage of the release in each project | +| `adoptionStages` | object \| null | Adoption stage of the release in each project, keyed by project slug | | `dryRun` | boolean | True when --dry-run made no change | | `org` | string | Organization slug (--dry-run) | @@ -385,7 +385,7 @@ Restore an archived release | `version` | string | Release version | | `shortVersion` | string | Shortened release version | | `versionInfo` | object \| null | Parsed version information | -| `status` | string | Release status (open or archived) | +| `status` | string | Release status | | `ref` | string \| null | Version control reference, such as a commit SHA or tag | | `url` | string \| null | Release URL | | `dateCreated` | string \| null | When the release was created (ISO 8601) | @@ -400,7 +400,7 @@ Restore an archived release | `authors` | array | Authors of the release's commits | | `owner` | object \| null | Release owner | | `projects` | array | Projects in the release | -| `adoptionStages` | object \| null | Adoption stage of the release in each project | +| `adoptionStages` | object \| null | Adoption stage of the release in each project, keyed by project slug | | `dryRun` | boolean | True when --dry-run made no change | | `org` | string | Organization slug (--dry-run) | @@ -529,7 +529,7 @@ Set commits for a release | `version` | string | Release version | | `shortVersion` | string | Shortened release version | | `versionInfo` | object \| null | Parsed version information | -| `status` | string | Release status (open or archived) | +| `status` | string | Release status | | `ref` | string \| null | Version control reference, such as a commit SHA or tag | | `url` | string \| null | Release URL | | `dateCreated` | string \| null | When the release was created (ISO 8601) | @@ -544,7 +544,7 @@ Set commits for a release | `authors` | array | Authors of the release's commits | | `owner` | object \| null | Release owner | | `projects` | array | Projects in the release | -| `adoptionStages` | object \| null | Adoption stage of the release in each project | +| `adoptionStages` | object \| null | Adoption stage of the release in each project, keyed by project slug | **Examples:** diff --git a/packages/cli/src/lib/api/issue-app-links.ts b/packages/cli/src/lib/api/issue-app-links.ts index 4e209ee18..6d06bda8f 100644 --- a/packages/cli/src/lib/api/issue-app-links.ts +++ b/packages/cli/src/lib/api/issue-app-links.ts @@ -318,8 +318,13 @@ async function getLinkForm( JSON.stringify(component.error), ); } - // App-defined form schemas are intentionally untyped in the API contract. - const form = safeParse(LinkFormSchema, component.schema.link); + // The component schema is typed per component type, but its form fields + // are app-defined, so validate the link form here. + const { schema } = component; + const form = safeParse( + LinkFormSchema, + schema.type === "issue-link" ? schema.link : undefined, + ); if (!form.success) { throw new ValidationError( `App ${installation.app.slug} does not expose a supported issue-link form`, diff --git a/packages/cli/src/lib/formatters/human.ts b/packages/cli/src/lib/formatters/human.ts index 13f27bd0c..57fff8a79 100644 --- a/packages/cli/src/lib/formatters/human.ts +++ b/packages/cli/src/lib/formatters/human.ts @@ -797,6 +797,19 @@ function formatPriorityLabel(priority?: string | null): string { } } +/** + * Display version of an issue's first or last release. Sentry returns only + * `version` for a release it can't load, so fall back to it. + */ +function releaseVersion( + release: SentryIssue["firstRelease"], +): string | undefined { + if (!release) { + return; + } + return "shortVersion" in release ? release.shortVersion : release.version; +} + /** * Format detailed issue information as rendered markdown. * @@ -850,8 +863,8 @@ export function formatIssueDetails(issue: SentryIssue): string { ]); } - const firstReleaseVersion = issue.firstRelease?.shortVersion; - const lastReleaseVersion = issue.lastRelease?.shortVersion; + const firstReleaseVersion = releaseVersion(issue.firstRelease); + const lastReleaseVersion = releaseVersion(issue.lastRelease); if (firstReleaseVersion || lastReleaseVersion) { const first = text(stringifyUnknown(firstReleaseVersion ?? "")); const last = text(stringifyUnknown(lastReleaseVersion ?? "")); 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 ff69ab9ec..cd1d21dbc 100644 --- a/packages/cli/test/lib/api/issue-app-links.test.ts +++ b/packages/cli/test/lib/api/issue-app-links.test.ts @@ -33,11 +33,16 @@ const LINK: AppIssueLink = { displayName: "ENG-42", webUrl: URL, }; +const APP_UUID = "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"; const INSTALLATION = { - uuid: "install-uuid", + uuid: "8d9f5e2a-3b4c-4d6e-9f01-2a3b4c5d6e7f", status: "installed", organization: { slug: ORG }, - app: { uuid: "app-uuid", slug: "linear", sentryAppId: 12 }, + app: { + uuid: APP_UUID, + slug: "linear", + sentryAppId: 12, + }, }; const FORM = { uri: "/hooks/sentry/issues/link", @@ -46,12 +51,17 @@ const FORM = { ], }; const COMPONENT = { - uuid: "component-uuid", + uuid: "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f", type: "issue-link", error: "", - sentryApp: { uuid: "app-uuid", slug: "linear", name: "Linear", avatars: [] }, + sentryApp: { + uuid: APP_UUID, + slug: "linear", + name: "Linear", + avatars: [], + }, installationUuid: INSTALLATION.uuid, - schema: { link: FORM }, + schema: { type: "issue-link", link: FORM, create: FORM }, }; let originalFetch: typeof globalThis.fetch; @@ -90,7 +100,9 @@ beforeEach(async () => { return json([installation]); } if (path.endsWith("/sentry-app-components/")) { - return json([{ ...COMPONENT, schema: { link: form } }]); + return json([ + { ...COMPONENT, schema: { ...COMPONENT.schema, link: form } }, + ]); } if (path.endsWith("/external-requests/")) { return json({ choices }); @@ -128,7 +140,7 @@ describe("app issue-link action", () => { request.url.includes("external-requests"), ); expect(search?.url).toContain( - "https://sentry.io/api/0/sentry-app-installations/install-uuid/", + `https://sentry.io/api/0/sentry-app-installations/${INSTALLATION.uuid}/`, ); expect(search?.url).toContain("query=ENG-42"); expect(search?.url).toContain("projectId=77"); @@ -394,7 +406,10 @@ describe("app issue-link action", () => { }); test("rejects unsupported app link forms instead of using direct registration", async () => { - form = undefined; + form = { + uri: "/hooks/sentry/issues/link", + required_fields: [{ name: "issueId", type: "checkbox" }], + }; await expect(resolveAppIssueLink(OPTIONS)).rejects.toThrow( "does not expose", ); diff --git a/packages/cli/test/lib/formatters/human.details.test.ts b/packages/cli/test/lib/formatters/human.details.test.ts index ad61d0d05..e3840d155 100644 --- a/packages/cli/test/lib/formatters/human.details.test.ts +++ b/packages/cli/test/lib/formatters/human.details.test.ts @@ -333,6 +333,17 @@ describe("formatIssueDetails", () => { expect(result).toContain("→"); }); + test("falls back to version for releases Sentry could not load", () => { + // Sentry returns only `version` when the release row no longer exists. + const issue = createMockIssue({ + firstRelease: { version: "backend@1.0.0" }, + lastRelease: { version: "backend@2.0.0" }, + }); + + const result = stripAnsi(formatIssueDetails(issue)); + expect(result).toContain("backend@1.0.0 → backend@2.0.0"); + }); + test("includes assignee name", () => { const issue = createMockIssue({ assignedTo: { name: "John Doe" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2673d8e26..8c3b6c713 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,8 +43,8 @@ catalogs: specifier: ^1.2.3 version: 1.2.3 '@sentry/api': - specifier: 1.51.0 - version: 1.51.0 + specifier: 1.53.0 + version: 1.53.0 '@sentry/cloudflare': specifier: 11.6.0 version: 11.6.0 @@ -513,7 +513,7 @@ importers: version: 2.1.5(hono@4.13.13) '@sentry/api': specifier: 'catalog:' - version: 1.51.0(typescript@5.9.3)(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7) + version: 1.53.0(typescript@5.9.3)(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7) '@sentry/core': specifier: 'catalog:' version: 11.6.0 @@ -865,7 +865,7 @@ importers: version: 2.4.0 '@sentry/api': specifier: 'catalog:' - version: 1.51.0(typescript@5.9.3)(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7) + version: 1.53.0(typescript@5.9.3)(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7) '@sentry/core': specifier: 'catalog:' version: 11.6.0 @@ -1048,7 +1048,7 @@ importers: dependencies: '@sentry/api': specifier: 'catalog:' - version: 1.51.0(typescript@5.9.3)(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7) + version: 1.53.0(typescript@5.9.3)(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7) '@sentry/search-syntax': specifier: 'catalog:' version: 1.0.0 @@ -4275,8 +4275,8 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@sentry/api@1.51.0': - resolution: {integrity: sha512-lp8nQxIKNcMR0WlwdLqvlAYY9zW7B4QFMxjl3dTWNM/o/2u+g6++hPnbXWkCPKQsJWTtQmv42Av1izlfaoFFGQ==} + '@sentry/api@1.53.0': + resolution: {integrity: sha512-xAQ6q/BZSOULvs3BHeRY99sEZkVFdaINRKE6a2s3KIjLRfjK/bniaNv9jKNiLFIHLH0NG+k7kDmdyKhl1+u5mQ==} engines: {node: '>=22'} peerDependencies: typescript: '*' @@ -12112,7 +12112,7 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@sentry/api@1.51.0(typescript@5.9.3)(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7)': + '@sentry/api@1.53.0(typescript@5.9.3)(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7)': dependencies: http-cache-semantics: 4.2.0 p-limit: 7.3.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 10ff6a807..133ce1bd1 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -51,7 +51,7 @@ catalog: "@modelcontextprotocol/server": 2.4.0 "@radix-ui/react-accordion": ^1.2.20 "@radix-ui/react-slot": ^1.2.3 - "@sentry/api": 1.51.0 + "@sentry/api": 1.53.0 "@sentry/cloudflare": 11.6.0 "@sentry/core": 11.6.0 "@sentry/node": 11.6.0 @@ -168,7 +168,7 @@ minimumReleaseAgeExclude: - "@oxlint/binding-win32-x64-msvc@1.87.0" - oxfmt@0.72.0 - oxlint@1.87.0 - - "@sentry/api@1.51.0" + - "@sentry/api@1.53.0" - miniflare@5.20261101.0-beta - wrangler@4.156.1 - "@sentry/symbolic@14.0.0-alpha.5"