@backstage/plugin-catalog-backend-module-azure 0.3.16-next.0 → 0.3.16-next.2
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 +23 -0
- package/dist/alpha.cjs.js +2 -0
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/alpha.d.ts +55 -1
- package/dist/events/AzureDevOpsScmEventsBridge.cjs.js +81 -0
- package/dist/events/AzureDevOpsScmEventsBridge.cjs.js.map +1 -0
- package/dist/events/analyzeAzureDevOpsWebhookEvent.cjs.js +311 -0
- package/dist/events/analyzeAzureDevOpsWebhookEvent.cjs.js.map +1 -0
- package/dist/module/catalogModuleAzureDevOpsEntityProvider.cjs.js +29 -2
- package/dist/module/catalogModuleAzureDevOpsEntityProvider.cjs.js.map +1 -1
- package/package.json +10 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-backend-module-azure
|
|
2
2
|
|
|
3
|
+
## 0.3.16-next.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @backstage/errors@1.3.0-next.0
|
|
9
|
+
- @backstage/plugin-catalog-node@2.2.0-next.2
|
|
10
|
+
- @backstage/integration@2.0.1-next.0
|
|
11
|
+
- @backstage/backend-plugin-api@1.9.0-next.2
|
|
12
|
+
- @backstage/config@1.3.7-next.0
|
|
13
|
+
- @backstage/plugin-events-node@0.4.21-next.2
|
|
14
|
+
- @backstage/plugin-catalog-common@1.1.9-next.0
|
|
15
|
+
|
|
16
|
+
## 0.3.16-next.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 39d27ee: Add Azure DevOps SCM event translation layer for instant catalog reprocessing.
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- @backstage/backend-plugin-api@1.9.0-next.1
|
|
23
|
+
- @backstage/plugin-catalog-node@2.1.1-next.1
|
|
24
|
+
- @backstage/plugin-events-node@0.4.21-next.1
|
|
25
|
+
|
|
3
26
|
## 0.3.16-next.0
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/alpha.cjs.js
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var catalogModuleAzureDevOpsEntityProvider = require('./module/catalogModuleAzureDevOpsEntityProvider.cjs.js');
|
|
6
|
+
var analyzeAzureDevOpsWebhookEvent = require('./events/analyzeAzureDevOpsWebhookEvent.cjs.js');
|
|
6
7
|
|
|
7
8
|
const _feature = catalogModuleAzureDevOpsEntityProvider.catalogModuleAzureEntityProvider;
|
|
8
9
|
|
|
10
|
+
exports.analyzeAzureDevOpsWebhookEvent = analyzeAzureDevOpsWebhookEvent.analyzeAzureDevOpsWebhookEvent;
|
|
9
11
|
exports.default = _feature;
|
|
10
12
|
//# sourceMappingURL=alpha.cjs.js.map
|
package/dist/alpha.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alpha.cjs.js","sources":["../src/alpha.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { default as feature } from './module';\n\n/** @alpha */\nconst _feature = feature;\nexport default _feature;\n"],"names":["feature"],"mappings":"
|
|
1
|
+
{"version":3,"file":"alpha.cjs.js","sources":["../src/alpha.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { default as feature } from './module';\n\n/** @alpha */\nconst _feature = feature;\nexport default _feature;\n\nexport {\n analyzeAzureDevOpsWebhookEvent,\n type AnalyzeAzureDevOpsWebhookEventOptions,\n type AnalyzeAzureDevOpsWebhookEventResult,\n} from './events/analyzeAzureDevOpsWebhookEvent';\n"],"names":["feature"],"mappings":";;;;;;;AAmBA,MAAM,QAAA,GAAWA;;;;;"}
|
package/dist/alpha.d.ts
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link analyzeAzureDevOpsWebhookEvent}.
|
|
6
|
+
* @alpha
|
|
7
|
+
*/
|
|
8
|
+
interface AnalyzeAzureDevOpsWebhookEventOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Predicate that returns true for file paths that are relevant to the
|
|
11
|
+
* catalog (e.g. paths ending in `.yaml` or `.yml`).
|
|
12
|
+
*/
|
|
13
|
+
isRelevantPath: (path: string) => boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The result of analyzing an Azure DevOps webhook event.
|
|
17
|
+
*
|
|
18
|
+
* - `ok` — one or more catalog SCM events were produced.
|
|
19
|
+
* - `ignored` — the event was valid but not relevant.
|
|
20
|
+
* - `aborted` — the event could not be fully processed due to missing data.
|
|
21
|
+
* - `unsupported-event` — the event type is not handled by this analyzer.
|
|
22
|
+
*
|
|
23
|
+
* @alpha
|
|
24
|
+
*/
|
|
25
|
+
type AnalyzeAzureDevOpsWebhookEventResult = {
|
|
26
|
+
result: 'unsupported-event';
|
|
27
|
+
event: string;
|
|
28
|
+
} | {
|
|
29
|
+
result: 'ignored';
|
|
30
|
+
reason: string;
|
|
31
|
+
} | {
|
|
32
|
+
result: 'aborted';
|
|
33
|
+
reason: string;
|
|
34
|
+
} | {
|
|
35
|
+
result: 'ok';
|
|
36
|
+
events: CatalogScmEvent[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Analyzes an Azure DevOps webhook event and translates it into zero or more
|
|
40
|
+
* catalog SCM events that entity providers can act on.
|
|
41
|
+
*
|
|
42
|
+
* Supported event types:
|
|
43
|
+
* - `git.push` — translates file-level adds, modifications, and deletions on
|
|
44
|
+
* the default branch into catalog SCM events for paths matching
|
|
45
|
+
* `isRelevantPath`.
|
|
46
|
+
* - `git.repo.created` — emits a `repository.created` event.
|
|
47
|
+
* - `git.repo.deleted` — emits a `repository.deleted` event.
|
|
48
|
+
* - `git.repo.statuschanged` — emits a `repository.updated` event.
|
|
49
|
+
* - `git.repo.renamed` — emits a `repository.moved` event with the old and
|
|
50
|
+
* new repository URLs.
|
|
51
|
+
*
|
|
52
|
+
* @alpha
|
|
53
|
+
*/
|
|
54
|
+
declare function analyzeAzureDevOpsWebhookEvent(eventType: string, eventPayload: unknown, options: AnalyzeAzureDevOpsWebhookEventOptions): Promise<AnalyzeAzureDevOpsWebhookEventResult>;
|
|
2
55
|
|
|
3
56
|
/** @alpha */
|
|
4
57
|
declare const _feature: _backstage_backend_plugin_api.BackendFeature;
|
|
5
58
|
|
|
6
|
-
export { _feature as default };
|
|
59
|
+
export { analyzeAzureDevOpsWebhookEvent, _feature as default };
|
|
60
|
+
export type { AnalyzeAzureDevOpsWebhookEventOptions, AnalyzeAzureDevOpsWebhookEventResult };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var analyzeAzureDevOpsWebhookEvent = require('./analyzeAzureDevOpsWebhookEvent.cjs.js');
|
|
4
|
+
|
|
5
|
+
class AzureDevOpsScmEventsBridge {
|
|
6
|
+
#logger;
|
|
7
|
+
#events;
|
|
8
|
+
#catalogScmEvents;
|
|
9
|
+
#shuttingDown;
|
|
10
|
+
#pendingPublish;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.#logger = options.logger;
|
|
13
|
+
this.#events = options.events;
|
|
14
|
+
this.#catalogScmEvents = options.catalogScmEvents;
|
|
15
|
+
this.#shuttingDown = false;
|
|
16
|
+
}
|
|
17
|
+
async start() {
|
|
18
|
+
await this.#events.subscribe({
|
|
19
|
+
id: "catalog-azure-devops-scm-events-bridge",
|
|
20
|
+
topics: ["azureDevOps"],
|
|
21
|
+
onEvent: this.#onEvent.bind(this)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async stop() {
|
|
25
|
+
this.#shuttingDown = true;
|
|
26
|
+
await this.#pendingPublish;
|
|
27
|
+
}
|
|
28
|
+
async #onEvent(params) {
|
|
29
|
+
const eventPayload = params.eventPayload;
|
|
30
|
+
const eventType = eventPayload?.eventType;
|
|
31
|
+
if (!eventType || !eventPayload) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
while (this.#pendingPublish) {
|
|
35
|
+
await this.#pendingPublish;
|
|
36
|
+
}
|
|
37
|
+
if (this.#shuttingDown) {
|
|
38
|
+
this.#logger.warn(
|
|
39
|
+
`Skipping Azure DevOps webhook event of type "${eventType}" on topic "${params.topic}" because the bridge is shutting down`
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.#pendingPublish = Promise.resolve().then(async () => {
|
|
44
|
+
try {
|
|
45
|
+
const output = await analyzeAzureDevOpsWebhookEvent.analyzeAzureDevOpsWebhookEvent(
|
|
46
|
+
eventType,
|
|
47
|
+
eventPayload,
|
|
48
|
+
{
|
|
49
|
+
isRelevantPath: (path) => path.endsWith(".yaml") || path.endsWith(".yml")
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
if (output.result === "ok") {
|
|
53
|
+
await this.#catalogScmEvents.publish(output.events);
|
|
54
|
+
} else if (output.result === "ignored") {
|
|
55
|
+
this.#logger.debug(
|
|
56
|
+
`Skipping Azure DevOps webhook event of type "${eventType}" on topic "${params.topic}" because it is ignored: ${output.reason}`
|
|
57
|
+
);
|
|
58
|
+
} else if (output.result === "aborted") {
|
|
59
|
+
this.#logger.warn(
|
|
60
|
+
`Skipping Azure DevOps webhook event of type "${eventType}" on topic "${params.topic}" because it is aborted: ${output.reason}`
|
|
61
|
+
);
|
|
62
|
+
} else if (output.result === "unsupported-event") {
|
|
63
|
+
this.#logger.debug(
|
|
64
|
+
`Skipping Azure DevOps webhook event of type "${eventType}" on topic "${params.topic}" because it is unsupported: ${output.event}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.#logger.warn(
|
|
69
|
+
`Failed to handle Azure DevOps webhook event of type "${eventType}"`,
|
|
70
|
+
error
|
|
71
|
+
);
|
|
72
|
+
} finally {
|
|
73
|
+
this.#pendingPublish = void 0;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
await this.#pendingPublish;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
exports.AzureDevOpsScmEventsBridge = AzureDevOpsScmEventsBridge;
|
|
81
|
+
//# sourceMappingURL=AzureDevOpsScmEventsBridge.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AzureDevOpsScmEventsBridge.cjs.js","sources":["../../src/events/AzureDevOpsScmEventsBridge.ts"],"sourcesContent":["/*\n * Copyright 2026 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 { analyzeAzureDevOpsWebhookEvent } from './analyzeAzureDevOpsWebhookEvent';\n\n/**\n * Takes Azure DevOps webhook events, analyzes them, and publishes them as\n * catalog SCM events that entity providers and others can subscribe to.\n */\nexport class AzureDevOpsScmEventsBridge {\n readonly #logger: LoggerService;\n readonly #events: EventsService;\n readonly #catalogScmEvents: CatalogScmEventsService;\n #shuttingDown: boolean;\n #pendingPublish: Promise<void> | undefined;\n\n constructor(options: {\n logger: LoggerService;\n events: EventsService;\n catalogScmEvents: CatalogScmEventsService;\n }) {\n this.#logger = options.logger;\n this.#events = options.events;\n this.#catalogScmEvents = options.catalogScmEvents;\n this.#shuttingDown = false;\n }\n\n async start() {\n await this.#events.subscribe({\n id: 'catalog-azure-devops-scm-events-bridge',\n topics: ['azureDevOps'],\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 eventPayload = params.eventPayload as\n | { eventType?: string }\n | undefined;\n const eventType = eventPayload?.eventType;\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 Azure DevOps 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 analyzeAzureDevOpsWebhookEvent(\n eventType,\n eventPayload,\n {\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 Azure DevOps 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 Azure DevOps 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 Azure DevOps 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 Azure DevOps 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":["analyzeAzureDevOpsWebhookEvent"],"mappings":";;;;AAyBO,MAAM,0BAAA,CAA2B;AAAA,EAC7B,OAAA;AAAA,EACA,OAAA;AAAA,EACA,iBAAA;AAAA,EACT,aAAA;AAAA,EACA,eAAA;AAAA,EAEA,YAAY,OAAA,EAIT;AACD,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,MAAA;AACvB,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,MAAA;AACvB,IAAA,IAAA,CAAK,oBAAoB,OAAA,CAAQ,gBAAA;AACjC,IAAA,IAAA,CAAK,aAAA,GAAgB,KAAA;AAAA,EACvB;AAAA,EAEA,MAAM,KAAA,GAAQ;AACZ,IAAA,MAAM,IAAA,CAAK,QAAQ,SAAA,CAAU;AAAA,MAC3B,EAAA,EAAI,wCAAA;AAAA,MACJ,MAAA,EAAQ,CAAC,aAAa,CAAA;AAAA,MACtB,OAAA,EAAS,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,IAAI;AAAA,KACjC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,IAAA,GAAO;AACX,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA;AACrB,IAAA,MAAM,IAAA,CAAK,eAAA;AAAA,EACb;AAAA,EAEA,MAAM,SAAS,MAAA,EAAoC;AACjD,IAAA,MAAM,eAAe,MAAA,CAAO,YAAA;AAG5B,IAAA,MAAM,YAAY,YAAA,EAAc,SAAA;AAChC,IAAA,IAAI,CAAC,SAAA,IAAa,CAAC,YAAA,EAAc;AAC/B,MAAA;AAAA,IACF;AAEA,IAAA,OAAO,KAAK,eAAA,EAAiB;AAC3B,MAAA,MAAM,IAAA,CAAK,eAAA;AAAA,IACb;AAEA,IAAA,IAAI,KAAK,aAAA,EAAe;AACtB,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA;AAAA,QACX,CAAA,6CAAA,EAAgD,SAAS,CAAA,YAAA,EAAe,MAAA,CAAO,KAAK,CAAA,qCAAA;AAAA,OACtF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,eAAA,GAAkB,OAAA,CAAQ,OAAA,EAAQ,CAAE,KAAK,YAAY;AACxD,MAAA,IAAI;AACF,QAAA,MAAM,SAAS,MAAMA,6DAAA;AAAA,UACnB,SAAA;AAAA,UACA,YAAA;AAAA,UACA;AAAA,YACE,cAAA,EAAgB,UACd,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,IAAK,IAAA,CAAK,SAAS,MAAM;AAAA;AAClD,SACF;AAEA,QAAA,IAAI,MAAA,CAAO,WAAW,IAAA,EAAM;AAC1B,UAAA,MAAM,IAAA,CAAK,iBAAA,CAAkB,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA;AAAA,QACpD,CAAA,MAAA,IAAW,MAAA,CAAO,MAAA,KAAW,SAAA,EAAW;AACtC,UAAA,IAAA,CAAK,OAAA,CAAQ,KAAA;AAAA,YACX,gDAAgD,SAAS,CAAA,YAAA,EAAe,OAAO,KAAK,CAAA,yBAAA,EAA4B,OAAO,MAAM,CAAA;AAAA,WAC/H;AAAA,QACF,CAAA,MAAA,IAAW,MAAA,CAAO,MAAA,KAAW,SAAA,EAAW;AACtC,UAAA,IAAA,CAAK,OAAA,CAAQ,IAAA;AAAA,YACX,gDAAgD,SAAS,CAAA,YAAA,EAAe,OAAO,KAAK,CAAA,yBAAA,EAA4B,OAAO,MAAM,CAAA;AAAA,WAC/H;AAAA,QACF,CAAA,MAAA,IAAW,MAAA,CAAO,MAAA,KAAW,mBAAA,EAAqB;AAChD,UAAA,IAAA,CAAK,OAAA,CAAQ,KAAA;AAAA,YACX,gDAAgD,SAAS,CAAA,YAAA,EAAe,OAAO,KAAK,CAAA,6BAAA,EAAgC,OAAO,KAAK,CAAA;AAAA,WAClI;AAAA,QACF;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,OAAA,CAAQ,IAAA;AAAA,UACX,wDAAwD,SAAS,CAAA,CAAA,CAAA;AAAA,UACjE;AAAA,SACF;AAAA,MACF,CAAA,SAAE;AACA,QAAA,IAAA,CAAK,eAAA,GAAkB,MAAA;AAAA,MACzB;AAAA,IACF,CAAC,CAAA;AAED,IAAA,MAAM,IAAA,CAAK,eAAA;AAAA,EACb;AACF;;;;"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
|
|
5
|
+
function asObject(value) {
|
|
6
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function asString(value) {
|
|
12
|
+
return typeof value === "string" ? value : void 0;
|
|
13
|
+
}
|
|
14
|
+
function normalizePath(path) {
|
|
15
|
+
if (!path) {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
19
|
+
}
|
|
20
|
+
function getRepository(resource) {
|
|
21
|
+
const repository = asObject(resource?.repository);
|
|
22
|
+
return {
|
|
23
|
+
name: asString(repository?.name),
|
|
24
|
+
defaultBranch: asString(repository?.defaultBranch),
|
|
25
|
+
remoteUrl: asString(repository?.remoteUrl)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function toLocationUrl(options) {
|
|
29
|
+
if (!options.remoteUrl) {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
const fullUrl = `${options.remoteUrl}?path=${options.path}`;
|
|
33
|
+
return encodeURI(fullUrl);
|
|
34
|
+
}
|
|
35
|
+
function toCommitUrl(repository, commit) {
|
|
36
|
+
if (commit.url) {
|
|
37
|
+
return commit.url;
|
|
38
|
+
}
|
|
39
|
+
if (repository.remoteUrl && commit.commitId) {
|
|
40
|
+
return `${repository.remoteUrl}/commit/${commit.commitId}`;
|
|
41
|
+
}
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
function toCatalogScmEventForPathState(options) {
|
|
45
|
+
const { repository, path, pathState, isRelevantPath } = options;
|
|
46
|
+
const commitUrl = toCommitUrl(repository, pathState.commit);
|
|
47
|
+
const context = commitUrl ? { commitUrl } : void 0;
|
|
48
|
+
if (pathState.type === "renamed") {
|
|
49
|
+
const fromRelevant = isRelevantPath(pathState.fromPath);
|
|
50
|
+
const toRelevant = isRelevantPath(path);
|
|
51
|
+
const fromUrl = toLocationUrl({
|
|
52
|
+
remoteUrl: repository.remoteUrl,
|
|
53
|
+
path: pathState.fromPath
|
|
54
|
+
});
|
|
55
|
+
const toUrl = toLocationUrl({
|
|
56
|
+
remoteUrl: repository.remoteUrl,
|
|
57
|
+
path
|
|
58
|
+
});
|
|
59
|
+
if (fromRelevant && toRelevant && fromUrl && toUrl) {
|
|
60
|
+
return [{ type: "location.moved", fromUrl, toUrl, context }];
|
|
61
|
+
}
|
|
62
|
+
if (fromRelevant && !toRelevant && fromUrl) {
|
|
63
|
+
return [{ type: "location.deleted", url: fromUrl, context }];
|
|
64
|
+
}
|
|
65
|
+
if (!fromRelevant && toRelevant && toUrl) {
|
|
66
|
+
return [{ type: "location.created", url: toUrl, context }];
|
|
67
|
+
}
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
if (!isRelevantPath(path)) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const url = toLocationUrl({
|
|
74
|
+
remoteUrl: repository.remoteUrl,
|
|
75
|
+
path
|
|
76
|
+
});
|
|
77
|
+
if (!url) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
if (pathState.type === "added") {
|
|
81
|
+
return [{ type: "location.created", url, context }];
|
|
82
|
+
}
|
|
83
|
+
if (pathState.type === "removed") {
|
|
84
|
+
return [{ type: "location.deleted", url, context }];
|
|
85
|
+
}
|
|
86
|
+
return [{ type: "location.updated", url, context }];
|
|
87
|
+
}
|
|
88
|
+
function normalizePushCommitChanges(commit) {
|
|
89
|
+
const normalized = [];
|
|
90
|
+
for (const path of commit.added ?? []) {
|
|
91
|
+
const normalizedPath = normalizePath(path);
|
|
92
|
+
if (normalizedPath) {
|
|
93
|
+
normalized.push({ type: "added", path: normalizedPath });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const path of commit.removed ?? []) {
|
|
97
|
+
const normalizedPath = normalizePath(path);
|
|
98
|
+
if (normalizedPath) {
|
|
99
|
+
normalized.push({ type: "removed", path: normalizedPath });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const path of commit.modified ?? []) {
|
|
103
|
+
const normalizedPath = normalizePath(path);
|
|
104
|
+
if (normalizedPath) {
|
|
105
|
+
normalized.push({ type: "changed", path: normalizedPath });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const change of commit.changes ?? []) {
|
|
109
|
+
const changeType = change.changeType?.toLowerCase() ?? "";
|
|
110
|
+
const toPath = normalizePath(
|
|
111
|
+
change.item?.path ?? change.path ?? change.newPath
|
|
112
|
+
);
|
|
113
|
+
const fromPath = normalizePath(
|
|
114
|
+
change.originalPath ?? change.item?.originalPath ?? change.oldPath ?? change.sourceServerItem
|
|
115
|
+
);
|
|
116
|
+
if (changeType.includes("rename") && fromPath && toPath) {
|
|
117
|
+
normalized.push({ type: "renamed", fromPath, toPath });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (changeType.includes("add") && toPath) {
|
|
121
|
+
normalized.push({ type: "added", path: toPath });
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (changeType.includes("delete") && (toPath ?? fromPath)) {
|
|
125
|
+
normalized.push({ type: "removed", path: toPath ?? fromPath });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if ((changeType.includes("edit") || changeType.includes("modify") || changeType.includes("update")) && (toPath ?? fromPath)) {
|
|
129
|
+
normalized.push({ type: "changed", path: toPath ?? fromPath });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return normalized;
|
|
133
|
+
}
|
|
134
|
+
function applyPushChange(state, change, commit) {
|
|
135
|
+
if (change.type === "renamed") {
|
|
136
|
+
const previous2 = state.get(change.fromPath);
|
|
137
|
+
state.delete(change.fromPath);
|
|
138
|
+
let next;
|
|
139
|
+
if (!previous2) {
|
|
140
|
+
next = { type: "renamed", fromPath: change.fromPath, commit };
|
|
141
|
+
} else if (previous2.type === "added") {
|
|
142
|
+
next = { type: "added", commit };
|
|
143
|
+
} else if (previous2.type === "changed") {
|
|
144
|
+
next = { type: "renamed", fromPath: change.fromPath, commit };
|
|
145
|
+
} else if (previous2.type === "renamed") {
|
|
146
|
+
next = { type: "renamed", fromPath: previous2.fromPath, commit };
|
|
147
|
+
}
|
|
148
|
+
if (next) {
|
|
149
|
+
state.set(change.toPath, next);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const previous = state.get(change.path);
|
|
154
|
+
if (change.type === "added") {
|
|
155
|
+
if (!previous) {
|
|
156
|
+
state.set(change.path, { type: "added", commit });
|
|
157
|
+
} else if (previous.type === "removed") {
|
|
158
|
+
state.set(change.path, { type: "changed", commit });
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (change.type === "removed") {
|
|
163
|
+
if (!previous) {
|
|
164
|
+
state.set(change.path, { type: "removed", commit });
|
|
165
|
+
} else if (previous.type === "added") {
|
|
166
|
+
state.delete(change.path);
|
|
167
|
+
} else if (previous.type === "changed") {
|
|
168
|
+
state.set(change.path, { type: "removed", commit });
|
|
169
|
+
} else if (previous.type === "renamed") {
|
|
170
|
+
state.delete(change.path);
|
|
171
|
+
state.set(previous.fromPath, { type: "removed", commit });
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!previous) {
|
|
176
|
+
state.set(change.path, { type: "changed", commit });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function replaceRepoNameInRemoteUrl(remoteUrl, repoName) {
|
|
180
|
+
if (!remoteUrl || !repoName) {
|
|
181
|
+
return void 0;
|
|
182
|
+
}
|
|
183
|
+
const gitMarker = "/_git/";
|
|
184
|
+
const gitIdx = remoteUrl.indexOf(gitMarker);
|
|
185
|
+
if (gitIdx === -1) {
|
|
186
|
+
return void 0;
|
|
187
|
+
}
|
|
188
|
+
const prefix = remoteUrl.slice(0, gitIdx + gitMarker.length);
|
|
189
|
+
const rest = remoteUrl.slice(gitIdx + gitMarker.length);
|
|
190
|
+
const endIdx = rest.search(/[/?#]/);
|
|
191
|
+
const suffix = endIdx === -1 ? "" : rest.slice(endIdx);
|
|
192
|
+
return `${prefix}${repoName}${suffix}`;
|
|
193
|
+
}
|
|
194
|
+
async function onPushEvent(eventPayload, options) {
|
|
195
|
+
const resource = asObject(eventPayload.resource);
|
|
196
|
+
const repository = getRepository(resource);
|
|
197
|
+
const refUpdates = resource?.refUpdates ?? [];
|
|
198
|
+
const commits = resource?.commits ?? [];
|
|
199
|
+
const contextUrl = asString(resource?.url) ?? repository.remoteUrl ?? "<unknown>";
|
|
200
|
+
if (commits.length === 0) {
|
|
201
|
+
return {
|
|
202
|
+
result: "ignored",
|
|
203
|
+
reason: `Azure DevOps push event does not contain commits: ${contextUrl}`
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (repository.defaultBranch) {
|
|
207
|
+
const updatesToDefaultBranch = refUpdates.filter(
|
|
208
|
+
(update) => update.name === repository.defaultBranch
|
|
209
|
+
);
|
|
210
|
+
if (updatesToDefaultBranch.length === 0) {
|
|
211
|
+
return {
|
|
212
|
+
result: "ignored",
|
|
213
|
+
reason: `Azure DevOps push event did not target the default branch, expected "${repository.defaultBranch}": ${contextUrl}`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const state = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const commit of commits) {
|
|
219
|
+
const changes = normalizePushCommitChanges(commit);
|
|
220
|
+
for (const change of changes) {
|
|
221
|
+
applyPushChange(state, change, commit);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (state.size === 0) {
|
|
225
|
+
return {
|
|
226
|
+
result: "ignored",
|
|
227
|
+
reason: `Azure DevOps push event did not affect any relevant paths: ${contextUrl}`
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const events = Array.from(state.entries()).flatMap(
|
|
231
|
+
([path, pathState]) => toCatalogScmEventForPathState({
|
|
232
|
+
repository,
|
|
233
|
+
path,
|
|
234
|
+
pathState,
|
|
235
|
+
isRelevantPath: options.isRelevantPath
|
|
236
|
+
})
|
|
237
|
+
);
|
|
238
|
+
if (events.length === 0) {
|
|
239
|
+
return {
|
|
240
|
+
result: "ignored",
|
|
241
|
+
reason: `Azure DevOps push event did not affect any relevant paths: ${contextUrl}`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return { result: "ok", events };
|
|
245
|
+
}
|
|
246
|
+
async function onRepositoryEvent(eventType, eventPayload) {
|
|
247
|
+
const resource = asObject(eventPayload.resource);
|
|
248
|
+
const repository = getRepository(resource);
|
|
249
|
+
const toUrl = repository.remoteUrl;
|
|
250
|
+
if (eventType === "git.repo.created" && toUrl) {
|
|
251
|
+
return {
|
|
252
|
+
result: "ok",
|
|
253
|
+
events: [{ type: "repository.created", url: toUrl }]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (eventType === "git.repo.deleted" && toUrl) {
|
|
257
|
+
return {
|
|
258
|
+
result: "ok",
|
|
259
|
+
events: [{ type: "repository.deleted", url: toUrl }]
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (eventType === "git.repo.statuschanged" && toUrl) {
|
|
263
|
+
return {
|
|
264
|
+
result: "ok",
|
|
265
|
+
events: [{ type: "repository.updated", url: toUrl }]
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (eventType === "git.repo.renamed" && toUrl) {
|
|
269
|
+
const oldName = asString(resource?.oldName);
|
|
270
|
+
const fromUrl = oldName ? replaceRepoNameInRemoteUrl(toUrl, oldName) : void 0;
|
|
271
|
+
if (!fromUrl) {
|
|
272
|
+
return {
|
|
273
|
+
result: "ignored",
|
|
274
|
+
reason: oldName ? "Azure DevOps repository renamed event has an unexpected repository.remoteUrl format" : "Azure DevOps repository renamed event is missing oldName"
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
result: "ok",
|
|
279
|
+
events: [{ type: "repository.moved", fromUrl, toUrl }]
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (eventType.startsWith("git.repo.")) {
|
|
283
|
+
return {
|
|
284
|
+
result: "unsupported-event",
|
|
285
|
+
event: eventType
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
result: "unsupported-event",
|
|
290
|
+
event: eventType
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async function analyzeAzureDevOpsWebhookEvent(eventType, eventPayload, options) {
|
|
294
|
+
const payload = asObject(eventPayload);
|
|
295
|
+
if (!payload) {
|
|
296
|
+
throw new errors.InputError("Azure DevOps webhook event payload is not an object");
|
|
297
|
+
}
|
|
298
|
+
if (eventType === "git.push") {
|
|
299
|
+
return await onPushEvent(payload, options);
|
|
300
|
+
}
|
|
301
|
+
if (eventType.startsWith("git.repo.")) {
|
|
302
|
+
return await onRepositoryEvent(eventType, payload);
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
result: "unsupported-event",
|
|
306
|
+
event: eventType
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
exports.analyzeAzureDevOpsWebhookEvent = analyzeAzureDevOpsWebhookEvent;
|
|
311
|
+
//# sourceMappingURL=analyzeAzureDevOpsWebhookEvent.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzeAzureDevOpsWebhookEvent.cjs.js","sources":["../../src/events/analyzeAzureDevOpsWebhookEvent.ts"],"sourcesContent":["/*\n * Copyright 2026 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 { InputError } from '@backstage/errors';\nimport { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha';\n\n/**\n * Options for {@link analyzeAzureDevOpsWebhookEvent}.\n * @alpha\n */\nexport interface AnalyzeAzureDevOpsWebhookEventOptions {\n /**\n * Predicate that returns true for file paths that are relevant to the\n * catalog (e.g. paths ending in `.yaml` or `.yml`).\n */\n isRelevantPath: (path: string) => boolean;\n}\n\n/**\n * The result of analyzing an Azure DevOps webhook event.\n *\n * - `ok` — one or more catalog SCM events were produced.\n * - `ignored` — the event was valid but not relevant.\n * - `aborted` — the event could not be fully processed due to missing data.\n * - `unsupported-event` — the event type is not handled by this analyzer.\n *\n * @alpha\n */\nexport type AnalyzeAzureDevOpsWebhookEventResult =\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\ntype JsonObject = Record<string, unknown>;\n\ntype AzureRepository = {\n name?: string;\n defaultBranch?: string;\n remoteUrl?: string;\n};\n\ntype AzurePushRefUpdate = {\n name?: string;\n};\n\ntype AzurePushCommit = {\n commitId?: string;\n url?: string;\n changes?: AzurePushCommitChange[];\n added?: string[];\n removed?: string[];\n modified?: string[];\n};\n\ntype AzurePushCommitChange = {\n changeType?: string;\n item?: {\n path?: string;\n originalPath?: string;\n };\n path?: string;\n newPath?: string;\n oldPath?: string;\n originalPath?: string;\n sourceServerItem?: string;\n};\n\ntype PushPathState =\n | {\n type: 'added';\n commit: AzurePushCommit;\n }\n | {\n type: 'removed';\n commit: AzurePushCommit;\n }\n | {\n type: 'changed';\n commit: AzurePushCommit;\n }\n | {\n type: 'renamed';\n fromPath: string;\n commit: AzurePushCommit;\n };\n\ntype NormalizedPushChange =\n | {\n type: 'added';\n path: string;\n }\n | {\n type: 'removed';\n path: string;\n }\n | {\n type: 'changed';\n path: string;\n }\n | {\n type: 'renamed';\n fromPath: string;\n toPath: string;\n };\n\nfunction asObject(value: unknown): JsonObject | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n return value as JsonObject;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction normalizePath(path: string | undefined): string | undefined {\n if (!path) {\n return undefined;\n }\n return path.startsWith('/') ? path : `/${path}`;\n}\n\nfunction getRepository(resource: JsonObject | undefined): AzureRepository {\n const repository = asObject(resource?.repository);\n return {\n name: asString(repository?.name),\n defaultBranch: asString(repository?.defaultBranch),\n remoteUrl: asString(repository?.remoteUrl),\n };\n}\n\nfunction toLocationUrl(options: {\n remoteUrl: string | undefined;\n path: string;\n}): string | undefined {\n if (!options.remoteUrl) {\n return undefined;\n }\n\n // Match the URL format produced by AzureDevOpsEntityProvider.createObjectUrl\n // which uses encodeURI on the full URL string with path and version as raw\n // query parameter values.\n //\n // The version parameter is intentionally omitted here because the entity\n // provider only includes it when the user explicitly configures a `branch`\n // in the provider config. Since we cannot know at analysis time whether a\n // branch was configured, omitting version matches the default provider\n // behavior and avoids URL mismatches that would prevent SCM events from\n // triggering catalog refreshes.\n const fullUrl = `${options.remoteUrl}?path=${options.path}`;\n return encodeURI(fullUrl);\n}\n\nfunction toCommitUrl(\n repository: AzureRepository,\n commit: AzurePushCommit,\n): string | undefined {\n if (commit.url) {\n return commit.url;\n }\n if (repository.remoteUrl && commit.commitId) {\n return `${repository.remoteUrl}/commit/${commit.commitId}`;\n }\n return undefined;\n}\n\nfunction toCatalogScmEventForPathState(options: {\n repository: AzureRepository;\n path: string;\n pathState: PushPathState;\n isRelevantPath: (path: string) => boolean;\n}): CatalogScmEvent[] {\n const { repository, path, pathState, isRelevantPath } = options;\n const commitUrl = toCommitUrl(repository, pathState.commit);\n const context = commitUrl ? { commitUrl } : undefined;\n\n if (pathState.type === 'renamed') {\n const fromRelevant = isRelevantPath(pathState.fromPath);\n const toRelevant = isRelevantPath(path);\n const fromUrl = toLocationUrl({\n remoteUrl: repository.remoteUrl,\n path: pathState.fromPath,\n });\n const toUrl = toLocationUrl({\n remoteUrl: repository.remoteUrl,\n path,\n });\n\n if (fromRelevant && toRelevant && fromUrl && toUrl) {\n return [{ type: 'location.moved', fromUrl, toUrl, context }];\n }\n if (fromRelevant && !toRelevant && fromUrl) {\n return [{ type: 'location.deleted', url: fromUrl, context }];\n }\n if (!fromRelevant && toRelevant && toUrl) {\n return [{ type: 'location.created', url: toUrl, context }];\n }\n return [];\n }\n\n if (!isRelevantPath(path)) {\n return [];\n }\n\n const url = toLocationUrl({\n remoteUrl: repository.remoteUrl,\n path,\n });\n if (!url) {\n return [];\n }\n\n if (pathState.type === 'added') {\n return [{ type: 'location.created', url, context }];\n }\n if (pathState.type === 'removed') {\n return [{ type: 'location.deleted', url, context }];\n }\n\n return [{ type: 'location.updated', url, context }];\n}\n\nfunction normalizePushCommitChanges(\n commit: AzurePushCommit,\n): NormalizedPushChange[] {\n const normalized: NormalizedPushChange[] = [];\n\n for (const path of commit.added ?? []) {\n const normalizedPath = normalizePath(path);\n if (normalizedPath) {\n normalized.push({ type: 'added', path: normalizedPath });\n }\n }\n\n for (const path of commit.removed ?? []) {\n const normalizedPath = normalizePath(path);\n if (normalizedPath) {\n normalized.push({ type: 'removed', path: normalizedPath });\n }\n }\n\n for (const path of commit.modified ?? []) {\n const normalizedPath = normalizePath(path);\n if (normalizedPath) {\n normalized.push({ type: 'changed', path: normalizedPath });\n }\n }\n\n for (const change of commit.changes ?? []) {\n const changeType = change.changeType?.toLowerCase() ?? '';\n const toPath = normalizePath(\n change.item?.path ?? change.path ?? change.newPath,\n );\n const fromPath = normalizePath(\n change.originalPath ??\n change.item?.originalPath ??\n change.oldPath ??\n change.sourceServerItem,\n );\n\n if (changeType.includes('rename') && fromPath && toPath) {\n normalized.push({ type: 'renamed', fromPath, toPath });\n continue;\n }\n\n if (changeType.includes('add') && toPath) {\n normalized.push({ type: 'added', path: toPath });\n continue;\n }\n\n if (changeType.includes('delete') && (toPath ?? fromPath)) {\n normalized.push({ type: 'removed', path: toPath ?? fromPath! });\n continue;\n }\n\n if (\n (changeType.includes('edit') ||\n changeType.includes('modify') ||\n changeType.includes('update')) &&\n (toPath ?? fromPath)\n ) {\n normalized.push({ type: 'changed', path: toPath ?? fromPath! });\n }\n }\n\n return normalized;\n}\n\nfunction applyPushChange(\n state: Map<string, PushPathState>,\n change: NormalizedPushChange,\n commit: AzurePushCommit,\n) {\n if (change.type === 'renamed') {\n const previous = state.get(change.fromPath);\n state.delete(change.fromPath);\n\n let next: PushPathState | undefined;\n if (!previous) {\n next = { type: 'renamed', fromPath: change.fromPath, commit };\n } else if (previous.type === 'added') {\n next = { type: 'added', commit };\n } else if (previous.type === 'changed') {\n next = { type: 'renamed', fromPath: change.fromPath, commit };\n } else if (previous.type === 'renamed') {\n next = { type: 'renamed', fromPath: previous.fromPath, commit };\n }\n\n if (next) {\n state.set(change.toPath, next);\n }\n return;\n }\n\n const previous = state.get(change.path);\n\n if (change.type === 'added') {\n if (!previous) {\n state.set(change.path, { type: 'added', commit });\n } else if (previous.type === 'removed') {\n state.set(change.path, { type: 'changed', commit });\n }\n return;\n }\n\n if (change.type === 'removed') {\n if (!previous) {\n state.set(change.path, { type: 'removed', commit });\n } else if (previous.type === 'added') {\n state.delete(change.path);\n } else if (previous.type === 'changed') {\n state.set(change.path, { type: 'removed', commit });\n } else if (previous.type === 'renamed') {\n state.delete(change.path);\n state.set(previous.fromPath, { type: 'removed', commit });\n }\n return;\n }\n\n if (!previous) {\n state.set(change.path, { type: 'changed', commit });\n }\n}\n\nfunction replaceRepoNameInRemoteUrl(\n remoteUrl: string | undefined,\n repoName: string | undefined,\n): string | undefined {\n if (!remoteUrl || !repoName) {\n return undefined;\n }\n const gitMarker = '/_git/';\n const gitIdx = remoteUrl.indexOf(gitMarker);\n if (gitIdx === -1) {\n return undefined;\n }\n const prefix = remoteUrl.slice(0, gitIdx + gitMarker.length);\n const rest = remoteUrl.slice(gitIdx + gitMarker.length);\n const endIdx = rest.search(/[/?#]/);\n const suffix = endIdx === -1 ? '' : rest.slice(endIdx);\n return `${prefix}${repoName}${suffix}`;\n}\n\nasync function onPushEvent(\n eventPayload: JsonObject,\n options: AnalyzeAzureDevOpsWebhookEventOptions,\n): Promise<AnalyzeAzureDevOpsWebhookEventResult> {\n const resource = asObject(eventPayload.resource);\n const repository = getRepository(resource);\n const refUpdates =\n (resource?.refUpdates as AzurePushRefUpdate[] | undefined) ?? [];\n const commits = (resource?.commits as AzurePushCommit[] | undefined) ?? [];\n const contextUrl =\n asString(resource?.url) ?? repository.remoteUrl ?? '<unknown>';\n\n if (commits.length === 0) {\n return {\n result: 'ignored',\n reason: `Azure DevOps push event does not contain commits: ${contextUrl}`,\n };\n }\n\n if (repository.defaultBranch) {\n const updatesToDefaultBranch = refUpdates.filter(\n update => update.name === repository.defaultBranch,\n );\n if (updatesToDefaultBranch.length === 0) {\n return {\n result: 'ignored',\n reason: `Azure DevOps push event did not target the default branch, expected \"${repository.defaultBranch}\": ${contextUrl}`,\n };\n }\n }\n\n const state = new Map<string, PushPathState>();\n\n for (const commit of commits) {\n const changes = normalizePushCommitChanges(commit);\n for (const change of changes) {\n applyPushChange(state, change, commit);\n }\n }\n\n if (state.size === 0) {\n return {\n result: 'ignored',\n reason: `Azure DevOps push event did not affect any relevant paths: ${contextUrl}`,\n };\n }\n\n const events = Array.from(state.entries()).flatMap(([path, pathState]) =>\n toCatalogScmEventForPathState({\n repository,\n path,\n pathState,\n isRelevantPath: options.isRelevantPath,\n }),\n );\n\n if (events.length === 0) {\n return {\n result: 'ignored',\n reason: `Azure DevOps push event did not affect any relevant paths: ${contextUrl}`,\n };\n }\n\n return { result: 'ok', events };\n}\n\nasync function onRepositoryEvent(\n eventType: string,\n eventPayload: JsonObject,\n): Promise<AnalyzeAzureDevOpsWebhookEventResult> {\n const resource = asObject(eventPayload.resource);\n const repository = getRepository(resource);\n const toUrl = repository.remoteUrl;\n\n if (eventType === 'git.repo.created' && toUrl) {\n return {\n result: 'ok',\n events: [{ type: 'repository.created', url: toUrl }],\n };\n }\n\n if (eventType === 'git.repo.deleted' && toUrl) {\n return {\n result: 'ok',\n events: [{ type: 'repository.deleted', url: toUrl }],\n };\n }\n\n if (eventType === 'git.repo.statuschanged' && toUrl) {\n return {\n result: 'ok',\n events: [{ type: 'repository.updated', url: toUrl }],\n };\n }\n\n if (eventType === 'git.repo.renamed' && toUrl) {\n const oldName = asString(resource?.oldName);\n const fromUrl = oldName\n ? replaceRepoNameInRemoteUrl(toUrl, oldName)\n : undefined;\n if (!fromUrl) {\n return {\n result: 'ignored',\n reason: oldName\n ? 'Azure DevOps repository renamed event has an unexpected repository.remoteUrl format'\n : 'Azure DevOps repository renamed event is missing oldName',\n };\n }\n\n return {\n result: 'ok',\n events: [{ type: 'repository.moved', fromUrl, toUrl }],\n };\n }\n\n if (eventType.startsWith('git.repo.')) {\n return {\n result: 'unsupported-event',\n event: eventType,\n };\n }\n\n return {\n result: 'unsupported-event',\n event: eventType,\n };\n}\n\n/**\n * Analyzes an Azure DevOps webhook event and translates it into zero or more\n * catalog SCM events that entity providers can act on.\n *\n * Supported event types:\n * - `git.push` — translates file-level adds, modifications, and deletions on\n * the default branch into catalog SCM events for paths matching\n * `isRelevantPath`.\n * - `git.repo.created` — emits a `repository.created` event.\n * - `git.repo.deleted` — emits a `repository.deleted` event.\n * - `git.repo.statuschanged` — emits a `repository.updated` event.\n * - `git.repo.renamed` — emits a `repository.moved` event with the old and\n * new repository URLs.\n *\n * @alpha\n */\nexport async function analyzeAzureDevOpsWebhookEvent(\n eventType: string,\n eventPayload: unknown,\n options: AnalyzeAzureDevOpsWebhookEventOptions,\n): Promise<AnalyzeAzureDevOpsWebhookEventResult> {\n const payload = asObject(eventPayload);\n if (!payload) {\n throw new InputError('Azure DevOps webhook event payload is not an object');\n }\n\n if (eventType === 'git.push') {\n return await onPushEvent(payload, options);\n }\n\n if (eventType.startsWith('git.repo.')) {\n return await onRepositoryEvent(eventType, payload);\n }\n\n return {\n result: 'unsupported-event',\n event: eventType,\n };\n}\n"],"names":["previous","InputError"],"mappings":";;;;AAmIA,SAAS,SAAS,KAAA,EAAwC;AACxD,EAAA,IAAI,CAAC,SAAS,OAAO,KAAA,KAAU,YAAY,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/D,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,SAAS,KAAA,EAAoC;AACpD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,MAAA;AAC7C;AAEA,SAAS,cAAc,IAAA,EAA8C;AACnE,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAC/C;AAEA,SAAS,cAAc,QAAA,EAAmD;AACxE,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,QAAA,EAAU,UAAU,CAAA;AAChD,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA,CAAS,UAAA,EAAY,IAAI,CAAA;AAAA,IAC/B,aAAA,EAAe,QAAA,CAAS,UAAA,EAAY,aAAa,CAAA;AAAA,IACjD,SAAA,EAAW,QAAA,CAAS,UAAA,EAAY,SAAS;AAAA,GAC3C;AACF;AAEA,SAAS,cAAc,OAAA,EAGA;AACrB,EAAA,IAAI,CAAC,QAAQ,SAAA,EAAW;AACtB,IAAA,OAAO,MAAA;AAAA,EACT;AAYA,EAAA,MAAM,UAAU,CAAA,EAAG,OAAA,CAAQ,SAAS,CAAA,MAAA,EAAS,QAAQ,IAAI,CAAA,CAAA;AACzD,EAAA,OAAO,UAAU,OAAO,CAAA;AAC1B;AAEA,SAAS,WAAA,CACP,YACA,MAAA,EACoB;AACpB,EAAA,IAAI,OAAO,GAAA,EAAK;AACd,IAAA,OAAO,MAAA,CAAO,GAAA;AAAA,EAChB;AACA,EAAA,IAAI,UAAA,CAAW,SAAA,IAAa,MAAA,CAAO,QAAA,EAAU;AAC3C,IAAA,OAAO,CAAA,EAAG,UAAA,CAAW,SAAS,CAAA,QAAA,EAAW,OAAO,QAAQ,CAAA,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,8BAA8B,OAAA,EAKjB;AACpB,EAAA,MAAM,EAAE,UAAA,EAAY,IAAA,EAAM,SAAA,EAAW,gBAAe,GAAI,OAAA;AACxD,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,UAAA,EAAY,SAAA,CAAU,MAAM,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,SAAA,GAAY,EAAE,SAAA,EAAU,GAAI,MAAA;AAE5C,EAAA,IAAI,SAAA,CAAU,SAAS,SAAA,EAAW;AAChC,IAAA,MAAM,YAAA,GAAe,cAAA,CAAe,SAAA,CAAU,QAAQ,CAAA;AACtD,IAAA,MAAM,UAAA,GAAa,eAAe,IAAI,CAAA;AACtC,IAAA,MAAM,UAAU,aAAA,CAAc;AAAA,MAC5B,WAAW,UAAA,CAAW,SAAA;AAAA,MACtB,MAAM,SAAA,CAAU;AAAA,KACjB,CAAA;AACD,IAAA,MAAM,QAAQ,aAAA,CAAc;AAAA,MAC1B,WAAW,UAAA,CAAW,SAAA;AAAA,MACtB;AAAA,KACD,CAAA;AAED,IAAA,IAAI,YAAA,IAAgB,UAAA,IAAc,OAAA,IAAW,KAAA,EAAO;AAClD,MAAA,OAAO,CAAC,EAAE,IAAA,EAAM,kBAAkB,OAAA,EAAS,KAAA,EAAO,SAAS,CAAA;AAAA,IAC7D;AACA,IAAA,IAAI,YAAA,IAAgB,CAAC,UAAA,IAAc,OAAA,EAAS;AAC1C,MAAA,OAAO,CAAC,EAAE,IAAA,EAAM,oBAAoB,GAAA,EAAK,OAAA,EAAS,SAAS,CAAA;AAAA,IAC7D;AACA,IAAA,IAAI,CAAC,YAAA,IAAgB,UAAA,IAAc,KAAA,EAAO;AACxC,MAAA,OAAO,CAAC,EAAE,IAAA,EAAM,oBAAoB,GAAA,EAAK,KAAA,EAAO,SAAS,CAAA;AAAA,IAC3D;AACA,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,IAAI,CAAC,cAAA,CAAe,IAAI,CAAA,EAAG;AACzB,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,MAAM,aAAA,CAAc;AAAA,IACxB,WAAW,UAAA,CAAW,SAAA;AAAA,IACtB;AAAA,GACD,CAAA;AACD,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,IAAI,SAAA,CAAU,SAAS,OAAA,EAAS;AAC9B,IAAA,OAAO,CAAC,EAAE,IAAA,EAAM,kBAAA,EAAoB,GAAA,EAAK,SAAS,CAAA;AAAA,EACpD;AACA,EAAA,IAAI,SAAA,CAAU,SAAS,SAAA,EAAW;AAChC,IAAA,OAAO,CAAC,EAAE,IAAA,EAAM,kBAAA,EAAoB,GAAA,EAAK,SAAS,CAAA;AAAA,EACpD;AAEA,EAAA,OAAO,CAAC,EAAE,IAAA,EAAM,kBAAA,EAAoB,GAAA,EAAK,SAAS,CAAA;AACpD;AAEA,SAAS,2BACP,MAAA,EACwB;AACxB,EAAA,MAAM,aAAqC,EAAC;AAE5C,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,KAAA,IAAS,EAAC,EAAG;AACrC,IAAA,MAAM,cAAA,GAAiB,cAAc,IAAI,CAAA;AACzC,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,UAAA,CAAW,KAAK,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,gBAAgB,CAAA;AAAA,IACzD;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,OAAA,IAAW,EAAC,EAAG;AACvC,IAAA,MAAM,cAAA,GAAiB,cAAc,IAAI,CAAA;AACzC,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,UAAA,CAAW,KAAK,EAAE,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,gBAAgB,CAAA;AAAA,IAC3D;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,QAAA,IAAY,EAAC,EAAG;AACxC,IAAA,MAAM,cAAA,GAAiB,cAAc,IAAI,CAAA;AACzC,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,UAAA,CAAW,KAAK,EAAE,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,gBAAgB,CAAA;AAAA,IAC3D;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,MAAA,IAAU,MAAA,CAAO,OAAA,IAAW,EAAC,EAAG;AACzC,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,UAAA,EAAY,WAAA,EAAY,IAAK,EAAA;AACvD,IAAA,MAAM,MAAA,GAAS,aAAA;AAAA,MACb,MAAA,CAAO,IAAA,EAAM,IAAA,IAAQ,MAAA,CAAO,QAAQ,MAAA,CAAO;AAAA,KAC7C;AACA,IAAA,MAAM,QAAA,GAAW,aAAA;AAAA,MACf,OAAO,YAAA,IACL,MAAA,CAAO,MAAM,YAAA,IACb,MAAA,CAAO,WACP,MAAA,CAAO;AAAA,KACX;AAEA,IAAA,IAAI,UAAA,CAAW,QAAA,CAAS,QAAQ,CAAA,IAAK,YAAY,MAAA,EAAQ;AACvD,MAAA,UAAA,CAAW,KAAK,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,QAAQ,CAAA;AACrD,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,UAAA,CAAW,QAAA,CAAS,KAAK,CAAA,IAAK,MAAA,EAAQ;AACxC,MAAA,UAAA,CAAW,KAAK,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,QAAQ,CAAA;AAC/C,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,UAAA,CAAW,QAAA,CAAS,QAAQ,CAAA,KAAM,UAAU,QAAA,CAAA,EAAW;AACzD,MAAA,UAAA,CAAW,KAAK,EAAE,IAAA,EAAM,WAAW,IAAA,EAAM,MAAA,IAAU,UAAW,CAAA;AAC9D,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CACG,UAAA,CAAW,QAAA,CAAS,MAAM,CAAA,IACzB,UAAA,CAAW,QAAA,CAAS,QAAQ,CAAA,IAC5B,UAAA,CAAW,QAAA,CAAS,QAAQ,CAAA,MAC7B,UAAU,QAAA,CAAA,EACX;AACA,MAAA,UAAA,CAAW,KAAK,EAAE,IAAA,EAAM,WAAW,IAAA,EAAM,MAAA,IAAU,UAAW,CAAA;AAAA,IAChE;AAAA,EACF;AAEA,EAAA,OAAO,UAAA;AACT;AAEA,SAAS,eAAA,CACP,KAAA,EACA,MAAA,EACA,MAAA,EACA;AACA,EAAA,IAAI,MAAA,CAAO,SAAS,SAAA,EAAW;AAC7B,IAAA,MAAMA,SAAAA,GAAW,KAAA,CAAM,GAAA,CAAI,MAAA,CAAO,QAAQ,CAAA;AAC1C,IAAA,KAAA,CAAM,MAAA,CAAO,OAAO,QAAQ,CAAA;AAE5B,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI,CAACA,SAAAA,EAAU;AACb,MAAA,IAAA,GAAO,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,MAAA,CAAO,UAAU,MAAA,EAAO;AAAA,IAC9D,CAAA,MAAA,IAAWA,SAAAA,CAAS,IAAA,KAAS,OAAA,EAAS;AACpC,MAAA,IAAA,GAAO,EAAE,IAAA,EAAM,OAAA,EAAS,MAAA,EAAO;AAAA,IACjC,CAAA,MAAA,IAAWA,SAAAA,CAAS,IAAA,KAAS,SAAA,EAAW;AACtC,MAAA,IAAA,GAAO,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,MAAA,CAAO,UAAU,MAAA,EAAO;AAAA,IAC9D,CAAA,MAAA,IAAWA,SAAAA,CAAS,IAAA,KAAS,SAAA,EAAW;AACtC,MAAA,IAAA,GAAO,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAUA,SAAAA,CAAS,UAAU,MAAA,EAAO;AAAA,IAChE;AAEA,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,KAAA,CAAM,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ,IAAI,CAAA;AAAA,IAC/B;AACA,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA;AAEtC,EAAA,IAAI,MAAA,CAAO,SAAS,OAAA,EAAS;AAC3B,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,KAAA,CAAM,IAAI,MAAA,CAAO,IAAA,EAAM,EAAE,IAAA,EAAM,OAAA,EAAS,QAAQ,CAAA;AAAA,IAClD,CAAA,MAAA,IAAW,QAAA,CAAS,IAAA,KAAS,SAAA,EAAW;AACtC,MAAA,KAAA,CAAM,IAAI,MAAA,CAAO,IAAA,EAAM,EAAE,IAAA,EAAM,SAAA,EAAW,QAAQ,CAAA;AAAA,IACpD;AACA,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,SAAS,SAAA,EAAW;AAC7B,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,KAAA,CAAM,IAAI,MAAA,CAAO,IAAA,EAAM,EAAE,IAAA,EAAM,SAAA,EAAW,QAAQ,CAAA;AAAA,IACpD,CAAA,MAAA,IAAW,QAAA,CAAS,IAAA,KAAS,OAAA,EAAS;AACpC,MAAA,KAAA,CAAM,MAAA,CAAO,OAAO,IAAI,CAAA;AAAA,IAC1B,CAAA,MAAA,IAAW,QAAA,CAAS,IAAA,KAAS,SAAA,EAAW;AACtC,MAAA,KAAA,CAAM,IAAI,MAAA,CAAO,IAAA,EAAM,EAAE,IAAA,EAAM,SAAA,EAAW,QAAQ,CAAA;AAAA,IACpD,CAAA,MAAA,IAAW,QAAA,CAAS,IAAA,KAAS,SAAA,EAAW;AACtC,MAAA,KAAA,CAAM,MAAA,CAAO,OAAO,IAAI,CAAA;AACxB,MAAA,KAAA,CAAM,IAAI,QAAA,CAAS,QAAA,EAAU,EAAE,IAAA,EAAM,SAAA,EAAW,QAAQ,CAAA;AAAA,IAC1D;AACA,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,KAAA,CAAM,IAAI,MAAA,CAAO,IAAA,EAAM,EAAE,IAAA,EAAM,SAAA,EAAW,QAAQ,CAAA;AAAA,EACpD;AACF;AAEA,SAAS,0BAAA,CACP,WACA,QAAA,EACoB;AACpB,EAAA,IAAI,CAAC,SAAA,IAAa,CAAC,QAAA,EAAU;AAC3B,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,SAAA,GAAY,QAAA;AAClB,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,SAAS,CAAA;AAC1C,EAAA,IAAI,WAAW,EAAA,EAAI;AACjB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,SAAS,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,MAAA,GAAS,UAAU,MAAM,CAAA;AAC3D,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,KAAA,CAAM,MAAA,GAAS,UAAU,MAAM,CAAA;AACtD,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA;AAClC,EAAA,MAAM,SAAS,MAAA,KAAW,EAAA,GAAK,EAAA,GAAK,IAAA,CAAK,MAAM,MAAM,CAAA;AACrD,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,QAAQ,GAAG,MAAM,CAAA,CAAA;AACtC;AAEA,eAAe,WAAA,CACb,cACA,OAAA,EAC+C;AAC/C,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,YAAA,CAAa,QAAQ,CAAA;AAC/C,EAAA,MAAM,UAAA,GAAa,cAAc,QAAQ,CAAA;AACzC,EAAA,MAAM,UAAA,GACH,QAAA,EAAU,UAAA,IAAmD,EAAC;AACjE,EAAA,MAAM,OAAA,GAAW,QAAA,EAAU,OAAA,IAA6C,EAAC;AACzE,EAAA,MAAM,aACJ,QAAA,CAAS,QAAA,EAAU,GAAG,CAAA,IAAK,WAAW,SAAA,IAAa,WAAA;AAErD,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,SAAA;AAAA,MACR,MAAA,EAAQ,qDAAqD,UAAU,CAAA;AAAA,KACzE;AAAA,EACF;AAEA,EAAA,IAAI,WAAW,aAAA,EAAe;AAC5B,IAAA,MAAM,yBAAyB,UAAA,CAAW,MAAA;AAAA,MACxC,CAAA,MAAA,KAAU,MAAA,CAAO,IAAA,KAAS,UAAA,CAAW;AAAA,KACvC;AACA,IAAA,IAAI,sBAAA,CAAuB,WAAW,CAAA,EAAG;AACvC,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,SAAA;AAAA,QACR,MAAA,EAAQ,CAAA,qEAAA,EAAwE,UAAA,CAAW,aAAa,MAAM,UAAU,CAAA;AAAA,OAC1H;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,KAAA,uBAAY,GAAA,EAA2B;AAE7C,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,OAAA,GAAU,2BAA2B,MAAM,CAAA;AACjD,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,eAAA,CAAgB,KAAA,EAAO,QAAQ,MAAM,CAAA;AAAA,IACvC;AAAA,EACF;AAEA,EAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,SAAA;AAAA,MACR,MAAA,EAAQ,8DAA8D,UAAU,CAAA;AAAA,KAClF;AAAA,EACF;AAEA,EAAA,MAAM,SAAS,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,OAAA,EAAS,CAAA,CAAE,OAAA;AAAA,IAAQ,CAAC,CAAC,IAAA,EAAM,SAAS,MAClE,6BAAA,CAA8B;AAAA,MAC5B,UAAA;AAAA,MACA,IAAA;AAAA,MACA,SAAA;AAAA,MACA,gBAAgB,OAAA,CAAQ;AAAA,KACzB;AAAA,GACH;AAEA,EAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,SAAA;AAAA,MACR,MAAA,EAAQ,8DAA8D,UAAU,CAAA;AAAA,KAClF;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,MAAA,EAAO;AAChC;AAEA,eAAe,iBAAA,CACb,WACA,YAAA,EAC+C;AAC/C,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,YAAA,CAAa,QAAQ,CAAA;AAC/C,EAAA,MAAM,UAAA,GAAa,cAAc,QAAQ,CAAA;AACzC,EAAA,MAAM,QAAQ,UAAA,CAAW,SAAA;AAEzB,EAAA,IAAI,SAAA,KAAc,sBAAsB,KAAA,EAAO;AAC7C,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,IAAA;AAAA,MACR,QAAQ,CAAC,EAAE,MAAM,oBAAA,EAAsB,GAAA,EAAK,OAAO;AAAA,KACrD;AAAA,EACF;AAEA,EAAA,IAAI,SAAA,KAAc,sBAAsB,KAAA,EAAO;AAC7C,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,IAAA;AAAA,MACR,QAAQ,CAAC,EAAE,MAAM,oBAAA,EAAsB,GAAA,EAAK,OAAO;AAAA,KACrD;AAAA,EACF;AAEA,EAAA,IAAI,SAAA,KAAc,4BAA4B,KAAA,EAAO;AACnD,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,IAAA;AAAA,MACR,QAAQ,CAAC,EAAE,MAAM,oBAAA,EAAsB,GAAA,EAAK,OAAO;AAAA,KACrD;AAAA,EACF;AAEA,EAAA,IAAI,SAAA,KAAc,sBAAsB,KAAA,EAAO;AAC7C,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAC1C,IAAA,MAAM,OAAA,GAAU,OAAA,GACZ,0BAAA,CAA2B,KAAA,EAAO,OAAO,CAAA,GACzC,MAAA;AACJ,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,SAAA;AAAA,QACR,MAAA,EAAQ,UACJ,qFAAA,GACA;AAAA,OACN;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,IAAA;AAAA,MACR,QAAQ,CAAC,EAAE,MAAM,kBAAA,EAAoB,OAAA,EAAS,OAAO;AAAA,KACvD;AAAA,EACF;AAEA,EAAA,IAAI,SAAA,CAAU,UAAA,CAAW,WAAW,CAAA,EAAG;AACrC,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,mBAAA;AAAA,MACR,KAAA,EAAO;AAAA,KACT;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,mBAAA;AAAA,IACR,KAAA,EAAO;AAAA,GACT;AACF;AAkBA,eAAsB,8BAAA,CACpB,SAAA,EACA,YAAA,EACA,OAAA,EAC+C;AAC/C,EAAA,MAAM,OAAA,GAAU,SAAS,YAAY,CAAA;AACrC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAIC,kBAAW,qDAAqD,CAAA;AAAA,EAC5E;AAEA,EAAA,IAAI,cAAc,UAAA,EAAY;AAC5B,IAAA,OAAO,MAAM,WAAA,CAAY,OAAA,EAAS,OAAO,CAAA;AAAA,EAC3C;AAEA,EAAA,IAAI,SAAA,CAAU,UAAA,CAAW,WAAW,CAAA,EAAG;AACrC,IAAA,OAAO,MAAM,iBAAA,CAAkB,SAAA,EAAW,OAAO,CAAA;AAAA,EACnD;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,mBAAA;AAAA,IACR,KAAA,EAAO;AAAA,GACT;AACF;;;;"}
|
|
@@ -2,8 +2,11 @@
|
|
|
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');
|
|
6
|
+
var pluginEventsNode = require('@backstage/plugin-events-node');
|
|
5
7
|
var AzureDevOpsEntityProvider = require('../providers/AzureDevOpsEntityProvider.cjs.js');
|
|
6
8
|
var AzureBlobStorageEntityProvider = require('../providers/AzureBlobStorageEntityProvider.cjs.js');
|
|
9
|
+
var AzureDevOpsScmEventsBridge = require('../events/AzureDevOpsScmEventsBridge.cjs.js');
|
|
7
10
|
|
|
8
11
|
const catalogModuleAzureEntityProvider = backendPluginApi.createBackendModule({
|
|
9
12
|
pluginId: "catalog",
|
|
@@ -14,9 +17,20 @@ const catalogModuleAzureEntityProvider = backendPluginApi.createBackendModule({
|
|
|
14
17
|
config: backendPluginApi.coreServices.rootConfig,
|
|
15
18
|
catalog: pluginCatalogNode.catalogProcessingExtensionPoint,
|
|
16
19
|
logger: backendPluginApi.coreServices.logger,
|
|
17
|
-
scheduler: backendPluginApi.coreServices.scheduler
|
|
20
|
+
scheduler: backendPluginApi.coreServices.scheduler,
|
|
21
|
+
events: pluginEventsNode.eventsServiceRef,
|
|
22
|
+
catalogScmEvents: alpha.catalogScmEventsServiceRef,
|
|
23
|
+
lifecycle: backendPluginApi.coreServices.lifecycle
|
|
18
24
|
},
|
|
19
|
-
async init({
|
|
25
|
+
async init({
|
|
26
|
+
config,
|
|
27
|
+
catalog,
|
|
28
|
+
logger,
|
|
29
|
+
scheduler,
|
|
30
|
+
events,
|
|
31
|
+
catalogScmEvents,
|
|
32
|
+
lifecycle
|
|
33
|
+
}) {
|
|
20
34
|
if (config.has("catalog.providers.azureBlob")) {
|
|
21
35
|
catalog.addEntityProvider(
|
|
22
36
|
AzureBlobStorageEntityProvider.AzureBlobStorageEntityProvider.fromConfig(config, {
|
|
@@ -33,6 +47,19 @@ const catalogModuleAzureEntityProvider = backendPluginApi.createBackendModule({
|
|
|
33
47
|
})
|
|
34
48
|
);
|
|
35
49
|
}
|
|
50
|
+
if (config.has("catalog.providers.azureDevOps")) {
|
|
51
|
+
const bridge = new AzureDevOpsScmEventsBridge.AzureDevOpsScmEventsBridge({
|
|
52
|
+
logger,
|
|
53
|
+
events,
|
|
54
|
+
catalogScmEvents
|
|
55
|
+
});
|
|
56
|
+
lifecycle.addStartupHook(async () => {
|
|
57
|
+
await bridge.start();
|
|
58
|
+
});
|
|
59
|
+
lifecycle.addShutdownHook(async () => {
|
|
60
|
+
await bridge.stop();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
36
63
|
}
|
|
37
64
|
});
|
|
38
65
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalogModuleAzureDevOpsEntityProvider.cjs.js","sources":["../../src/module/catalogModuleAzureDevOpsEntityProvider.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 { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';\nimport {\n AzureBlobStorageEntityProvider,\n AzureDevOpsEntityProvider,\n} from '../providers';\n\n/**\n * Registers the AzureDevOpsEntityProvider with the catalog processing extension point.\n *\n * @public\n */\nexport const catalogModuleAzureEntityProvider = createBackendModule({\n pluginId: 'catalog',\n moduleId: 'azure-providers',\n register(env) {\n env.registerInit({\n deps: {\n config: coreServices.rootConfig,\n catalog: catalogProcessingExtensionPoint,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n },\n async init({
|
|
1
|
+
{"version":3,"file":"catalogModuleAzureDevOpsEntityProvider.cjs.js","sources":["../../src/module/catalogModuleAzureDevOpsEntityProvider.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 { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';\nimport { catalogScmEventsServiceRef } from '@backstage/plugin-catalog-node/alpha';\nimport { eventsServiceRef } from '@backstage/plugin-events-node';\nimport {\n AzureBlobStorageEntityProvider,\n AzureDevOpsEntityProvider,\n} from '../providers';\nimport { AzureDevOpsScmEventsBridge } from '../events/AzureDevOpsScmEventsBridge';\n\n/**\n * Registers the AzureDevOpsEntityProvider with the catalog processing extension point.\n *\n * @public\n */\nexport const catalogModuleAzureEntityProvider = createBackendModule({\n pluginId: 'catalog',\n moduleId: 'azure-providers',\n register(env) {\n env.registerInit({\n deps: {\n config: coreServices.rootConfig,\n catalog: catalogProcessingExtensionPoint,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n events: eventsServiceRef,\n catalogScmEvents: catalogScmEventsServiceRef,\n lifecycle: coreServices.lifecycle,\n },\n async init({\n config,\n catalog,\n logger,\n scheduler,\n events,\n catalogScmEvents,\n lifecycle,\n }) {\n // Check for Azure Blob Storage provider configuration and register it\n if (config.has('catalog.providers.azureBlob')) {\n catalog.addEntityProvider(\n AzureBlobStorageEntityProvider.fromConfig(config, {\n logger,\n scheduler,\n }),\n );\n }\n\n // Check for Azure DevOps provider configuration and register it\n if (config.has('catalog.providers.azureDevOps')) {\n catalog.addEntityProvider(\n AzureDevOpsEntityProvider.fromConfig(config, {\n logger,\n scheduler,\n }),\n );\n }\n\n // Only wire up the SCM events bridge when Azure DevOps provider\n // configuration is present — Azure Blob Storage users should not be\n // required to handle Azure DevOps webhook events.\n if (config.has('catalog.providers.azureDevOps')) {\n const bridge = new AzureDevOpsScmEventsBridge({\n logger,\n events,\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});\n"],"names":["createBackendModule","coreServices","catalogProcessingExtensionPoint","eventsServiceRef","catalogScmEventsServiceRef","AzureBlobStorageEntityProvider","AzureDevOpsEntityProvider","AzureDevOpsScmEventsBridge"],"mappings":";;;;;;;;;;AAkCO,MAAM,mCAAmCA,oCAAA,CAAoB;AAAA,EAClE,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,iBAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,QAAQC,6BAAA,CAAa,UAAA;AAAA,QACrB,OAAA,EAASC,iDAAA;AAAA,QACT,QAAQD,6BAAA,CAAa,MAAA;AAAA,QACrB,WAAWA,6BAAA,CAAa,SAAA;AAAA,QACxB,MAAA,EAAQE,iCAAA;AAAA,QACR,gBAAA,EAAkBC,gCAAA;AAAA,QAClB,WAAWH,6BAAA,CAAa;AAAA,OAC1B;AAAA,MACA,MAAM,IAAA,CAAK;AAAA,QACT,MAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAA;AAAA,QACA,SAAA;AAAA,QACA,MAAA;AAAA,QACA,gBAAA;AAAA,QACA;AAAA,OACF,EAAG;AAED,QAAA,IAAI,MAAA,CAAO,GAAA,CAAI,6BAA6B,CAAA,EAAG;AAC7C,UAAA,OAAA,CAAQ,iBAAA;AAAA,YACNI,6DAAA,CAA+B,WAAW,MAAA,EAAQ;AAAA,cAChD,MAAA;AAAA,cACA;AAAA,aACD;AAAA,WACH;AAAA,QACF;AAGA,QAAA,IAAI,MAAA,CAAO,GAAA,CAAI,+BAA+B,CAAA,EAAG;AAC/C,UAAA,OAAA,CAAQ,iBAAA;AAAA,YACNC,mDAAA,CAA0B,WAAW,MAAA,EAAQ;AAAA,cAC3C,MAAA;AAAA,cACA;AAAA,aACD;AAAA,WACH;AAAA,QACF;AAKA,QAAA,IAAI,MAAA,CAAO,GAAA,CAAI,+BAA+B,CAAA,EAAG;AAC/C,UAAA,MAAM,MAAA,GAAS,IAAIC,qDAAA,CAA2B;AAAA,YAC5C,MAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA,WACD,CAAA;AACD,UAAA,SAAA,CAAU,eAAe,YAAY;AACnC,YAAA,MAAM,OAAO,KAAA,EAAM;AAAA,UACrB,CAAC,CAAA;AACD,UAAA,SAAA,CAAU,gBAAgB,YAAY;AACpC,YAAA,MAAM,OAAO,IAAA,EAAK;AAAA,UACpB,CAAC,CAAA;AAAA,QACH;AAAA,MACF;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-catalog-backend-module-azure",
|
|
3
|
-
"version": "0.3.16-next.
|
|
3
|
+
"version": "0.3.16-next.2",
|
|
4
4
|
"description": "A Backstage catalog backend module that helps integrate towards Azure",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin-module",
|
|
@@ -67,16 +67,18 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@azure/identity": "^4.0.0",
|
|
69
69
|
"@azure/storage-blob": "^12.5.0",
|
|
70
|
-
"@backstage/backend-plugin-api": "1.
|
|
71
|
-
"@backstage/config": "1.3.
|
|
72
|
-
"@backstage/
|
|
73
|
-
"@backstage/
|
|
74
|
-
"@backstage/plugin-catalog-
|
|
70
|
+
"@backstage/backend-plugin-api": "1.9.0-next.2",
|
|
71
|
+
"@backstage/config": "1.3.7-next.0",
|
|
72
|
+
"@backstage/errors": "1.3.0-next.0",
|
|
73
|
+
"@backstage/integration": "2.0.1-next.0",
|
|
74
|
+
"@backstage/plugin-catalog-common": "1.1.9-next.0",
|
|
75
|
+
"@backstage/plugin-catalog-node": "2.2.0-next.2",
|
|
76
|
+
"@backstage/plugin-events-node": "0.4.21-next.2",
|
|
75
77
|
"uuid": "^11.0.0"
|
|
76
78
|
},
|
|
77
79
|
"devDependencies": {
|
|
78
|
-
"@backstage/backend-test-utils": "1.11.2-next.
|
|
79
|
-
"@backstage/cli": "0.36.1-next.
|
|
80
|
+
"@backstage/backend-test-utils": "1.11.2-next.2",
|
|
81
|
+
"@backstage/cli": "0.36.1-next.2",
|
|
80
82
|
"msw": "^1.0.0"
|
|
81
83
|
},
|
|
82
84
|
"configSchema": "config.d.ts"
|