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

Potentially problematic release.


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

package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @backstage/backend-app-api
2
2
 
3
+ ## 0.5.3-next.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 154632d8753b: Add support for discovering additional service factories during startup.
8
+ - cb7fc410ed99: The experimental backend feature discovery now only considers default exports from packages. It no longer filters packages to include based on the package role, except that `'cli'` packages are ignored. However, the `"backstage"` field is still required in `package.json`.
9
+ - 3b30b179cb38: Add support for installing features as package imports, for example `backend.add(import('my-plugin'))`.
10
+ - Updated dependencies
11
+ - @backstage/config@1.1.0-next.2
12
+ - @backstage/errors@1.2.2-next.0
13
+ - @backstage/types@1.1.1-next.0
14
+ - @backstage/plugin-permission-node@0.7.14-next.3
15
+ - @backstage/backend-plugin-api@0.6.3-next.3
16
+ - @backstage/backend-common@0.19.5-next.3
17
+ - @backstage/backend-tasks@0.5.8-next.3
18
+ - @backstage/cli-common@0.1.12
19
+ - @backstage/cli-node@0.1.4-next.0
20
+ - @backstage/config-loader@1.5.0-next.3
21
+ - @backstage/plugin-auth-node@0.3.0-next.3
22
+
23
+ ## 0.5.3-next.2
24
+
25
+ ### Patch Changes
26
+
27
+ - 37a20c7f14aa: Adds include and exclude configuration to feature discovery of backend packages
28
+ Adds alpha modules to feature discovery
29
+ - 3fc64b9e2f8f: Extension points are now tracked via their ID rather than reference, in order to support package duplication.
30
+ - b219d097b3f4: Backend startup will now fail if any circular service dependencies are detected.
31
+ - Updated dependencies
32
+ - @backstage/config-loader@1.5.0-next.2
33
+ - @backstage/config@1.1.0-next.1
34
+ - @backstage/backend-tasks@0.5.8-next.2
35
+ - @backstage/backend-common@0.19.5-next.2
36
+ - @backstage/plugin-auth-node@0.3.0-next.2
37
+ - @backstage/plugin-permission-node@0.7.14-next.2
38
+ - @backstage/backend-plugin-api@0.6.3-next.2
39
+ - @backstage/cli-common@0.1.12
40
+ - @backstage/cli-node@0.1.3
41
+ - @backstage/errors@1.2.1
42
+ - @backstage/types@1.1.0
43
+
3
44
  ## 0.5.3-next.1
4
45
 
5
46
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-app-api",
3
- "version": "0.5.3-next.1",
3
+ "version": "0.5.3-next.3",
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,6 @@ 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"];
15
14
  async function findClosestPackageDir(searchDir) {
16
15
  let path$1 = searchDir;
17
16
  for (let i = 0; i < 1e3; i++) {
@@ -31,38 +30,67 @@ async function findClosestPackageDir(searchDir) {
31
30
  );
32
31
  }
33
32
  class PackageDiscoveryService {
34
- constructor(config) {
33
+ constructor(config, logger) {
35
34
  this.config = config;
35
+ this.logger = logger;
36
+ }
37
+ getDependencyNames(path) {
38
+ const { dependencies } = require(path);
39
+ const packagesConfig = this.config.getOptional("backend.packages");
40
+ const dependencyNames = Object.keys(dependencies || {});
41
+ if (packagesConfig === "all") {
42
+ return dependencyNames;
43
+ }
44
+ const includedPackagesConfig = this.config.getOptionalStringArray(
45
+ "backend.packages.include"
46
+ );
47
+ const includedPackages = includedPackagesConfig ? new Set(includedPackagesConfig) : dependencyNames;
48
+ const excludedPackagesSet = new Set(
49
+ this.config.getOptionalStringArray("backend.packages.exclude")
50
+ );
51
+ return [...includedPackages].filter((name) => !excludedPackagesSet.has(name));
36
52
  }
37
53
  async getBackendFeatures() {
38
- var _a, _b;
39
- if (this.config.getOptionalString("backend.packages") !== "all") {
54
+ var _a;
55
+ const packagesConfig = this.config.getOptional("backend.packages");
56
+ if (!packagesConfig || Object.keys(packagesConfig).length === 0) {
40
57
  return { features: [] };
41
58
  }
42
59
  const packageDir = await findClosestPackageDir(process.argv[1]);
43
60
  if (!packageDir) {
44
61
  throw new Error("Package discovery failed to find package.json");
45
62
  }
46
- const { dependencies } = require(path.resolve(
47
- packageDir,
48
- "package.json"
49
- ));
50
- const dependencyNames = Object.keys(dependencies || {});
63
+ const dependencyNames = this.getDependencyNames(
64
+ path.resolve(packageDir, "package.json")
65
+ );
51
66
  const features = [];
52
67
  for (const name of dependencyNames) {
53
68
  const depPkg = require(require.resolve(`${name}/package.json`, {
54
69
  paths: [packageDir]
55
70
  }));
56
- if (!LOADED_PACKAGE_ROLES.includes((_b = (_a = depPkg == null ? void 0 : depPkg.backstage) == null ? void 0 : _a.role) != null ? _b : "")) {
71
+ if (!(depPkg == null ? void 0 : depPkg.backstage) || ((_a = depPkg == null ? void 0 : depPkg.backstage) == null ? void 0 : _a.role) === "cli") {
57
72
  continue;
58
73
  }
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);
74
+ const exportedModulePaths = [
75
+ require.resolve(name, {
76
+ paths: [packageDir]
77
+ })
78
+ ];
79
+ try {
80
+ exportedModulePaths.push(
81
+ require.resolve(`${name}/alpha`, { paths: [packageDir] })
82
+ );
83
+ } catch {
84
+ }
85
+ for (const modulePath of exportedModulePaths) {
86
+ const mod = require(modulePath);
87
+ if (isBackendFeature(mod.default)) {
88
+ this.logger.info(`Detected: ${name}`);
89
+ features.push(mod.default);
63
90
  }
64
- if (isBackendFeatureFactory(exportValue)) {
65
- features.push(exportValue());
91
+ if (isBackendFeatureFactory(mod.default)) {
92
+ this.logger.info(`Detected: ${name}`);
93
+ features.push(mod.default());
66
94
  }
67
95
  }
68
96
  }
@@ -72,10 +100,11 @@ class PackageDiscoveryService {
72
100
  const featureDiscoveryServiceFactory = backendPluginApi.createServiceFactory({
73
101
  service: alpha.featureDiscoveryServiceRef,
74
102
  deps: {
75
- config: backendPluginApi.coreServices.rootConfig
103
+ config: backendPluginApi.coreServices.rootConfig,
104
+ logger: backendPluginApi.coreServices.rootLogger
76
105
  },
77
- factory({ config }) {
78
- return new PackageDiscoveryService(config);
106
+ factory({ config, logger }) {
107
+ return new PackageDiscoveryService(config, logger);
79
108
  }
80
109
  });
81
110
  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\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 (!depPkg?.backstage || depPkg?.backstage?.role === 'cli') {\n continue; // Not a backstage package, ignore\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 mod = require(modulePath);\n\n if (isBackendFeature(mod.default)) {\n this.logger.info(`Detected: ${name}`);\n features.push(mod.default);\n }\n if (isBackendFeatureFactory(mod.default)) {\n this.logger.info(`Detected: ${name}`);\n features.push(mod.default());\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, _addedFactoryIds, _instantiatedFactories, _resolveFactory, resolveFactory_fn, _checkForMissingDeps, checkForMissingDeps_fn;
1264
1310
  function toInternalServiceFactory(factory) {
1265
1311
  const f = factory;
1266
1312
  if (f.$$type !== "@backstage/BackendFeature") {
@@ -1278,7 +1324,7 @@ 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);
@@ -1286,17 +1332,62 @@ class ServiceRegistry {
1286
1332
  __privateAdd$3(this, _loadedDefaultFactories, void 0);
1287
1333
  __privateAdd$3(this, _implementations, void 0);
1288
1334
  __privateAdd$3(this, _rootServiceImplementations, /* @__PURE__ */ new Map());
1335
+ __privateAdd$3(this, _addedFactoryIds, /* @__PURE__ */ new Set());
1336
+ __privateAdd$3(this, _instantiatedFactories, /* @__PURE__ */ new Set());
1289
1337
  __privateSet$3(this, _providedFactories, new Map(
1290
1338
  factories.map((sf) => [sf.service.id, toInternalServiceFactory(sf)])
1291
1339
  ));
1292
1340
  __privateSet$3(this, _loadedDefaultFactories, /* @__PURE__ */ new Map());
1293
1341
  __privateSet$3(this, _implementations, /* @__PURE__ */ new Map());
1294
1342
  }
1343
+ static create(factories) {
1344
+ const registry = new _ServiceRegistry(factories);
1345
+ registry.checkForCircularDeps();
1346
+ return registry;
1347
+ }
1348
+ checkForCircularDeps() {
1349
+ const graph = DependencyGraph.fromIterable(
1350
+ Array.from(__privateGet$3(this, _providedFactories)).map(
1351
+ ([serviceId, serviceFactory]) => ({
1352
+ value: serviceId,
1353
+ provides: [serviceId],
1354
+ consumes: Object.values(serviceFactory.deps).map((d) => d.id)
1355
+ })
1356
+ )
1357
+ );
1358
+ const circularDependencies = Array.from(graph.detectCircularDependencies());
1359
+ if (circularDependencies.length) {
1360
+ const cycles = circularDependencies.map((c) => c.map((id) => `'${id}'`).join(" -> ")).join("\n ");
1361
+ throw new errors.ConflictError(`Circular dependencies detected:
1362
+ ${cycles}`);
1363
+ }
1364
+ }
1365
+ add(factory) {
1366
+ const factoryId = factory.service.id;
1367
+ if (factoryId === backendPluginApi.coreServices.pluginMetadata.id) {
1368
+ throw new Error(
1369
+ `The ${backendPluginApi.coreServices.pluginMetadata.id} service cannot be overridden`
1370
+ );
1371
+ }
1372
+ if (__privateGet$3(this, _addedFactoryIds).has(factoryId)) {
1373
+ throw new Error(
1374
+ `Duplicate service implementations provided for ${factoryId}`
1375
+ );
1376
+ }
1377
+ if (__privateGet$3(this, _instantiatedFactories).has(factoryId)) {
1378
+ throw new Error(
1379
+ `Unable to set service factory with id ${factoryId}, service has already been instantiated`
1380
+ );
1381
+ }
1382
+ __privateGet$3(this, _addedFactoryIds).add(factoryId);
1383
+ __privateGet$3(this, _providedFactories).set(factoryId, toInternalServiceFactory(factory));
1384
+ }
1295
1385
  getServiceRefs() {
1296
1386
  return Array.from(__privateGet$3(this, _providedFactories).values()).map((f) => f.service);
1297
1387
  }
1298
1388
  get(ref, pluginId) {
1299
1389
  var _a;
1390
+ __privateGet$3(this, _instantiatedFactories).add(ref.id);
1300
1391
  return (_a = __privateMethod$2(this, _resolveFactory, resolveFactory_fn).call(this, ref, pluginId)) == null ? void 0 : _a.then((factory) => {
1301
1392
  if (factory.service.scope === "root") {
1302
1393
  let existing = __privateGet$3(this, _rootServiceImplementations).get(factory);
@@ -1367,11 +1458,13 @@ class ServiceRegistry {
1367
1458
  return result;
1368
1459
  });
1369
1460
  }
1370
- }
1461
+ };
1371
1462
  _providedFactories = new WeakMap();
1372
1463
  _loadedDefaultFactories = new WeakMap();
1373
1464
  _implementations = new WeakMap();
1374
1465
  _rootServiceImplementations = new WeakMap();
1466
+ _addedFactoryIds = new WeakMap();
1467
+ _instantiatedFactories = new WeakMap();
1375
1468
  _resolveFactory = new WeakSet();
1376
1469
  resolveFactory_fn = function(ref, pluginId) {
1377
1470
  if (ref.id === backendPluginApi.coreServices.pluginMetadata.id) {
@@ -1420,6 +1513,7 @@ checkForMissingDeps_fn = function(factory, pluginId) {
1420
1513
  );
1421
1514
  }
1422
1515
  };
1516
+ let ServiceRegistry = _ServiceRegistry;
1423
1517
 
1424
1518
  var __accessCheck$2 = (obj, member, msg) => {
1425
1519
  if (!member.has(obj))
@@ -1443,7 +1537,7 @@ var __privateMethod$1 = (obj, member, method) => {
1443
1537
  __accessCheck$2(obj, member, "access private method");
1444
1538
  return method;
1445
1539
  };
1446
- var _startPromise, _features, _extensionPoints, _serviceHolder, _providedServiceFactories, _defaultApiFactories, _getInitDeps, getInitDeps_fn, _addFeature, addFeature_fn, _doStart, doStart_fn, _getRootLifecycleImpl, getRootLifecycleImpl_fn, _getPluginLifecycleImpl, getPluginLifecycleImpl_fn;
1540
+ var _startPromise, _features, _extensionPoints, _serviceRegistry, _registeredFeatures, _getInitDeps, getInitDeps_fn, _addFeature, addFeature_fn, _doStart, doStart_fn, _getRootLifecycleImpl, getRootLifecycleImpl_fn, _getPluginLifecycleImpl, getPluginLifecycleImpl_fn;
1447
1541
  class BackendInitializer {
1448
1542
  constructor(defaultApiFactories) {
1449
1543
  __privateAdd$2(this, _getInitDeps);
@@ -1455,16 +1549,15 @@ class BackendInitializer {
1455
1549
  __privateAdd$2(this, _startPromise, void 0);
1456
1550
  __privateAdd$2(this, _features, new Array());
1457
1551
  __privateAdd$2(this, _extensionPoints, /* @__PURE__ */ new Map());
1458
- __privateAdd$2(this, _serviceHolder, void 0);
1459
- __privateAdd$2(this, _providedServiceFactories, new Array());
1460
- __privateAdd$2(this, _defaultApiFactories, void 0);
1461
- __privateSet$2(this, _defaultApiFactories, defaultApiFactories);
1552
+ __privateAdd$2(this, _serviceRegistry, void 0);
1553
+ __privateAdd$2(this, _registeredFeatures, new Array());
1554
+ __privateSet$2(this, _serviceRegistry, ServiceRegistry.create([...defaultApiFactories]));
1462
1555
  }
1463
1556
  add(feature) {
1464
1557
  if (__privateGet$2(this, _startPromise)) {
1465
1558
  throw new Error("feature can not be added after the backend has started");
1466
1559
  }
1467
- __privateMethod$1(this, _addFeature, addFeature_fn).call(this, feature);
1560
+ __privateGet$2(this, _registeredFeatures).push(Promise.resolve(feature));
1468
1561
  }
1469
1562
  async start() {
1470
1563
  if (__privateGet$2(this, _startPromise)) {
@@ -1503,15 +1596,14 @@ class BackendInitializer {
1503
1596
  _startPromise = new WeakMap();
1504
1597
  _features = new WeakMap();
1505
1598
  _extensionPoints = new WeakMap();
1506
- _serviceHolder = new WeakMap();
1507
- _providedServiceFactories = new WeakMap();
1508
- _defaultApiFactories = new WeakMap();
1599
+ _serviceRegistry = new WeakMap();
1600
+ _registeredFeatures = new WeakMap();
1509
1601
  _getInitDeps = new WeakSet();
1510
1602
  getInitDeps_fn = async function(deps, pluginId) {
1511
1603
  const result = /* @__PURE__ */ new Map();
1512
1604
  const missingRefs = /* @__PURE__ */ new Set();
1513
1605
  for (const [name, ref] of Object.entries(deps)) {
1514
- const ep = __privateGet$2(this, _extensionPoints).get(ref);
1606
+ const ep = __privateGet$2(this, _extensionPoints).get(ref.id);
1515
1607
  if (ep) {
1516
1608
  if (ep.pluginId !== pluginId) {
1517
1609
  throw new Error(
@@ -1520,7 +1612,7 @@ getInitDeps_fn = async function(deps, pluginId) {
1520
1612
  }
1521
1613
  result.set(name, ep.impl);
1522
1614
  } else {
1523
- const impl = await __privateGet$2(this, _serviceHolder).get(
1615
+ const impl = await __privateGet$2(this, _serviceRegistry).get(
1524
1616
  ref,
1525
1617
  pluginId
1526
1618
  );
@@ -1547,19 +1639,7 @@ addFeature_fn = function(feature) {
1547
1639
  );
1548
1640
  }
1549
1641
  if (isServiceFactory(feature)) {
1550
- if (feature.service.id === backendPluginApi.coreServices.pluginMetadata.id) {
1551
- throw new Error(
1552
- `The ${backendPluginApi.coreServices.pluginMetadata.id} service cannot be overridden`
1553
- );
1554
- }
1555
- if (__privateGet$2(this, _providedServiceFactories).find(
1556
- (sf) => sf.service.id === feature.service.id
1557
- )) {
1558
- throw new Error(
1559
- `Duplicate service implementations provided for ${feature.service.id}`
1560
- );
1561
- }
1562
- __privateGet$2(this, _providedServiceFactories).push(feature);
1642
+ __privateGet$2(this, _serviceRegistry).add(feature);
1563
1643
  } else if (isInternalBackendFeature(feature)) {
1564
1644
  if (feature.version !== "v1") {
1565
1645
  throw new Error(
@@ -1575,11 +1655,11 @@ addFeature_fn = function(feature) {
1575
1655
  };
1576
1656
  _doStart = new WeakSet();
1577
1657
  doStart_fn = async function() {
1578
- __privateSet$2(this, _serviceHolder, new ServiceRegistry([
1579
- ...__privateGet$2(this, _defaultApiFactories),
1580
- ...__privateGet$2(this, _providedServiceFactories)
1581
- ]));
1582
- const featureDiscovery = await __privateGet$2(this, _serviceHolder).get(
1658
+ __privateGet$2(this, _serviceRegistry).checkForCircularDeps();
1659
+ for (const feature of __privateGet$2(this, _registeredFeatures)) {
1660
+ __privateMethod$1(this, _addFeature, addFeature_fn).call(this, await feature);
1661
+ }
1662
+ const featureDiscovery = await __privateGet$2(this, _serviceRegistry).get(
1583
1663
  alpha.featureDiscoveryServiceRef,
1584
1664
  "root"
1585
1665
  );
@@ -1588,10 +1668,11 @@ doStart_fn = async function() {
1588
1668
  for (const feature of features) {
1589
1669
  __privateMethod$1(this, _addFeature, addFeature_fn).call(this, feature);
1590
1670
  }
1671
+ __privateGet$2(this, _serviceRegistry).checkForCircularDeps();
1591
1672
  }
1592
- for (const ref of __privateGet$2(this, _serviceHolder).getServiceRefs()) {
1673
+ for (const ref of __privateGet$2(this, _serviceRegistry).getServiceRefs()) {
1593
1674
  if (ref.scope === "root") {
1594
- await __privateGet$2(this, _serviceHolder).get(ref, "root");
1675
+ await __privateGet$2(this, _serviceRegistry).get(ref, "root");
1595
1676
  }
1596
1677
  }
1597
1678
  const pluginInits = /* @__PURE__ */ new Map();
@@ -1601,12 +1682,12 @@ doStart_fn = async function() {
1601
1682
  const provides = /* @__PURE__ */ new Set();
1602
1683
  if (r.type === "plugin" || r.type === "module") {
1603
1684
  for (const [extRef, extImpl] of r.extensionPoints) {
1604
- if (__privateGet$2(this, _extensionPoints).has(extRef)) {
1685
+ if (__privateGet$2(this, _extensionPoints).has(extRef.id)) {
1605
1686
  throw new Error(
1606
1687
  `ExtensionPoint with ID '${extRef.id}' is already registered`
1607
1688
  );
1608
1689
  }
1609
- __privateGet$2(this, _extensionPoints).set(extRef, {
1690
+ __privateGet$2(this, _extensionPoints).set(extRef.id, {
1610
1691
  impl: extImpl,
1611
1692
  pluginId: r.pluginId
1612
1693
  });
@@ -1693,7 +1774,7 @@ doStart_fn = async function() {
1693
1774
  const lifecycleService = await __privateMethod$1(this, _getRootLifecycleImpl, getRootLifecycleImpl_fn).call(this);
1694
1775
  await lifecycleService.startup();
1695
1776
  if (process.env.NODE_ENV !== "test") {
1696
- const rootLogger = await __privateGet$2(this, _serviceHolder).get(
1777
+ const rootLogger = await __privateGet$2(this, _serviceRegistry).get(
1697
1778
  backendPluginApi.coreServices.rootLogger,
1698
1779
  "root"
1699
1780
  );
@@ -1709,7 +1790,7 @@ doStart_fn = async function() {
1709
1790
  };
1710
1791
  _getRootLifecycleImpl = new WeakSet();
1711
1792
  getRootLifecycleImpl_fn = async function() {
1712
- const lifecycleService = await __privateGet$2(this, _serviceHolder).get(
1793
+ const lifecycleService = await __privateGet$2(this, _serviceRegistry).get(
1713
1794
  backendPluginApi.coreServices.rootLifecycle,
1714
1795
  "root"
1715
1796
  );
@@ -1720,7 +1801,7 @@ getRootLifecycleImpl_fn = async function() {
1720
1801
  };
1721
1802
  _getPluginLifecycleImpl = new WeakSet();
1722
1803
  getPluginLifecycleImpl_fn = async function(pluginId) {
1723
- const lifecycleService = await __privateGet$2(this, _serviceHolder).get(
1804
+ const lifecycleService = await __privateGet$2(this, _serviceRegistry).get(
1724
1805
  backendPluginApi.coreServices.lifecycle,
1725
1806
  pluginId
1726
1807
  );
@@ -1761,7 +1842,11 @@ class BackstageBackend {
1761
1842
  __privateSet$1(this, _initializer, new BackendInitializer(defaultServiceFactories));
1762
1843
  }
1763
1844
  add(feature) {
1764
- __privateGet$1(this, _initializer).add(typeof feature === "function" ? feature() : feature);
1845
+ if (isPromise(feature)) {
1846
+ __privateGet$1(this, _initializer).add(feature.then((f) => unwrapFeature(f.default)));
1847
+ } else {
1848
+ __privateGet$1(this, _initializer).add(unwrapFeature(feature));
1849
+ }
1765
1850
  }
1766
1851
  async start() {
1767
1852
  await __privateGet$1(this, _initializer).start();
@@ -1771,6 +1856,12 @@ class BackstageBackend {
1771
1856
  }
1772
1857
  }
1773
1858
  _initializer = new WeakMap();
1859
+ function isPromise(value) {
1860
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
1861
+ }
1862
+ function unwrapFeature(feature) {
1863
+ return typeof feature === "function" ? feature() : feature;
1864
+ }
1774
1865
 
1775
1866
  function createSpecializedBackend(options) {
1776
1867
  const services = options.defaultServiceFactories.map(