@backstage/backend-app-api 1.7.3-next.0 → 1.7.3
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 +18 -0
- package/dist/backend-internal/src/wiring/helpers.cjs.js +13 -6
- package/dist/backend-internal/src/wiring/helpers.cjs.js.map +1 -1
- package/dist/connections-node/src/DefaultConnectionsService.cjs.js +34 -136
- package/dist/connections-node/src/DefaultConnectionsService.cjs.js.map +1 -1
- package/dist/connections-node/src/lookup.cjs.js +0 -5
- package/dist/connections-node/src/lookup.cjs.js.map +1 -1
- package/dist/connections-node/src/lookupStrategies.cjs.js +30 -0
- package/dist/connections-node/src/lookupStrategies.cjs.js.map +1 -0
- package/dist/wiring/BackendInitializer.cjs.js +92 -73
- package/dist/wiring/BackendInitializer.cjs.js.map +1 -1
- package/dist/wiring/BackstageBackend.cjs.js +1 -1
- package/dist/wiring/BackstageBackend.cjs.js.map +1 -1
- package/dist/wiring/ServiceRegistry.cjs.js +34 -18
- package/dist/wiring/ServiceRegistry.cjs.js.map +1 -1
- package/dist/wiring/validateBackendFeature.cjs.js +215 -0
- package/dist/wiring/validateBackendFeature.cjs.js.map +1 -0
- package/package.json +9 -9
- package/dist/connections-node/src/combineConnectionSources.cjs.js +0 -26
- package/dist/connections-node/src/combineConnectionSources.cjs.js.map +0 -1
- package/dist/connections-node/src/getLegacyIntegrations.cjs.js +0 -339
- package/dist/connections-node/src/getLegacyIntegrations.cjs.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @backstage/backend-app-api
|
|
2
2
|
|
|
3
|
+
## 1.7.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 64cea29: Updated the backend runtime to use the internal connection service implementation after the shared connection contract moved into `@backstage/connections`.
|
|
8
|
+
- 03133fc: Hardened backend startup against malformed installed backend features, with contextual input errors and configured boot-failure handling when invalid registrations can be attributed to a plugin or module.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/connections@0.3.0
|
|
11
|
+
- @backstage/backend-plugin-api@1.10.0
|
|
12
|
+
|
|
13
|
+
## 1.7.3-next.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 03133fc: Hardened backend startup against malformed installed backend features, with contextual input errors and configured boot-failure handling when invalid registrations can be attributed to a plugin or module.
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @backstage/connections@0.3.0-next.2
|
|
20
|
+
|
|
3
21
|
## 1.7.3-next.0
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -4,13 +4,20 @@ function isPromise(value) {
|
|
|
4
4
|
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
5
5
|
}
|
|
6
6
|
function unwrapFeature(feature) {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
let current = feature;
|
|
8
|
+
for (let i = 0; i < 2; i++) {
|
|
9
|
+
if (current !== null && (typeof current === "object" || typeof current === "function")) {
|
|
10
|
+
if ("$$type" in current) {
|
|
11
|
+
return current;
|
|
12
|
+
}
|
|
13
|
+
if ("default" in current) {
|
|
14
|
+
current = current.default;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
break;
|
|
9
19
|
}
|
|
10
|
-
|
|
11
|
-
return feature.default;
|
|
12
|
-
}
|
|
13
|
-
return feature;
|
|
20
|
+
return current;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
exports.isPromise = isPromise;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.cjs.js","sources":["../../../../../backend-internal/src/wiring/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { BackendFeature } from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport function isPromise<T>(value: unknown | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function'\n );\n}\n\n/**\n * Unwraps a backend feature from a possibly double-nested default export.\n * This is a workaround where default exports get transpiled to\n * `exports['default'] = ...` in CommonJS modules, which in turn results\n * in a double `{ default: { default: ... } }` nesting when importing\n * using a dynamic import.\n *\n * @internal\n */\nexport function unwrapFeature(\n feature: BackendFeature | { default: BackendFeature },\n): BackendFeature {\n if ('$$type' in
|
|
1
|
+
{"version":3,"file":"helpers.cjs.js","sources":["../../../../../backend-internal/src/wiring/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { BackendFeature } from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport function isPromise<T>(value: unknown | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function'\n );\n}\n\n/**\n * Unwraps a backend feature from a possibly double-nested default export.\n * This is a workaround where default exports get transpiled to\n * `exports['default'] = ...` in CommonJS modules, which in turn results\n * in a double `{ default: { default: ... } }` nesting when importing\n * using a dynamic import.\n *\n * @internal\n */\nexport function unwrapFeature(\n feature: BackendFeature | { default: BackendFeature },\n): BackendFeature {\n let current: unknown = feature;\n for (let i = 0; i < 2; i++) {\n if (\n current !== null &&\n (typeof current === 'object' || typeof current === 'function')\n ) {\n if ('$$type' in current) {\n return current as BackendFeature;\n }\n if ('default' in current) {\n current = current.default;\n continue;\n }\n }\n break;\n }\n\n return current as BackendFeature;\n}\n"],"names":[],"mappings":";;AAmBO,SAAS,UAAa,KAAA,EAAkD;AAC7E,EAAA,OACE,OAAO,UAAU,QAAA,IACjB,KAAA,KAAU,QACV,MAAA,IAAU,KAAA,IACV,OAAO,KAAA,CAAM,IAAA,KAAS,UAAA;AAE1B;AAWO,SAAS,cACd,OAAA,EACgB;AAChB,EAAA,IAAI,OAAA,GAAmB,OAAA;AACvB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,IAAA,IACE,YAAY,IAAA,KACX,OAAO,YAAY,QAAA,IAAY,OAAO,YAAY,UAAA,CAAA,EACnD;AACA,MAAA,IAAI,YAAY,OAAA,EAAS;AACvB,QAAA,OAAO,OAAA;AAAA,MACT;AACA,MAAA,IAAI,aAAa,OAAA,EAAS;AACxB,QAAA,OAAA,GAAU,OAAA,CAAQ,OAAA;AAClB,QAAA;AAAA,MACF;AAAA,IACF;AACA,IAAA;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;;;"}
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var connections = require('@backstage/connections');
|
|
3
4
|
var lookup = require('./lookup.cjs.js');
|
|
5
|
+
var lookupStrategies = require('./lookupStrategies.cjs.js');
|
|
4
6
|
var errors = require('@backstage/errors');
|
|
5
|
-
var v4 = require('zod/v4');
|
|
6
|
-
var getLegacyIntegrations = require('./getLegacyIntegrations.cjs.js');
|
|
7
|
-
var combineConnectionSources = require('./combineConnectionSources.cjs.js');
|
|
8
7
|
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const cause = errors.toError(e.cause);
|
|
16
|
-
if (cause.name === "ZodError") {
|
|
17
|
-
return v4.z.prettifyError(cause);
|
|
18
|
-
}
|
|
8
|
+
function getLookupStrategy(name) {
|
|
9
|
+
return lookupStrategies.lookupStrategies[name];
|
|
10
|
+
}
|
|
11
|
+
function connectionIdentityOf(strategy, connection) {
|
|
12
|
+
if (!strategy.identityField) {
|
|
13
|
+
return void 0;
|
|
19
14
|
}
|
|
20
|
-
|
|
15
|
+
const value = connection[strategy.identityField];
|
|
16
|
+
return typeof value === "string" ? value : void 0;
|
|
21
17
|
}
|
|
22
18
|
class PluginConnectionsService {
|
|
23
19
|
logger;
|
|
@@ -30,40 +26,40 @@ class PluginConnectionsService {
|
|
|
30
26
|
const result = await this.findOptional(options);
|
|
31
27
|
if (!result) {
|
|
32
28
|
throw new errors.NotFoundError(
|
|
33
|
-
`Connection not found for type "${options.type}"
|
|
29
|
+
`Connection not found for type "${options.type}"`
|
|
34
30
|
);
|
|
35
31
|
}
|
|
36
32
|
return result;
|
|
37
33
|
}
|
|
38
34
|
async findOptional({
|
|
39
35
|
type,
|
|
40
|
-
|
|
36
|
+
query,
|
|
41
37
|
authMethods
|
|
42
38
|
}) {
|
|
39
|
+
const connectionType = lookup.getConnectionType(type);
|
|
40
|
+
const strategy = getLookupStrategy(connectionType.lookupStrategy);
|
|
41
|
+
const identity = strategy.identityFromQuery(query);
|
|
43
42
|
this.logger.debug(
|
|
44
|
-
`Finding connection of type "${type}" matching
|
|
43
|
+
`Finding connection of type "${type}"${identity ? ` matching ${strategy.identityField} "${identity}"` : ""}`
|
|
45
44
|
);
|
|
46
|
-
let
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
throw new errors.InputError(
|
|
51
|
-
`Invalid url "${url}" passed to ConnectionsService.find`
|
|
45
|
+
let connection;
|
|
46
|
+
if (identity !== void 0) {
|
|
47
|
+
connection = this.connections.find(
|
|
48
|
+
(c) => c.type === type && connectionIdentityOf(strategy, c) === identity
|
|
52
49
|
);
|
|
50
|
+
} else {
|
|
51
|
+
connection = this.connections.find((c) => c.type === type);
|
|
53
52
|
}
|
|
54
|
-
const connection = this.connections.find(
|
|
55
|
-
(c) => c.type === type && c.host === host
|
|
56
|
-
);
|
|
57
53
|
if (!connection) {
|
|
58
54
|
return void 0;
|
|
59
55
|
}
|
|
60
56
|
if (connection.auth.length === 0) {
|
|
61
57
|
throw new errors.NotAllowedError(
|
|
62
|
-
`Connection of type "${type}" for
|
|
58
|
+
`Connection of type "${type}"${identity ? ` for ${strategy.identityField} "${identity}"` : ""} has no auth method available to this plugin`
|
|
63
59
|
);
|
|
64
60
|
}
|
|
65
|
-
const matchAuth =
|
|
66
|
-
const selected = matchAuth ? matchAuth(connection.auth,
|
|
61
|
+
const matchAuth = connectionType.matchAuth;
|
|
62
|
+
const selected = matchAuth ? matchAuth(connection.auth, query) : connection.auth[0];
|
|
67
63
|
if (!selected) {
|
|
68
64
|
return void 0;
|
|
69
65
|
}
|
|
@@ -72,6 +68,9 @@ class PluginConnectionsService {
|
|
|
72
68
|
`Connection not found for type "${type}" with auth method "${selected.method}"`
|
|
73
69
|
);
|
|
74
70
|
}
|
|
71
|
+
this.logger.debug(
|
|
72
|
+
`Selected connection of type "${type}"${identity ? ` for ${strategy.identityField} "${identity}"` : ""} using auth method "${selected.method}"`
|
|
73
|
+
);
|
|
75
74
|
return {
|
|
76
75
|
...connection,
|
|
77
76
|
auth: selected
|
|
@@ -92,120 +91,19 @@ class DefaultConnectionsService {
|
|
|
92
91
|
return new DefaultConnectionsService(options.logger, options.config);
|
|
93
92
|
}
|
|
94
93
|
#registerConnectionsFromConfig() {
|
|
95
|
-
const legacy = this.#validateLegacy(getLegacyIntegrations.getLegacyIntegrations(this.config));
|
|
96
|
-
const rawConnections = this.config.getOptional("connections");
|
|
97
|
-
if (rawConnections !== void 0 && !Array.isArray(rawConnections)) {
|
|
98
|
-
throw new errors.InputError(
|
|
99
|
-
'Expected "connections" config to be an array of connection objects'
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
const fromConfig = this.#validateConfig(
|
|
103
|
-
rawConnections ?? []
|
|
104
|
-
);
|
|
105
|
-
if (legacy.length === 0 && fromConfig.length === 0) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
94
|
this.connections.push(
|
|
109
|
-
...
|
|
95
|
+
...connections.buildConnectionsFromConfig({
|
|
96
|
+
config: this.config,
|
|
97
|
+
logger: this.logger
|
|
98
|
+
})
|
|
110
99
|
);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const host = c.host;
|
|
114
|
-
const key = `${c.type} ${host}`;
|
|
115
|
-
if (seen.has(key)) {
|
|
116
|
-
throw new errors.InputError(
|
|
117
|
-
`Duplicate connection of type "${c.type}" for host "${host}"`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
seen.add(key);
|
|
100
|
+
if (this.connections.length === 0) {
|
|
101
|
+
return;
|
|
121
102
|
}
|
|
122
|
-
this.#assignDefaultTitles();
|
|
123
|
-
this.#assignDefaultAuthTitles();
|
|
124
103
|
this.logger.info(
|
|
125
104
|
`Loaded ${this.connections.length} connection${this.connections.length === 1 ? "" : "s"} from configuration`
|
|
126
105
|
);
|
|
127
106
|
}
|
|
128
|
-
#validateConfig(raw) {
|
|
129
|
-
return raw.map((v) => {
|
|
130
|
-
try {
|
|
131
|
-
return this.#validateConnection(v);
|
|
132
|
-
} catch (e) {
|
|
133
|
-
const type = typeof v.type === "string" ? v.type : "unknown";
|
|
134
|
-
throw new errors.InputError(
|
|
135
|
-
`Invalid connection of type "${type}" in connections config:
|
|
136
|
-
${describeError(
|
|
137
|
-
e
|
|
138
|
-
)}`
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
#validateLegacy(raw) {
|
|
144
|
-
const result = [];
|
|
145
|
-
for (const v of raw) {
|
|
146
|
-
try {
|
|
147
|
-
result.push(this.#validateConnection(v));
|
|
148
|
-
} catch (e) {
|
|
149
|
-
const type = typeof v.type === "string" ? v.type : "unknown";
|
|
150
|
-
this.logger.error(
|
|
151
|
-
`Failed to validate connection of type "${type}":
|
|
152
|
-
${describeError(
|
|
153
|
-
e
|
|
154
|
-
)}`
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return result;
|
|
159
|
-
}
|
|
160
|
-
#validateConnection(connection) {
|
|
161
|
-
if (typeof connection.type !== "string") {
|
|
162
|
-
throw new errors.InputError(`Unrecognised connection type ${connection.type}`);
|
|
163
|
-
}
|
|
164
|
-
if (!lookup.isConnectionTypeKey(connection.type)) {
|
|
165
|
-
throw new errors.InputError(`Unrecognised connection type ${connection.type}`);
|
|
166
|
-
}
|
|
167
|
-
const parsed = lookup.getConnectionType(connection.type).configSchema.parse(
|
|
168
|
-
connection
|
|
169
|
-
);
|
|
170
|
-
if (parsed.auth.length === 0) {
|
|
171
|
-
throw new errors.InputError(
|
|
172
|
-
`Connection of type "${connection.type}" must configure at least one auth method`
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
return parsed;
|
|
176
|
-
}
|
|
177
|
-
#assignDefaultTitles() {
|
|
178
|
-
const typeCounts = /* @__PURE__ */ new Map();
|
|
179
|
-
for (const c of this.connections) {
|
|
180
|
-
const type = c.type;
|
|
181
|
-
typeCounts.set(type, (typeCounts.get(type) ?? 0) + 1);
|
|
182
|
-
}
|
|
183
|
-
for (const c of this.connections) {
|
|
184
|
-
if (!c.title) {
|
|
185
|
-
const type = c.type;
|
|
186
|
-
const displayName = lookup.getConnectionType(type).title;
|
|
187
|
-
const host = c.host;
|
|
188
|
-
c.title = typeCounts.get(type) > 1 ? `${displayName} (${host})` : displayName;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
#assignDefaultAuthTitles() {
|
|
193
|
-
for (const c of this.connections) {
|
|
194
|
-
const type = c.type;
|
|
195
|
-
const connectionType = lookup.getConnectionType(type);
|
|
196
|
-
for (const auth of c.auth) {
|
|
197
|
-
const authMethod = connectionType.authMethods.find(
|
|
198
|
-
(am) => am.method === auth.method
|
|
199
|
-
);
|
|
200
|
-
if (!authMethod) {
|
|
201
|
-
throw new Error(
|
|
202
|
-
`Unknown auth method "${auth.method}" for connection type "${type}"`
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
auth.title ??= authMethod.title;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
107
|
#getConnectionsForPlugin(pluginId) {
|
|
210
108
|
return this.connections.flatMap(({ match, auth, ...rest }) => {
|
|
211
109
|
if (match && !match.plugins.includes(pluginId)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultConnectionsService.cjs.js","sources":["../../../../connections-node/src/DefaultConnectionsService.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 */\nimport {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport type {\n Connection,\n ConnectionAuthMethodKey,\n ConnectionsService,\n ConnectionTypeKey,\n} from '@backstage/connections';\nimport { getConnectionType, isConnectionTypeKey } from './lookup';\nimport type { RootConnection } from './types';\nimport { JsonObject } from '@backstage/types';\nimport {\n InputError,\n NotAllowedError,\n NotFoundError,\n toError,\n} from '@backstage/errors';\nimport { z } from 'zod/v4';\nimport { getLegacyIntegrations } from './getLegacyIntegrations';\nimport { combineConnectionSources } from './combineConnectionSources';\n\nfunction describeError(error: unknown): string {\n const e = toError(error);\n if (e.name === 'ZodError') {\n return z.prettifyError(e as unknown as z.ZodError);\n }\n if (e.cause !== undefined) {\n const cause = toError(e.cause);\n if (cause.name === 'ZodError') {\n return z.prettifyError(cause as unknown as z.ZodError);\n }\n }\n return e.message;\n}\n\nclass PluginConnectionsService implements ConnectionsService {\n private readonly logger: LoggerService;\n private readonly connections: Connection[];\n\n constructor(logger: LoggerService, connections: Connection[]) {\n this.logger = logger;\n this.connections = connections;\n }\n\n async find<\n TType extends ConnectionTypeKey,\n TAuthMethod extends ConnectionAuthMethodKey<TType>,\n >(options: {\n type: TType;\n url: string;\n authMethods: readonly [TAuthMethod, ...TAuthMethod[]];\n }): Promise<Connection<TType, TAuthMethod>> {\n const result = await this.findOptional(options);\n if (!result) {\n throw new NotFoundError(\n `Connection not found for type \"${options.type}\" matching url \"${options.url}\"`,\n );\n }\n return result;\n }\n\n async findOptional<\n TType extends ConnectionTypeKey,\n TAuthMethod extends ConnectionAuthMethodKey<TType>,\n >({\n type,\n url,\n authMethods,\n }: {\n type: TType;\n url: string;\n authMethods: readonly [TAuthMethod, ...TAuthMethod[]];\n }): Promise<Connection<TType, TAuthMethod> | undefined> {\n this.logger.debug(\n `Finding connection of type \"${type}\" matching url \"${url}\"`,\n );\n let host: string;\n try {\n host = new URL(url).host;\n } catch {\n throw new InputError(\n `Invalid url \"${url}\" passed to ConnectionsService.find`,\n );\n }\n\n const connection = this.connections.find(\n c => c.type === type && (c as { host?: unknown }).host === host,\n ) as Connection<TType> | undefined;\n\n if (!connection) {\n return undefined;\n }\n\n if (connection.auth.length === 0) {\n throw new NotAllowedError(\n `Connection of type \"${type}\" for host \"${host}\" has no auth method available to this plugin`,\n );\n }\n\n const matchAuth = getConnectionType(type).matchAuth as\n | ((authMethods: any[], query: string) => any | undefined)\n | undefined;\n\n // We take the host-matched connection and check to see if there's an auth method better suited to the current url\n // e.g. org selection\n const selected = matchAuth\n ? matchAuth(connection.auth, url)\n : connection.auth[0];\n\n if (!selected) {\n return undefined;\n }\n\n // Now we compare user requested auth methods with what the connection can provide\n if (!(authMethods as readonly string[]).includes(selected.method)) {\n throw new NotAllowedError(\n `Connection not found for type \"${type}\" with auth method \"${selected.method}\"`,\n );\n }\n\n return {\n ...connection,\n auth: selected,\n } as Connection<TType, TAuthMethod>;\n }\n}\n\n/** @public */\nexport class DefaultConnectionsService {\n private readonly logger: LoggerService;\n private readonly connections: RootConnection[];\n private readonly config: RootConfigService;\n\n private constructor(logger: LoggerService, config: RootConfigService) {\n this.logger = logger;\n this.config = config;\n this.connections = [];\n this.#registerConnectionsFromConfig();\n }\n\n static create(options: {\n logger: LoggerService;\n config: RootConfigService;\n }): DefaultConnectionsService {\n return new DefaultConnectionsService(options.logger, options.config);\n }\n\n #registerConnectionsFromConfig(): void {\n const legacy = this.#validateLegacy(getLegacyIntegrations(this.config));\n\n const rawConnections = this.config.getOptional('connections');\n if (rawConnections !== undefined && !Array.isArray(rawConnections)) {\n throw new InputError(\n 'Expected \"connections\" config to be an array of connection objects',\n );\n }\n\n const fromConfig = this.#validateConfig(\n (rawConnections as JsonObject[] | undefined) ?? [],\n );\n\n if (legacy.length === 0 && fromConfig.length === 0) {\n return;\n }\n\n this.connections.push(\n ...combineConnectionSources(legacy, fromConfig, this.logger),\n );\n\n const seen = new Set<string>();\n for (const c of this.connections) {\n const host = (c as unknown as { host: string }).host;\n const key = `${c.type} ${host}`;\n if (seen.has(key)) {\n throw new InputError(\n `Duplicate connection of type \"${c.type}\" for host \"${host}\"`,\n );\n }\n seen.add(key);\n }\n\n this.#assignDefaultTitles();\n this.#assignDefaultAuthTitles();\n\n this.logger.info(\n `Loaded ${this.connections.length} connection${\n this.connections.length === 1 ? '' : 's'\n } from configuration`,\n );\n }\n\n #validateConfig(raw: JsonObject[]): RootConnection[] {\n return raw.map(v => {\n try {\n return this.#validateConnection(v);\n } catch (e) {\n const type = typeof v.type === 'string' ? v.type : 'unknown';\n throw new InputError(\n `Invalid connection of type \"${type}\" in connections config:\\n${describeError(\n e,\n )}`,\n );\n }\n });\n }\n\n #validateLegacy(raw: JsonObject[]): RootConnection[] {\n const result: RootConnection[] = [];\n for (const v of raw) {\n try {\n result.push(this.#validateConnection(v));\n } catch (e) {\n const type = typeof v.type === 'string' ? v.type : 'unknown';\n this.logger.error(\n `Failed to validate connection of type \"${type}\":\\n${describeError(\n e,\n )}`,\n );\n }\n }\n return result;\n }\n\n #validateConnection(connection: JsonObject): RootConnection {\n if (typeof connection.type !== 'string') {\n throw new InputError(`Unrecognised connection type ${connection.type}`);\n }\n\n if (!isConnectionTypeKey(connection.type)) {\n throw new InputError(`Unrecognised connection type ${connection.type}`);\n }\n\n const parsed = getConnectionType(connection.type).configSchema.parse(\n connection,\n );\n if (parsed.auth.length === 0) {\n throw new InputError(\n `Connection of type \"${connection.type}\" must configure at least one auth method`,\n );\n }\n return parsed;\n }\n\n #assignDefaultTitles(): void {\n const typeCounts = new Map<string, number>();\n for (const c of this.connections) {\n const type = c.type as ConnectionTypeKey;\n typeCounts.set(type, (typeCounts.get(type) ?? 0) + 1);\n }\n for (const c of this.connections) {\n if (!c.title) {\n const type = c.type as ConnectionTypeKey;\n const displayName = getConnectionType(type).title;\n const host = (c as unknown as { host: string }).host;\n (c as { title?: string }).title =\n typeCounts.get(type)! > 1 ? `${displayName} (${host})` : displayName;\n }\n }\n }\n\n #assignDefaultAuthTitles(): void {\n for (const c of this.connections) {\n const type = c.type as ConnectionTypeKey;\n const connectionType = getConnectionType(type);\n for (const auth of c.auth) {\n const authMethod = connectionType.authMethods.find(\n am => am.method === auth.method,\n );\n // The config schema only allows methods declared by the connection\n // type, so failing to find one means that invariant has been broken.\n if (!authMethod) {\n throw new Error(\n `Unknown auth method \"${auth.method}\" for connection type \"${type}\"`,\n );\n }\n auth.title ??= authMethod.title;\n }\n }\n }\n\n #getConnectionsForPlugin(pluginId: string): Connection[] {\n // Filter connections and hide auth methods based on these conditions:\n // 1. Include Connections with no plugin matcher condition\n // 2. Include Connections with a plugin matcher condition for this plugin\n // 3. Include auth methods with no plugin matcher condition\n // 4. Remove auth methods with a plugin matcher condition for other plugins\n return this.connections.flatMap(({ match, auth, ...rest }) => {\n if (match && !match.plugins.includes(pluginId)) {\n return [];\n }\n\n const pluginMatched: Connection['auth'] = [];\n const unmatched: Connection['auth'] = [];\n for (const { match: authMatch, ...authRest } of auth) {\n if (authMatch) {\n if (!authMatch.plugins.includes(pluginId)) continue;\n pluginMatched.push(authRest as Connection['auth'][number]);\n } else {\n unmatched.push(authRest as Connection['auth'][number]);\n }\n }\n\n return [\n { ...rest, auth: [...pluginMatched, ...unmatched] } as Connection,\n ];\n });\n }\n\n forPlugin(\n pluginId: string,\n options?: {\n logger: LoggerService;\n },\n ): ConnectionsService {\n const logger = options?.logger ?? this.logger;\n return new PluginConnectionsService(\n logger,\n this.#getConnectionsForPlugin(pluginId),\n );\n }\n}\n"],"names":["toError","z","NotFoundError","InputError","NotAllowedError","getConnectionType","getLegacyIntegrations","combineConnectionSources","isConnectionTypeKey"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefaultConnectionsService.cjs.js","sources":["../../../../connections-node/src/DefaultConnectionsService.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 */\nimport {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport type {\n Connection,\n ConnectionAuthMethodKey,\n ConnectionsService,\n ConnectionTypeKey,\n LookupConnectionType,\n LookupStrategy,\n ConfiguredConnection,\n} from '@backstage/connections';\nimport { buildConnectionsFromConfig } from '@backstage/connections';\nimport { getConnectionType } from './lookup';\nimport { lookupStrategies } from './lookupStrategies';\nimport { NotAllowedError, NotFoundError } from '@backstage/errors';\n\nfunction getLookupStrategy<K extends LookupStrategy>(\n name: K,\n): (typeof lookupStrategies)[K] {\n return lookupStrategies[name];\n}\n\n// The identity field name is only known at runtime, so the typed connection\n// object cannot be indexed directly; this helper contains the erased read.\nfunction connectionIdentityOf(\n strategy: { identityField?: string },\n connection: object,\n): string | undefined {\n if (!strategy.identityField) {\n return undefined;\n }\n const value = (connection as Record<string, unknown>)[strategy.identityField];\n return typeof value === 'string' ? value : undefined;\n}\n\nclass PluginConnectionsService implements ConnectionsService {\n private readonly logger: LoggerService;\n private readonly connections: Connection[];\n\n constructor(logger: LoggerService, connections: Connection[]) {\n this.logger = logger;\n this.connections = connections;\n }\n\n async find<\n TType extends ConnectionTypeKey,\n TAuthMethod extends ConnectionAuthMethodKey<TType>,\n >(options: {\n type: TType;\n query: LookupConnectionType<TType>['query'];\n authMethods: readonly [TAuthMethod, ...TAuthMethod[]];\n }): Promise<Connection<TType, TAuthMethod>> {\n const result = await this.findOptional(options);\n if (!result) {\n throw new NotFoundError(\n `Connection not found for type \"${options.type}\"`,\n );\n }\n return result;\n }\n\n async findOptional<\n TType extends ConnectionTypeKey,\n TAuthMethod extends ConnectionAuthMethodKey<TType>,\n >({\n type,\n query,\n authMethods,\n }: {\n type: TType;\n query: LookupConnectionType<TType>['query'];\n authMethods: readonly [TAuthMethod, ...TAuthMethod[]];\n }): Promise<Connection<TType, TAuthMethod> | undefined> {\n const connectionType = getConnectionType(type);\n const strategy = getLookupStrategy(connectionType.lookupStrategy);\n const identity = strategy.identityFromQuery(query);\n\n this.logger.debug(\n `Finding connection of type \"${type}\"${\n identity ? ` matching ${strategy.identityField} \"${identity}\"` : ''\n }`,\n );\n\n let connection: Connection<TType> | undefined;\n if (identity !== undefined) {\n connection = this.connections.find(\n c => c.type === type && connectionIdentityOf(strategy, c) === identity,\n ) as Connection<TType> | undefined;\n } else {\n connection = this.connections.find(c => c.type === type) as\n | Connection<TType>\n | undefined;\n }\n\n if (!connection) {\n return undefined;\n }\n\n if (connection.auth.length === 0) {\n throw new NotAllowedError(\n `Connection of type \"${type}\"${\n identity ? ` for ${strategy.identityField} \"${identity}\"` : ''\n } has no auth method available to this plugin`,\n );\n }\n\n const matchAuth = connectionType.matchAuth as\n | ((authMethods: any[], query: any) => any | undefined)\n | undefined;\n\n const selected = matchAuth\n ? matchAuth(connection.auth, query)\n : connection.auth[0];\n\n if (!selected) {\n return undefined;\n }\n\n if (!(authMethods as readonly string[]).includes(selected.method)) {\n throw new NotAllowedError(\n `Connection not found for type \"${type}\" with auth method \"${selected.method}\"`,\n );\n }\n\n this.logger.debug(\n `Selected connection of type \"${type}\"${\n identity ? ` for ${strategy.identityField} \"${identity}\"` : ''\n } using auth method \"${selected.method}\"`,\n );\n\n return {\n ...connection,\n auth: selected,\n } as Connection<TType, TAuthMethod>;\n }\n}\n\n/** @public */\nexport class DefaultConnectionsService {\n private readonly logger: LoggerService;\n private readonly connections: ConfiguredConnection[];\n private readonly config: RootConfigService;\n\n private constructor(logger: LoggerService, config: RootConfigService) {\n this.logger = logger;\n this.config = config;\n this.connections = [];\n this.#registerConnectionsFromConfig();\n }\n\n static create(options: {\n logger: LoggerService;\n config: RootConfigService;\n }): DefaultConnectionsService {\n return new DefaultConnectionsService(options.logger, options.config);\n }\n\n #registerConnectionsFromConfig(): void {\n this.connections.push(\n ...buildConnectionsFromConfig({\n config: this.config,\n logger: this.logger,\n }),\n );\n\n if (this.connections.length === 0) {\n return;\n }\n\n this.logger.info(\n `Loaded ${this.connections.length} connection${\n this.connections.length === 1 ? '' : 's'\n } from configuration`,\n );\n }\n\n #getConnectionsForPlugin(pluginId: string): Connection[] {\n // Filter connections and hide auth methods based on these conditions:\n // 1. Include Connections with no plugin matcher condition\n // 2. Include Connections with a plugin matcher condition for this plugin\n // 3. Include auth methods with no plugin matcher condition\n // 4. Remove auth methods with a plugin matcher condition for other plugins\n return this.connections.flatMap(({ match, auth, ...rest }) => {\n if (match && !match.plugins.includes(pluginId)) {\n return [];\n }\n\n const pluginMatched: Connection['auth'] = [];\n const unmatched: Connection['auth'] = [];\n for (const { match: authMatch, ...authRest } of auth) {\n if (authMatch) {\n if (!authMatch.plugins.includes(pluginId)) continue;\n pluginMatched.push(authRest as Connection['auth'][number]);\n } else {\n unmatched.push(authRest as Connection['auth'][number]);\n }\n }\n\n return [\n { ...rest, auth: [...pluginMatched, ...unmatched] } as Connection,\n ];\n });\n }\n\n forPlugin(\n pluginId: string,\n options?: {\n logger: LoggerService;\n },\n ): ConnectionsService {\n const logger = options?.logger ?? this.logger;\n return new PluginConnectionsService(\n logger,\n this.#getConnectionsForPlugin(pluginId),\n );\n }\n}\n"],"names":["lookupStrategies","NotFoundError","getConnectionType","NotAllowedError","buildConnectionsFromConfig"],"mappings":"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"}
|
|
@@ -6,11 +6,6 @@ const connectionTypesMap = new Map(Object.entries(connections.connectionTypes));
|
|
|
6
6
|
function getConnectionType(key) {
|
|
7
7
|
return connectionTypesMap.get(key);
|
|
8
8
|
}
|
|
9
|
-
function isConnectionTypeKey(value) {
|
|
10
|
-
if (!value) return false;
|
|
11
|
-
return connectionTypesMap.has(value);
|
|
12
|
-
}
|
|
13
9
|
|
|
14
10
|
exports.getConnectionType = getConnectionType;
|
|
15
|
-
exports.isConnectionTypeKey = isConnectionTypeKey;
|
|
16
11
|
//# sourceMappingURL=lookup.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lookup.cjs.js","sources":["../../../../connections-node/src/lookup.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 */\nimport {\n LookupConnectionType,\n ConnectionTypeKey,\n connectionTypes,\n} from '@backstage/connections';\n\nconst connectionTypesMap = new Map(Object.entries(connectionTypes));\n\nexport function getConnectionType<T extends ConnectionTypeKey>(\n key: T,\n): LookupConnectionType<T> {\n return connectionTypesMap.get(key) as LookupConnectionType<T>;\n}\n\nexport function isConnectionTypeKey(\n value: string | undefined,\n): value is ConnectionTypeKey {\n if (!value) return false;\n\n return connectionTypesMap.has(value);\n}\n"],"names":["connectionTypes"],"mappings":";;;;AAqBA,MAAM,qBAAqB,IAAI,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQA,2BAAe,CAAC,CAAA;AAE3D,SAAS,kBACd,GAAA,EACyB;AACzB,EAAA,OAAO,kBAAA,CAAmB,IAAI,GAAG,CAAA;AACnC
|
|
1
|
+
{"version":3,"file":"lookup.cjs.js","sources":["../../../../connections-node/src/lookup.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 */\nimport {\n LookupConnectionType,\n ConnectionTypeKey,\n connectionTypes,\n} from '@backstage/connections';\n\nconst connectionTypesMap = new Map(Object.entries(connectionTypes));\n\nexport function getConnectionType<T extends ConnectionTypeKey>(\n key: T,\n): LookupConnectionType<T> {\n return connectionTypesMap.get(key) as LookupConnectionType<T>;\n}\n\nexport function isConnectionTypeKey(\n value: string | undefined,\n): value is ConnectionTypeKey {\n if (!value) return false;\n\n return connectionTypesMap.has(value);\n}\n"],"names":["connectionTypes"],"mappings":";;;;AAqBA,MAAM,qBAAqB,IAAI,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQA,2BAAe,CAAC,CAAA;AAE3D,SAAS,kBACd,GAAA,EACyB;AACzB,EAAA,OAAO,kBAAA,CAAmB,IAAI,GAAG,CAAA;AACnC;;;;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
|
|
5
|
+
const lookupStrategies = {
|
|
6
|
+
host: {
|
|
7
|
+
identityField: "host",
|
|
8
|
+
identityFromQuery(query) {
|
|
9
|
+
const { url } = query;
|
|
10
|
+
try {
|
|
11
|
+
return new URL(url).host;
|
|
12
|
+
} catch {
|
|
13
|
+
throw new errors.InputError(
|
|
14
|
+
`Invalid url "${url}" passed to ConnectionsService.find`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
// AWS has no identity field to match against — all accounts live under a
|
|
20
|
+
// single connection. Account selection is handled entirely by the
|
|
21
|
+
// connection type's matchAuth implementation.
|
|
22
|
+
aws: {
|
|
23
|
+
identityFromQuery() {
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
exports.lookupStrategies = lookupStrategies;
|
|
30
|
+
//# sourceMappingURL=lookupStrategies.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lookupStrategies.cjs.js","sources":["../../../../connections-node/src/lookupStrategies.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 */\nimport { InputError } from '@backstage/errors';\nimport type { LookupStrategy } from '@backstage/connections';\n\n// The concrete strategy is only known at runtime, so definitions receive the\n// query with its type erased and narrow it to their own query shape.\ntype LookupStrategyDefinition = {\n identityField?: string;\n identityFromQuery(query: unknown): string | undefined;\n};\n\n/**\n * The definitions of all lookup strategies, keyed by the `lookupStrategy` of\n * each connection type. Each definition knows how to derive the identity to\n * match connections against from the query passed to `ConnectionsService.find`.\n *\n * The `identityField` of each strategy is mirrored by the `identityFields` map\n * in the config pipeline of the `@backstage/connections` package; keep the two\n * in sync when adding or changing strategies.\n *\n * @internal\n */\nexport const lookupStrategies: Record<\n LookupStrategy,\n LookupStrategyDefinition\n> = {\n host: {\n identityField: 'host',\n identityFromQuery(query) {\n const { url } = query as { url: string };\n try {\n return new URL(url).host;\n } catch {\n throw new InputError(\n `Invalid url \"${url}\" passed to ConnectionsService.find`,\n );\n }\n },\n },\n // AWS has no identity field to match against — all accounts live under a\n // single connection. Account selection is handled entirely by the\n // connection type's matchAuth implementation.\n aws: {\n identityFromQuery() {\n return undefined;\n },\n },\n};\n"],"names":["InputError"],"mappings":";;;;AAoCO,MAAM,gBAAA,GAGT;AAAA,EACF,IAAA,EAAM;AAAA,IACJ,aAAA,EAAe,MAAA;AAAA,IACf,kBAAkB,KAAA,EAAO;AACvB,MAAA,MAAM,EAAE,KAAI,GAAI,KAAA;AAChB,MAAA,IAAI;AACF,QAAA,OAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,IAAA;AAAA,MACtB,CAAA,CAAA,MAAQ;AACN,QAAA,MAAM,IAAIA,iBAAA;AAAA,UACR,gBAAgB,GAAG,CAAA,mCAAA;AAAA,SACrB;AAAA,MACF;AAAA,IACF;AAAA,GACF;AAAA;AAAA;AAAA;AAAA,EAIA,GAAA,EAAK;AAAA,IACH,iBAAA,GAAoB;AAClB,MAAA,OAAO,MAAA;AAAA,IACT;AAAA;AAEJ;;;;"}
|