@backstage/plugin-catalog-backend-module-github 0.12.2-next.2 → 0.12.3-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 +30 -0
- package/dist/events/GithubScmEventsBridge.cjs.js +85 -0
- package/dist/events/GithubScmEventsBridge.cjs.js.map +1 -0
- package/dist/events/analyzeGithubWebhookEvent.cjs.js +376 -0
- package/dist/events/analyzeGithubWebhookEvent.cjs.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/module/githubCatalogModule.cjs.js +23 -2
- package/dist/module/githubCatalogModule.cjs.js.map +1 -1
- package/dist/util/octokitProviderService.cjs.js +67 -0
- package/dist/util/octokitProviderService.cjs.js.map +1 -0
- package/package.json +19 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-backend-module-github
|
|
2
2
|
|
|
3
|
+
## 0.12.3-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/integration@1.21.0-next.0
|
|
10
|
+
- @backstage/plugin-catalog-node@2.1.0-next.0
|
|
11
|
+
- @backstage/backend-plugin-api@1.7.1-next.0
|
|
12
|
+
- @backstage/catalog-model@1.7.6
|
|
13
|
+
- @backstage/config@1.3.6
|
|
14
|
+
- @backstage/errors@1.2.7
|
|
15
|
+
- @backstage/types@1.2.2
|
|
16
|
+
- @backstage/plugin-catalog-common@1.1.8
|
|
17
|
+
- @backstage/plugin-events-node@0.4.20-next.0
|
|
18
|
+
|
|
19
|
+
## 0.12.2
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports.
|
|
24
|
+
- 7455dae: Use node prefix on native imports
|
|
25
|
+
- 34cc520: Implemented translation of webhook events into `catalogScmEventsServiceRef` events.
|
|
26
|
+
- Updated dependencies
|
|
27
|
+
- @backstage/integration@1.20.0
|
|
28
|
+
- @backstage/plugin-catalog-node@2.0.0
|
|
29
|
+
- @backstage/backend-plugin-api@1.7.0
|
|
30
|
+
- @backstage/plugin-catalog-common@1.1.8
|
|
31
|
+
- @backstage/plugin-events-node@0.4.19
|
|
32
|
+
|
|
3
33
|
## 0.12.2-next.2
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var analyzeGithubWebhookEvent = require('./analyzeGithubWebhookEvent.cjs.js');
|
|
4
|
+
|
|
5
|
+
class GithubScmEventsBridge {
|
|
6
|
+
#logger;
|
|
7
|
+
#events;
|
|
8
|
+
#octokitProvider;
|
|
9
|
+
#catalogScmEvents;
|
|
10
|
+
#shuttingDown;
|
|
11
|
+
#pendingPublish;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.#logger = options.logger;
|
|
14
|
+
this.#events = options.events;
|
|
15
|
+
this.#octokitProvider = options.octokitProvider;
|
|
16
|
+
this.#catalogScmEvents = options.catalogScmEvents;
|
|
17
|
+
this.#shuttingDown = false;
|
|
18
|
+
}
|
|
19
|
+
async start() {
|
|
20
|
+
await this.#events.subscribe({
|
|
21
|
+
id: "catalog-github-scm-events-bridge",
|
|
22
|
+
topics: ["github"],
|
|
23
|
+
onEvent: this.#onEvent.bind(this)
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async stop() {
|
|
27
|
+
this.#shuttingDown = true;
|
|
28
|
+
await this.#pendingPublish;
|
|
29
|
+
}
|
|
30
|
+
async #onEvent(params) {
|
|
31
|
+
const eventType = params.metadata?.["x-github-event"];
|
|
32
|
+
const eventPayload = params.eventPayload;
|
|
33
|
+
if (!eventType || !eventPayload) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
while (this.#pendingPublish) {
|
|
37
|
+
await this.#pendingPublish;
|
|
38
|
+
}
|
|
39
|
+
if (this.#shuttingDown) {
|
|
40
|
+
this.#logger.warn(
|
|
41
|
+
`Skipping GitHub webhook event of type "${eventType}" on topic "${params.topic}" because the bridge is shutting down`
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.#pendingPublish = Promise.resolve().then(async () => {
|
|
46
|
+
try {
|
|
47
|
+
const output = await analyzeGithubWebhookEvent.analyzeGithubWebhookEvent(
|
|
48
|
+
eventType,
|
|
49
|
+
eventPayload,
|
|
50
|
+
{
|
|
51
|
+
octokitProvider: this.#octokitProvider,
|
|
52
|
+
logger: this.#logger,
|
|
53
|
+
isRelevantPath: (path) => path.endsWith(".yaml") || path.endsWith(".yml")
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
if (output.result === "ok") {
|
|
57
|
+
await this.#catalogScmEvents.publish(output.events);
|
|
58
|
+
} else if (output.result === "ignored") {
|
|
59
|
+
this.#logger.debug(
|
|
60
|
+
`Skipping GitHub webhook event of type "${eventType}" on topic "${params.topic}" because it is ignored: ${output.reason}`
|
|
61
|
+
);
|
|
62
|
+
} else if (output.result === "aborted") {
|
|
63
|
+
this.#logger.warn(
|
|
64
|
+
`Skipping GitHub webhook event of type "${eventType}" on topic "${params.topic}" because it is aborted: ${output.reason}`
|
|
65
|
+
);
|
|
66
|
+
} else if (output.result === "unsupported-event") {
|
|
67
|
+
this.#logger.debug(
|
|
68
|
+
`Skipping GitHub webhook event of type "${eventType}" on topic "${params.topic}" because it is unsupported: ${output.event}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
this.#logger.warn(
|
|
73
|
+
`Failed to handle GitHub webhook event of type "${eventType}"`,
|
|
74
|
+
error
|
|
75
|
+
);
|
|
76
|
+
} finally {
|
|
77
|
+
this.#pendingPublish = void 0;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
await this.#pendingPublish;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
exports.GithubScmEventsBridge = GithubScmEventsBridge;
|
|
85
|
+
//# sourceMappingURL=GithubScmEventsBridge.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GithubScmEventsBridge.cjs.js","sources":["../../src/events/GithubScmEventsBridge.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { CatalogScmEventsService } from '@backstage/plugin-catalog-node/alpha';\nimport { EventParams, EventsService } from '@backstage/plugin-events-node';\nimport { analyzeGithubWebhookEvent } from './analyzeGithubWebhookEvent';\nimport { OctokitProviderService } from '../util/octokitProviderService';\n\n/**\n * Takes GitHub webhook events, analyzes them, and publishes them as catalog SCM\n * events that entity providers and others can subscribe to.\n */\nexport class GithubScmEventsBridge {\n readonly #logger: LoggerService;\n readonly #events: EventsService;\n readonly #octokitProvider: OctokitProviderService;\n readonly #catalogScmEvents: CatalogScmEventsService;\n #shuttingDown: boolean;\n #pendingPublish: Promise<void> | undefined;\n\n constructor(options: {\n logger: LoggerService;\n events: EventsService;\n octokitProvider: OctokitProviderService;\n catalogScmEvents: CatalogScmEventsService;\n }) {\n this.#logger = options.logger;\n this.#events = options.events;\n this.#octokitProvider = options.octokitProvider;\n this.#catalogScmEvents = options.catalogScmEvents;\n this.#shuttingDown = false;\n }\n\n async start() {\n await this.#events.subscribe({\n id: 'catalog-github-scm-events-bridge',\n topics: ['github'],\n onEvent: this.#onEvent.bind(this),\n });\n }\n\n async stop() {\n this.#shuttingDown = true;\n await this.#pendingPublish;\n }\n\n async #onEvent(params: EventParams): Promise<void> {\n const eventType = params.metadata?.['x-github-event'] as string | undefined;\n const eventPayload = params.eventPayload;\n if (!eventType || !eventPayload) {\n return;\n }\n\n while (this.#pendingPublish) {\n await this.#pendingPublish;\n }\n\n if (this.#shuttingDown) {\n this.#logger.warn(\n `Skipping GitHub webhook event of type \"${eventType}\" on topic \"${params.topic}\" because the bridge is shutting down`,\n );\n return;\n }\n\n this.#pendingPublish = Promise.resolve().then(async () => {\n try {\n const output = await analyzeGithubWebhookEvent(\n eventType,\n eventPayload,\n {\n octokitProvider: this.#octokitProvider,\n logger: this.#logger,\n isRelevantPath: path =>\n path.endsWith('.yaml') || path.endsWith('.yml'),\n },\n );\n\n if (output.result === 'ok') {\n await this.#catalogScmEvents.publish(output.events);\n } else if (output.result === 'ignored') {\n this.#logger.debug(\n `Skipping GitHub webhook event of type \"${eventType}\" on topic \"${params.topic}\" because it is ignored: ${output.reason}`,\n );\n } else if (output.result === 'aborted') {\n this.#logger.warn(\n `Skipping GitHub webhook event of type \"${eventType}\" on topic \"${params.topic}\" because it is aborted: ${output.reason}`,\n );\n } else if (output.result === 'unsupported-event') {\n this.#logger.debug(\n `Skipping GitHub webhook event of type \"${eventType}\" on topic \"${params.topic}\" because it is unsupported: ${output.event}`,\n );\n }\n } catch (error) {\n this.#logger.warn(\n `Failed to handle GitHub webhook event of type \"${eventType}\"`,\n error,\n );\n } finally {\n this.#pendingPublish = undefined;\n }\n });\n\n await this.#pendingPublish;\n }\n}\n"],"names":["analyzeGithubWebhookEvent"],"mappings":"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"}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
|
|
5
|
+
function pathStateToCatalogScmEvent(path, event, repository) {
|
|
6
|
+
const toBlobUrl = (p) => `${repository.html_url}/blob/${repository.default_branch}/${p}`;
|
|
7
|
+
const context = {
|
|
8
|
+
commitUrl: "html_url" in event.commit ? event.commit.html_url : event.commit.url
|
|
9
|
+
};
|
|
10
|
+
switch (event.type) {
|
|
11
|
+
case "added":
|
|
12
|
+
return {
|
|
13
|
+
type: "location.created",
|
|
14
|
+
url: toBlobUrl(path),
|
|
15
|
+
context
|
|
16
|
+
};
|
|
17
|
+
case "removed":
|
|
18
|
+
return {
|
|
19
|
+
type: "location.deleted",
|
|
20
|
+
url: toBlobUrl(path),
|
|
21
|
+
context
|
|
22
|
+
};
|
|
23
|
+
case "modified":
|
|
24
|
+
return {
|
|
25
|
+
type: "location.updated",
|
|
26
|
+
url: toBlobUrl(path),
|
|
27
|
+
context
|
|
28
|
+
};
|
|
29
|
+
case "renamed":
|
|
30
|
+
return {
|
|
31
|
+
type: "location.moved",
|
|
32
|
+
fromUrl: toBlobUrl(event.fromPath),
|
|
33
|
+
toUrl: toBlobUrl(path),
|
|
34
|
+
context
|
|
35
|
+
};
|
|
36
|
+
case "changed":
|
|
37
|
+
return {
|
|
38
|
+
type: "location.updated",
|
|
39
|
+
url: toBlobUrl(path),
|
|
40
|
+
context
|
|
41
|
+
};
|
|
42
|
+
default:
|
|
43
|
+
throw new Error(`Unknown file event type: ${event.type}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function analyzeGithubWebhookEvent(eventType, eventPayload, options) {
|
|
47
|
+
if (!eventPayload || typeof eventPayload !== "object" || Array.isArray(eventPayload)) {
|
|
48
|
+
throw new errors.InputError("GitHub webhook event payload is not an object");
|
|
49
|
+
}
|
|
50
|
+
if (eventType === "push") {
|
|
51
|
+
return await onPushEvent(eventPayload, options);
|
|
52
|
+
}
|
|
53
|
+
if (eventType === "repository") {
|
|
54
|
+
const repositoryEvent = eventPayload;
|
|
55
|
+
const action = repositoryEvent.action;
|
|
56
|
+
if (action === "created") {
|
|
57
|
+
return await onRepositoryCreatedEvent(repositoryEvent);
|
|
58
|
+
} else if (action === "deleted") {
|
|
59
|
+
return await onRepositoryDeletedEvent(repositoryEvent);
|
|
60
|
+
} else if (action === "archived") {
|
|
61
|
+
return await onRepositoryArchivedEvent(repositoryEvent);
|
|
62
|
+
} else if (action === "unarchived") {
|
|
63
|
+
return await onRepositoryUnarchivedEvent(repositoryEvent);
|
|
64
|
+
} else if (action === "privatized") {
|
|
65
|
+
return await onRepositoryPrivatizedEvent(repositoryEvent);
|
|
66
|
+
} else if (action === "publicized") {
|
|
67
|
+
return await onRepositoryPublicizedEvent(repositoryEvent);
|
|
68
|
+
} else if (action === "renamed") {
|
|
69
|
+
return await onRepositoryRenamedEvent(repositoryEvent);
|
|
70
|
+
} else if (action === "edited") {
|
|
71
|
+
return await onRepositoryEditedEvent(repositoryEvent);
|
|
72
|
+
} else if (action === "transferred") {
|
|
73
|
+
return await onRepositoryTransferredEvent(repositoryEvent);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
result: "unsupported-event",
|
|
77
|
+
event: `${eventType}.${action}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
result: "unsupported-event",
|
|
82
|
+
event: eventType
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function onPushEvent(event, options) {
|
|
86
|
+
const contextUrl = event.compare || "<unknown>";
|
|
87
|
+
if (event.commits.length >= 2048) {
|
|
88
|
+
return {
|
|
89
|
+
result: "aborted",
|
|
90
|
+
reason: `GitHub push event has too many commits (${event.commits.length}), assuming that this is not a complete push event: ${contextUrl}`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const defaultBranchRef = `refs/heads/${event.repository.default_branch}`;
|
|
94
|
+
if (event.ref !== defaultBranchRef) {
|
|
95
|
+
return {
|
|
96
|
+
result: "ignored",
|
|
97
|
+
reason: `GitHub push event did not target the default branch, found "${event.ref}" but expected "${defaultBranchRef}": ${contextUrl}`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const interestingShorthandCommits = (event.commits ?? []).filter(
|
|
101
|
+
(commit) => commit.added?.some(options.isRelevantPath) || commit.removed?.some(options.isRelevantPath) || commit.modified?.some(options.isRelevantPath)
|
|
102
|
+
);
|
|
103
|
+
if (!interestingShorthandCommits.length) {
|
|
104
|
+
return {
|
|
105
|
+
result: "ignored",
|
|
106
|
+
reason: `GitHub push event did not affect any relevant paths: ${contextUrl}`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const hasAddedPaths = interestingShorthandCommits.some(
|
|
110
|
+
(commit) => (commit.added ?? []).filter(options.isRelevantPath).length > 0
|
|
111
|
+
);
|
|
112
|
+
const hasRemovedPaths = interestingShorthandCommits.some(
|
|
113
|
+
(commit) => (commit.removed ?? []).filter(options.isRelevantPath).length > 0
|
|
114
|
+
);
|
|
115
|
+
if (!(hasAddedPaths && hasRemovedPaths)) {
|
|
116
|
+
const addedOrRemovedPaths = /* @__PURE__ */ new Map();
|
|
117
|
+
const changedPaths = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const commit of interestingShorthandCommits) {
|
|
119
|
+
for (const path of commit.modified?.filter(options.isRelevantPath) ?? []) {
|
|
120
|
+
if (!addedOrRemovedPaths.has(path)) {
|
|
121
|
+
changedPaths.set(path, { type: "changed", commit });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const path of commit.added?.filter(options.isRelevantPath) ?? []) {
|
|
125
|
+
changedPaths.delete(path);
|
|
126
|
+
addedOrRemovedPaths.set(path, { type: "added", commit });
|
|
127
|
+
}
|
|
128
|
+
for (const path of commit.removed?.filter(options.isRelevantPath) ?? []) {
|
|
129
|
+
changedPaths.delete(path);
|
|
130
|
+
addedOrRemovedPaths.set(path, { type: "removed", commit });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const allPaths = new Map([
|
|
134
|
+
...changedPaths.entries(),
|
|
135
|
+
...addedOrRemovedPaths.entries()
|
|
136
|
+
]);
|
|
137
|
+
return {
|
|
138
|
+
result: "ok",
|
|
139
|
+
events: Array.from(allPaths.entries()).map(
|
|
140
|
+
([path, e]) => pathStateToCatalogScmEvent(path, e, event.repository)
|
|
141
|
+
)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const pathState = /* @__PURE__ */ new Map();
|
|
145
|
+
const octokit = await options.octokitProvider.getOctokit(
|
|
146
|
+
event.repository.html_url
|
|
147
|
+
);
|
|
148
|
+
for (const eventCommit of interestingShorthandCommits) {
|
|
149
|
+
let commit;
|
|
150
|
+
for (let page = 1; page <= 10; ++page) {
|
|
151
|
+
const response = await octokit.rest.repos.getCommit({
|
|
152
|
+
page,
|
|
153
|
+
per_page: 300,
|
|
154
|
+
owner: event.repository.owner.login,
|
|
155
|
+
repo: event.repository.name,
|
|
156
|
+
ref: eventCommit.id
|
|
157
|
+
});
|
|
158
|
+
if (!commit) {
|
|
159
|
+
commit = response.data;
|
|
160
|
+
} else {
|
|
161
|
+
commit.files = [
|
|
162
|
+
...commit.files ?? [],
|
|
163
|
+
...response.data.files ?? []
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
if (!response.headers?.link?.includes('rel="next"')) {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!commit) {
|
|
171
|
+
throw new Error(`Failed to fetch commit for ${contextUrl}`);
|
|
172
|
+
}
|
|
173
|
+
for (const file of commit.files ?? []) {
|
|
174
|
+
const previous = pathState.get(file.previous_filename || file.filename);
|
|
175
|
+
let next;
|
|
176
|
+
if (file.status === "added") {
|
|
177
|
+
if (!previous) {
|
|
178
|
+
next = { type: "added", commit };
|
|
179
|
+
} else if (previous.type === "removed") {
|
|
180
|
+
next = { type: "changed", commit };
|
|
181
|
+
} else {
|
|
182
|
+
next = previous;
|
|
183
|
+
options.logger.debug(
|
|
184
|
+
`Unexpected commit state transition from ${previous.type} to ${file.status} in ${event.compare}`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
} else if (file.status === "removed") {
|
|
188
|
+
if (!previous) {
|
|
189
|
+
next = { type: "removed", commit };
|
|
190
|
+
} else if (previous.type === "added") {
|
|
191
|
+
next = void 0;
|
|
192
|
+
} else if (previous.type === "changed") {
|
|
193
|
+
next = { type: "removed", commit };
|
|
194
|
+
} else if (previous.type === "renamed") {
|
|
195
|
+
if (!pathState.has(previous.fromPath)) {
|
|
196
|
+
pathState.set(previous.fromPath, { type: "removed", commit });
|
|
197
|
+
}
|
|
198
|
+
next = void 0;
|
|
199
|
+
} else {
|
|
200
|
+
next = previous;
|
|
201
|
+
options.logger.debug(
|
|
202
|
+
`Unexpected commit state transition from ${previous.type} to ${file.status} in ${event.compare}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
} else if (file.status === "renamed") {
|
|
206
|
+
pathState.delete(file.previous_filename);
|
|
207
|
+
if (!previous) {
|
|
208
|
+
next = { type: "renamed", fromPath: file.previous_filename, commit };
|
|
209
|
+
} else if (previous.type === "added") {
|
|
210
|
+
next = { type: "added", commit };
|
|
211
|
+
} else if (previous.type === "changed") {
|
|
212
|
+
next = { type: "renamed", fromPath: file.previous_filename, commit };
|
|
213
|
+
} else if (previous.type === "renamed") {
|
|
214
|
+
next = { type: "renamed", fromPath: previous.fromPath, commit };
|
|
215
|
+
} else {
|
|
216
|
+
next = void 0;
|
|
217
|
+
options.logger.debug(
|
|
218
|
+
`Unexpected commit state transition from ${previous.type} to ${file.status} in ${event.compare}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
} else if (file.status === "changed" || file.status === "modified") {
|
|
222
|
+
if (!previous) {
|
|
223
|
+
next = { type: "changed", commit };
|
|
224
|
+
} else if (previous.type === "added") {
|
|
225
|
+
next = previous;
|
|
226
|
+
} else if (previous.type === "changed") {
|
|
227
|
+
next = previous;
|
|
228
|
+
} else if (previous.type === "renamed") {
|
|
229
|
+
next = previous;
|
|
230
|
+
} else {
|
|
231
|
+
next = previous;
|
|
232
|
+
options.logger.debug(
|
|
233
|
+
`Unexpected commit state transition from ${previous.type} to ${file.status} in ${event.compare}`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
next = previous;
|
|
238
|
+
}
|
|
239
|
+
if (next) {
|
|
240
|
+
pathState.set(file.filename, next);
|
|
241
|
+
} else {
|
|
242
|
+
pathState.delete(file.filename);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
result: "ok",
|
|
248
|
+
events: Array.from(pathState.entries()).flatMap(
|
|
249
|
+
([path, e]) => options.isRelevantPath(path) ? [pathStateToCatalogScmEvent(path, e, event.repository)] : []
|
|
250
|
+
)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
async function onRepositoryArchivedEvent(event) {
|
|
254
|
+
return {
|
|
255
|
+
result: "ok",
|
|
256
|
+
events: [
|
|
257
|
+
{
|
|
258
|
+
type: "repository.updated",
|
|
259
|
+
url: event.repository.html_url
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async function onRepositoryUnarchivedEvent(event) {
|
|
265
|
+
return {
|
|
266
|
+
result: "ok",
|
|
267
|
+
events: [
|
|
268
|
+
{
|
|
269
|
+
type: "repository.updated",
|
|
270
|
+
url: event.repository.html_url
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
async function onRepositoryPrivatizedEvent(event) {
|
|
276
|
+
return {
|
|
277
|
+
result: "ok",
|
|
278
|
+
events: [
|
|
279
|
+
{
|
|
280
|
+
type: "repository.updated",
|
|
281
|
+
url: event.repository.html_url
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async function onRepositoryPublicizedEvent(event) {
|
|
287
|
+
return {
|
|
288
|
+
result: "ok",
|
|
289
|
+
events: [
|
|
290
|
+
{
|
|
291
|
+
type: "repository.updated",
|
|
292
|
+
url: event.repository.html_url
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async function onRepositoryCreatedEvent(event) {
|
|
298
|
+
return {
|
|
299
|
+
result: "ok",
|
|
300
|
+
events: [
|
|
301
|
+
{
|
|
302
|
+
type: "repository.created",
|
|
303
|
+
url: event.repository.html_url
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
async function onRepositoryDeletedEvent(event) {
|
|
309
|
+
return {
|
|
310
|
+
result: "ok",
|
|
311
|
+
events: [
|
|
312
|
+
{
|
|
313
|
+
type: "repository.deleted",
|
|
314
|
+
url: event.repository.html_url
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
async function onRepositoryRenamedEvent(event) {
|
|
320
|
+
const oldUrlPrefix = `${event.repository.owner.html_url}/${event.changes.repository.name.from}`;
|
|
321
|
+
const newUrlPrefix = `${event.repository.html_url}`;
|
|
322
|
+
return {
|
|
323
|
+
result: "ok",
|
|
324
|
+
events: [
|
|
325
|
+
{
|
|
326
|
+
type: "repository.moved",
|
|
327
|
+
fromUrl: oldUrlPrefix,
|
|
328
|
+
toUrl: newUrlPrefix
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
async function onRepositoryEditedEvent(event) {
|
|
334
|
+
if (event.changes.default_branch) {
|
|
335
|
+
const oldUrlPrefix = `${event.repository.html_url}/blob/${event.changes.default_branch.from}`;
|
|
336
|
+
const newUrlPrefix = `${event.repository.html_url}/blob/${event.repository.default_branch}`;
|
|
337
|
+
return {
|
|
338
|
+
result: "ok",
|
|
339
|
+
events: [
|
|
340
|
+
{
|
|
341
|
+
type: "repository.moved",
|
|
342
|
+
fromUrl: oldUrlPrefix,
|
|
343
|
+
toUrl: newUrlPrefix
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const changes = Object.keys(event.changes).map((c) => `'${c}'`).join(", ");
|
|
349
|
+
return {
|
|
350
|
+
result: "ignored",
|
|
351
|
+
reason: `GitHub repository edited event not handled: [${changes}]`
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
async function onRepositoryTransferredEvent(event) {
|
|
355
|
+
const from = event.changes.owner.from;
|
|
356
|
+
let oldOwnerUrl = from.user?.html_url ?? from.organization?.html_url;
|
|
357
|
+
if (!oldOwnerUrl) {
|
|
358
|
+
const base = event.repository.html_url.split("/").slice(0, -2).join("/");
|
|
359
|
+
oldOwnerUrl = `${base}/${from.user?.login ?? from.organization?.login}`;
|
|
360
|
+
}
|
|
361
|
+
const oldUrlPrefix = `${oldOwnerUrl}/${event.repository.name}`;
|
|
362
|
+
const newUrlPrefix = `${event.repository.html_url}`;
|
|
363
|
+
return {
|
|
364
|
+
result: "ok",
|
|
365
|
+
events: [
|
|
366
|
+
{
|
|
367
|
+
type: "repository.moved",
|
|
368
|
+
fromUrl: oldUrlPrefix,
|
|
369
|
+
toUrl: newUrlPrefix
|
|
370
|
+
}
|
|
371
|
+
]
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
exports.analyzeGithubWebhookEvent = analyzeGithubWebhookEvent;
|
|
376
|
+
//# sourceMappingURL=analyzeGithubWebhookEvent.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzeGithubWebhookEvent.cjs.js","sources":["../../src/events/analyzeGithubWebhookEvent.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { InputError } from '@backstage/errors';\nimport { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha';\nimport type {\n Commit,\n Organization,\n PushEvent,\n Repository,\n RepositoryArchivedEvent,\n RepositoryCreatedEvent,\n RepositoryDeletedEvent,\n RepositoryEditedEvent,\n RepositoryEvent,\n RepositoryPrivatizedEvent,\n RepositoryPublicizedEvent,\n RepositoryRenamedEvent,\n RepositoryTransferredEvent,\n RepositoryUnarchivedEvent,\n User,\n} from '@octokit/webhooks-types';\nimport { OctokitProviderService } from '../util/octokitProviderService';\nimport { Octokit } from 'octokit';\n\nexport interface AnalyzeWebhookEventOptions {\n octokitProvider: OctokitProviderService;\n logger: LoggerService;\n isRelevantPath: (path: string) => boolean;\n}\n\nexport type AnalyzeWebhookEventResult =\n | {\n result: 'unsupported-event';\n event: string;\n }\n | {\n result: 'ignored';\n reason: string;\n }\n | {\n result: 'aborted';\n reason: string;\n }\n | {\n result: 'ok';\n events: CatalogScmEvent[];\n };\n\n// Either a shorthand commit from a webhook event, or the full commit returned\n// by the client API\ntype AnyCommit =\n | Commit\n | Awaited<ReturnType<Octokit['rest']['repos']['getCommit']>>['data'];\n\n/**\n * Keeps track of intermediate state of individual paths while processing push events.\n */\ntype PathState =\n | {\n type: 'added';\n commit: AnyCommit;\n }\n | {\n type: 'removed';\n commit: AnyCommit;\n }\n | {\n type: 'modified';\n commit: AnyCommit;\n }\n | {\n type: 'renamed';\n fromPath: string;\n commit: AnyCommit;\n }\n | {\n type: 'changed';\n commit: AnyCommit;\n };\n\nfunction pathStateToCatalogScmEvent(\n path: string,\n event: PathState,\n repository: Repository,\n): CatalogScmEvent {\n const toBlobUrl = (p: string) =>\n `${repository.html_url}/blob/${repository.default_branch}/${p}`;\n\n const context = {\n commitUrl:\n 'html_url' in event.commit ? event.commit.html_url : event.commit.url,\n };\n\n switch (event.type) {\n case 'added':\n return {\n type: 'location.created',\n url: toBlobUrl(path),\n context,\n };\n case 'removed':\n return {\n type: 'location.deleted',\n url: toBlobUrl(path),\n context,\n };\n case 'modified':\n return {\n type: 'location.updated',\n url: toBlobUrl(path),\n context,\n };\n case 'renamed':\n return {\n type: 'location.moved',\n fromUrl: toBlobUrl(event.fromPath),\n toUrl: toBlobUrl(path),\n context,\n };\n case 'changed':\n return {\n type: 'location.updated',\n url: toBlobUrl(path),\n context,\n };\n default:\n // @ts-expect-error Intentionally expected, to check for exhaustive checking of the types\n throw new Error(`Unknown file event type: ${event.type}`);\n }\n}\n\n/**\n * Analyzes a GitHub webhook event and returns details about actions that the\n * event might require.\n */\nexport async function analyzeGithubWebhookEvent(\n eventType: string,\n eventPayload: unknown,\n options: AnalyzeWebhookEventOptions,\n): Promise<AnalyzeWebhookEventResult> {\n if (\n !eventPayload ||\n typeof eventPayload !== 'object' ||\n Array.isArray(eventPayload)\n ) {\n throw new InputError('GitHub webhook event payload is not an object');\n }\n\n if (eventType === 'push') {\n return await onPushEvent(eventPayload as PushEvent, options);\n }\n\n if (eventType === 'repository') {\n const repositoryEvent = eventPayload as RepositoryEvent;\n const action = repositoryEvent.action;\n if (action === 'created') {\n return await onRepositoryCreatedEvent(repositoryEvent);\n } else if (action === 'deleted') {\n return await onRepositoryDeletedEvent(repositoryEvent);\n } else if (action === 'archived') {\n return await onRepositoryArchivedEvent(repositoryEvent);\n } else if (action === 'unarchived') {\n return await onRepositoryUnarchivedEvent(repositoryEvent);\n } else if (action === 'privatized') {\n return await onRepositoryPrivatizedEvent(repositoryEvent);\n } else if (action === 'publicized') {\n return await onRepositoryPublicizedEvent(repositoryEvent);\n } else if (action === 'renamed') {\n return await onRepositoryRenamedEvent(repositoryEvent);\n } else if (action === 'edited') {\n return await onRepositoryEditedEvent(repositoryEvent);\n } else if (action === 'transferred') {\n return await onRepositoryTransferredEvent(repositoryEvent);\n }\n return {\n result: 'unsupported-event',\n event: `${eventType}.${action}`,\n };\n }\n\n return {\n result: 'unsupported-event',\n event: eventType,\n };\n}\n\n// #region Push events\n\nasync function onPushEvent(\n event: PushEvent,\n options: AnalyzeWebhookEventOptions,\n): Promise<AnalyzeWebhookEventResult> {\n const contextUrl = event.compare || '<unknown>';\n\n // NOTE: The following caveats are mentioned in https://docs.github.com/en/webhooks/webhook-events-and-payloads#push\n // * Events will not be created if more than 5000 branches are pushed at once.\n // * Events will not be created for tags when more than three tags are pushed at once.\n // * The commits array includes a maximum of 2048 commits. If necessary, you can use the Commits API to fetch additional commits.\n // TODO(freben): Do we need to support reading the commits using the separate API under some circumstances?\n if (event.commits.length >= 2048) {\n return {\n result: 'aborted',\n reason: `GitHub push event has too many commits (${event.commits.length}), assuming that this is not a complete push event: ${contextUrl}`,\n };\n }\n\n // We ignore any event that doesn't target the default branch as this\n // is where the metadata files should be stored for now.\n const defaultBranchRef = `refs/heads/${event.repository.default_branch}`;\n if (event.ref !== defaultBranchRef) {\n return {\n result: 'ignored',\n reason: `GitHub push event did not target the default branch, found \"${event.ref}\" but expected \"${defaultBranchRef}\": ${contextUrl}`,\n };\n }\n\n /*\n * STEP 1: Find interesting commits\n *\n * We go through the commits and exclude the ones that are not interesting. We\n * consider a commit interesting if it mentions any relevant paths. We don't\n * want to unnecessarily call out to the GitHub API for commits that don't\n * affect files that we care about.\n *\n * The raw webhook event only contains shorthand commit data, not the full set\n * of information. Notably it does not contain the \"files\" array that details\n * what precise type of action was taken on individual paths. Instead it\n * presents three summary arrays per commit: \"added\", \"removed\", and\n * \"modified\", that just hold path strings.\n */\n\n const interestingShorthandCommits = (event.commits ?? []).filter(\n commit =>\n commit.added?.some(options.isRelevantPath) ||\n commit.removed?.some(options.isRelevantPath) ||\n commit.modified?.some(options.isRelevantPath),\n );\n\n if (!interestingShorthandCommits.length) {\n return {\n result: 'ignored',\n reason: `GitHub push event did not affect any relevant paths: ${contextUrl}`,\n };\n }\n\n /*\n * STEP 2: Simple case\n *\n * We go through the interesting shorthand commits in time order and make a\n * basic assessment of what happened. Most of the time this will be enough to\n * determine that (for relevant paths) there were only additions or only\n * removals - and possibly some changes. In that case we don't have to pay the\n * cost of fetching the detailed commits, because it's safe to assume that\n * there were no complex renames/moves or similar.\n *\n * If we find that any path is mentioned more than once, we bail out and go to\n * the complex case below instead.\n */\n\n const hasAddedPaths = interestingShorthandCommits.some(\n commit => (commit.added ?? []).filter(options.isRelevantPath).length > 0,\n );\n const hasRemovedPaths = interestingShorthandCommits.some(\n commit => (commit.removed ?? []).filter(options.isRelevantPath).length > 0,\n );\n\n if (!(hasAddedPaths && hasRemovedPaths)) {\n const addedOrRemovedPaths = new Map<string, PathState>();\n const changedPaths = new Map<string, PathState>();\n\n for (const commit of interestingShorthandCommits) {\n for (const path of commit.modified?.filter(options.isRelevantPath) ??\n []) {\n if (!addedOrRemovedPaths.has(path)) {\n changedPaths.set(path, { type: 'changed', commit });\n }\n }\n for (const path of commit.added?.filter(options.isRelevantPath) ?? []) {\n changedPaths.delete(path);\n addedOrRemovedPaths.set(path, { type: 'added', commit });\n }\n for (const path of commit.removed?.filter(options.isRelevantPath) ?? []) {\n changedPaths.delete(path);\n addedOrRemovedPaths.set(path, { type: 'removed', commit });\n }\n }\n\n const allPaths = new Map([\n ...changedPaths.entries(),\n ...addedOrRemovedPaths.entries(),\n ]);\n\n return {\n result: 'ok',\n events: Array.from(allPaths.entries()).map(([path, e]) =>\n pathStateToCatalogScmEvent(path, e, event.repository),\n ),\n };\n }\n\n /*\n * STEP 3: Complex case\n *\n * There seem to be a more complex overall set of changes here. There are both\n * additions and removals that look interesting. Now we need to analyze the\n * commits more carefully, to see which changes constitute purely individual\n * additions/removals, and which ones are actually moves. We do this by\n * iterating through the commits as fetched from the remote (since they\n * contain richer information than the webhook), and computing the \"compound\"\n * outcome (e.g. an add followed by a remove of the same file can be ignored).\n */\n\n const pathState = new Map<string, PathState>();\n\n const octokit = await options.octokitProvider.getOctokit(\n event.repository.html_url,\n );\n\n for (const eventCommit of interestingShorthandCommits) {\n // As noted in the getCommit documentation, if there's a large number of\n // files in the commit then only at most 300 of them will be returned along\n // with pagination link header, and then going up to a total of at most\n // 3000 files. But we also want to use the convenient octokit API so we\n // paginate in this kind of clunky way and end whenever there's no more rel\n // next URL.\n let commit:\n | Awaited<ReturnType<Octokit['rest']['repos']['getCommit']>>['data']\n | undefined;\n for (let page = 1; page <= 10; ++page) {\n const response = await octokit.rest.repos.getCommit({\n page,\n per_page: 300,\n owner: event.repository.owner.login,\n repo: event.repository.name,\n ref: eventCommit.id,\n });\n\n if (!commit) {\n commit = response.data;\n } else {\n commit.files = [\n ...(commit.files ?? []),\n ...(response.data.files ?? []),\n ];\n }\n\n if (!response.headers?.link?.includes('rel=\"next\"')) {\n break;\n }\n }\n\n if (!commit) {\n throw new Error(`Failed to fetch commit for ${contextUrl}`);\n }\n\n // We somewhat wastefully track all paths here whether they seem initially\n // relevant or not, because we don't know yet whether they will be\n // moved/renamed into or out of relevance.\n for (const file of commit.files ?? []) {\n const previous = pathState.get(file.previous_filename || file.filename);\n let next: PathState | undefined;\n if (file.status === 'added') {\n if (!previous) {\n // First time we see this file in this set of commits\n next = { type: 'added', commit };\n } else if (previous.type === 'removed') {\n // Removed and then added again - assume changes\n next = { type: 'changed', commit };\n } else {\n // Should not happen; added/changed/moved -> added\n next = previous;\n options.logger.debug(\n `Unexpected commit state transition from ${previous.type} to ${file.status} in ${event.compare}`,\n );\n }\n } else if (file.status === 'removed') {\n if (!previous) {\n // First time we see this file in this set of commits\n next = { type: 'removed', commit };\n } else if (previous.type === 'added') {\n // It was first added and then removed - turn into noop\n next = undefined;\n } else if (previous.type === 'changed') {\n // Changed and then removed; convert to removed\n next = { type: 'removed', commit };\n } else if (previous.type === 'renamed') {\n // It was renamed and then removed - convert to removal of the ORIGINAL path\n if (!pathState.has(previous.fromPath)) {\n pathState.set(previous.fromPath, { type: 'removed', commit });\n }\n next = undefined;\n } else {\n // Should not happen; removed -> removed\n next = previous;\n options.logger.debug(\n `Unexpected commit state transition from ${previous.type} to ${file.status} in ${event.compare}`,\n );\n }\n } else if (file.status === 'renamed') {\n pathState.delete(file.previous_filename!);\n if (!previous) {\n // First time we see this file in this set of commits\n next = { type: 'renamed', fromPath: file.previous_filename!, commit };\n } else if (previous.type === 'added') {\n // It was first added and then moved - this sums to still just an add but in the new path\n next = { type: 'added', commit };\n } else if (previous.type === 'changed') {\n // Changed and then moved; convert to moved\n next = { type: 'renamed', fromPath: file.previous_filename!, commit };\n } else if (previous.type === 'renamed') {\n // It was renamed and then renamed again\n next = { type: 'renamed', fromPath: previous.fromPath, commit };\n } else {\n // Should not happen; removed -> renamed\n next = undefined;\n options.logger.debug(\n `Unexpected commit state transition from ${previous.type} to ${file.status} in ${event.compare}`,\n );\n }\n } else if (file.status === 'changed' || file.status === 'modified') {\n if (!previous) {\n // First time we see this file in this set of commits\n next = { type: 'changed', commit };\n } else if (previous.type === 'added') {\n // It was first added and then changed - still an add\n next = previous;\n } else if (previous.type === 'changed') {\n // Changed twice - still just a change\n next = previous;\n } else if (previous.type === 'renamed') {\n // Renamed and then changed - still a rename\n next = previous;\n } else {\n // Should not happen; removed -> changed but still keep the old state because it makes the most sense\n next = previous;\n options.logger.debug(\n `Unexpected commit state transition from ${previous.type} to ${file.status} in ${event.compare}`,\n );\n }\n } else {\n // remaining statuses are 'copied' and 'unchanged' - ignoring\n next = previous;\n }\n\n if (next) {\n pathState.set(file.filename, next);\n } else {\n pathState.delete(file.filename);\n }\n }\n }\n\n return {\n result: 'ok',\n events: Array.from(pathState.entries()).flatMap(([path, e]) =>\n options.isRelevantPath(path)\n ? [pathStateToCatalogScmEvent(path, e, event.repository)]\n : [],\n ),\n };\n}\n\n// #endregion\n// #region Repository events\n\nasync function onRepositoryArchivedEvent(\n event: RepositoryArchivedEvent,\n): Promise<AnalyzeWebhookEventResult> {\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.updated',\n url: event.repository.html_url,\n },\n ],\n };\n}\n\nasync function onRepositoryUnarchivedEvent(\n event: RepositoryUnarchivedEvent,\n): Promise<AnalyzeWebhookEventResult> {\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.updated',\n url: event.repository.html_url,\n },\n ],\n };\n}\n\nasync function onRepositoryPrivatizedEvent(\n event: RepositoryPrivatizedEvent,\n): Promise<AnalyzeWebhookEventResult> {\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.updated',\n url: event.repository.html_url,\n },\n ],\n };\n}\n\nasync function onRepositoryPublicizedEvent(\n event: RepositoryPublicizedEvent,\n): Promise<AnalyzeWebhookEventResult> {\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.updated',\n url: event.repository.html_url,\n },\n ],\n };\n}\n\nasync function onRepositoryCreatedEvent(\n event: RepositoryCreatedEvent,\n): Promise<AnalyzeWebhookEventResult> {\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.created',\n url: event.repository.html_url,\n },\n ],\n };\n}\n\nasync function onRepositoryDeletedEvent(\n event: RepositoryDeletedEvent,\n): Promise<AnalyzeWebhookEventResult> {\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.deleted',\n url: event.repository.html_url,\n },\n ],\n };\n}\n\nasync function onRepositoryRenamedEvent(\n event: RepositoryRenamedEvent,\n): Promise<AnalyzeWebhookEventResult> {\n const oldUrlPrefix = `${event.repository.owner.html_url}/${event.changes.repository.name.from}`;\n const newUrlPrefix = `${event.repository.html_url}`;\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.moved',\n fromUrl: oldUrlPrefix,\n toUrl: newUrlPrefix,\n },\n ],\n };\n}\n\nasync function onRepositoryEditedEvent(\n event: RepositoryEditedEvent,\n): Promise<AnalyzeWebhookEventResult> {\n if (event.changes.default_branch) {\n const oldUrlPrefix = `${event.repository.html_url}/blob/${event.changes.default_branch.from}`;\n const newUrlPrefix = `${event.repository.html_url}/blob/${event.repository.default_branch}`;\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.moved',\n fromUrl: oldUrlPrefix,\n toUrl: newUrlPrefix,\n },\n ],\n };\n }\n\n const changes = Object.keys(event.changes)\n .map(c => `'${c}'`)\n .join(', ');\n return {\n result: 'ignored',\n reason: `GitHub repository edited event not handled: [${changes}]`,\n };\n}\n\nasync function onRepositoryTransferredEvent(\n event: RepositoryTransferredEvent,\n): Promise<AnalyzeWebhookEventResult> {\n // TODO(freben): The web interface allows renaming while transferring, but\n // we do not yet have a way of detecting that because weirdly, the actual\n // webhooks that then get sent are\n //\n // - First a transferred event OLD_OWNER -> NEW_OWNER/NEW_REPO_NAME\n // (no mention of the OLD_REPO_NAME anywhere in the event payload)\n //\n // - Then a renamed event for NEW_OWNER of OLD_REPO_NAME -> NEW_REPO_NAME\n // (where the former of course never existed as far as we know;\n // no mention of the OLD_OWNER anywhere in the event payload)\n //\n // Handling this seems to need some form of state handling across events.\n //\n // Therefore, this code expects that OLD_REPO_NAME === NEW_REPO_NAME\n // and assumes that we did not perform a rename as part of the transfer.\n\n // The documentation states that there is an organization property in from,\n // but the types disagree. Hence the cast.\n // https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=transferred#repository\n const from = event.changes.owner.from as {\n user?: User;\n organization?: Organization;\n };\n\n let oldOwnerUrl = from.user?.html_url ?? from.organization?.html_url;\n if (!oldOwnerUrl) {\n // Some servers do not supply an html_url field, so we\n // construct it by removing the last two segments of the\n // target URL instead (which we know ends with \"/owner/repo\")\n const base = event.repository.html_url.split('/').slice(0, -2).join('/');\n oldOwnerUrl = `${base}/${from.user?.login ?? from.organization?.login}`;\n }\n\n const oldUrlPrefix = `${oldOwnerUrl}/${event.repository.name}`;\n const newUrlPrefix = `${event.repository.html_url}`;\n return {\n result: 'ok',\n events: [\n {\n type: 'repository.moved',\n fromUrl: oldUrlPrefix,\n toUrl: newUrlPrefix,\n },\n ],\n };\n}\n\n// #endregion\n"],"names":["InputError"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { graphql } from '@octokit/graphql';
|
|
|
9
9
|
import { EventSubscriber, EventsService, EventParams } from '@backstage/plugin-events-node';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Registers
|
|
12
|
+
* Registers all relevant GitHub integration points into the catalog backend.
|
|
13
13
|
*
|
|
14
14
|
* @public
|
|
15
15
|
*/
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
4
|
var pluginCatalogNode = require('@backstage/plugin-catalog-node');
|
|
5
|
+
var alpha = require('@backstage/plugin-catalog-node/alpha');
|
|
5
6
|
var pluginEventsNode = require('@backstage/plugin-events-node');
|
|
6
7
|
var GithubEntityProvider = require('../providers/GithubEntityProvider.cjs.js');
|
|
7
8
|
var GithubLocationAnalyzer = require('../analyzers/GithubLocationAnalyzer.cjs.js');
|
|
9
|
+
var octokitProviderService = require('../util/octokitProviderService.cjs.js');
|
|
10
|
+
var GithubScmEventsBridge = require('../events/GithubScmEventsBridge.cjs.js');
|
|
8
11
|
|
|
9
12
|
const githubCatalogModule = backendPluginApi.createBackendModule({
|
|
10
13
|
pluginId: "catalog",
|
|
@@ -19,7 +22,10 @@ const githubCatalogModule = backendPluginApi.createBackendModule({
|
|
|
19
22
|
events: pluginEventsNode.eventsServiceRef,
|
|
20
23
|
logger: backendPluginApi.coreServices.logger,
|
|
21
24
|
scheduler: backendPluginApi.coreServices.scheduler,
|
|
22
|
-
catalog: pluginCatalogNode.catalogServiceRef
|
|
25
|
+
catalog: pluginCatalogNode.catalogServiceRef,
|
|
26
|
+
octokitProvider: octokitProviderService.octokitProviderServiceRef,
|
|
27
|
+
catalogScmEvents: alpha.catalogScmEventsServiceRef,
|
|
28
|
+
lifecycle: backendPluginApi.coreServices.lifecycle
|
|
23
29
|
},
|
|
24
30
|
async init({
|
|
25
31
|
catalogProcessing,
|
|
@@ -29,7 +35,10 @@ const githubCatalogModule = backendPluginApi.createBackendModule({
|
|
|
29
35
|
scheduler,
|
|
30
36
|
catalogAnalyzers,
|
|
31
37
|
auth,
|
|
32
|
-
catalog
|
|
38
|
+
catalog,
|
|
39
|
+
octokitProvider,
|
|
40
|
+
catalogScmEvents,
|
|
41
|
+
lifecycle
|
|
33
42
|
}) {
|
|
34
43
|
catalogAnalyzers.addScmLocationAnalyzer(
|
|
35
44
|
new GithubLocationAnalyzer.GithubLocationAnalyzer({
|
|
@@ -45,6 +54,18 @@ const githubCatalogModule = backendPluginApi.createBackendModule({
|
|
|
45
54
|
scheduler
|
|
46
55
|
})
|
|
47
56
|
);
|
|
57
|
+
const bridge = new GithubScmEventsBridge.GithubScmEventsBridge({
|
|
58
|
+
logger,
|
|
59
|
+
events,
|
|
60
|
+
octokitProvider,
|
|
61
|
+
catalogScmEvents
|
|
62
|
+
});
|
|
63
|
+
lifecycle.addStartupHook(async () => {
|
|
64
|
+
await bridge.start();
|
|
65
|
+
});
|
|
66
|
+
lifecycle.addShutdownHook(async () => {
|
|
67
|
+
await bridge.stop();
|
|
68
|
+
});
|
|
48
69
|
}
|
|
49
70
|
});
|
|
50
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubCatalogModule.cjs.js","sources":["../../src/module/githubCatalogModule.ts"],"sourcesContent":["/*\n * Copyright 2022 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 {\n catalogAnalysisExtensionPoint,\n catalogProcessingExtensionPoint,\n} from '@backstage/plugin-catalog-node';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { eventsServiceRef } from '@backstage/plugin-events-node';\nimport { GithubEntityProvider } from '../providers/GithubEntityProvider';\nimport { GithubLocationAnalyzer } from '../analyzers/GithubLocationAnalyzer';\n\n/**\n * Registers
|
|
1
|
+
{"version":3,"file":"githubCatalogModule.cjs.js","sources":["../../src/module/githubCatalogModule.ts"],"sourcesContent":["/*\n * Copyright 2022 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 {\n catalogAnalysisExtensionPoint,\n catalogProcessingExtensionPoint,\n} from '@backstage/plugin-catalog-node';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { catalogScmEventsServiceRef } from '@backstage/plugin-catalog-node/alpha';\nimport { eventsServiceRef } from '@backstage/plugin-events-node';\nimport { GithubEntityProvider } from '../providers/GithubEntityProvider';\nimport { GithubLocationAnalyzer } from '../analyzers/GithubLocationAnalyzer';\nimport { octokitProviderServiceRef } from '../util/octokitProviderService';\nimport { GithubScmEventsBridge } from '../events/GithubScmEventsBridge';\n\n/**\n * Registers all relevant GitHub integration points into the catalog backend.\n *\n * @public\n */\nexport const githubCatalogModule = createBackendModule({\n pluginId: 'catalog',\n moduleId: 'github',\n register(env) {\n env.registerInit({\n deps: {\n catalogAnalyzers: catalogAnalysisExtensionPoint,\n auth: coreServices.auth,\n catalogProcessing: catalogProcessingExtensionPoint,\n config: coreServices.rootConfig,\n events: eventsServiceRef,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n catalog: catalogServiceRef,\n octokitProvider: octokitProviderServiceRef,\n catalogScmEvents: catalogScmEventsServiceRef,\n lifecycle: coreServices.lifecycle,\n },\n async init({\n catalogProcessing,\n config,\n events,\n logger,\n scheduler,\n catalogAnalyzers,\n auth,\n catalog,\n octokitProvider,\n catalogScmEvents,\n lifecycle,\n }) {\n catalogAnalyzers.addScmLocationAnalyzer(\n new GithubLocationAnalyzer({\n config,\n auth,\n catalog,\n }),\n );\n\n catalogProcessing.addEntityProvider(\n GithubEntityProvider.fromConfig(config, {\n events,\n logger,\n scheduler,\n }),\n );\n\n const bridge = new GithubScmEventsBridge({\n logger,\n events,\n octokitProvider,\n catalogScmEvents,\n });\n lifecycle.addStartupHook(async () => {\n await bridge.start();\n });\n lifecycle.addShutdownHook(async () => {\n await bridge.stop();\n });\n },\n });\n },\n});\n"],"names":["createBackendModule","catalogAnalysisExtensionPoint","coreServices","catalogProcessingExtensionPoint","eventsServiceRef","catalogServiceRef","octokitProviderServiceRef","catalogScmEventsServiceRef","GithubLocationAnalyzer","GithubEntityProvider","GithubScmEventsBridge"],"mappings":";;;;;;;;;;;AAqCO,MAAM,sBAAsBA,oCAAA,CAAoB;AAAA,EACrD,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,QAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,gBAAA,EAAkBC,+CAAA;AAAA,QAClB,MAAMC,6BAAA,CAAa,IAAA;AAAA,QACnB,iBAAA,EAAmBC,iDAAA;AAAA,QACnB,QAAQD,6BAAA,CAAa,UAAA;AAAA,QACrB,MAAA,EAAQE,iCAAA;AAAA,QACR,QAAQF,6BAAA,CAAa,MAAA;AAAA,QACrB,WAAWA,6BAAA,CAAa,SAAA;AAAA,QACxB,OAAA,EAASG,mCAAA;AAAA,QACT,eAAA,EAAiBC,gDAAA;AAAA,QACjB,gBAAA,EAAkBC,gCAAA;AAAA,QAClB,WAAWL,6BAAA,CAAa;AAAA,OAC1B;AAAA,MACA,MAAM,IAAA,CAAK;AAAA,QACT,iBAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,SAAA;AAAA,QACA,gBAAA;AAAA,QACA,IAAA;AAAA,QACA,OAAA;AAAA,QACA,eAAA;AAAA,QACA,gBAAA;AAAA,QACA;AAAA,OACF,EAAG;AACD,QAAA,gBAAA,CAAiB,sBAAA;AAAA,UACf,IAAIM,6CAAA,CAAuB;AAAA,YACzB,MAAA;AAAA,YACA,IAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAEA,QAAA,iBAAA,CAAkB,iBAAA;AAAA,UAChBC,yCAAA,CAAqB,WAAW,MAAA,EAAQ;AAAA,YACtC,MAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAEA,QAAA,MAAM,MAAA,GAAS,IAAIC,2CAAA,CAAsB;AAAA,UACvC,MAAA;AAAA,UACA,MAAA;AAAA,UACA,eAAA;AAAA,UACA;AAAA,SACD,CAAA;AACD,QAAA,SAAA,CAAU,eAAe,YAAY;AACnC,UAAA,MAAM,OAAO,KAAA,EAAM;AAAA,QACrB,CAAC,CAAA;AACD,QAAA,SAAA,CAAU,gBAAgB,YAAY;AACpC,UAAA,MAAM,OAAO,IAAA,EAAK;AAAA,QACpB,CAAC,CAAA;AAAA,MACH;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var integration = require('@backstage/integration');
|
|
5
|
+
var types = require('@backstage/types');
|
|
6
|
+
var octokit = require('octokit');
|
|
7
|
+
|
|
8
|
+
class OctokitProviderImpl {
|
|
9
|
+
#integrations;
|
|
10
|
+
#githubCredentials;
|
|
11
|
+
#octokitCache;
|
|
12
|
+
#octokitCacheTtl;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.#integrations = integration.ScmIntegrations.fromConfig(config);
|
|
15
|
+
this.#githubCredentials = integration.DefaultGithubCredentialsProvider.fromIntegrations(
|
|
16
|
+
this.#integrations
|
|
17
|
+
);
|
|
18
|
+
this.#octokitCache = /* @__PURE__ */ new Map();
|
|
19
|
+
this.#octokitCacheTtl = { hours: 1 };
|
|
20
|
+
}
|
|
21
|
+
async getOctokit(url) {
|
|
22
|
+
const integration = this.#integrations.github.byUrl(url);
|
|
23
|
+
if (!integration) {
|
|
24
|
+
throw new Error(`No integration found for url: ${url}`);
|
|
25
|
+
}
|
|
26
|
+
const key = integration.config.host;
|
|
27
|
+
if (this.#octokitCache.has(key)) {
|
|
28
|
+
return this.#octokitCache.get(key);
|
|
29
|
+
}
|
|
30
|
+
const { createCallbackAuth } = await import('@octokit/auth-callback');
|
|
31
|
+
const octokit$1 = new octokit.Octokit({
|
|
32
|
+
baseUrl: integration.config.apiBaseUrl,
|
|
33
|
+
authStrategy: createCallbackAuth,
|
|
34
|
+
auth: {
|
|
35
|
+
callback: async () => {
|
|
36
|
+
try {
|
|
37
|
+
const credentials = await this.#githubCredentials.getCredentials({
|
|
38
|
+
url
|
|
39
|
+
});
|
|
40
|
+
return credentials.token;
|
|
41
|
+
} catch {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
this.#octokitCache.set(key, octokit$1);
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
this.#octokitCache.delete(key);
|
|
50
|
+
}, types.durationToMilliseconds(this.#octokitCacheTtl));
|
|
51
|
+
return octokit$1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const octokitProviderServiceRef = backendPluginApi.createServiceRef({
|
|
55
|
+
id: "octokitProvider",
|
|
56
|
+
scope: "root",
|
|
57
|
+
defaultFactory: async (service) => backendPluginApi.createServiceFactory({
|
|
58
|
+
service,
|
|
59
|
+
deps: { config: backendPluginApi.coreServices.rootConfig },
|
|
60
|
+
async factory({ config }) {
|
|
61
|
+
return new OctokitProviderImpl(config);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
exports.octokitProviderServiceRef = octokitProviderServiceRef;
|
|
67
|
+
//# sourceMappingURL=octokitProviderService.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"octokitProviderService.cjs.js","sources":["../../src/util/octokitProviderService.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 createServiceFactory,\n createServiceRef,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport {\n DefaultGithubCredentialsProvider,\n GithubCredentialsProvider,\n ScmIntegrationRegistry,\n ScmIntegrations,\n} from '@backstage/integration';\nimport { durationToMilliseconds, HumanDuration } from '@backstage/types';\nimport { Octokit } from 'octokit';\n\nexport interface OctokitProviderService {\n getOctokit: (url: string) => Promise<Octokit>;\n}\n\nclass OctokitProviderImpl implements OctokitProviderService {\n readonly #integrations: ScmIntegrationRegistry;\n readonly #githubCredentials: GithubCredentialsProvider;\n readonly #octokitCache: Map<string, Octokit>;\n readonly #octokitCacheTtl: HumanDuration;\n\n constructor(config: RootConfigService) {\n this.#integrations = ScmIntegrations.fromConfig(config);\n this.#githubCredentials = DefaultGithubCredentialsProvider.fromIntegrations(\n this.#integrations,\n );\n this.#octokitCache = new Map();\n this.#octokitCacheTtl = { hours: 1 };\n }\n\n async getOctokit(url: string): Promise<Octokit> {\n // TODO(freben): Be smart and cache these more granularly, e.g. by\n // organization or even repo.\n const integration = this.#integrations.github.byUrl(url);\n if (!integration) {\n throw new Error(`No integration found for url: ${url}`);\n }\n const key = integration.config.host;\n\n if (this.#octokitCache.has(key)) {\n return this.#octokitCache.get(key)!;\n }\n\n const { createCallbackAuth } = await import('@octokit/auth-callback');\n\n const octokit = new Octokit({\n baseUrl: integration.config.apiBaseUrl,\n authStrategy: createCallbackAuth,\n auth: {\n callback: async () => {\n try {\n const credentials = await this.#githubCredentials.getCredentials({\n url,\n });\n return credentials.token;\n } catch {\n return undefined;\n }\n },\n },\n });\n\n this.#octokitCache.set(key, octokit);\n setTimeout(() => {\n this.#octokitCache.delete(key);\n }, durationToMilliseconds(this.#octokitCacheTtl));\n\n return octokit;\n }\n}\n\n/**\n * This will have to live here, until we have a proper shared one in an\n * integrations layer.\n */\nexport const octokitProviderServiceRef =\n createServiceRef<OctokitProviderService>({\n id: 'octokitProvider',\n scope: 'root',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: { config: coreServices.rootConfig },\n async factory({ config }) {\n return new OctokitProviderImpl(config);\n },\n }),\n });\n"],"names":["ScmIntegrations","DefaultGithubCredentialsProvider","octokit","Octokit","durationToMilliseconds","createServiceRef","createServiceFactory","coreServices"],"mappings":";;;;;;;AAmCA,MAAM,mBAAA,CAAsD;AAAA,EACjD,aAAA;AAAA,EACA,kBAAA;AAAA,EACA,aAAA;AAAA,EACA,gBAAA;AAAA,EAET,YAAY,MAAA,EAA2B;AACrC,IAAA,IAAA,CAAK,aAAA,GAAgBA,2BAAA,CAAgB,UAAA,CAAW,MAAM,CAAA;AACtD,IAAA,IAAA,CAAK,qBAAqBC,4CAAA,CAAiC,gBAAA;AAAA,MACzD,IAAA,CAAK;AAAA,KACP;AACA,IAAA,IAAA,CAAK,aAAA,uBAAoB,GAAA,EAAI;AAC7B,IAAA,IAAA,CAAK,gBAAA,GAAmB,EAAE,KAAA,EAAO,CAAA,EAAE;AAAA,EACrC;AAAA,EAEA,MAAM,WAAW,GAAA,EAA+B;AAG9C,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,aAAA,CAAc,MAAA,CAAO,MAAM,GAAG,CAAA;AACvD,IAAA,IAAI,CAAC,WAAA,EAAa;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,GAAG,CAAA,CAAE,CAAA;AAAA,IACxD;AACA,IAAA,MAAM,GAAA,GAAM,YAAY,MAAA,CAAO,IAAA;AAE/B,IAAA,IAAI,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA,EAAG;AAC/B,MAAA,OAAO,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA;AAAA,IACnC;AAEA,IAAA,MAAM,EAAE,kBAAA,EAAmB,GAAI,MAAM,OAAO,wBAAwB,CAAA;AAEpE,IAAA,MAAMC,SAAA,GAAU,IAAIC,eAAA,CAAQ;AAAA,MAC1B,OAAA,EAAS,YAAY,MAAA,CAAO,UAAA;AAAA,MAC5B,YAAA,EAAc,kBAAA;AAAA,MACd,IAAA,EAAM;AAAA,QACJ,UAAU,YAAY;AACpB,UAAA,IAAI;AACF,YAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,kBAAA,CAAmB,cAAA,CAAe;AAAA,cAC/D;AAAA,aACD,CAAA;AACD,YAAA,OAAO,WAAA,CAAY,KAAA;AAAA,UACrB,CAAA,CAAA,MAAQ;AACN,YAAA,OAAO,MAAA;AAAA,UACT;AAAA,QACF;AAAA;AACF,KACD,CAAA;AAED,IAAA,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,GAAA,EAAKD,SAAO,CAAA;AACnC,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,IAAA,CAAK,aAAA,CAAc,OAAO,GAAG,CAAA;AAAA,IAC/B,CAAA,EAAGE,4BAAA,CAAuB,IAAA,CAAK,gBAAgB,CAAC,CAAA;AAEhD,IAAA,OAAOF,SAAA;AAAA,EACT;AACF;AAMO,MAAM,4BACXG,iCAAA,CAAyC;AAAA,EACvC,EAAA,EAAI,iBAAA;AAAA,EACJ,KAAA,EAAO,MAAA;AAAA,EACP,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAA,CAAqB;AAAA,IACnB,OAAA;AAAA,IACA,IAAA,EAAM,EAAE,MAAA,EAAQC,6BAAA,CAAa,UAAA,EAAW;AAAA,IACxC,MAAM,OAAA,CAAQ,EAAE,MAAA,EAAO,EAAG;AACxB,MAAA,OAAO,IAAI,oBAAoB,MAAM,CAAA;AAAA,IACvC;AAAA,GACD;AACL,CAAC;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-catalog-backend-module-github",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3-next.0",
|
|
4
4
|
"description": "A Backstage catalog backend module that helps integrate towards GitHub",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin-module",
|
|
@@ -65,25 +65,35 @@
|
|
|
65
65
|
"test": "backstage-cli package test"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@backstage/backend-plugin-api": "1.7.
|
|
68
|
+
"@backstage/backend-plugin-api": "1.7.1-next.0",
|
|
69
69
|
"@backstage/catalog-model": "1.7.6",
|
|
70
70
|
"@backstage/config": "1.3.6",
|
|
71
|
-
"@backstage/
|
|
72
|
-
"@backstage/
|
|
73
|
-
"@backstage/plugin-catalog-
|
|
74
|
-
"@backstage/plugin-
|
|
71
|
+
"@backstage/errors": "1.2.7",
|
|
72
|
+
"@backstage/integration": "1.21.0-next.0",
|
|
73
|
+
"@backstage/plugin-catalog-common": "1.1.8",
|
|
74
|
+
"@backstage/plugin-catalog-node": "2.1.0-next.0",
|
|
75
|
+
"@backstage/plugin-events-node": "0.4.20-next.0",
|
|
76
|
+
"@backstage/types": "1.2.2",
|
|
77
|
+
"@octokit/auth-callback": "^5.0.0",
|
|
75
78
|
"@octokit/core": "^5.2.0",
|
|
76
79
|
"@octokit/graphql": "^7.0.2",
|
|
77
80
|
"@octokit/plugin-throttling": "^8.1.3",
|
|
78
81
|
"@octokit/rest": "^19.0.3",
|
|
82
|
+
"@octokit/webhooks-types": "^7.6.1",
|
|
79
83
|
"git-url-parse": "^15.0.0",
|
|
80
84
|
"lodash": "^4.17.21",
|
|
81
|
-
"minimatch": "^
|
|
85
|
+
"minimatch": "^10.2.1",
|
|
86
|
+
"octokit": "^3.0.0",
|
|
82
87
|
"uuid": "^11.0.0"
|
|
83
88
|
},
|
|
84
89
|
"devDependencies": {
|
|
85
|
-
"@backstage/backend-
|
|
86
|
-
"@backstage/
|
|
90
|
+
"@backstage/backend-defaults": "0.15.3-next.0",
|
|
91
|
+
"@backstage/backend-test-utils": "1.11.1-next.0",
|
|
92
|
+
"@backstage/cli": "0.35.5-next.0",
|
|
93
|
+
"@backstage/plugin-catalog-backend": "3.5.0-next.0",
|
|
94
|
+
"@backstage/plugin-events-backend": "0.5.12-next.0",
|
|
95
|
+
"@backstage/plugin-events-backend-module-github": "0.4.10-next.0",
|
|
96
|
+
"@backstage/plugin-events-backend-module-google-pubsub": "0.2.1-next.0",
|
|
87
97
|
"@types/lodash": "^4.14.151",
|
|
88
98
|
"msw": "^2.0.0",
|
|
89
99
|
"type-fest": "^4.41.0"
|