@backstage/plugin-notifications-backend-module-slack 0.1.0-next.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/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @backstage/plugin-notifications-backend-module-slack
2
+
3
+ ## 0.1.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 552170d: Added a new Slack NotificationProcessor for use with the notifications plugin
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/backend-plugin-api@1.2.1
13
+ - @backstage/catalog-client@1.9.1
14
+ - @backstage/catalog-model@1.7.3
15
+ - @backstage/config@1.3.2
16
+ - @backstage/errors@1.2.7
17
+ - @backstage/types@1.2.1
18
+ - @backstage/plugin-notifications-common@0.0.8
19
+ - @backstage/plugin-notifications-node@0.2.13
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @backstage/plugin-notifications-backend-module-slack
2
+
3
+ The Slack backend module for the notifications plugin.
4
+
5
+ See [Built-in Processors](https://backstage.io/docs/notifications/processors/#built-in-processors) for detailed documentation
package/config.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Copyright 2025 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export interface Config {
17
+ notifications?: {
18
+ processors?: {
19
+ slack?: Array<{
20
+ /**
21
+ * Slack Bot Token. Usually starts with `xoxb-`.
22
+ * @visibility secret
23
+ */
24
+ token?: string;
25
+ /**
26
+ * Broadcast notification receivers when receiver is set to config
27
+ * These can be Slack User IDs, Slack User Email addresses, Slack Channel
28
+ * Names, or Slack Channel IDs. Any valid identifier that chat.postMessage can accept.
29
+ */
30
+ broadcastChannels?: string[];
31
+ }>;
32
+ };
33
+ };
34
+ }
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ require('@backstage/catalog-model');
6
+ require('@backstage/errors');
7
+ require('@backstage/types');
8
+ require('@opentelemetry/api');
9
+ require('@slack/web-api');
10
+ require('dataloader');
11
+ require('p-throttle');
12
+ var constants = require('./lib/constants.cjs.js');
13
+ var module$1 = require('./module.cjs.js');
14
+
15
+
16
+
17
+ exports.ANNOTATION_SLACK_BOT_NOTIFY = constants.ANNOTATION_SLACK_BOT_NOTIFY;
18
+ exports.default = module$1.notificationsModuleSlack;
19
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,25 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
+
3
+ /**
4
+ * @public
5
+ * The annotation key for the entity's Slack ID. This can be
6
+ * any valid chat.postMessage destination including:
7
+ * - A user ID (U12345678)
8
+ * - A channel ID (C12345678)
9
+ * - A DM ID (D12345678)
10
+ * - A group ID (S12345678)
11
+ *
12
+ * It can also be a user's email address or a channel name,
13
+ * however IDs are preferred.
14
+ */
15
+ declare const ANNOTATION_SLACK_BOT_NOTIFY = "slack.com/bot-notify";
16
+
17
+ /**
18
+ * The Slack notification processor for use with the notifications plugin.
19
+ * This allows sending of notifications via Slack DMs or to channels.
20
+ *
21
+ * @public
22
+ */
23
+ declare const notificationsModuleSlack: _backstage_backend_plugin_api.BackendFeature;
24
+
25
+ export { ANNOTATION_SLACK_BOT_NOTIFY, notificationsModuleSlack as default };
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+
3
+ var catalogModel = require('@backstage/catalog-model');
4
+ var errors = require('@backstage/errors');
5
+ var types = require('@backstage/types');
6
+ var api = require('@opentelemetry/api');
7
+ var webApi = require('@slack/web-api');
8
+ var DataLoader = require('dataloader');
9
+ var pThrottle = require('p-throttle');
10
+ var constants = require('./constants.cjs.js');
11
+ var util = require('./util.cjs.js');
12
+
13
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
14
+
15
+ var DataLoader__default = /*#__PURE__*/_interopDefaultCompat(DataLoader);
16
+ var pThrottle__default = /*#__PURE__*/_interopDefaultCompat(pThrottle);
17
+
18
+ class SlackNotificationProcessor {
19
+ logger;
20
+ catalog;
21
+ auth;
22
+ slack;
23
+ sendNotifications;
24
+ messagesSent;
25
+ messagesFailed;
26
+ broadcastChannels;
27
+ static fromConfig(config, options) {
28
+ const slackConfig = config.getOptionalConfigArray("notifications.processors.slack") ?? [];
29
+ return slackConfig.map((c) => {
30
+ const token = c.getString("token");
31
+ const slack = options.slack ?? new webApi.WebClient(token);
32
+ const broadcastChannels = c.getOptionalStringArray("broadcastChannels");
33
+ return new SlackNotificationProcessor({
34
+ slack,
35
+ broadcastChannels,
36
+ ...options
37
+ });
38
+ });
39
+ }
40
+ constructor(options) {
41
+ const { auth, catalog, logger, slack, broadcastChannels } = options;
42
+ this.logger = logger;
43
+ this.catalog = catalog;
44
+ this.auth = auth;
45
+ this.slack = slack;
46
+ this.broadcastChannels = broadcastChannels;
47
+ const meter = api.metrics.getMeter("default");
48
+ this.messagesSent = meter.createCounter(
49
+ "notifications.processors.slack.sent.count",
50
+ {
51
+ description: "Number of messages sent to Slack successfully"
52
+ }
53
+ );
54
+ this.messagesFailed = meter.createCounter(
55
+ "notifications.processors.slack.error.count",
56
+ {
57
+ description: "Number of messages that failed to send to Slack"
58
+ }
59
+ );
60
+ const throttle = pThrottle__default.default({
61
+ limit: 10,
62
+ interval: types.durationToMilliseconds({ minutes: 1 })
63
+ });
64
+ const throttled = throttle(
65
+ (opts) => this.sendNotification(opts)
66
+ );
67
+ this.sendNotifications = async (opts) => {
68
+ const results = await Promise.allSettled(
69
+ opts.map((message) => throttled(message))
70
+ );
71
+ let successCount = 0;
72
+ let failureCount = 0;
73
+ results.forEach((result) => {
74
+ if (result.status === "fulfilled") {
75
+ successCount++;
76
+ } else {
77
+ this.logger.error(
78
+ `Failed to send Slack channel notification: ${result.reason.message}`
79
+ );
80
+ failureCount++;
81
+ }
82
+ });
83
+ this.messagesSent.add(successCount);
84
+ this.messagesFailed.add(failureCount);
85
+ };
86
+ }
87
+ getName() {
88
+ return "SlackNotificationProcessor";
89
+ }
90
+ async processOptions(options) {
91
+ if (options.recipients.type !== "entity") {
92
+ return options;
93
+ }
94
+ const entityRefs = [options.recipients.entityRef].flat();
95
+ const outbound = [];
96
+ await Promise.all(
97
+ entityRefs.map(async (entityRef) => {
98
+ const compoundEntityRef = catalogModel.parseEntityRef(entityRef);
99
+ if (compoundEntityRef.kind === "user") {
100
+ return;
101
+ }
102
+ let channel;
103
+ try {
104
+ channel = await this.getSlackNotificationTarget(entityRef);
105
+ } catch (error) {
106
+ this.logger.error(
107
+ `Failed to get Slack channel for entity: ${error.message}`
108
+ );
109
+ return;
110
+ }
111
+ if (!channel) {
112
+ this.logger.debug(`No Slack channel found for entity: ${entityRef}`);
113
+ return;
114
+ }
115
+ this.logger.debug(
116
+ `Sending notification with payload: ${JSON.stringify(
117
+ options.payload
118
+ )}`
119
+ );
120
+ const payload = util.toChatPostMessageArgs({
121
+ channel,
122
+ payload: options.payload
123
+ });
124
+ this.logger.debug(
125
+ `Sending Slack channel notification: ${JSON.stringify(payload)}`
126
+ );
127
+ outbound.push(payload);
128
+ })
129
+ );
130
+ console.log("dispatching message");
131
+ await this.sendNotifications(outbound);
132
+ return options;
133
+ }
134
+ async postProcess(notification, options) {
135
+ const destinations = [];
136
+ if (notification.user === null) {
137
+ destinations.push(...this.broadcastChannels ?? []);
138
+ } else if (options.recipients.type === "entity") {
139
+ const entityRefs = [options.recipients.entityRef].flat();
140
+ if (entityRefs.some((e) => catalogModel.parseEntityRef(e).kind === "group")) {
141
+ return;
142
+ }
143
+ const destination = await this.getSlackNotificationTarget(
144
+ notification.user
145
+ );
146
+ if (!destination) {
147
+ this.logger.error(
148
+ `No slack.com/bot-notify annotation found for user: ${notification.user}`
149
+ );
150
+ return;
151
+ }
152
+ destinations.push(destination);
153
+ }
154
+ if (destinations.length === 0) {
155
+ return;
156
+ }
157
+ const outbound = destinations.map(
158
+ (channel) => util.toChatPostMessageArgs({ channel, payload: options.payload })
159
+ );
160
+ outbound.forEach((payload) => {
161
+ this.logger.debug(`Sending notification: ${JSON.stringify(payload)}`);
162
+ });
163
+ await this.sendNotifications(outbound);
164
+ }
165
+ async getEntities(entityRefs) {
166
+ const { token } = await this.auth.getPluginRequestToken({
167
+ onBehalfOf: await this.auth.getOwnServiceCredentials(),
168
+ targetPluginId: "catalog"
169
+ });
170
+ const response = await this.catalog.getEntitiesByRefs(
171
+ {
172
+ entityRefs: entityRefs.slice(),
173
+ fields: [`metadata.annotations.${constants.ANNOTATION_SLACK_BOT_NOTIFY}`]
174
+ },
175
+ {
176
+ token
177
+ }
178
+ );
179
+ return response.items;
180
+ }
181
+ async getSlackNotificationTarget(entityRef) {
182
+ const entityLoader = new DataLoader__default.default(
183
+ (entityRefs) => this.getEntities(entityRefs)
184
+ );
185
+ const entity = await entityLoader.load(entityRef);
186
+ if (!entity) {
187
+ console.log(`Entity not found: ${entityRef}`);
188
+ throw new errors.NotFoundError(`Entity not found: ${entityRef}`);
189
+ }
190
+ return entity?.metadata?.annotations?.[constants.ANNOTATION_SLACK_BOT_NOTIFY];
191
+ }
192
+ async sendNotification(args) {
193
+ const response = await this.slack.chat.postMessage(args);
194
+ if (!response.ok) {
195
+ throw new Error(`Failed to send notification: ${response.error}`);
196
+ }
197
+ }
198
+ }
199
+
200
+ exports.SlackNotificationProcessor = SlackNotificationProcessor;
201
+ //# sourceMappingURL=SlackNotificationProcessor.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackNotificationProcessor.cjs.js","sources":["../../src/lib/SlackNotificationProcessor.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n DiscoveryService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogApi } from '@backstage/catalog-client';\nimport { Entity, parseEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport { Notification } from '@backstage/plugin-notifications-common';\nimport {\n NotificationProcessor,\n NotificationSendOptions,\n} from '@backstage/plugin-notifications-node';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { ChatPostMessageArguments, WebClient } from '@slack/web-api';\nimport DataLoader from 'dataloader';\nimport pThrottle from 'p-throttle';\nimport { ANNOTATION_SLACK_BOT_NOTIFY } from './constants';\nimport { toChatPostMessageArgs } from './util';\n\nexport class SlackNotificationProcessor implements NotificationProcessor {\n private readonly logger: LoggerService;\n private readonly catalog: CatalogApi;\n private readonly auth: AuthService;\n private readonly slack: WebClient;\n private readonly sendNotifications;\n private readonly messagesSent: Counter;\n private readonly messagesFailed: Counter;\n private readonly broadcastChannels?: string[];\n\n static fromConfig(\n config: Config,\n options: {\n auth: AuthService;\n discovery: DiscoveryService;\n logger: LoggerService;\n catalog: CatalogApi;\n slack?: WebClient;\n broadcastChannels?: string[];\n },\n ): SlackNotificationProcessor[] {\n const slackConfig =\n config.getOptionalConfigArray('notifications.processors.slack') ?? [];\n return slackConfig.map(c => {\n const token = c.getString('token');\n const slack = options.slack ?? new WebClient(token);\n const broadcastChannels = c.getOptionalStringArray('broadcastChannels');\n return new SlackNotificationProcessor({\n slack,\n broadcastChannels,\n ...options,\n });\n });\n }\n\n private constructor(options: {\n slack: WebClient;\n auth: AuthService;\n discovery: DiscoveryService;\n logger: LoggerService;\n catalog: CatalogApi;\n broadcastChannels?: string[];\n }) {\n const { auth, catalog, logger, slack, broadcastChannels } = options;\n this.logger = logger;\n this.catalog = catalog;\n this.auth = auth;\n this.slack = slack;\n this.broadcastChannels = broadcastChannels;\n\n const meter = metrics.getMeter('default');\n this.messagesSent = meter.createCounter(\n 'notifications.processors.slack.sent.count',\n {\n description: 'Number of messages sent to Slack successfully',\n },\n );\n this.messagesFailed = meter.createCounter(\n 'notifications.processors.slack.error.count',\n {\n description: 'Number of messages that failed to send to Slack',\n },\n );\n\n const throttle = pThrottle({\n limit: 10,\n interval: durationToMilliseconds({ minutes: 1 }),\n });\n const throttled = throttle((opts: ChatPostMessageArguments) =>\n this.sendNotification(opts),\n );\n this.sendNotifications = async (opts: ChatPostMessageArguments[]) => {\n const results = await Promise.allSettled(\n opts.map(message => throttled(message)),\n );\n\n let successCount = 0;\n let failureCount = 0;\n\n results.forEach(result => {\n if (result.status === 'fulfilled') {\n successCount++;\n } else {\n this.logger.error(\n `Failed to send Slack channel notification: ${result.reason.message}`,\n );\n failureCount++;\n }\n });\n\n this.messagesSent.add(successCount);\n this.messagesFailed.add(failureCount);\n };\n }\n\n getName(): string {\n return 'SlackNotificationProcessor';\n }\n\n async processOptions(\n options: NotificationSendOptions,\n ): Promise<NotificationSendOptions> {\n if (options.recipients.type !== 'entity') {\n return options;\n }\n\n const entityRefs = [options.recipients.entityRef].flat();\n\n const outbound: ChatPostMessageArguments[] = [];\n await Promise.all(\n entityRefs.map(async entityRef => {\n const compoundEntityRef = parseEntityRef(entityRef);\n // skip users as they are sent direct messages\n if (compoundEntityRef.kind === 'user') {\n return;\n }\n\n let channel;\n try {\n channel = await this.getSlackNotificationTarget(entityRef);\n } catch (error) {\n this.logger.error(\n `Failed to get Slack channel for entity: ${\n (error as Error).message\n }`,\n );\n return;\n }\n\n if (!channel) {\n this.logger.debug(`No Slack channel found for entity: ${entityRef}`);\n return;\n }\n\n this.logger.debug(\n `Sending notification with payload: ${JSON.stringify(\n options.payload,\n )}`,\n );\n\n const payload = toChatPostMessageArgs({\n channel,\n payload: options.payload,\n });\n\n this.logger.debug(\n `Sending Slack channel notification: ${JSON.stringify(payload)}`,\n );\n outbound.push(payload);\n }),\n );\n\n console.log('dispatching message');\n await this.sendNotifications(outbound);\n\n return options;\n }\n\n async postProcess(\n notification: Notification,\n options: NotificationSendOptions,\n ): Promise<void> {\n const destinations: string[] = [];\n\n // Handle broadcast case\n if (notification.user === null) {\n destinations.push(...(this.broadcastChannels ?? []));\n } else if (options.recipients.type === 'entity') {\n // Handle user-specific notification\n const entityRefs = [options.recipients.entityRef].flat();\n if (entityRefs.some(e => parseEntityRef(e).kind === 'group')) {\n // We've already dispatched a slack channel message, so let's not send a DM.\n return;\n }\n\n const destination = await this.getSlackNotificationTarget(\n notification.user,\n );\n\n if (!destination) {\n this.logger.error(\n `No slack.com/bot-notify annotation found for user: ${notification.user}`,\n );\n return;\n }\n\n destinations.push(destination);\n }\n\n // If no destinations, nothing to do\n if (destinations.length === 0) {\n return;\n }\n\n // Prepare outbound messages\n const outbound = destinations.map(channel =>\n toChatPostMessageArgs({ channel, payload: options.payload }),\n );\n\n // Log debug info\n outbound.forEach(payload => {\n this.logger.debug(`Sending notification: ${JSON.stringify(payload)}`);\n });\n\n // Send notifications\n await this.sendNotifications(outbound);\n }\n\n async getEntities(\n entityRefs: readonly string[],\n ): Promise<(Entity | undefined)[]> {\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: await this.auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n\n const response = await this.catalog.getEntitiesByRefs(\n {\n entityRefs: entityRefs.slice(),\n fields: [`metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`],\n },\n {\n token,\n },\n );\n\n return response.items;\n }\n\n async getSlackNotificationTarget(\n entityRef: string,\n ): Promise<string | undefined> {\n const entityLoader = new DataLoader<string, Entity | undefined>(\n entityRefs => this.getEntities(entityRefs),\n );\n const entity = await entityLoader.load(entityRef);\n\n if (!entity) {\n console.log(`Entity not found: ${entityRef}`);\n throw new NotFoundError(`Entity not found: ${entityRef}`);\n }\n\n return entity?.metadata?.annotations?.[ANNOTATION_SLACK_BOT_NOTIFY];\n }\n\n async sendNotification(args: ChatPostMessageArguments): Promise<void> {\n const response = await this.slack.chat.postMessage(args);\n\n if (!response.ok) {\n throw new Error(`Failed to send notification: ${response.error}`);\n }\n }\n}\n"],"names":["WebClient","metrics","pThrottle","durationToMilliseconds","parseEntityRef","toChatPostMessageArgs","ANNOTATION_SLACK_BOT_NOTIFY","DataLoader","NotFoundError"],"mappings":";;;;;;;;;;;;;;;;;AAsCO,MAAM,0BAA4D,CAAA;AAAA,EACtD,MAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,iBAAA;AAAA,EACA,YAAA;AAAA,EACA,cAAA;AAAA,EACA,iBAAA;AAAA,EAEjB,OAAO,UACL,CAAA,MAAA,EACA,OAQ8B,EAAA;AAC9B,IAAA,MAAM,WACJ,GAAA,MAAA,CAAO,sBAAuB,CAAA,gCAAgC,KAAK,EAAC;AACtE,IAAO,OAAA,WAAA,CAAY,IAAI,CAAK,CAAA,KAAA;AAC1B,MAAM,MAAA,KAAA,GAAQ,CAAE,CAAA,SAAA,CAAU,OAAO,CAAA;AACjC,MAAA,MAAM,KAAQ,GAAA,OAAA,CAAQ,KAAS,IAAA,IAAIA,iBAAU,KAAK,CAAA;AAClD,MAAM,MAAA,iBAAA,GAAoB,CAAE,CAAA,sBAAA,CAAuB,mBAAmB,CAAA;AACtE,MAAA,OAAO,IAAI,0BAA2B,CAAA;AAAA,QACpC,KAAA;AAAA,QACA,iBAAA;AAAA,QACA,GAAG;AAAA,OACJ,CAAA;AAAA,KACF,CAAA;AAAA;AACH,EAEQ,YAAY,OAOjB,EAAA;AACD,IAAA,MAAM,EAAE,IAAM,EAAA,OAAA,EAAS,MAAQ,EAAA,KAAA,EAAO,mBAAsB,GAAA,OAAA;AAC5D,IAAA,IAAA,CAAK,MAAS,GAAA,MAAA;AACd,IAAA,IAAA,CAAK,OAAU,GAAA,OAAA;AACf,IAAA,IAAA,CAAK,IAAO,GAAA,IAAA;AACZ,IAAA,IAAA,CAAK,KAAQ,GAAA,KAAA;AACb,IAAA,IAAA,CAAK,iBAAoB,GAAA,iBAAA;AAEzB,IAAM,MAAA,KAAA,GAAQC,WAAQ,CAAA,QAAA,CAAS,SAAS,CAAA;AACxC,IAAA,IAAA,CAAK,eAAe,KAAM,CAAA,aAAA;AAAA,MACxB,2CAAA;AAAA,MACA;AAAA,QACE,WAAa,EAAA;AAAA;AACf,KACF;AACA,IAAA,IAAA,CAAK,iBAAiB,KAAM,CAAA,aAAA;AAAA,MAC1B,4CAAA;AAAA,MACA;AAAA,QACE,WAAa,EAAA;AAAA;AACf,KACF;AAEA,IAAA,MAAM,WAAWC,0BAAU,CAAA;AAAA,MACzB,KAAO,EAAA,EAAA;AAAA,MACP,QAAU,EAAAC,4BAAA,CAAuB,EAAE,OAAA,EAAS,GAAG;AAAA,KAChD,CAAA;AACD,IAAA,MAAM,SAAY,GAAA,QAAA;AAAA,MAAS,CAAC,IAAA,KAC1B,IAAK,CAAA,gBAAA,CAAiB,IAAI;AAAA,KAC5B;AACA,IAAK,IAAA,CAAA,iBAAA,GAAoB,OAAO,IAAqC,KAAA;AACnE,MAAM,MAAA,OAAA,GAAU,MAAM,OAAQ,CAAA,UAAA;AAAA,QAC5B,IAAK,CAAA,GAAA,CAAI,CAAW,OAAA,KAAA,SAAA,CAAU,OAAO,CAAC;AAAA,OACxC;AAEA,MAAA,IAAI,YAAe,GAAA,CAAA;AACnB,MAAA,IAAI,YAAe,GAAA,CAAA;AAEnB,MAAA,OAAA,CAAQ,QAAQ,CAAU,MAAA,KAAA;AACxB,QAAI,IAAA,MAAA,CAAO,WAAW,WAAa,EAAA;AACjC,UAAA,YAAA,EAAA;AAAA,SACK,MAAA;AACL,UAAA,IAAA,CAAK,MAAO,CAAA,KAAA;AAAA,YACV,CAAA,2CAAA,EAA8C,MAAO,CAAA,MAAA,CAAO,OAAO,CAAA;AAAA,WACrE;AACA,UAAA,YAAA,EAAA;AAAA;AACF,OACD,CAAA;AAED,MAAK,IAAA,CAAA,YAAA,CAAa,IAAI,YAAY,CAAA;AAClC,MAAK,IAAA,CAAA,cAAA,CAAe,IAAI,YAAY,CAAA;AAAA,KACtC;AAAA;AACF,EAEA,OAAkB,GAAA;AAChB,IAAO,OAAA,4BAAA;AAAA;AACT,EAEA,MAAM,eACJ,OACkC,EAAA;AAClC,IAAI,IAAA,OAAA,CAAQ,UAAW,CAAA,IAAA,KAAS,QAAU,EAAA;AACxC,MAAO,OAAA,OAAA;AAAA;AAGT,IAAA,MAAM,aAAa,CAAC,OAAA,CAAQ,UAAW,CAAA,SAAS,EAAE,IAAK,EAAA;AAEvD,IAAA,MAAM,WAAuC,EAAC;AAC9C,IAAA,MAAM,OAAQ,CAAA,GAAA;AAAA,MACZ,UAAA,CAAW,GAAI,CAAA,OAAM,SAAa,KAAA;AAChC,QAAM,MAAA,iBAAA,GAAoBC,4BAAe,SAAS,CAAA;AAElD,QAAI,IAAA,iBAAA,CAAkB,SAAS,MAAQ,EAAA;AACrC,UAAA;AAAA;AAGF,QAAI,IAAA,OAAA;AACJ,QAAI,IAAA;AACF,UAAU,OAAA,GAAA,MAAM,IAAK,CAAA,0BAAA,CAA2B,SAAS,CAAA;AAAA,iBAClD,KAAO,EAAA;AACd,UAAA,IAAA,CAAK,MAAO,CAAA,KAAA;AAAA,YACV,CAAA,wCAAA,EACG,MAAgB,OACnB,CAAA;AAAA,WACF;AACA,UAAA;AAAA;AAGF,QAAA,IAAI,CAAC,OAAS,EAAA;AACZ,UAAA,IAAA,CAAK,MAAO,CAAA,KAAA,CAAM,CAAsC,mCAAA,EAAA,SAAS,CAAE,CAAA,CAAA;AACnE,UAAA;AAAA;AAGF,QAAA,IAAA,CAAK,MAAO,CAAA,KAAA;AAAA,UACV,sCAAsC,IAAK,CAAA,SAAA;AAAA,YACzC,OAAQ,CAAA;AAAA,WACT,CAAA;AAAA,SACH;AAEA,QAAA,MAAM,UAAUC,0BAAsB,CAAA;AAAA,UACpC,OAAA;AAAA,UACA,SAAS,OAAQ,CAAA;AAAA,SAClB,CAAA;AAED,QAAA,IAAA,CAAK,MAAO,CAAA,KAAA;AAAA,UACV,CAAuC,oCAAA,EAAA,IAAA,CAAK,SAAU,CAAA,OAAO,CAAC,CAAA;AAAA,SAChE;AACA,QAAA,QAAA,CAAS,KAAK,OAAO,CAAA;AAAA,OACtB;AAAA,KACH;AAEA,IAAA,OAAA,CAAQ,IAAI,qBAAqB,CAAA;AACjC,IAAM,MAAA,IAAA,CAAK,kBAAkB,QAAQ,CAAA;AAErC,IAAO,OAAA,OAAA;AAAA;AACT,EAEA,MAAM,WACJ,CAAA,YAAA,EACA,OACe,EAAA;AACf,IAAA,MAAM,eAAyB,EAAC;AAGhC,IAAI,IAAA,YAAA,CAAa,SAAS,IAAM,EAAA;AAC9B,MAAA,YAAA,CAAa,IAAK,CAAA,GAAI,IAAK,CAAA,iBAAA,IAAqB,EAAG,CAAA;AAAA,KAC1C,MAAA,IAAA,OAAA,CAAQ,UAAW,CAAA,IAAA,KAAS,QAAU,EAAA;AAE/C,MAAA,MAAM,aAAa,CAAC,OAAA,CAAQ,UAAW,CAAA,SAAS,EAAE,IAAK,EAAA;AACvD,MAAI,IAAA,UAAA,CAAW,KAAK,CAAK,CAAA,KAAAD,2BAAA,CAAe,CAAC,CAAE,CAAA,IAAA,KAAS,OAAO,CAAG,EAAA;AAE5D,QAAA;AAAA;AAGF,MAAM,MAAA,WAAA,GAAc,MAAM,IAAK,CAAA,0BAAA;AAAA,QAC7B,YAAa,CAAA;AAAA,OACf;AAEA,MAAA,IAAI,CAAC,WAAa,EAAA;AAChB,QAAA,IAAA,CAAK,MAAO,CAAA,KAAA;AAAA,UACV,CAAA,mDAAA,EAAsD,aAAa,IAAI,CAAA;AAAA,SACzE;AACA,QAAA;AAAA;AAGF,MAAA,YAAA,CAAa,KAAK,WAAW,CAAA;AAAA;AAI/B,IAAI,IAAA,YAAA,CAAa,WAAW,CAAG,EAAA;AAC7B,MAAA;AAAA;AAIF,IAAA,MAAM,WAAW,YAAa,CAAA,GAAA;AAAA,MAAI,aAChCC,0BAAsB,CAAA,EAAE,SAAS,OAAS,EAAA,OAAA,CAAQ,SAAS;AAAA,KAC7D;AAGA,IAAA,QAAA,CAAS,QAAQ,CAAW,OAAA,KAAA;AAC1B,MAAA,IAAA,CAAK,OAAO,KAAM,CAAA,CAAA,sBAAA,EAAyB,KAAK,SAAU,CAAA,OAAO,CAAC,CAAE,CAAA,CAAA;AAAA,KACrE,CAAA;AAGD,IAAM,MAAA,IAAA,CAAK,kBAAkB,QAAQ,CAAA;AAAA;AACvC,EAEA,MAAM,YACJ,UACiC,EAAA;AACjC,IAAA,MAAM,EAAE,KAAM,EAAA,GAAI,MAAM,IAAA,CAAK,KAAK,qBAAsB,CAAA;AAAA,MACtD,UAAY,EAAA,MAAM,IAAK,CAAA,IAAA,CAAK,wBAAyB,EAAA;AAAA,MACrD,cAAgB,EAAA;AAAA,KACjB,CAAA;AAED,IAAM,MAAA,QAAA,GAAW,MAAM,IAAA,CAAK,OAAQ,CAAA,iBAAA;AAAA,MAClC;AAAA,QACE,UAAA,EAAY,WAAW,KAAM,EAAA;AAAA,QAC7B,MAAQ,EAAA,CAAC,CAAwB,qBAAA,EAAAC,qCAA2B,CAAE,CAAA;AAAA,OAChE;AAAA,MACA;AAAA,QACE;AAAA;AACF,KACF;AAEA,IAAA,OAAO,QAAS,CAAA,KAAA;AAAA;AAClB,EAEA,MAAM,2BACJ,SAC6B,EAAA;AAC7B,IAAA,MAAM,eAAe,IAAIC,2BAAA;AAAA,MACvB,CAAA,UAAA,KAAc,IAAK,CAAA,WAAA,CAAY,UAAU;AAAA,KAC3C;AACA,IAAA,MAAM,MAAS,GAAA,MAAM,YAAa,CAAA,IAAA,CAAK,SAAS,CAAA;AAEhD,IAAA,IAAI,CAAC,MAAQ,EAAA;AACX,MAAQ,OAAA,CAAA,GAAA,CAAI,CAAqB,kBAAA,EAAA,SAAS,CAAE,CAAA,CAAA;AAC5C,MAAA,MAAM,IAAIC,oBAAA,CAAc,CAAqB,kBAAA,EAAA,SAAS,CAAE,CAAA,CAAA;AAAA;AAG1D,IAAO,OAAA,MAAA,EAAQ,QAAU,EAAA,WAAA,GAAcF,qCAA2B,CAAA;AAAA;AACpE,EAEA,MAAM,iBAAiB,IAA+C,EAAA;AACpE,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,KAAM,CAAA,IAAA,CAAK,YAAY,IAAI,CAAA;AAEvD,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAgC,6BAAA,EAAA,QAAA,CAAS,KAAK,CAAE,CAAA,CAAA;AAAA;AAClE;AAEJ;;;;"}
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ const ANNOTATION_SLACK_BOT_NOTIFY = "slack.com/bot-notify";
4
+
5
+ exports.ANNOTATION_SLACK_BOT_NOTIFY = ANNOTATION_SLACK_BOT_NOTIFY;
6
+ //# sourceMappingURL=constants.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.cjs.js","sources":["../../src/lib/constants.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @public\n * The annotation key for the entity's Slack ID. This can be\n * any valid chat.postMessage destination including:\n * - A user ID (U12345678)\n * - A channel ID (C12345678)\n * - A DM ID (D12345678)\n * - A group ID (S12345678)\n *\n * It can also be a user's email address or a channel name,\n * however IDs are preferred.\n */\nexport const ANNOTATION_SLACK_BOT_NOTIFY = 'slack.com/bot-notify';\n"],"names":[],"mappings":";;AA4BO,MAAM,2BAA8B,GAAA;;;;"}
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ function toChatPostMessageArgs(options) {
4
+ const { channel, payload } = options;
5
+ const args = {
6
+ channel,
7
+ text: payload.title,
8
+ attachments: [
9
+ {
10
+ color: getColor(payload.severity),
11
+ blocks: toSlackBlockKit(payload),
12
+ fallback: payload.title
13
+ }
14
+ ]
15
+ };
16
+ return args;
17
+ }
18
+ function toSlackBlockKit(payload) {
19
+ const { description, link, severity, topic } = payload;
20
+ return [
21
+ {
22
+ type: "section",
23
+ ...description && {
24
+ text: {
25
+ type: "mrkdwn",
26
+ text: description ?? "No description provided"
27
+ }
28
+ },
29
+ accessory: {
30
+ type: "button",
31
+ text: {
32
+ type: "plain_text",
33
+ text: "View More"
34
+ },
35
+ ...link && { url: link },
36
+ action_id: "button-action"
37
+ }
38
+ },
39
+ {
40
+ type: "context",
41
+ elements: [
42
+ {
43
+ type: "plain_text",
44
+ text: `Severity: ${severity ?? "normal"}`,
45
+ emoji: true
46
+ },
47
+ {
48
+ type: "plain_text",
49
+ text: `Topic: ${topic ?? "N/A"}`,
50
+ emoji: true
51
+ }
52
+ ]
53
+ }
54
+ ];
55
+ }
56
+ function getColor(severity) {
57
+ switch (severity) {
58
+ case "critical":
59
+ return "#FF0000";
60
+ // Red
61
+ case "high":
62
+ return "#FFA500";
63
+ // Orange
64
+ case "low":
65
+ case "normal":
66
+ default:
67
+ return "#00A699";
68
+ }
69
+ }
70
+
71
+ exports.toChatPostMessageArgs = toChatPostMessageArgs;
72
+ exports.toSlackBlockKit = toSlackBlockKit;
73
+ //# sourceMappingURL=util.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.cjs.js","sources":["../../src/lib/util.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n NotificationPayload,\n NotificationSeverity,\n} from '@backstage/plugin-notifications-common';\nimport { ChatPostMessageArguments, KnownBlock } from '@slack/web-api';\n\nexport function toChatPostMessageArgs(options: {\n channel: string;\n payload: NotificationPayload;\n}): ChatPostMessageArguments {\n const { channel, payload } = options;\n\n const args: ChatPostMessageArguments = {\n channel,\n text: payload.title,\n attachments: [\n {\n color: getColor(payload.severity),\n blocks: toSlackBlockKit(payload),\n fallback: payload.title,\n },\n ],\n };\n\n return args;\n}\n\nexport function toSlackBlockKit(payload: NotificationPayload): KnownBlock[] {\n const { description, link, severity, topic } = payload;\n return [\n {\n type: 'section',\n ...(description && {\n text: {\n type: 'mrkdwn',\n text: description ?? 'No description provided',\n },\n }),\n accessory: {\n type: 'button',\n text: {\n type: 'plain_text',\n text: 'View More',\n },\n ...(link && { url: link }),\n action_id: 'button-action',\n },\n },\n {\n type: 'context',\n elements: [\n {\n type: 'plain_text',\n text: `Severity: ${severity ?? 'normal'}`,\n emoji: true,\n },\n {\n type: 'plain_text',\n text: `Topic: ${topic ?? 'N/A'}`,\n emoji: true,\n },\n ],\n },\n ];\n}\n\nfunction getColor(severity: NotificationSeverity | undefined) {\n switch (severity) {\n case 'critical':\n return '#FF0000'; // Red\n case 'high':\n return '#FFA500'; // Orange\n case 'low':\n case 'normal':\n default:\n return '#00A699'; // Neutral color\n }\n}\n"],"names":[],"mappings":";;AAsBO,SAAS,sBAAsB,OAGT,EAAA;AAC3B,EAAM,MAAA,EAAE,OAAS,EAAA,OAAA,EAAY,GAAA,OAAA;AAE7B,EAAA,MAAM,IAAiC,GAAA;AAAA,IACrC,OAAA;AAAA,IACA,MAAM,OAAQ,CAAA,KAAA;AAAA,IACd,WAAa,EAAA;AAAA,MACX;AAAA,QACE,KAAA,EAAO,QAAS,CAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,QAChC,MAAA,EAAQ,gBAAgB,OAAO,CAAA;AAAA,QAC/B,UAAU,OAAQ,CAAA;AAAA;AACpB;AACF,GACF;AAEA,EAAO,OAAA,IAAA;AACT;AAEO,SAAS,gBAAgB,OAA4C,EAAA;AAC1E,EAAA,MAAM,EAAE,WAAA,EAAa,IAAM,EAAA,QAAA,EAAU,OAAU,GAAA,OAAA;AAC/C,EAAO,OAAA;AAAA,IACL;AAAA,MACE,IAAM,EAAA,SAAA;AAAA,MACN,GAAI,WAAe,IAAA;AAAA,QACjB,IAAM,EAAA;AAAA,UACJ,IAAM,EAAA,QAAA;AAAA,UACN,MAAM,WAAe,IAAA;AAAA;AACvB,OACF;AAAA,MACA,SAAW,EAAA;AAAA,QACT,IAAM,EAAA,QAAA;AAAA,QACN,IAAM,EAAA;AAAA,UACJ,IAAM,EAAA,YAAA;AAAA,UACN,IAAM,EAAA;AAAA,SACR;AAAA,QACA,GAAI,IAAA,IAAQ,EAAE,GAAA,EAAK,IAAK,EAAA;AAAA,QACxB,SAAW,EAAA;AAAA;AACb,KACF;AAAA,IACA;AAAA,MACE,IAAM,EAAA,SAAA;AAAA,MACN,QAAU,EAAA;AAAA,QACR;AAAA,UACE,IAAM,EAAA,YAAA;AAAA,UACN,IAAA,EAAM,CAAa,UAAA,EAAA,QAAA,IAAY,QAAQ,CAAA,CAAA;AAAA,UACvC,KAAO,EAAA;AAAA,SACT;AAAA,QACA;AAAA,UACE,IAAM,EAAA,YAAA;AAAA,UACN,IAAA,EAAM,CAAU,OAAA,EAAA,KAAA,IAAS,KAAK,CAAA,CAAA;AAAA,UAC9B,KAAO,EAAA;AAAA;AACT;AACF;AACF,GACF;AACF;AAEA,SAAS,SAAS,QAA4C,EAAA;AAC5D,EAAA,QAAQ,QAAU;AAAA,IAChB,KAAK,UAAA;AACH,MAAO,OAAA,SAAA;AAAA;AAAA,IACT,KAAK,MAAA;AACH,MAAO,OAAA,SAAA;AAAA;AAAA,IACT,KAAK,KAAA;AAAA,IACL,KAAK,QAAA;AAAA,IACL;AACE,MAAO,OAAA,SAAA;AAAA;AAEb;;;;;"}
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var catalogClient = require('@backstage/catalog-client');
5
+ var pluginNotificationsNode = require('@backstage/plugin-notifications-node');
6
+ var SlackNotificationProcessor = require('./lib/SlackNotificationProcessor.cjs.js');
7
+
8
+ const notificationsModuleSlack = backendPluginApi.createBackendModule({
9
+ pluginId: "notifications",
10
+ moduleId: "slack",
11
+ register(reg) {
12
+ reg.registerInit({
13
+ deps: {
14
+ auth: backendPluginApi.coreServices.auth,
15
+ config: backendPluginApi.coreServices.rootConfig,
16
+ discovery: backendPluginApi.coreServices.discovery,
17
+ logger: backendPluginApi.coreServices.logger,
18
+ notifications: pluginNotificationsNode.notificationsProcessingExtensionPoint
19
+ },
20
+ async init({ auth, config, discovery, logger, notifications }) {
21
+ const catalogClient$1 = new catalogClient.CatalogClient({
22
+ discoveryApi: discovery
23
+ });
24
+ notifications.addProcessor(
25
+ SlackNotificationProcessor.SlackNotificationProcessor.fromConfig(config, {
26
+ auth,
27
+ discovery,
28
+ logger,
29
+ catalog: catalogClient$1
30
+ })
31
+ );
32
+ }
33
+ });
34
+ }
35
+ });
36
+
37
+ exports.notificationsModuleSlack = notificationsModuleSlack;
38
+ //# sourceMappingURL=module.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node';\nimport { SlackNotificationProcessor } from './lib/SlackNotificationProcessor';\n\n/**\n * The Slack notification processor for use with the notifications plugin.\n * This allows sending of notifications via Slack DMs or to channels.\n *\n * @public\n */\nexport const notificationsModuleSlack = createBackendModule({\n pluginId: 'notifications',\n moduleId: 'slack',\n register(reg) {\n reg.registerInit({\n deps: {\n auth: coreServices.auth,\n config: coreServices.rootConfig,\n discovery: coreServices.discovery,\n logger: coreServices.logger,\n notifications: notificationsProcessingExtensionPoint,\n },\n async init({ auth, config, discovery, logger, notifications }) {\n const catalogClient = new CatalogClient({\n discoveryApi: discovery,\n });\n\n notifications.addProcessor(\n SlackNotificationProcessor.fromConfig(config, {\n auth,\n discovery,\n logger,\n catalog: catalogClient,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendModule","coreServices","notificationsProcessingExtensionPoint","catalogClient","CatalogClient","SlackNotificationProcessor"],"mappings":";;;;;;;AA6BO,MAAM,2BAA2BA,oCAAoB,CAAA;AAAA,EAC1D,QAAU,EAAA,eAAA;AAAA,EACV,QAAU,EAAA,OAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,aAAe,EAAAC;AAAA,OACjB;AAAA,MACA,MAAM,KAAK,EAAE,IAAA,EAAM,QAAQ,SAAW,EAAA,MAAA,EAAQ,eAAiB,EAAA;AAC7D,QAAM,MAAAC,eAAA,GAAgB,IAAIC,2BAAc,CAAA;AAAA,UACtC,YAAc,EAAA;AAAA,SACf,CAAA;AAED,QAAc,aAAA,CAAA,YAAA;AAAA,UACZC,qDAAA,CAA2B,WAAW,MAAQ,EAAA;AAAA,YAC5C,IAAA;AAAA,YACA,SAAA;AAAA,YACA,MAAA;AAAA,YACA,OAAS,EAAAF;AAAA,WACV;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@backstage/plugin-notifications-backend-module-slack",
3
+ "version": "0.1.0-next.0",
4
+ "description": "The slack backend module for the notifications plugin.",
5
+ "backstage": {
6
+ "role": "backend-plugin-module",
7
+ "pluginId": "notifications",
8
+ "pluginPackage": "@backstage/plugin-notifications-backend",
9
+ "features": {
10
+ ".": "@backstage/BackendFeature"
11
+ }
12
+ },
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "main": "dist/index.cjs.js",
16
+ "types": "dist/index.d.ts"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/backstage/backstage",
21
+ "directory": "plugins/notifications-backend-module-slack"
22
+ },
23
+ "license": "Apache-2.0",
24
+ "main": "dist/index.cjs.js",
25
+ "types": "dist/index.d.ts",
26
+ "files": [
27
+ "dist",
28
+ "config.d.ts"
29
+ ],
30
+ "scripts": {
31
+ "build": "backstage-cli package build",
32
+ "clean": "backstage-cli package clean",
33
+ "lint": "backstage-cli package lint",
34
+ "prepack": "backstage-cli package prepack",
35
+ "postpack": "backstage-cli package postpack",
36
+ "start": "backstage-cli package start",
37
+ "test": "backstage-cli package test"
38
+ },
39
+ "dependencies": {
40
+ "@backstage/backend-plugin-api": "1.2.1",
41
+ "@backstage/catalog-client": "1.9.1",
42
+ "@backstage/catalog-model": "1.7.3",
43
+ "@backstage/config": "1.3.2",
44
+ "@backstage/errors": "1.2.7",
45
+ "@backstage/plugin-notifications-common": "0.0.8",
46
+ "@backstage/plugin-notifications-node": "0.2.13",
47
+ "@backstage/types": "1.2.1",
48
+ "@opentelemetry/api": "^1.9.0",
49
+ "@slack/bolt": "^3.21.4",
50
+ "@slack/types": "^2.14.0",
51
+ "@slack/web-api": "^7.5.0",
52
+ "dataloader": "^2.0.0",
53
+ "p-throttle": "^4.1.1"
54
+ },
55
+ "devDependencies": {
56
+ "@backstage/backend-test-utils": "1.3.2-next.1",
57
+ "@backstage/cli": "0.32.0-next.1",
58
+ "@backstage/plugin-catalog-node": "1.16.1",
59
+ "@backstage/test-utils": "1.7.6",
60
+ "@faker-js/faker": "^8.4.1",
61
+ "msw": "^2.0.0"
62
+ },
63
+ "configSchema": "config.d.ts"
64
+ }