@backstage/backend-app-api 0.5.2 → 0.5.3-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.

Potentially problematic release.


This version of @backstage/backend-app-api might be problematic. Click here for more details.

package/CHANGELOG.md CHANGED
@@ -1,38 +1,59 @@
1
1
  # @backstage/backend-app-api
2
2
 
3
- ## 0.5.2
3
+ ## 0.5.3-next.2
4
4
 
5
5
  ### Patch Changes
6
6
 
7
+ - 37a20c7f14aa: Adds include and exclude configuration to feature discovery of backend packages
8
+ Adds alpha modules to feature discovery
9
+ - 3fc64b9e2f8f: Extension points are now tracked via their ID rather than reference, in order to support package duplication.
10
+ - b219d097b3f4: Backend startup will now fail if any circular service dependencies are detected.
7
11
  - Updated dependencies
8
- - @backstage/backend-common@0.19.4
9
- - @backstage/backend-tasks@0.5.7
10
- - @backstage/plugin-auth-node@0.2.19
11
- - @backstage/plugin-permission-node@0.7.13
12
- - @backstage/backend-plugin-api@0.6.2
12
+ - @backstage/config-loader@1.5.0-next.2
13
+ - @backstage/config@1.1.0-next.1
14
+ - @backstage/backend-tasks@0.5.8-next.2
15
+ - @backstage/backend-common@0.19.5-next.2
16
+ - @backstage/plugin-auth-node@0.3.0-next.2
17
+ - @backstage/plugin-permission-node@0.7.14-next.2
18
+ - @backstage/backend-plugin-api@0.6.3-next.2
19
+ - @backstage/cli-common@0.1.12
20
+ - @backstage/cli-node@0.1.3
21
+ - @backstage/errors@1.2.1
22
+ - @backstage/types@1.1.0
23
+
24
+ ## 0.5.3-next.1
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies
29
+ - @backstage/config@1.1.0-next.0
30
+ - @backstage/backend-tasks@0.5.8-next.1
31
+ - @backstage/backend-common@0.19.5-next.1
32
+ - @backstage/backend-plugin-api@0.6.3-next.1
33
+ - @backstage/config-loader@1.5.0-next.1
34
+ - @backstage/plugin-auth-node@0.3.0-next.1
35
+ - @backstage/plugin-permission-node@0.7.14-next.1
13
36
  - @backstage/cli-common@0.1.12
14
37
  - @backstage/cli-node@0.1.3
15
- - @backstage/config@1.0.8
16
- - @backstage/config-loader@1.4.0
17
38
  - @backstage/errors@1.2.1
18
39
  - @backstage/types@1.1.0
19
40
 
20
- ## 0.5.1
41
+ ## 0.5.2-next.0
21
42
 
22
43
  ### Patch Changes
23
44
 
24
45
  - Updated dependencies
25
- - @backstage/backend-common@0.19.3
26
- - @backstage/backend-tasks@0.5.6
27
- - @backstage/plugin-auth-node@0.2.18
28
- - @backstage/plugin-permission-node@0.7.12
29
- - @backstage/backend-plugin-api@0.6.1
46
+ - @backstage/plugin-auth-node@0.3.0-next.0
47
+ - @backstage/backend-common@0.19.4-next.0
48
+ - @backstage/config-loader@1.5.0-next.0
49
+ - @backstage/backend-tasks@0.5.7-next.0
50
+ - @backstage/backend-plugin-api@0.6.2-next.0
30
51
  - @backstage/cli-common@0.1.12
31
52
  - @backstage/cli-node@0.1.3
32
53
  - @backstage/config@1.0.8
33
- - @backstage/config-loader@1.4.0
34
54
  - @backstage/errors@1.2.1
35
55
  - @backstage/types@1.1.0
56
+ - @backstage/plugin-permission-node@0.7.13-next.0
36
57
 
37
58
  ## 0.5.0
38
59
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-app-api",
3
- "version": "0.5.2",
3
+ "version": "0.5.3-next.2",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/dist/alpha.cjs.js CHANGED
@@ -11,7 +11,7 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
11
11
 
12
12
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
13
13
 
14
- const LOADED_PACKAGE_ROLES = ["backend-plugin", "backend-module"];
14
+ const LOADED_PACKAGE_ROLES = ["backend-plugin", "backend-plugin-module"];
15
15
  async function findClosestPackageDir(searchDir) {
16
16
  let path$1 = searchDir;
17
17
  for (let i = 0; i < 1e3; i++) {
@@ -31,23 +31,39 @@ async function findClosestPackageDir(searchDir) {
31
31
  );
32
32
  }
33
33
  class PackageDiscoveryService {
34
- constructor(config) {
34
+ constructor(config, logger) {
35
35
  this.config = config;
36
+ this.logger = logger;
37
+ }
38
+ getDependencyNames(path) {
39
+ const { dependencies } = require(path);
40
+ const packagesConfig = this.config.getOptional("backend.packages");
41
+ const dependencyNames = Object.keys(dependencies || {});
42
+ if (packagesConfig === "all") {
43
+ return dependencyNames;
44
+ }
45
+ const includedPackagesConfig = this.config.getOptionalStringArray(
46
+ "backend.packages.include"
47
+ );
48
+ const includedPackages = includedPackagesConfig ? new Set(includedPackagesConfig) : dependencyNames;
49
+ const excludedPackagesSet = new Set(
50
+ this.config.getOptionalStringArray("backend.packages.exclude")
51
+ );
52
+ return [...includedPackages].filter((name) => !excludedPackagesSet.has(name));
36
53
  }
37
54
  async getBackendFeatures() {
38
55
  var _a, _b;
39
- if (this.config.getOptionalString("backend.packages") !== "all") {
56
+ const packagesConfig = this.config.getOptional("backend.packages");
57
+ if (!packagesConfig || Object.keys(packagesConfig).length === 0) {
40
58
  return { features: [] };
41
59
  }
42
60
  const packageDir = await findClosestPackageDir(process.argv[1]);
43
61
  if (!packageDir) {
44
62
  throw new Error("Package discovery failed to find package.json");
45
63
  }
46
- const { dependencies } = require(path.resolve(
47
- packageDir,
48
- "package.json"
49
- ));
50
- const dependencyNames = Object.keys(dependencies || {});
64
+ const dependencyNames = this.getDependencyNames(
65
+ path.resolve(packageDir, "package.json")
66
+ );
51
67
  const features = [];
52
68
  for (const name of dependencyNames) {
53
69
  const depPkg = require(require.resolve(`${name}/package.json`, {
@@ -56,13 +72,28 @@ class PackageDiscoveryService {
56
72
  if (!LOADED_PACKAGE_ROLES.includes((_b = (_a = depPkg == null ? void 0 : depPkg.backstage) == null ? void 0 : _a.role) != null ? _b : "")) {
57
73
  continue;
58
74
  }
59
- const depModule = require(require.resolve(name, { paths: [packageDir] }));
60
- for (const exportValue of Object.values(depModule)) {
61
- if (isBackendFeature(exportValue)) {
62
- features.push(exportValue);
63
- }
64
- if (isBackendFeatureFactory(exportValue)) {
65
- features.push(exportValue());
75
+ const exportedModulePaths = [
76
+ require.resolve(name, {
77
+ paths: [packageDir]
78
+ })
79
+ ];
80
+ try {
81
+ exportedModulePaths.push(
82
+ require.resolve(`${name}/alpha`, { paths: [packageDir] })
83
+ );
84
+ } catch {
85
+ }
86
+ for (const modulePath of exportedModulePaths) {
87
+ const module = require(modulePath);
88
+ for (const [exportName, exportValue] of Object.entries(module)) {
89
+ if (isBackendFeature(exportValue)) {
90
+ this.logger.info(`Detected: ${name}#${exportName}`);
91
+ features.push(exportValue);
92
+ }
93
+ if (isBackendFeatureFactory(exportValue)) {
94
+ this.logger.info(`Detected: ${name}#${exportName}`);
95
+ features.push(exportValue());
96
+ }
66
97
  }
67
98
  }
68
99
  }
@@ -72,10 +103,11 @@ class PackageDiscoveryService {
72
103
  const featureDiscoveryServiceFactory = backendPluginApi.createServiceFactory({
73
104
  service: alpha.featureDiscoveryServiceRef,
74
105
  deps: {
75
- config: backendPluginApi.coreServices.rootConfig
106
+ config: backendPluginApi.coreServices.rootConfig,
107
+ logger: backendPluginApi.coreServices.rootLogger
76
108
  },
77
- factory({ config }) {
78
- return new PackageDiscoveryService(config);
109
+ factory({ config, logger }) {
110
+ return new PackageDiscoveryService(config, logger);
79
111
  }
80
112
  });
81
113
  function isBackendFeature(value) {
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.cjs.js","sources":["../src/alpha/featureDiscoveryServiceFactory.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 {\n BackendFeature,\n RootConfigService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n featureDiscoveryServiceRef,\n FeatureDiscoveryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { resolve as resolvePath, dirname } from 'path';\nimport fs from 'fs-extra';\nimport { BackstagePackageJson } from '@backstage/cli-node';\n\nconst LOADED_PACKAGE_ROLES = ['backend-plugin', 'backend-module'];\n\n/** @internal */\nasync function findClosestPackageDir(\n searchDir: string,\n): Promise<string | undefined> {\n let path = searchDir;\n\n // Some confidence check to avoid infinite loop\n for (let i = 0; i < 1000; i++) {\n const packagePath = resolvePath(path, 'package.json');\n const exists = await fs.pathExists(packagePath);\n if (exists) {\n return path;\n }\n\n const newPath = dirname(path);\n if (newPath === path) {\n return undefined;\n }\n path = newPath;\n }\n\n throw new Error(\n `Iteration limit reached when searching for root package.json at ${searchDir}`,\n );\n}\n\n/** @internal */\nclass PackageDiscoveryService implements FeatureDiscoveryService {\n constructor(private readonly config: RootConfigService) {}\n\n async getBackendFeatures(): Promise<{ features: Array<BackendFeature> }> {\n if (this.config.getOptionalString('backend.packages') !== 'all') {\n return { features: [] };\n }\n\n const packageDir = await findClosestPackageDir(process.argv[1]);\n if (!packageDir) {\n throw new Error('Package discovery failed to find package.json');\n }\n const { dependencies } = require(resolvePath(\n packageDir,\n 'package.json',\n )) as BackstagePackageJson;\n const dependencyNames = Object.keys(dependencies || {});\n\n const features: BackendFeature[] = [];\n\n for (const name of dependencyNames) {\n const depPkg = require(require.resolve(`${name}/package.json`, {\n paths: [packageDir],\n })) as BackstagePackageJson;\n if (!LOADED_PACKAGE_ROLES.includes(depPkg?.backstage?.role ?? '')) {\n continue;\n }\n const depModule = require(require.resolve(name, { paths: [packageDir] }));\n for (const exportValue of Object.values(depModule)) {\n if (isBackendFeature(exportValue)) {\n features.push(exportValue);\n }\n if (isBackendFeatureFactory(exportValue)) {\n features.push(exportValue());\n }\n }\n }\n\n return { features };\n }\n}\n\n/** @alpha */\nexport const featureDiscoveryServiceFactory = createServiceFactory({\n service: featureDiscoveryServiceRef,\n deps: {\n config: coreServices.rootConfig,\n },\n factory({ config }) {\n return new PackageDiscoveryService(config);\n },\n});\n\nfunction isBackendFeature(value: unknown): value is BackendFeature {\n return (\n !!value &&\n typeof value === 'object' &&\n (value as BackendFeature).$$type === '@backstage/BackendFeature'\n );\n}\n\nfunction isBackendFeatureFactory(\n value: unknown,\n): value is () => BackendFeature {\n return (\n !!value &&\n typeof value === 'function' &&\n (value as any).$$type === '@backstage/BackendFeatureFactory'\n );\n}\n"],"names":["path","resolvePath","fs","dirname","createServiceFactory","featureDiscoveryServiceRef","coreServices"],"mappings":"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"}
1
+ {"version":3,"file":"alpha.cjs.js","sources":["../src/alpha/featureDiscoveryServiceFactory.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 {\n BackendFeature,\n RootConfigService,\n RootLoggerService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n featureDiscoveryServiceRef,\n FeatureDiscoveryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { resolve as resolvePath, dirname } from 'path';\nimport fs from 'fs-extra';\nimport { BackstagePackageJson } from '@backstage/cli-node';\n\nconst LOADED_PACKAGE_ROLES = ['backend-plugin', 'backend-plugin-module'];\n\n/** @internal */\nasync function findClosestPackageDir(\n searchDir: string,\n): Promise<string | undefined> {\n let path = searchDir;\n\n // Some confidence check to avoid infinite loop\n for (let i = 0; i < 1000; i++) {\n const packagePath = resolvePath(path, 'package.json');\n const exists = await fs.pathExists(packagePath);\n if (exists) {\n return path;\n }\n\n const newPath = dirname(path);\n if (newPath === path) {\n return undefined;\n }\n path = newPath;\n }\n\n throw new Error(\n `Iteration limit reached when searching for root package.json at ${searchDir}`,\n );\n}\n\n/** @internal */\nclass PackageDiscoveryService implements FeatureDiscoveryService {\n constructor(\n private readonly config: RootConfigService,\n private readonly logger: RootLoggerService,\n ) {}\n\n getDependencyNames(path: string) {\n const { dependencies } = require(path) as BackstagePackageJson;\n const packagesConfig = this.config.getOptional('backend.packages');\n\n const dependencyNames = Object.keys(dependencies || {});\n\n if (packagesConfig === 'all') {\n return dependencyNames;\n }\n\n const includedPackagesConfig = this.config.getOptionalStringArray(\n 'backend.packages.include',\n );\n\n const includedPackages = includedPackagesConfig\n ? new Set(includedPackagesConfig)\n : dependencyNames;\n const excludedPackagesSet = new Set(\n this.config.getOptionalStringArray('backend.packages.exclude'),\n );\n\n return [...includedPackages].filter(name => !excludedPackagesSet.has(name));\n }\n\n async getBackendFeatures(): Promise<{ features: Array<BackendFeature> }> {\n const packagesConfig = this.config.getOptional('backend.packages');\n if (!packagesConfig || Object.keys(packagesConfig).length === 0) {\n return { features: [] };\n }\n\n const packageDir = await findClosestPackageDir(process.argv[1]);\n if (!packageDir) {\n throw new Error('Package discovery failed to find package.json');\n }\n const dependencyNames = this.getDependencyNames(\n resolvePath(packageDir, 'package.json'),\n );\n\n const features: BackendFeature[] = [];\n\n for (const name of dependencyNames) {\n const depPkg = require(require.resolve(`${name}/package.json`, {\n paths: [packageDir],\n })) as BackstagePackageJson;\n if (!LOADED_PACKAGE_ROLES.includes(depPkg?.backstage?.role ?? '')) {\n continue;\n }\n\n const exportedModulePaths = [\n require.resolve(name, {\n paths: [packageDir],\n }),\n ];\n\n // Find modules exported as alpha\n try {\n exportedModulePaths.push(\n require.resolve(`${name}/alpha`, { paths: [packageDir] }),\n );\n } catch {\n /* ignore */\n }\n\n for (const modulePath of exportedModulePaths) {\n const module = require(modulePath);\n for (const [exportName, exportValue] of Object.entries(module)) {\n if (isBackendFeature(exportValue)) {\n this.logger.info(`Detected: ${name}#${exportName}`);\n features.push(exportValue);\n }\n if (isBackendFeatureFactory(exportValue)) {\n this.logger.info(`Detected: ${name}#${exportName}`);\n features.push(exportValue());\n }\n }\n }\n }\n\n return { features };\n }\n}\n\n/** @alpha */\nexport const featureDiscoveryServiceFactory = createServiceFactory({\n service: featureDiscoveryServiceRef,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.rootLogger,\n },\n factory({ config, logger }) {\n return new PackageDiscoveryService(config, logger);\n },\n});\n\nfunction isBackendFeature(value: unknown): value is BackendFeature {\n return (\n !!value &&\n typeof value === 'object' &&\n (value as BackendFeature).$$type === '@backstage/BackendFeature'\n );\n}\n\nfunction isBackendFeatureFactory(\n value: unknown,\n): value is () => BackendFeature {\n return (\n !!value &&\n typeof value === 'function' &&\n (value as any).$$type === '@backstage/BackendFeatureFactory'\n );\n}\n"],"names":["path","resolvePath","fs","dirname","createServiceFactory","featureDiscoveryServiceRef","coreServices"],"mappings":"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"}
package/dist/index.cjs.js CHANGED
@@ -1093,7 +1093,11 @@ var __privateSet$4 = (obj, member, value, setter) => {
1093
1093
  setter ? setter.call(obj, value) : member.set(obj, value);
1094
1094
  return value;
1095
1095
  };
1096
- var _nodes, _allProvided;
1096
+ var __privateMethod$3 = (obj, member, method) => {
1097
+ __accessCheck$4(obj, member, "access private method");
1098
+ return method;
1099
+ };
1100
+ var _nodeIds, _cycleKeys, _getCycleKey, getCycleKey_fn, _nodes, _allProvided;
1097
1101
  class Node {
1098
1102
  constructor(value, consumes, provides) {
1099
1103
  this.value = value;
@@ -1108,6 +1112,33 @@ class Node {
1108
1112
  );
1109
1113
  }
1110
1114
  }
1115
+ const _CycleKeySet = class _CycleKeySet {
1116
+ constructor(nodes) {
1117
+ __privateAdd$4(this, _getCycleKey);
1118
+ __privateAdd$4(this, _nodeIds, void 0);
1119
+ __privateAdd$4(this, _cycleKeys, void 0);
1120
+ __privateSet$4(this, _nodeIds, new Map(nodes.map((n, i) => [n.value, i])));
1121
+ __privateSet$4(this, _cycleKeys, /* @__PURE__ */ new Set());
1122
+ }
1123
+ static from(nodes) {
1124
+ return new _CycleKeySet(nodes);
1125
+ }
1126
+ tryAdd(path) {
1127
+ const cycleKey = __privateMethod$3(this, _getCycleKey, getCycleKey_fn).call(this, path);
1128
+ if (__privateGet$4(this, _cycleKeys).has(cycleKey)) {
1129
+ return false;
1130
+ }
1131
+ __privateGet$4(this, _cycleKeys).add(cycleKey);
1132
+ return true;
1133
+ }
1134
+ };
1135
+ _nodeIds = new WeakMap();
1136
+ _cycleKeys = new WeakMap();
1137
+ _getCycleKey = new WeakSet();
1138
+ getCycleKey_fn = function(path) {
1139
+ return path.map((n) => __privateGet$4(this, _nodeIds).get(n)).sort().join(",");
1140
+ };
1141
+ let CycleKeySet = _CycleKeySet;
1111
1142
  const _DependencyGraph = class _DependencyGraph {
1112
1143
  constructor(nodes) {
1113
1144
  __privateAdd$4(this, _nodes, void 0);
@@ -1135,7 +1166,9 @@ const _DependencyGraph = class _DependencyGraph {
1135
1166
  }
1136
1167
  return new _DependencyGraph(nodes);
1137
1168
  }
1138
- // Find all nodes that consume dependencies that are not provided by any other node
1169
+ /**
1170
+ * Find all nodes that consume dependencies that are not provided by any other node.
1171
+ */
1139
1172
  findUnsatisfiedDeps() {
1140
1173
  const unsatisfiedDependencies = [];
1141
1174
  for (const node of __privateGet$4(this, _nodes).values()) {
@@ -1148,9 +1181,19 @@ const _DependencyGraph = class _DependencyGraph {
1148
1181
  }
1149
1182
  return unsatisfiedDependencies;
1150
1183
  }
1151
- // Detect circular dependencies within the graph, returning the path of nodes that
1152
- // form a cycle, with the same node as the first and last element of the array.
1184
+ /**
1185
+ * Detect the first circular dependency within the graph, returning the path of nodes that
1186
+ * form a cycle, with the same node as the first and last element of the array.
1187
+ */
1153
1188
  detectCircularDependency() {
1189
+ return this.detectCircularDependencies().next().value;
1190
+ }
1191
+ /**
1192
+ * Detect circular dependencies within the graph, returning the path of nodes that
1193
+ * form a cycle, with the same node as the first and last element of the array.
1194
+ */
1195
+ *detectCircularDependencies() {
1196
+ const cycleKeys = CycleKeySet.from(__privateGet$4(this, _nodes));
1154
1197
  for (const startNode of __privateGet$4(this, _nodes)) {
1155
1198
  const visited = /* @__PURE__ */ new Set();
1156
1199
  const stack = new Array([
@@ -1163,16 +1206,19 @@ const _DependencyGraph = class _DependencyGraph {
1163
1206
  continue;
1164
1207
  }
1165
1208
  visited.add(node);
1166
- for (const produced of node.provides) {
1167
- const consumerNodes = __privateGet$4(this, _nodes).filter(
1168
- (other) => other.consumes.has(produced)
1209
+ for (const consumed of node.consumes) {
1210
+ const providerNodes = __privateGet$4(this, _nodes).filter(
1211
+ (other) => other.provides.has(consumed)
1169
1212
  );
1170
- for (const consumer of consumerNodes) {
1171
- if (consumer === startNode) {
1172
- return [...path, startNode.value];
1213
+ for (const provider of providerNodes) {
1214
+ if (provider === startNode) {
1215
+ if (cycleKeys.tryAdd(path)) {
1216
+ yield [...path, startNode.value];
1217
+ }
1218
+ break;
1173
1219
  }
1174
- if (!visited.has(consumer)) {
1175
- stack.push([consumer, [...path, consumer.value]]);
1220
+ if (!visited.has(provider)) {
1221
+ stack.push([provider, [...path, provider.value]]);
1176
1222
  }
1177
1223
  }
1178
1224
  }
@@ -1260,7 +1306,7 @@ var __privateMethod$2 = (obj, member, method) => {
1260
1306
  __accessCheck$3(obj, member, "access private method");
1261
1307
  return method;
1262
1308
  };
1263
- var _providedFactories, _loadedDefaultFactories, _implementations, _rootServiceImplementations, _resolveFactory, resolveFactory_fn, _checkForMissingDeps, checkForMissingDeps_fn;
1309
+ var _providedFactories, _loadedDefaultFactories, _implementations, _rootServiceImplementations, _resolveFactory, resolveFactory_fn, _checkForMissingDeps, checkForMissingDeps_fn, _checkForCircularDeps, checkForCircularDeps_fn;
1264
1310
  function toInternalServiceFactory(factory) {
1265
1311
  const f = factory;
1266
1312
  if (f.$$type !== "@backstage/BackendFeature") {
@@ -1278,10 +1324,11 @@ const pluginMetadataServiceFactory = backendPluginApi.createServiceFactory(
1278
1324
  factory: async () => ({ getId: () => options == null ? void 0 : options.pluginId })
1279
1325
  })
1280
1326
  );
1281
- class ServiceRegistry {
1327
+ const _ServiceRegistry = class _ServiceRegistry {
1282
1328
  constructor(factories) {
1283
1329
  __privateAdd$3(this, _resolveFactory);
1284
1330
  __privateAdd$3(this, _checkForMissingDeps);
1331
+ __privateAdd$3(this, _checkForCircularDeps);
1285
1332
  __privateAdd$3(this, _providedFactories, void 0);
1286
1333
  __privateAdd$3(this, _loadedDefaultFactories, void 0);
1287
1334
  __privateAdd$3(this, _implementations, void 0);
@@ -1292,6 +1339,12 @@ class ServiceRegistry {
1292
1339
  __privateSet$3(this, _loadedDefaultFactories, /* @__PURE__ */ new Map());
1293
1340
  __privateSet$3(this, _implementations, /* @__PURE__ */ new Map());
1294
1341
  }
1342
+ static create(factories) {
1343
+ var _a;
1344
+ const registry = new _ServiceRegistry(factories);
1345
+ __privateMethod$2(_a = registry, _checkForCircularDeps, checkForCircularDeps_fn).call(_a);
1346
+ return registry;
1347
+ }
1295
1348
  getServiceRefs() {
1296
1349
  return Array.from(__privateGet$3(this, _providedFactories).values()).map((f) => f.service);
1297
1350
  }
@@ -1367,7 +1420,7 @@ class ServiceRegistry {
1367
1420
  return result;
1368
1421
  });
1369
1422
  }
1370
- }
1423
+ };
1371
1424
  _providedFactories = new WeakMap();
1372
1425
  _loadedDefaultFactories = new WeakMap();
1373
1426
  _implementations = new WeakMap();
@@ -1420,6 +1473,25 @@ checkForMissingDeps_fn = function(factory, pluginId) {
1420
1473
  );
1421
1474
  }
1422
1475
  };
1476
+ _checkForCircularDeps = new WeakSet();
1477
+ checkForCircularDeps_fn = function() {
1478
+ const graph = DependencyGraph.fromIterable(
1479
+ Array.from(__privateGet$3(this, _providedFactories)).map(
1480
+ ([serviceId, serviceFactory]) => ({
1481
+ value: serviceId,
1482
+ provides: [serviceId],
1483
+ consumes: Object.values(serviceFactory.deps).map((d) => d.id)
1484
+ })
1485
+ )
1486
+ );
1487
+ const circularDependencies = Array.from(graph.detectCircularDependencies());
1488
+ if (circularDependencies.length) {
1489
+ const cycles = circularDependencies.map((c) => c.map((id) => `'${id}'`).join(" -> ")).join("\n ");
1490
+ throw new errors.ConflictError(`Circular dependencies detected:
1491
+ ${cycles}`);
1492
+ }
1493
+ };
1494
+ let ServiceRegistry = _ServiceRegistry;
1423
1495
 
1424
1496
  var __accessCheck$2 = (obj, member, msg) => {
1425
1497
  if (!member.has(obj))
@@ -1511,7 +1583,7 @@ getInitDeps_fn = async function(deps, pluginId) {
1511
1583
  const result = /* @__PURE__ */ new Map();
1512
1584
  const missingRefs = /* @__PURE__ */ new Set();
1513
1585
  for (const [name, ref] of Object.entries(deps)) {
1514
- const ep = __privateGet$2(this, _extensionPoints).get(ref);
1586
+ const ep = __privateGet$2(this, _extensionPoints).get(ref.id);
1515
1587
  if (ep) {
1516
1588
  if (ep.pluginId !== pluginId) {
1517
1589
  throw new Error(
@@ -1575,7 +1647,7 @@ addFeature_fn = function(feature) {
1575
1647
  };
1576
1648
  _doStart = new WeakSet();
1577
1649
  doStart_fn = async function() {
1578
- __privateSet$2(this, _serviceHolder, new ServiceRegistry([
1650
+ __privateSet$2(this, _serviceHolder, ServiceRegistry.create([
1579
1651
  ...__privateGet$2(this, _defaultApiFactories),
1580
1652
  ...__privateGet$2(this, _providedServiceFactories)
1581
1653
  ]));
@@ -1601,12 +1673,12 @@ doStart_fn = async function() {
1601
1673
  const provides = /* @__PURE__ */ new Set();
1602
1674
  if (r.type === "plugin" || r.type === "module") {
1603
1675
  for (const [extRef, extImpl] of r.extensionPoints) {
1604
- if (__privateGet$2(this, _extensionPoints).has(extRef)) {
1676
+ if (__privateGet$2(this, _extensionPoints).has(extRef.id)) {
1605
1677
  throw new Error(
1606
1678
  `ExtensionPoint with ID '${extRef.id}' is already registered`
1607
1679
  );
1608
1680
  }
1609
- __privateGet$2(this, _extensionPoints).set(extRef, {
1681
+ __privateGet$2(this, _extensionPoints).set(extRef.id, {
1610
1682
  impl: extImpl,
1611
1683
  pluginId: r.pluginId
1612
1684
  });