@backstage/plugin-events-backend-module-google-pubsub 0.1.1-next.1 → 0.1.2-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 +21 -0
- package/README.md +18 -5
- package/config.d.ts +62 -0
- package/dist/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.cjs.js +102 -0
- package/dist/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.cjs.js.map +1 -0
- package/dist/EventConsumingGooglePubSubPublisher/config.cjs.js +100 -0
- package/dist/EventConsumingGooglePubSubPublisher/config.cjs.js.map +1 -0
- package/dist/EventConsumingGooglePubSubPublisher/module.cjs.js +31 -0
- package/dist/EventConsumingGooglePubSubPublisher/module.cjs.js.map +1 -0
- package/dist/GooglePubSubConsumingEventPublisher/config.cjs.js +2 -2
- package/dist/GooglePubSubConsumingEventPublisher/config.cjs.js.map +1 -1
- package/dist/GooglePubSubConsumingEventPublisher/module.cjs.js.map +1 -1
- package/dist/index.cjs.js +9 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @backstage/plugin-events-backend-module-google-pubsub
|
|
2
2
|
|
|
3
|
+
## 0.1.2-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @backstage/config@1.3.3-next.0
|
|
9
|
+
- @backstage/backend-plugin-api@1.4.1-next.0
|
|
10
|
+
- @backstage/plugin-events-node@0.4.13-next.0
|
|
11
|
+
|
|
12
|
+
## 0.1.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- f983e99: Add an `EventConsumingGooglePubSubPublisher`, for pushing Backstage events to pubsub
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
- @backstage/backend-plugin-api@1.4.0
|
|
19
|
+
- @backstage/config@1.3.2
|
|
20
|
+
- @backstage/errors@1.2.7
|
|
21
|
+
- @backstage/types@1.2.1
|
|
22
|
+
- @backstage/plugin-events-node@0.4.12
|
|
23
|
+
|
|
3
24
|
## 0.1.1-next.1
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -19,11 +19,24 @@ events:
|
|
|
19
19
|
# The fully qualified name of the subscription
|
|
20
20
|
subscriptionName: 'projects/my-google-project/subscriptions/github-enterprise-events'
|
|
21
21
|
# The event system topic to transfer to. This can also be just a plain string
|
|
22
|
-
targetTopic:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
targetTopic: 'github.{{ event.attributes.x-github-event }}'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The following configuration enables the transfer of events from a Backstage events topic into a Google
|
|
26
|
+
Pub/Sub topic.
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
events:
|
|
30
|
+
modules:
|
|
31
|
+
googlePubSub:
|
|
32
|
+
eventConsumingGooglePubSubPublisher:
|
|
33
|
+
subscriptions:
|
|
34
|
+
# A unique key for your subscription, to be used in logging and metrics
|
|
35
|
+
mySubscription:
|
|
36
|
+
# The source topic (or array of topics)
|
|
37
|
+
sourceTopic: 'github'
|
|
38
|
+
# The fully qualified name of the target topic
|
|
39
|
+
targetTopicName: 'projects/my-google-project/topics/github-enterprise-events'
|
|
27
40
|
```
|
|
28
41
|
|
|
29
42
|
## Installation
|
package/config.d.ts
CHANGED
|
@@ -90,6 +90,68 @@ export interface Config {
|
|
|
90
90
|
};
|
|
91
91
|
};
|
|
92
92
|
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Configuration for `EventConsumingGooglePubSubPublisher`, which
|
|
96
|
+
* consumes messages from the Backstage events system and forwards them
|
|
97
|
+
* into Google Pub/Sub topics.
|
|
98
|
+
*/
|
|
99
|
+
eventConsumingGooglePubSubPublisher?: {
|
|
100
|
+
subscriptions: {
|
|
101
|
+
[name: string]: {
|
|
102
|
+
/**
|
|
103
|
+
* The name of the events backend topic(s) that messages are
|
|
104
|
+
* consumed from.
|
|
105
|
+
*/
|
|
106
|
+
sourceTopic: string | string[];
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The complete name of the Google Pub/Sub subscription to forward
|
|
110
|
+
* events to, on the form
|
|
111
|
+
* `projects/PROJECT_ID/topics/TOPIC_ID`.
|
|
112
|
+
*
|
|
113
|
+
* The value can contain placeholders on the form `{{
|
|
114
|
+
* message.attributes.foo }}`, to mirror attribute `foo` as the
|
|
115
|
+
* whole or part of the topic name.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
*
|
|
119
|
+
* This example expects the events topic to contain GitHub
|
|
120
|
+
* webhook events where the HTTP headers were mapped into
|
|
121
|
+
* event metadata fields. The outcome should be that messages
|
|
122
|
+
* end up on event topics such as `github.push`,
|
|
123
|
+
* `github.repository` etc which matches the [`@backstage/plugin-events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github) structure.
|
|
124
|
+
*
|
|
125
|
+
* ```yaml
|
|
126
|
+
* targetTopic: 'projects/my-project/topics/github.{{ event.metadata.x-github-event }}'
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
targetTopicName: string;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Event metadata fields are by default copied to the Pub/Sub
|
|
133
|
+
* message attribute. This setting allows you to override or amend
|
|
134
|
+
* those attributes.
|
|
135
|
+
*
|
|
136
|
+
* @remarks
|
|
137
|
+
*
|
|
138
|
+
* The values can contain placeholders on the form `{{ event.metadata.foo }}`,
|
|
139
|
+
* to mirror metadata field `foo` as the whole or part of a
|
|
140
|
+
* message attribute value.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
*
|
|
144
|
+
* ```yaml
|
|
145
|
+
* messageAttributes:
|
|
146
|
+
* x-gitHub-event: '{{ event.metadata.event }}'
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
messageAttributes?: {
|
|
150
|
+
[key: string]: string;
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
};
|
|
93
155
|
};
|
|
94
156
|
};
|
|
95
157
|
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var pubsub = require('@google-cloud/pubsub');
|
|
4
|
+
var api = require('@opentelemetry/api');
|
|
5
|
+
var config = require('./config.cjs.js');
|
|
6
|
+
|
|
7
|
+
class EventConsumingGooglePubSubPublisher {
|
|
8
|
+
#logger;
|
|
9
|
+
#events;
|
|
10
|
+
#tasks;
|
|
11
|
+
#pubSubFactory;
|
|
12
|
+
#metrics;
|
|
13
|
+
#activeClientsByProjectId;
|
|
14
|
+
static create(options) {
|
|
15
|
+
const publisher = new EventConsumingGooglePubSubPublisher({
|
|
16
|
+
logger: options.logger,
|
|
17
|
+
events: options.events,
|
|
18
|
+
tasks: config.readSubscriptionTasksFromConfig(options.config),
|
|
19
|
+
pubSubFactory: (projectId) => new pubsub.PubSub({ projectId })
|
|
20
|
+
});
|
|
21
|
+
options.rootLifecycle.addStartupHook(async () => {
|
|
22
|
+
await publisher.start();
|
|
23
|
+
});
|
|
24
|
+
options.rootLifecycle.addBeforeShutdownHook(async () => {
|
|
25
|
+
await publisher.stop();
|
|
26
|
+
});
|
|
27
|
+
return publisher;
|
|
28
|
+
}
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.#logger = options.logger;
|
|
31
|
+
this.#events = options.events;
|
|
32
|
+
this.#tasks = options.tasks;
|
|
33
|
+
this.#pubSubFactory = options.pubSubFactory;
|
|
34
|
+
const meter = api.metrics.getMeter("default");
|
|
35
|
+
this.#metrics = {
|
|
36
|
+
messages: meter.createCounter(
|
|
37
|
+
"events.google.pubsub.publisher.messages.total",
|
|
38
|
+
{
|
|
39
|
+
description: "Number of Pub/Sub messages sent by EventConsumingGooglePubSubPublisher",
|
|
40
|
+
unit: "short"
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
};
|
|
44
|
+
this.#activeClientsByProjectId = /* @__PURE__ */ new Map();
|
|
45
|
+
}
|
|
46
|
+
async start() {
|
|
47
|
+
for (const task of this.#tasks) {
|
|
48
|
+
this.#logger.info(
|
|
49
|
+
`Starting publisher: id=${task.id} sourceTopics=${task.sourceTopics.join(",")} targetTopic=${task.targetTopicPattern}`
|
|
50
|
+
);
|
|
51
|
+
await this.#events.subscribe({
|
|
52
|
+
id: `EventConsumingGooglePubSubPublisher.${task.id}`,
|
|
53
|
+
topics: task.sourceTopics,
|
|
54
|
+
onEvent: async (event) => {
|
|
55
|
+
let status = "failed";
|
|
56
|
+
try {
|
|
57
|
+
const topic = task.mapToTopic(event);
|
|
58
|
+
if (!topic) {
|
|
59
|
+
status = "ignored";
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let pubsub = this.#activeClientsByProjectId.get(topic.project);
|
|
63
|
+
if (!pubsub) {
|
|
64
|
+
pubsub = this.#pubSubFactory(topic.project);
|
|
65
|
+
this.#activeClientsByProjectId.set(topic.project, pubsub);
|
|
66
|
+
}
|
|
67
|
+
await pubsub.topic(topic.topic).publishMessage({
|
|
68
|
+
json: event.eventPayload,
|
|
69
|
+
attributes: task.mapToAttributes(event)
|
|
70
|
+
});
|
|
71
|
+
status = "success";
|
|
72
|
+
} catch (error) {
|
|
73
|
+
this.#logger.error(
|
|
74
|
+
"Error publishing Google Pub/Sub message",
|
|
75
|
+
error
|
|
76
|
+
);
|
|
77
|
+
status = "failed";
|
|
78
|
+
throw error;
|
|
79
|
+
} finally {
|
|
80
|
+
this.#metrics.messages.add(1, {
|
|
81
|
+
subscription: task.id,
|
|
82
|
+
status
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async stop() {
|
|
90
|
+
const clients = Array.from(this.#activeClientsByProjectId.values());
|
|
91
|
+
this.#activeClientsByProjectId = /* @__PURE__ */ new Map();
|
|
92
|
+
await Promise.allSettled(
|
|
93
|
+
clients.map(async (client) => {
|
|
94
|
+
this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);
|
|
95
|
+
await client.close();
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
exports.EventConsumingGooglePubSubPublisher = EventConsumingGooglePubSubPublisher;
|
|
102
|
+
//# sourceMappingURL=EventConsumingGooglePubSubPublisher.cjs.js.map
|
package/dist/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.cjs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventConsumingGooglePubSubPublisher.cjs.js","sources":["../../src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.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 LoggerService,\n RootConfigService,\n RootLifecycleService,\n} from '@backstage/backend-plugin-api';\nimport { EventsService } from '@backstage/plugin-events-node';\nimport { PubSub } from '@google-cloud/pubsub';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { readSubscriptionTasksFromConfig } from './config';\nimport { SubscriptionTask } from './types';\n\n/**\n * Reads messages off of the events system and forwards them into Google Pub/Sub\n * topics.\n */\nexport class EventConsumingGooglePubSubPublisher {\n readonly #logger: LoggerService;\n readonly #events: EventsService;\n readonly #tasks: SubscriptionTask[];\n readonly #pubSubFactory: (projectId: string) => PubSub;\n readonly #metrics: { messages: Counter };\n #activeClientsByProjectId: Map<string, PubSub>;\n\n static create(options: {\n config: RootConfigService;\n logger: LoggerService;\n rootLifecycle: RootLifecycleService;\n events: EventsService;\n }) {\n const publisher = new EventConsumingGooglePubSubPublisher({\n logger: options.logger,\n events: options.events,\n tasks: readSubscriptionTasksFromConfig(options.config),\n pubSubFactory: projectId => new PubSub({ projectId }),\n });\n\n options.rootLifecycle.addStartupHook(async () => {\n await publisher.start();\n });\n\n options.rootLifecycle.addBeforeShutdownHook(async () => {\n await publisher.stop();\n });\n\n return publisher;\n }\n\n constructor(options: {\n logger: LoggerService;\n events: EventsService;\n tasks: SubscriptionTask[];\n pubSubFactory: (projectId: string) => PubSub;\n }) {\n this.#logger = options.logger;\n this.#events = options.events;\n this.#tasks = options.tasks;\n this.#pubSubFactory = options.pubSubFactory;\n\n const meter = metrics.getMeter('default');\n this.#metrics = {\n messages: meter.createCounter(\n 'events.google.pubsub.publisher.messages.total',\n {\n description:\n 'Number of Pub/Sub messages sent by EventConsumingGooglePubSubPublisher',\n unit: 'short',\n },\n ),\n };\n\n this.#activeClientsByProjectId = new Map();\n }\n\n async start() {\n for (const task of this.#tasks) {\n this.#logger.info(\n `Starting publisher: id=${\n task.id\n } sourceTopics=${task.sourceTopics.join(',')} targetTopic=${\n task.targetTopicPattern\n }`,\n );\n\n await this.#events.subscribe({\n id: `EventConsumingGooglePubSubPublisher.${task.id}`,\n topics: task.sourceTopics,\n onEvent: async event => {\n let status: 'success' | 'failed' | 'ignored' = 'failed';\n try {\n const topic = task.mapToTopic(event);\n if (!topic) {\n status = 'ignored';\n return;\n }\n\n let pubsub = this.#activeClientsByProjectId.get(topic.project);\n if (!pubsub) {\n pubsub = this.#pubSubFactory(topic.project);\n this.#activeClientsByProjectId.set(topic.project, pubsub);\n }\n\n await pubsub.topic(topic.topic).publishMessage({\n json: event.eventPayload,\n attributes: task.mapToAttributes(event),\n });\n\n status = 'success';\n } catch (error) {\n this.#logger.error(\n 'Error publishing Google Pub/Sub message',\n error,\n );\n status = 'failed';\n throw error;\n } finally {\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: status,\n });\n }\n },\n });\n }\n }\n\n async stop() {\n const clients = Array.from(this.#activeClientsByProjectId.values());\n this.#activeClientsByProjectId = new Map();\n\n await Promise.allSettled(\n clients.map(async client => {\n this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);\n await client.close();\n }),\n );\n }\n}\n"],"names":["readSubscriptionTasksFromConfig","PubSub","metrics"],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
var createPatternResolver = require('../util/createPatternResolver.cjs.js');
|
|
5
|
+
|
|
6
|
+
function readSubscriptionTasksFromConfig(rootConfig) {
|
|
7
|
+
const subscriptionsConfig = rootConfig.getOptionalConfig(
|
|
8
|
+
"events.modules.googlePubSub.eventConsumingGooglePubSubPublisher.subscriptions"
|
|
9
|
+
);
|
|
10
|
+
if (!subscriptionsConfig) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return subscriptionsConfig.keys().map((subscriptionId) => {
|
|
14
|
+
if (!subscriptionId.match(/^[-_\w]+$/)) {
|
|
15
|
+
throw new errors.InputError(
|
|
16
|
+
`Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const config = subscriptionsConfig.getConfig(subscriptionId);
|
|
20
|
+
const sourceTopics = readSourceTopics(config);
|
|
21
|
+
const mapToTopic = readTopicMapper(config);
|
|
22
|
+
const mapToAttributes = readAttributeMapper(config);
|
|
23
|
+
return {
|
|
24
|
+
id: subscriptionId,
|
|
25
|
+
sourceTopics,
|
|
26
|
+
targetTopicPattern: config.getString("targetTopicName"),
|
|
27
|
+
mapToTopic,
|
|
28
|
+
mapToAttributes
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function readSourceTopics(config) {
|
|
33
|
+
if (Array.isArray(config.getOptional("sourceTopic"))) {
|
|
34
|
+
return config.getStringArray("sourceTopic");
|
|
35
|
+
}
|
|
36
|
+
return [config.getString("sourceTopic")];
|
|
37
|
+
}
|
|
38
|
+
function readTopicMapper(config) {
|
|
39
|
+
const regex = /^projects\/([^/]+)\/topics\/(.+)$/;
|
|
40
|
+
const targetTopicPattern = config.getString("targetTopicName");
|
|
41
|
+
let parts = targetTopicPattern.match(regex);
|
|
42
|
+
if (!parts) {
|
|
43
|
+
throw new errors.InputError(
|
|
44
|
+
`Expected Google Pub/Sub 'targetTopicName' to be on the form 'projects/PROJECT_ID/topics/TOPIC_ID' but got '${targetTopicPattern}'`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const patternResolver = createPatternResolver.createPatternResolver(targetTopicPattern);
|
|
48
|
+
return (event) => {
|
|
49
|
+
try {
|
|
50
|
+
parts = patternResolver({ event }).match(regex);
|
|
51
|
+
if (!parts) {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
project: parts[1],
|
|
56
|
+
topic: parts[2]
|
|
57
|
+
};
|
|
58
|
+
} catch {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function readAttributeMapper(config) {
|
|
64
|
+
const setters = new Array();
|
|
65
|
+
const eventMetadata = config.getOptionalConfig("messageAttributes");
|
|
66
|
+
if (eventMetadata) {
|
|
67
|
+
for (const key of eventMetadata?.keys() ?? []) {
|
|
68
|
+
const valuePattern = eventMetadata.getString(key);
|
|
69
|
+
const patternResolver = createPatternResolver.createPatternResolver(valuePattern);
|
|
70
|
+
setters.push(({ event, attributes }) => {
|
|
71
|
+
try {
|
|
72
|
+
const value = patternResolver({ event });
|
|
73
|
+
if (value) {
|
|
74
|
+
attributes[key] = value;
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return (event) => {
|
|
82
|
+
const result = {};
|
|
83
|
+
for (const [key, value] of Object.entries(event.metadata ?? {})) {
|
|
84
|
+
if (value) {
|
|
85
|
+
if (typeof value === "string") {
|
|
86
|
+
result[key] = value;
|
|
87
|
+
} else if (Array.isArray(value) && value.length > 0) {
|
|
88
|
+
result[key] = value.join(",");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const setter of setters) {
|
|
93
|
+
setter({ event, attributes: result });
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
exports.readSubscriptionTasksFromConfig = readSubscriptionTasksFromConfig;
|
|
100
|
+
//# sourceMappingURL=config.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.cjs.js","sources":["../../src/EventConsumingGooglePubSubPublisher/config.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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { EventParams } from '@backstage/plugin-events-node';\nimport { createPatternResolver } from '../util/createPatternResolver';\nimport { SubscriptionTask } from './types';\n\nexport function readSubscriptionTasksFromConfig(\n rootConfig: RootConfigService,\n): SubscriptionTask[] {\n const subscriptionsConfig = rootConfig.getOptionalConfig(\n 'events.modules.googlePubSub.eventConsumingGooglePubSubPublisher.subscriptions',\n );\n if (!subscriptionsConfig) {\n return [];\n }\n\n return subscriptionsConfig.keys().map(subscriptionId => {\n if (!subscriptionId.match(/^[-_\\w]+$/)) {\n throw new InputError(\n `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`,\n );\n }\n\n const config = subscriptionsConfig.getConfig(subscriptionId);\n const sourceTopics = readSourceTopics(config);\n const mapToTopic = readTopicMapper(config);\n const mapToAttributes = readAttributeMapper(config);\n\n return {\n id: subscriptionId,\n sourceTopics: sourceTopics,\n targetTopicPattern: config.getString('targetTopicName'),\n mapToTopic,\n mapToAttributes,\n };\n });\n}\n\nfunction readSourceTopics(config: Config): string[] {\n if (Array.isArray(config.getOptional('sourceTopic'))) {\n return config.getStringArray('sourceTopic');\n }\n return [config.getString('sourceTopic')];\n}\n\n/**\n * Handles the `targetTopicName` configuration field.\n */\nfunction readTopicMapper(\n config: Config,\n): (event: EventParams) => { project: string; topic: string } | undefined {\n const regex = /^projects\\/([^/]+)\\/topics\\/(.+)$/;\n\n const targetTopicPattern = config.getString('targetTopicName');\n let parts = targetTopicPattern.match(regex);\n if (!parts) {\n throw new InputError(\n `Expected Google Pub/Sub 'targetTopicName' to be on the form 'projects/PROJECT_ID/topics/TOPIC_ID' but got '${targetTopicPattern}'`,\n );\n }\n\n const patternResolver = createPatternResolver(targetTopicPattern);\n\n return event => {\n try {\n parts = patternResolver({ event }).match(regex);\n if (!parts) {\n return undefined;\n }\n return {\n project: parts[1],\n topic: parts[2],\n };\n } catch {\n // could not map to a topic\n return undefined;\n }\n };\n}\n\n/**\n * Handles the `messageAttributes` configuration field.\n */\nfunction readAttributeMapper(\n config: Config,\n): (event: EventParams) => Record<string, string> {\n const setters = new Array<\n (options: {\n event: EventParams;\n attributes: Record<string, string>;\n }) => void\n >();\n\n const eventMetadata = config.getOptionalConfig('messageAttributes');\n if (eventMetadata) {\n for (const key of eventMetadata?.keys() ?? []) {\n const valuePattern = eventMetadata.getString(key);\n const patternResolver = createPatternResolver(valuePattern);\n setters.push(({ event, attributes }) => {\n try {\n const value = patternResolver({ event });\n if (value) {\n attributes[key] = value;\n }\n } catch {\n // ignore silently, keep original\n }\n });\n }\n }\n\n return event => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(event.metadata ?? {})) {\n if (value) {\n if (typeof value === 'string') {\n result[key] = value;\n } else if (Array.isArray(value) && value.length > 0) {\n // Google Pub/Sub does not support array values\n result[key] = value.join(',');\n }\n }\n }\n for (const setter of setters) {\n setter({ event, attributes: result });\n }\n return result;\n };\n}\n"],"names":["InputError","createPatternResolver"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var pluginEventsNode = require('@backstage/plugin-events-node');
|
|
5
|
+
var EventConsumingGooglePubSubPublisher = require('./EventConsumingGooglePubSubPublisher.cjs.js');
|
|
6
|
+
|
|
7
|
+
const eventsModuleEventConsumingGooglePubSubPublisher = backendPluginApi.createBackendModule({
|
|
8
|
+
pluginId: "events",
|
|
9
|
+
moduleId: "event-consuming-google-pubsub-publisher",
|
|
10
|
+
register(reg) {
|
|
11
|
+
reg.registerInit({
|
|
12
|
+
deps: {
|
|
13
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
14
|
+
logger: backendPluginApi.coreServices.logger,
|
|
15
|
+
rootLifecycle: backendPluginApi.coreServices.rootLifecycle,
|
|
16
|
+
events: pluginEventsNode.eventsServiceRef
|
|
17
|
+
},
|
|
18
|
+
async init({ config, logger, rootLifecycle, events }) {
|
|
19
|
+
EventConsumingGooglePubSubPublisher.EventConsumingGooglePubSubPublisher.create({
|
|
20
|
+
config,
|
|
21
|
+
logger,
|
|
22
|
+
rootLifecycle,
|
|
23
|
+
events
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
exports.eventsModuleEventConsumingGooglePubSubPublisher = eventsModuleEventConsumingGooglePubSubPublisher;
|
|
31
|
+
//# sourceMappingURL=module.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.cjs.js","sources":["../../src/EventConsumingGooglePubSubPublisher/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 */\n\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { eventsServiceRef } from '@backstage/plugin-events-node';\nimport { EventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher';\n\n/**\n * Reads messages off of the events system and forwards them into Google Pub/Sub\n * topics.\n *\n * @public\n */\nexport const eventsModuleEventConsumingGooglePubSubPublisher =\n createBackendModule({\n pluginId: 'events',\n moduleId: 'event-consuming-google-pubsub-publisher',\n register(reg) {\n reg.registerInit({\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n rootLifecycle: coreServices.rootLifecycle,\n events: eventsServiceRef,\n },\n async init({ config, logger, rootLifecycle, events }) {\n EventConsumingGooglePubSubPublisher.create({\n config,\n logger,\n rootLifecycle,\n events,\n });\n },\n });\n },\n });\n"],"names":["createBackendModule","coreServices","eventsServiceRef","EventConsumingGooglePubSubPublisher"],"mappings":";;;;;;AA6BO,MAAM,kDACXA,oCAAoB,CAAA;AAAA,EAClB,QAAU,EAAA,QAAA;AAAA,EACV,QAAU,EAAA,yCAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,eAAeA,6BAAa,CAAA,aAAA;AAAA,QAC5B,MAAQ,EAAAC;AAAA,OACV;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,QAAQ,MAAQ,EAAA,aAAA,EAAe,QAAU,EAAA;AACpD,QAAAC,uEAAA,CAAoC,MAAO,CAAA;AAAA,UACzC,MAAA;AAAA,UACA,MAAA;AAAA,UACA,aAAA;AAAA,UACA;AAAA,SACD,CAAA;AAAA;AACH,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
|
|
@@ -13,7 +13,7 @@ function readSubscriptionTasksFromConfig(rootConfig) {
|
|
|
13
13
|
return subscriptionsConfig.keys().map((subscriptionId) => {
|
|
14
14
|
if (!subscriptionId.match(/^[-_\w]+$/)) {
|
|
15
15
|
throw new errors.InputError(
|
|
16
|
-
`Expected
|
|
16
|
+
`Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
const config = subscriptionsConfig.getConfig(subscriptionId);
|
|
@@ -36,7 +36,7 @@ function readSubscriptionName(config) {
|
|
|
36
36
|
);
|
|
37
37
|
if (!parts) {
|
|
38
38
|
throw new errors.InputError(
|
|
39
|
-
`Expected
|
|
39
|
+
`Expected Google Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/config.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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { Message } from '@google-cloud/pubsub';\nimport { createPatternResolver } from '../util/createPatternResolver';\nimport { SubscriptionTask } from './types';\n\nexport function readSubscriptionTasksFromConfig(\n rootConfig: RootConfigService,\n): SubscriptionTask[] {\n const subscriptionsConfig = rootConfig.getOptionalConfig(\n 'events.modules.googlePubSub.googlePubSubConsumingEventPublisher.subscriptions',\n );\n if (!subscriptionsConfig) {\n return [];\n }\n\n return subscriptionsConfig.keys().map(subscriptionId => {\n if (!subscriptionId.match(/^[-_\\w]+$/)) {\n throw new InputError(\n `Expected
|
|
1
|
+
{"version":3,"file":"config.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/config.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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { Message } from '@google-cloud/pubsub';\nimport { createPatternResolver } from '../util/createPatternResolver';\nimport { SubscriptionTask } from './types';\n\nexport function readSubscriptionTasksFromConfig(\n rootConfig: RootConfigService,\n): SubscriptionTask[] {\n const subscriptionsConfig = rootConfig.getOptionalConfig(\n 'events.modules.googlePubSub.googlePubSubConsumingEventPublisher.subscriptions',\n );\n if (!subscriptionsConfig) {\n return [];\n }\n\n return subscriptionsConfig.keys().map(subscriptionId => {\n if (!subscriptionId.match(/^[-_\\w]+$/)) {\n throw new InputError(\n `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`,\n );\n }\n\n const config = subscriptionsConfig.getConfig(subscriptionId);\n const { project, subscription } = readSubscriptionName(config);\n const mapToTopic = readTopicMapper(config);\n const mapToMetadata = readMetadataMapper(config);\n\n return {\n id: subscriptionId,\n project,\n subscription,\n mapToTopic,\n mapToMetadata,\n };\n });\n}\n\nfunction readSubscriptionName(config: Config): {\n project: string;\n subscription: string;\n} {\n const subscriptionName = config.getString('subscriptionName');\n const parts = subscriptionName.match(\n /^projects\\/([^/]+)\\/subscriptions\\/(.+)$/,\n );\n if (!parts) {\n throw new InputError(\n `Expected Google Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`,\n );\n }\n return {\n project: parts[1],\n subscription: parts[2],\n };\n}\n\n/**\n * Handles the `targetTopic` configuration field.\n */\nfunction readTopicMapper(\n config: Config,\n): (message: Message) => string | undefined {\n const targetTopicPattern = config.getString('targetTopic');\n const patternResolver = createPatternResolver(targetTopicPattern);\n return message => {\n try {\n return patternResolver({ message });\n } catch {\n // could not map to a topic\n return undefined;\n }\n };\n}\n\n/**\n * Handles the `eventMetadata` configuration field.\n */\nfunction readMetadataMapper(\n config: Config,\n): (message: Message) => Record<string, string> {\n const setters = new Array<\n (options: { message: Message; metadata: Record<string, string> }) => void\n >();\n\n const eventMetadata = config.getOptionalConfig('eventMetadata');\n if (eventMetadata) {\n for (const key of eventMetadata?.keys() ?? []) {\n const valuePattern = eventMetadata.getString(key);\n const patternResolver = createPatternResolver(valuePattern);\n setters.push(({ message, metadata }) => {\n try {\n const value = patternResolver({ message });\n if (value) {\n metadata[key] = value;\n }\n } catch {\n // ignore silently, keep original\n }\n });\n }\n }\n\n return message => {\n const result: Record<string, string> = {\n ...message.attributes,\n };\n for (const setter of setters) {\n setter({ message, metadata: result });\n }\n return result;\n };\n}\n"],"names":["InputError","createPatternResolver"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/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 */\n\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { eventsServiceRef } from '@backstage/plugin-events-node';\nimport { GooglePubSubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher';\n\n/**\n * Reads messages off of Google Pub/Sub subscriptions and forwards them into the\n * Backstage events system.\n
|
|
1
|
+
{"version":3,"file":"module.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/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 */\n\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { eventsServiceRef } from '@backstage/plugin-events-node';\nimport { GooglePubSubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher';\n\n/**\n * Reads messages off of Google Pub/Sub subscriptions and forwards them into the\n * Backstage events system.\n */\nexport const eventsModuleGooglePubsubConsumingEventPublisher =\n createBackendModule({\n pluginId: 'events',\n moduleId: 'google-pubsub-consuming-event-publisher',\n register(reg) {\n reg.registerInit({\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n rootLifecycle: coreServices.rootLifecycle,\n events: eventsServiceRef,\n },\n async init({ config, logger, rootLifecycle, events }) {\n GooglePubSubConsumingEventPublisher.create({\n config,\n logger,\n rootLifecycle,\n events,\n });\n },\n });\n },\n });\n"],"names":["createBackendModule","coreServices","eventsServiceRef","GooglePubSubConsumingEventPublisher"],"mappings":";;;;;;AA2BO,MAAM,kDACXA,oCAAoB,CAAA;AAAA,EAClB,QAAU,EAAA,QAAA;AAAA,EACV,QAAU,EAAA,yCAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,eAAeA,6BAAa,CAAA,aAAA;AAAA,QAC5B,MAAQ,EAAAC;AAAA,OACV;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,QAAQ,MAAQ,EAAA,aAAA,EAAe,QAAU,EAAA;AACpD,QAAAC,uEAAA,CAAoC,MAAO,CAAA;AAAA,UACzC,MAAA;AAAA,UACA,MAAA;AAAA,UACA,aAAA;AAAA,UACA;AAAA,SACD,CAAA;AAAA;AACH,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
|
package/dist/index.cjs.js
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
|
+
var module$2 = require('./EventConsumingGooglePubSubPublisher/module.cjs.js');
|
|
5
7
|
var module$1 = require('./GooglePubSubConsumingEventPublisher/module.cjs.js');
|
|
6
8
|
|
|
9
|
+
var index = backendPluginApi.createBackendFeatureLoader({
|
|
10
|
+
*loader() {
|
|
11
|
+
yield module$1.eventsModuleGooglePubsubConsumingEventPublisher;
|
|
12
|
+
yield module$2.eventsModuleEventConsumingGooglePubSubPublisher;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
7
15
|
|
|
8
|
-
|
|
9
|
-
exports.default = module$1.eventsModuleGooglePubsubConsumingEventPublisher;
|
|
16
|
+
exports.default = index;
|
|
10
17
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/index.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 { createBackendFeatureLoader } from '@backstage/backend-plugin-api';\nimport { eventsModuleEventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher';\nimport { eventsModuleGooglePubsubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher';\n\n/**\n * The google-pubsub backend module for the events plugin.\n *\n * @packageDocumentation\n */\n\nexport default createBackendFeatureLoader({\n *loader() {\n yield eventsModuleGooglePubsubConsumingEventPublisher;\n yield eventsModuleEventConsumingGooglePubSubPublisher;\n },\n});\n"],"names":["createBackendFeatureLoader","eventsModuleGooglePubsubConsumingEventPublisher","eventsModuleEventConsumingGooglePubSubPublisher"],"mappings":";;;;;;;;AA0BA,YAAeA,2CAA2B,CAAA;AAAA,EACxC,CAAC,MAAS,GAAA;AACR,IAAM,MAAAC,wDAAA;AACN,IAAM,MAAAC,wDAAA;AAAA;AAEV,CAAC,CAAA;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Backstage events system.
|
|
4
|
+
* The google-pubsub backend module for the events plugin.
|
|
6
5
|
*
|
|
7
|
-
* @
|
|
6
|
+
* @packageDocumentation
|
|
8
7
|
*/
|
|
9
|
-
declare const
|
|
8
|
+
declare const _default: _backstage_backend_plugin_api.BackendFeature;
|
|
10
9
|
|
|
11
|
-
export {
|
|
10
|
+
export { _default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-events-backend-module-google-pubsub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2-next.0",
|
|
4
4
|
"description": "The google-pubsub backend module for the events plugin.",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin-module",
|
|
@@ -37,19 +37,19 @@
|
|
|
37
37
|
"test": "backstage-cli package test"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@backstage/backend-plugin-api": "1.4.
|
|
41
|
-
"@backstage/config": "1.3.
|
|
40
|
+
"@backstage/backend-plugin-api": "1.4.1-next.0",
|
|
41
|
+
"@backstage/config": "1.3.3-next.0",
|
|
42
42
|
"@backstage/errors": "1.2.7",
|
|
43
|
-
"@backstage/plugin-events-node": "0.4.
|
|
43
|
+
"@backstage/plugin-events-node": "0.4.13-next.0",
|
|
44
44
|
"@backstage/types": "1.2.1",
|
|
45
45
|
"@google-cloud/pubsub": "^4.10.0",
|
|
46
46
|
"@opentelemetry/api": "^1.9.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@backstage/backend-defaults": "0.
|
|
50
|
-
"@backstage/backend-test-utils": "1.
|
|
51
|
-
"@backstage/cli": "0.
|
|
52
|
-
"@backstage/plugin-events-backend": "0.5.
|
|
49
|
+
"@backstage/backend-defaults": "0.11.1-next.1",
|
|
50
|
+
"@backstage/backend-test-utils": "1.7.0-next.1",
|
|
51
|
+
"@backstage/cli": "0.33.1-next.1",
|
|
52
|
+
"@backstage/plugin-events-backend": "0.5.4-next.0",
|
|
53
53
|
"wait-for-expect": "^3.0.2"
|
|
54
54
|
},
|
|
55
55
|
"configSchema": "config.d.ts",
|