@backstage/backend-app-api 1.7.3-next.0 → 1.7.3-next.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @backstage/backend-app-api
2
2
 
3
+ ## 1.7.3-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 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.
8
+ - Updated dependencies
9
+ - @backstage/connections@0.3.0-next.2
10
+
3
11
  ## 1.7.3-next.0
4
12
 
5
13
  ### 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
- if ("$$type" in feature) {
8
- return feature;
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
- if ("default" in feature) {
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 feature) {\n return feature;\n }\n\n if ('default' in feature) {\n return feature.default;\n }\n\n return feature;\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,YAAY,OAAA,EAAS;AACvB,IAAA,OAAO,OAAA;AAAA,EACT;AAEA,EAAA,IAAI,aAAa,OAAA,EAAS;AACxB,IAAA,OAAO,OAAA,CAAQ,OAAA;AAAA,EACjB;AAEA,EAAA,OAAO,OAAA;AACT;;;;;"}
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,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var lookup = require('./lookup.cjs.js');
4
+ var lookupStrategies = require('./lookupStrategies.cjs.js');
4
5
  var errors = require('@backstage/errors');
5
6
  var v4 = require('zod/v4');
6
7
  var getLegacyIntegrations = require('./getLegacyIntegrations.cjs.js');
@@ -19,6 +20,16 @@ function describeError(error) {
19
20
  }
20
21
  return e.message;
21
22
  }
23
+ function getLookupStrategy(name) {
24
+ return lookupStrategies.lookupStrategies[name];
25
+ }
26
+ function connectionIdentityOf(strategy, connection) {
27
+ if (!strategy.identityField) {
28
+ return void 0;
29
+ }
30
+ const value = connection[strategy.identityField];
31
+ return typeof value === "string" ? value : void 0;
32
+ }
22
33
  class PluginConnectionsService {
23
34
  logger;
24
35
  connections;
@@ -30,40 +41,40 @@ class PluginConnectionsService {
30
41
  const result = await this.findOptional(options);
31
42
  if (!result) {
32
43
  throw new errors.NotFoundError(
33
- `Connection not found for type "${options.type}" matching url "${options.url}"`
44
+ `Connection not found for type "${options.type}"`
34
45
  );
35
46
  }
36
47
  return result;
37
48
  }
38
49
  async findOptional({
39
50
  type,
40
- url,
51
+ query,
41
52
  authMethods
42
53
  }) {
54
+ const connectionType = lookup.getConnectionType(type);
55
+ const strategy = getLookupStrategy(connectionType.lookupStrategy);
56
+ const identity = strategy.identityFromQuery(query);
43
57
  this.logger.debug(
44
- `Finding connection of type "${type}" matching url "${url}"`
58
+ `Finding connection of type "${type}"${identity ? ` matching ${strategy.identityField} "${identity}"` : ""}`
45
59
  );
46
- let host;
47
- try {
48
- host = new URL(url).host;
49
- } catch {
50
- throw new errors.InputError(
51
- `Invalid url "${url}" passed to ConnectionsService.find`
60
+ let connection;
61
+ if (identity !== void 0) {
62
+ connection = this.connections.find(
63
+ (c) => c.type === type && connectionIdentityOf(strategy, c) === identity
52
64
  );
65
+ } else {
66
+ connection = this.connections.find((c) => c.type === type);
53
67
  }
54
- const connection = this.connections.find(
55
- (c) => c.type === type && c.host === host
56
- );
57
68
  if (!connection) {
58
69
  return void 0;
59
70
  }
60
71
  if (connection.auth.length === 0) {
61
72
  throw new errors.NotAllowedError(
62
- `Connection of type "${type}" for host "${host}" has no auth method available to this plugin`
73
+ `Connection of type "${type}"${identity ? ` for ${strategy.identityField} "${identity}"` : ""} has no auth method available to this plugin`
63
74
  );
64
75
  }
65
- const matchAuth = lookup.getConnectionType(type).matchAuth;
66
- const selected = matchAuth ? matchAuth(connection.auth, url) : connection.auth[0];
76
+ const matchAuth = connectionType.matchAuth;
77
+ const selected = matchAuth ? matchAuth(connection.auth, query) : connection.auth[0];
67
78
  if (!selected) {
68
79
  return void 0;
69
80
  }
@@ -72,6 +83,9 @@ class PluginConnectionsService {
72
83
  `Connection not found for type "${type}" with auth method "${selected.method}"`
73
84
  );
74
85
  }
86
+ this.logger.debug(
87
+ `Selected connection of type "${type}"${identity ? ` for ${strategy.identityField} "${identity}"` : ""} using auth method "${selected.method}"`
88
+ );
75
89
  return {
76
90
  ...connection,
77
91
  auth: selected
@@ -102,6 +116,9 @@ class DefaultConnectionsService {
102
116
  const fromConfig = this.#validateConfig(
103
117
  rawConnections ?? []
104
118
  );
119
+ this.logger.debug(
120
+ `Connections configuration resolved ${legacy.length} connection${legacy.length === 1 ? "" : "s"} from legacy integrations and ${fromConfig.length} explicit connection${fromConfig.length === 1 ? "" : "s"}`
121
+ );
105
122
  if (legacy.length === 0 && fromConfig.length === 0) {
106
123
  return;
107
124
  }
@@ -110,11 +127,13 @@ class DefaultConnectionsService {
110
127
  );
111
128
  const seen = /* @__PURE__ */ new Set();
112
129
  for (const c of this.connections) {
113
- const host = c.host;
114
- const key = `${c.type} ${host}`;
130
+ const connectionType = lookup.getConnectionType(c.type);
131
+ const strategy = getLookupStrategy(connectionType.lookupStrategy);
132
+ const identity = connectionIdentityOf(strategy, c);
133
+ const key = `${c.type} ${identity ?? ""}`;
115
134
  if (seen.has(key)) {
116
135
  throw new errors.InputError(
117
- `Duplicate connection of type "${c.type}" for host "${host}"`
136
+ identity !== void 0 ? `Duplicate connection of type "${c.type}" for ${strategy.identityField} "${identity}"` : `Duplicate connection of type "${c.type}"`
118
137
  );
119
138
  }
120
139
  seen.add(key);
@@ -164,15 +183,44 @@ ${describeError(
164
183
  if (!lookup.isConnectionTypeKey(connection.type)) {
165
184
  throw new errors.InputError(`Unrecognised connection type ${connection.type}`);
166
185
  }
167
- const parsed = lookup.getConnectionType(connection.type).configSchema.parse(
168
- connection
169
- );
170
- if (parsed.auth.length === 0) {
186
+ const connectionType = lookup.getConnectionType(connection.type);
187
+ const rawAuth = connection.auth;
188
+ if (!Array.isArray(rawAuth) || rawAuth.length === 0) {
171
189
  throw new errors.InputError(
172
190
  `Connection of type "${connection.type}" must configure at least one auth method`
173
191
  );
174
192
  }
175
- return parsed;
193
+ const auth = rawAuth.map((entry) => {
194
+ if (typeof entry.method !== "string") {
195
+ throw new errors.InputError(
196
+ `Auth entry for connection type "${connection.type}" is missing a "method" field`
197
+ );
198
+ }
199
+ const authMethod = connectionType.authMethods.find(
200
+ (am) => am.method === entry.method
201
+ );
202
+ if (!authMethod) {
203
+ throw new errors.InputError(
204
+ `Unknown auth method "${entry.method}" for connection type "${connection.type}"`
205
+ );
206
+ }
207
+ const { method, title: title2, match: match2, ...rest } = entry;
208
+ return {
209
+ ...authMethod.configSchema.parse(rest),
210
+ method,
211
+ title: title2,
212
+ match: match2
213
+ };
214
+ });
215
+ const { type, auth: _, title, match, ...configFields } = connection;
216
+ const parsed = connectionType.configSchema.parse(configFields);
217
+ return {
218
+ ...parsed,
219
+ type: connection.type,
220
+ title,
221
+ match,
222
+ auth
223
+ };
176
224
  }
177
225
  #assignDefaultTitles() {
178
226
  const typeCounts = /* @__PURE__ */ new Map();
@@ -183,9 +231,13 @@ ${describeError(
183
231
  for (const c of this.connections) {
184
232
  if (!c.title) {
185
233
  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;
234
+ const connectionType = lookup.getConnectionType(type);
235
+ const displayName = connectionType.title;
236
+ const identity = connectionIdentityOf(
237
+ getLookupStrategy(connectionType.lookupStrategy),
238
+ c
239
+ );
240
+ c.title = typeCounts.get(type) > 1 && identity ? `${displayName} (${identity})` : displayName;
189
241
  }
190
242
  }
191
243
  }
@@ -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} from '@backstage/connections';\nimport { getConnectionType, isConnectionTypeKey } from './lookup';\nimport { lookupStrategies } from './lookupStrategies';\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\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: 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 this.logger.debug(\n `Connections configuration resolved ${legacy.length} connection${\n legacy.length === 1 ? '' : 's'\n } from legacy integrations and ${fromConfig.length} explicit connection${\n fromConfig.length === 1 ? '' : 's'\n }`,\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 connectionType = getConnectionType(c.type as ConnectionTypeKey);\n const strategy = getLookupStrategy(connectionType.lookupStrategy);\n const identity = connectionIdentityOf(strategy, c);\n const key = `${c.type} ${identity ?? ''}`;\n if (seen.has(key)) {\n throw new InputError(\n identity !== undefined\n ? `Duplicate connection of type \"${c.type}\" for ${strategy.identityField} \"${identity}\"`\n : `Duplicate connection of type \"${c.type}\"`,\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 connectionType = getConnectionType(connection.type);\n\n const rawAuth = connection.auth;\n if (!Array.isArray(rawAuth) || rawAuth.length === 0) {\n throw new InputError(\n `Connection of type \"${connection.type}\" must configure at least one auth method`,\n );\n }\n\n const auth = (rawAuth as JsonObject[]).map(entry => {\n if (typeof entry.method !== 'string') {\n throw new InputError(\n `Auth entry for connection type \"${connection.type}\" is missing a \"method\" field`,\n );\n }\n const authMethod = connectionType.authMethods.find(\n am => am.method === entry.method,\n );\n if (!authMethod) {\n throw new InputError(\n `Unknown auth method \"${entry.method}\" for connection type \"${connection.type}\"`,\n );\n }\n const { method, title, match, ...rest } = entry;\n return {\n ...authMethod.configSchema.parse(rest),\n method,\n title: title as string | undefined,\n match: match as { plugins: string[] } | undefined,\n } as RootConnection['auth'][number];\n });\n\n const { type, auth: _, title, match, ...configFields } = connection;\n const parsed = connectionType.configSchema.parse(configFields);\n\n return {\n ...parsed,\n type: connection.type,\n title: title as string | undefined,\n match: match as { plugins: string[] } | undefined,\n auth,\n } as RootConnection;\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 connectionType = getConnectionType(type);\n const displayName = connectionType.title;\n const identity = connectionIdentityOf(\n getLookupStrategy(connectionType.lookupStrategy),\n c,\n );\n (c as { title?: string }).title =\n typeCounts.get(type)! > 1 && identity\n ? `${displayName} (${identity})`\n : 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","lookupStrategies","NotFoundError","getConnectionType","NotAllowedError","getLegacyIntegrations","InputError","combineConnectionSources","isConnectionTypeKey","title","match"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+
5
+ const lookupStrategies = {
6
+ host: {
7
+ identityField: "host",
8
+ identityFromQuery(query) {
9
+ try {
10
+ return new URL(query.url).host;
11
+ } catch {
12
+ throw new errors.InputError(
13
+ `Invalid url "${query.url}" passed to ConnectionsService.find`
14
+ );
15
+ }
16
+ }
17
+ },
18
+ aws: {
19
+ identityFromQuery(_query) {
20
+ return void 0;
21
+ }
22
+ }
23
+ };
24
+
25
+ exports.lookupStrategies = lookupStrategies;
26
+ //# 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';\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 * @internal\n */\nexport const lookupStrategies = {\n host: {\n identityField: 'host' as const,\n identityFromQuery(query: { url: string }): string | undefined {\n try {\n return new URL(query.url).host;\n } catch {\n throw new InputError(\n `Invalid url \"${query.url}\" passed to ConnectionsService.find`,\n );\n }\n },\n },\n aws: {\n identityFromQuery(_query: {\n accountId?: string;\n arn?: string;\n }): string | undefined {\n return undefined;\n },\n },\n};\n"],"names":["InputError"],"mappings":";;;;AAwBO,MAAM,gBAAA,GAAmB;AAAA,EAC9B,IAAA,EAAM;AAAA,IACJ,aAAA,EAAe,MAAA;AAAA,IACf,kBAAkB,KAAA,EAA4C;AAC5D,MAAA,IAAI;AACF,QAAA,OAAO,IAAI,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,IAAA;AAAA,MAC5B,CAAA,CAAA,MAAQ;AACN,QAAA,MAAM,IAAIA,iBAAA;AAAA,UACR,CAAA,aAAA,EAAgB,MAAM,GAAG,CAAA,mCAAA;AAAA,SAC3B;AAAA,MACF;AAAA,IACF;AAAA,GACF;AAAA,EACA,GAAA,EAAK;AAAA,IACH,kBAAkB,MAAA,EAGK;AACrB,MAAA,OAAO,MAAA;AAAA,IACT;AAAA;AAEJ;;;;"}
@@ -12,6 +12,7 @@ var service = require('../connections-node/src/service.cjs.js');
12
12
  require('../connections-node/src/lookup.cjs.js');
13
13
  require('zod/v4');
14
14
  var withDeclaredConnections = require('./withDeclaredConnections.cjs.js');
15
+ var validateBackendFeature = require('./validateBackendFeature.cjs.js');
15
16
  var OpaqueExtensionPointFactoryMiddleware = require('../backend-internal/src/wiring/OpaqueExtensionPointFactoryMiddleware.cjs.js');
16
17
  var helpers = require('../backend-internal/src/wiring/helpers.cjs.js');
17
18
 
@@ -74,9 +75,8 @@ function collectCallerConnectionRegistrations(registrations) {
74
75
  }
75
76
  return byCaller;
76
77
  }
77
- function createRootInstanceMetadataServiceFactory(rawRegistrations) {
78
+ function createRootInstanceMetadataServiceFactory(registrations) {
78
79
  const installedPlugins = /* @__PURE__ */ new Map();
79
- const registrations = rawRegistrations.filter((registration) => registration.featureType === "registrations").flatMap((registration) => registration.getRegistrations());
80
80
  const plugins = registrations.filter(
81
81
  (registration) => registration.type === "plugin" || registration.type === "plugin-v1.1"
82
82
  );
@@ -119,6 +119,7 @@ class BackendInitializer {
119
119
  #startPromise;
120
120
  #stopPromise;
121
121
  #registrations = new Array();
122
+ #allRegistrations = new Array();
122
123
  #extensionPoints = /* @__PURE__ */ new Map();
123
124
  #serviceRegistry;
124
125
  #registeredFeatures = new Array();
@@ -212,19 +213,24 @@ class BackendInitializer {
212
213
  if (this.#startPromise) {
213
214
  throw new Error("feature can not be added after the backend has started");
214
215
  }
215
- this.#registeredFeatures.push(Promise.resolve(feature));
216
+ this.#registeredFeatures.push({
217
+ feature: Promise.resolve(feature),
218
+ source: `features[${this.#registeredFeatures.length}]`
219
+ });
216
220
  }
217
- #addFeature(feature) {
218
- if (isServiceFactory(feature)) {
219
- this.#serviceRegistry.add(feature);
220
- } else if (isBackendFeatureLoader(feature)) {
221
- this.#registeredFeatureLoaders.push(feature);
222
- } else if (isBackendRegistrations(feature)) {
223
- this.#registrations.push(feature);
221
+ #addFeature(feature, source) {
222
+ if (feature.type === "service") {
223
+ this.#serviceRegistry.add(feature.feature, source);
224
+ } else if (feature.type === "loader") {
225
+ this.#registeredFeatureLoaders.push({
226
+ feature: feature.feature,
227
+ source
228
+ });
224
229
  } else {
225
- throw new Error(
226
- `Failed to add feature, invalid feature ${JSON.stringify(feature)}`
227
- );
230
+ this.#registrations.push({
231
+ feature: feature.feature,
232
+ source
233
+ });
228
234
  }
229
235
  }
230
236
  async start() {
@@ -240,12 +246,17 @@ class BackendInitializer {
240
246
  }
241
247
  async #doStart() {
242
248
  this.#serviceRegistry.checkForCircularDeps();
243
- for (const feature of this.#registeredFeatures) {
244
- this.#addFeature(await feature);
249
+ for (const { feature, source: fallbackSource } of this.#registeredFeatures) {
250
+ const resolvedFeature = await feature;
251
+ const source = validateBackendFeature.describeBackendFeature(resolvedFeature) ?? fallbackSource;
252
+ this.#addFeature(validateBackendFeature.validateBackendFeature(resolvedFeature, source), source);
245
253
  }
246
254
  await this.#applyBackendFeatureLoaders(this.#registeredFeatureLoaders);
255
+ this.#allRegistrations = this.#registrations.flatMap(
256
+ ({ feature, source }) => validateBackendFeature.validateBackendRegistrations(feature, source)
257
+ );
247
258
  this.#serviceRegistry.add(
248
- createRootInstanceMetadataServiceFactory(this.#registrations)
259
+ createRootInstanceMetadataServiceFactory(this.#allRegistrations)
249
260
  );
250
261
  if (process.env.NODE_ENV !== "test") {
251
262
  const rootLogger2 = await this.#serviceRegistry.get(
@@ -270,9 +281,7 @@ class BackendInitializer {
270
281
  backendPluginApi.coreServices.rootLogger,
271
282
  "root"
272
283
  );
273
- const allRegistrations = this.#registrations.flatMap(
274
- (f) => f.getRegistrations()
275
- );
284
+ const allRegistrations = this.#allRegistrations;
276
285
  this.#callerConnectionRegistrations = collectCallerConnectionRegistrations(allRegistrations);
277
286
  const allPluginIds = [
278
287
  ...new Set(
@@ -368,8 +377,11 @@ class BackendInitializer {
368
377
  const pluginInits = /* @__PURE__ */ new Map();
369
378
  const moduleInits = /* @__PURE__ */ new Map();
370
379
  for (const r of allRegistrations) {
380
+ const pluginId = "pluginId" in r && typeof r.pluginId === "string" ? r.pluginId : void 0;
381
+ const moduleId = "moduleId" in r && typeof r.moduleId === "string" ? r.moduleId : void 0;
371
382
  const addedExtensionPointIds = [];
372
383
  try {
384
+ validateBackendFeature.validateBackendRegistration(r);
373
385
  const provides = /* @__PURE__ */ new Set();
374
386
  if (r.type === "plugin" || r.type === "module") {
375
387
  for (const [extRef, extImpl] of r.extensionPoints) {
@@ -433,12 +445,12 @@ class BackendInitializer {
433
445
  for (const id of addedExtensionPointIds) {
434
446
  this.#extensionPoints.delete(id);
435
447
  }
436
- if ("pluginId" in r && "moduleId" in r) {
437
- resultCollector.onPluginModuleResult(r.pluginId, r.moduleId, err);
438
- } else if ("pluginId" in r) {
439
- pluginInits.delete(r.pluginId);
440
- moduleInits.delete(r.pluginId);
441
- resultCollector.onPluginResult(r.pluginId, err);
448
+ if (pluginId !== void 0 && moduleId !== void 0) {
449
+ resultCollector.onPluginModuleResult(pluginId, moduleId, err);
450
+ } else if (pluginId !== void 0) {
451
+ pluginInits.delete(pluginId);
452
+ moduleInits.delete(pluginId);
453
+ resultCollector.onPluginResult(pluginId, err);
442
454
  } else {
443
455
  throw err;
444
456
  }
@@ -465,11 +477,9 @@ class BackendInitializer {
465
477
  const rootLifecycleService = await this.#getRootLifecycleImpl();
466
478
  await rootLifecycleService.beforeShutdown();
467
479
  const allPlugins = /* @__PURE__ */ new Set();
468
- for (const feature of this.#registrations) {
469
- for (const r of feature.getRegistrations()) {
470
- if (r.type === "plugin" || r.type === "plugin-v1.1") {
471
- allPlugins.add(r.pluginId);
472
- }
480
+ for (const registration of this.#allRegistrations) {
481
+ if (registration.type === "plugin" || registration.type === "plugin-v1.1") {
482
+ allPlugins.add(registration.pluginId);
473
483
  }
474
484
  }
475
485
  await Promise.allSettled(
@@ -513,10 +523,19 @@ class BackendInitializer {
513
523
  }
514
524
  async #applyBackendFeatureLoaders(loaders) {
515
525
  const servicesAddedByLoaders = /* @__PURE__ */ new Map();
516
- for (const loader of loaders) {
526
+ for (const installedLoader of loaders) {
527
+ const { feature: loader, source: loaderSource } = installedLoader;
528
+ if (loader.deps !== void 0) {
529
+ validateBackendFeature.assertObject(
530
+ loader.deps,
531
+ `${loaderSource}.deps`,
532
+ "a dependency object"
533
+ );
534
+ }
517
535
  const deps = /* @__PURE__ */ new Map();
518
536
  const missingRefs = /* @__PURE__ */ new Set();
519
537
  for (const [name, ref] of Object.entries(loader.deps ?? {})) {
538
+ validateBackendFeature.validateServiceRef(ref, `${loaderSource}.deps.${name}`);
520
539
  if (ref.scope !== "root") {
521
540
  throw new Error(
522
541
  `Feature loaders can only depend on root scoped services, but '${name}' is scoped to '${ref.scope}'. Offending loader is ${loader.description}`
@@ -538,35 +557,64 @@ class BackendInitializer {
538
557
  `No service available for the following ref(s): ${missing}, depended on by feature loader ${loader.description}`
539
558
  );
540
559
  }
541
- const result = await loader.loader(Object.fromEntries(deps)).then((features) => features.map(helpers.unwrapFeature)).catch((error) => {
560
+ let result;
561
+ try {
562
+ result = await loader.loader(Object.fromEntries(deps));
563
+ } catch (error) {
542
564
  throw new errors.ForwardedError(
543
565
  `Feature loader ${loader.description} failed`,
544
566
  error
545
567
  );
568
+ }
569
+ if (!Array.isArray(result)) {
570
+ validateBackendFeature.throwInvalidBackendFeature(
571
+ `${loaderSource} -> loader output`,
572
+ "an array of backend features",
573
+ result
574
+ );
575
+ }
576
+ const loadedFeatures = result.map((feature, index) => {
577
+ const fallbackSource = `${loaderSource} -> loader output[${index}]`;
578
+ const unwrappedFeature = helpers.unwrapFeature(
579
+ feature
580
+ );
581
+ const description = validateBackendFeature.describeBackendFeature(unwrappedFeature);
582
+ const source = description ? `${description} (returned by ${loaderSource}, output[${index}])` : fallbackSource;
583
+ return {
584
+ feature: validateBackendFeature.validateBackendFeature(unwrappedFeature, source),
585
+ source
586
+ };
546
587
  });
547
588
  let didAddServiceFactory = false;
548
589
  const newLoaders = new Array();
549
- for await (const feature of result) {
550
- if (isBackendFeatureLoader(feature)) {
551
- newLoaders.push(feature);
552
- } else {
553
- if (isServiceFactory(feature) && !feature.service.multiton) {
590
+ for (const loadedFeature of loadedFeatures) {
591
+ const { feature, source } = loadedFeature;
592
+ if (feature.type === "loader") {
593
+ newLoaders.push({ feature: feature.feature, source });
594
+ } else if (feature.type === "service") {
595
+ validateBackendFeature.validateServiceRef(feature.feature.service, `${source}.service`);
596
+ if (!feature.feature.service.multiton) {
554
597
  const conflictingLoader = servicesAddedByLoaders.get(
555
- feature.service.id
598
+ feature.feature.service.id
556
599
  );
557
600
  if (conflictingLoader) {
558
601
  throw new Error(
559
- `Duplicate service implementations provided for ${feature.service.id} by both feature loader ${loader.description} and feature loader ${conflictingLoader.description}`
602
+ `Duplicate service implementations provided for ${feature.feature.service.id} by both feature loader ${loader.description} and feature loader ${conflictingLoader.feature.description}`
560
603
  );
561
604
  }
562
- if (!this.#serviceRegistry.hasBeenAdded(feature.service)) {
605
+ if (!this.#serviceRegistry.hasBeenAdded(feature.feature.service)) {
563
606
  didAddServiceFactory = true;
564
- servicesAddedByLoaders.set(feature.service.id, loader);
565
- this.#addFeature(feature);
607
+ servicesAddedByLoaders.set(
608
+ feature.feature.service.id,
609
+ installedLoader
610
+ );
611
+ this.#addFeature(feature, source);
566
612
  }
567
613
  } else {
568
- this.#addFeature(feature);
614
+ this.#addFeature(feature, source);
569
615
  }
616
+ } else {
617
+ this.#addFeature(feature, source);
570
618
  }
571
619
  }
572
620
  if (didAddServiceFactory) {
@@ -578,35 +626,6 @@ class BackendInitializer {
578
626
  }
579
627
  }
580
628
  }
581
- function toInternalBackendFeature(feature) {
582
- if (feature.$$type !== "@backstage/BackendFeature") {
583
- throw new Error(`Invalid BackendFeature, bad type '${feature.$$type}'`);
584
- }
585
- const internal = feature;
586
- if (internal.version !== "v1") {
587
- throw new Error(
588
- `Invalid BackendFeature, bad version '${internal.version}'`
589
- );
590
- }
591
- return internal;
592
- }
593
- function isServiceFactory(feature) {
594
- const internal = toInternalBackendFeature(feature);
595
- if (internal.featureType === "service") {
596
- return true;
597
- }
598
- return "service" in internal;
599
- }
600
- function isBackendRegistrations(feature) {
601
- const internal = toInternalBackendFeature(feature);
602
- if (internal.featureType === "registrations") {
603
- return true;
604
- }
605
- return "getRegistrations" in internal;
606
- }
607
- function isBackendFeatureLoader(feature) {
608
- return toInternalBackendFeature(feature).featureType === "loader";
609
- }
610
629
 
611
630
  exports.BackendInitializer = BackendInitializer;
612
631
  //# sourceMappingURL=BackendInitializer.cjs.js.map