@sentry/junior-sentry 0.242.0 → 0.243.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,7 @@ Verified CLI surface (check live `sentry --help` before blocking):
36
36
 
37
37
  Set `SENTRY_READ_TOKEN` to a read-only internal integration token. Junior then uses it for Sentry API reads (`GET`, `HEAD`, `OPTIONS`), including Explore queries, scheduled tasks, and event automations. Writes and `/api/0/users/` requests still use the requesting user's OAuth token.
38
38
 
39
- ## Issue webhooks
39
+ ## Issue and metric alert webhooks
40
40
 
41
41
  Use a **Sentry internal integration** (not a public app install flow):
42
42
 
@@ -44,6 +44,13 @@ Use a **Sentry internal integration** (not a public app install flow):
44
44
  2. Point it at `https://<junior-host>/api/webhooks/sentry`.
45
45
  3. Set the org slug as `SENTRY_WEBHOOK_ORG` and the integration client secret as `SENTRY_WEBHOOK_SECRET`, then redeploy.
46
46
 
47
- Junior verifies `Sentry-Hook-Signature`, rejects payloads outside `SENTRY_WEBHOOK_ORG`, and publishes `issue.created` for `org/project#issueId` and `org/project`. Create watches/event automations before the issue arrives; unmatched deliveries are not replayed.
47
+ For metric alerts, also enable **Alert Rule Action** on the integration and add it as a `sentry_app` trigger action on each alert. Sentry sends metric alerts only to alert actions.
48
+
49
+ Junior verifies `Sentry-Hook-Signature`, rejects payloads outside `SENTRY_WEBHOOK_ORG`, and publishes:
50
+
51
+ - `issue.created` for `org/project#issueId` and `org/project`.
52
+ - `metric_alert.critical`, `metric_alert.warning`, and `metric_alert.resolved` for `metric-alert:org/alertRuleId`.
53
+
54
+ Create watches/event automations before the webhook arrives; unmatched deliveries are not replayed.
48
55
 
49
56
  Full guide: https://junior.sentry.dev/extend/sentry-plugin/
@@ -15,6 +15,28 @@ function sentryProjectResource(input) {
15
15
  };
16
16
  }
17
17
 
18
+ // src/events/metric-alert.ts
19
+ var SENTRY_METRIC_ALERT_EVENTS = [
20
+ "metric_alert.critical",
21
+ "metric_alert.warning",
22
+ "metric_alert.resolved"
23
+ ];
24
+ var SENTRY_METRIC_ALERT_SUGGESTED_EVENTS = [
25
+ "metric_alert.critical",
26
+ "metric_alert.warning"
27
+ ];
28
+ var SENTRY_METRIC_ALERT_IDENTIFIER = {
29
+ format: "metric-alert:org/alertRuleId",
30
+ pattern: /^metric-alert:[^\s/:#]+\/\d+$/
31
+ };
32
+ function sentryMetricAlertResource(input) {
33
+ return {
34
+ identifier: `metric-alert:${input.org}/${input.alertRuleId}`,
35
+ label: `Sentry metric alert ${input.org}/${input.alertRuleId}`,
36
+ namespace: "sentry"
37
+ };
38
+ }
39
+
18
40
  // src/webhooks/events.ts
19
41
  import { z } from "zod";
20
42
  var issueWebhookSchema = z.object({
@@ -38,6 +60,19 @@ var issueWebhookSchema = z.object({
38
60
  }),
39
61
  installation: z.object({ uuid: z.string().min(1) }).passthrough()
40
62
  }).passthrough();
63
+ var metricAlertWebhookSchema = z.object({
64
+ action: z.string(),
65
+ data: z.object({
66
+ description_text: z.string().optional(),
67
+ description_title: z.string().optional(),
68
+ metric_alert: z.object({
69
+ alert_rule: z.object({ id: z.coerce.string().min(1) }).passthrough(),
70
+ id: z.coerce.string().min(1)
71
+ }).passthrough(),
72
+ web_url: z.string().url()
73
+ }),
74
+ installation: z.object({ uuid: z.string().min(1) }).passthrough()
75
+ }).passthrough();
41
76
  function organizationSlug(issueApiUrl) {
42
77
  try {
43
78
  const segments = new URL(issueApiUrl).pathname.split("/").filter(Boolean);
@@ -70,8 +105,13 @@ function issueText(issue) {
70
105
  ].filter((part) => part !== void 0);
71
106
  return parts.length > 0 ? parts.join("\n") : void 0;
72
107
  }
73
- function normalizeSentryEvents(input) {
74
- if (input.hookResource !== "issue") return [];
108
+ function webOrganizationSlug(webUrl) {
109
+ const fromPath = organizationSlug(webUrl);
110
+ if (fromPath) return fromPath;
111
+ const labels = new URL(webUrl).hostname.split(".");
112
+ return labels.length >= 3 ? labels[0] : void 0;
113
+ }
114
+ function normalizeIssueEvents(input) {
75
115
  const parsed = issueWebhookSchema.safeParse(input.body);
76
116
  if (!parsed.success || parsed.data.action !== "created") return [];
77
117
  const issue = parsed.data.data.issue;
@@ -103,6 +143,47 @@ function normalizeSentryEvents(input) {
103
143
  { ...event, identifier: projectResource.identifier }
104
144
  ];
105
145
  }
146
+ function normalizeMetricAlertEvents(input) {
147
+ const parsed = metricAlertWebhookSchema.safeParse(input.body);
148
+ if (!parsed.success) return [];
149
+ const eventType = `metric_alert.${parsed.data.action}`;
150
+ if (!SENTRY_METRIC_ALERT_EVENTS.includes(eventType)) {
151
+ return [];
152
+ }
153
+ const { data } = parsed.data;
154
+ const org = webOrganizationSlug(data.web_url)?.toLowerCase();
155
+ if (!org || org !== input.webhookOrg.toLowerCase()) return [];
156
+ const alertRuleId = data.metric_alert.alert_rule.id;
157
+ const incidentId = data.metric_alert.id;
158
+ const resource = sentryMetricAlertResource({ alertRuleId, org });
159
+ const untrustedText = [
160
+ data.description_title,
161
+ data.description_text,
162
+ `URL: ${data.web_url}`
163
+ ].filter((part) => Boolean(part)).join("\n");
164
+ return [
165
+ {
166
+ // One event per incident state. Sentry retries get new Request-IDs.
167
+ eventKey: `sentry:${resource.identifier}:${incidentId}:${eventType}`,
168
+ eventType,
169
+ identifier: resource.identifier,
170
+ occurredAtMs: providerTime(input.hookTimestamp) ?? Date.now(),
171
+ trustedSummary: `${resource.label} is ${parsed.data.action}.`,
172
+ data: { alertRuleId, incidentId, url: data.web_url },
173
+ untrustedText
174
+ }
175
+ ];
176
+ }
177
+ function normalizeSentryEvents(input) {
178
+ switch (input.hookResource) {
179
+ case "issue":
180
+ return normalizeIssueEvents(input);
181
+ case "metric_alert":
182
+ return normalizeMetricAlertEvents(input);
183
+ default:
184
+ return [];
185
+ }
186
+ }
106
187
 
107
188
  // src/webhooks/handler.ts
108
189
  import { createHmac, timingSafeEqual } from "crypto";
@@ -176,6 +257,9 @@ export {
176
257
  SENTRY_ISSUE_EVENTS,
177
258
  sentryIssueResource,
178
259
  sentryProjectResource,
260
+ SENTRY_METRIC_ALERT_EVENTS,
261
+ SENTRY_METRIC_ALERT_SUGGESTED_EVENTS,
262
+ SENTRY_METRIC_ALERT_IDENTIFIER,
179
263
  normalizeSentryEvents,
180
264
  createSentryWebhookRoute,
181
265
  sentryWebhookSecret,
@@ -0,0 +1,14 @@
1
+ import type { SubscribableResource } from "@sentry/junior-plugin-api";
2
+ /** Metric alert states, named after Sentry's `metric_alert.<action>` webhooks. */
3
+ export declare const SENTRY_METRIC_ALERT_EVENTS: readonly ["metric_alert.critical", "metric_alert.warning", "metric_alert.resolved"];
4
+ export declare const SENTRY_METRIC_ALERT_SUGGESTED_EVENTS: string[];
5
+ /** The identifier shape that `sentryMetricAlertResource` builds. */
6
+ export declare const SENTRY_METRIC_ALERT_IDENTIFIER: {
7
+ format: string;
8
+ pattern: RegExp;
9
+ };
10
+ /** Build the stable Sentry metric alert identity shared by tools and webhooks. */
11
+ export declare function sentryMetricAlertResource(input: {
12
+ alertRuleId: string;
13
+ org: string;
14
+ }): Pick<SubscribableResource, "identifier" | "label" | "namespace">;
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * Sentry plugin runtime boundary.
3
3
  *
4
4
  * This package owns per-user Sentry OAuth, org read access, CLI setup,
5
- * internal-integration issue webhook normalization, and Sentry resource
6
- * identities. Junior core owns watches and event automations.
5
+ * internal-integration issue and metric alert webhook normalization, and
6
+ * Sentry resource identities. Junior core owns watches and event automations.
7
7
  */
8
8
  import { type PluginRegistration } from "@sentry/junior-plugin-api";
9
9
  /** Register Sentry runtime metadata and signed event ingress. */
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  SENTRY_ISSUE_EVENTS,
3
+ SENTRY_METRIC_ALERT_EVENTS,
4
+ SENTRY_METRIC_ALERT_IDENTIFIER,
5
+ SENTRY_METRIC_ALERT_SUGGESTED_EVENTS,
3
6
  createSentryWebhookRoute,
4
7
  sentryWebhookOrg,
5
8
  sentryWebhookSecret
6
- } from "./chunk-UITYE7V6.js";
9
+ } from "./chunk-6VG4NPNH.js";
7
10
 
8
11
  // src/index.ts
9
12
  import {
@@ -79,6 +82,12 @@ function sentryPlugin() {
79
82
  type: "project",
80
83
  supportedEvents: [...SENTRY_ISSUE_EVENTS],
81
84
  suggestedEvents: [...SENTRY_ISSUE_EVENTS]
85
+ },
86
+ {
87
+ type: "metric_alert",
88
+ identifier: SENTRY_METRIC_ALERT_IDENTIFIER,
89
+ supportedEvents: [...SENTRY_METRIC_ALERT_EVENTS],
90
+ suggestedEvents: SENTRY_METRIC_ALERT_SUGGESTED_EVENTS
82
91
  }
83
92
  ],
84
93
  isEnabled: () => Boolean(sentryWebhookSecret()) && Boolean(sentryWebhookOrg()),
@@ -95,7 +104,7 @@ function sentryPlugin() {
95
104
  domains: SENTRY_API_DOMAINS,
96
105
  type: "oauth-bearer"
97
106
  },
98
- description: "Investigate Sentry telemetry, manage alerting, and receive issue webhooks",
107
+ description: "Investigate Sentry telemetry, manage alerting, and receive issue and metric alert webhooks",
99
108
  displayName: "Sentry",
100
109
  envVars: {
101
110
  SENTRY_CLIENT_ID: {},
package/dist/testing.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  sentryProjectResource,
6
6
  sentryWebhookOrg,
7
7
  sentryWebhookSecret
8
- } from "./chunk-UITYE7V6.js";
8
+ } from "./chunk-6VG4NPNH.js";
9
9
  export {
10
10
  createSentryWebhookRoute,
11
11
  normalizeSentryEvents,
@@ -1,8 +1,10 @@
1
1
  import type { EventInput } from "@sentry/junior-plugin-api";
2
- /** Normalize one verified Sentry webhook into issue- and project-scoped events. */
3
- export declare function normalizeSentryEvents(input: {
2
+ interface WebhookInput {
4
3
  body: unknown;
5
4
  hookResource: string;
6
5
  hookTimestamp?: string;
7
6
  webhookOrg: string;
8
- }): EventInput[];
7
+ }
8
+ /** Normalize one verified Sentry webhook into subscribable Sentry events. */
9
+ export declare function normalizeSentryEvents(input: WebhookInput): EventInput[];
10
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-sentry",
3
- "version": "0.242.0",
3
+ "version": "0.243.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "zod": "^4.5.4",
30
- "@sentry/junior-plugin-api": "0.242.0"
30
+ "@sentry/junior-plugin-api": "0.243.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@oxlint/plugins": "1.79.0",