@backstage/backend-test-utils 0.4.5-next.0 → 0.4.5-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/index.cjs.js +143 -105
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +25 -25
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @backstage/backend-test-utils
|
|
2
2
|
|
|
3
|
+
## 0.4.5-next.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`.
|
|
8
|
+
- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/backend-defaults@0.4.2-next.2
|
|
11
|
+
- @backstage/backend-plugin-api@0.8.0-next.2
|
|
12
|
+
- @backstage/backend-app-api@0.8.1-next.2
|
|
13
|
+
- @backstage/plugin-auth-node@0.5.0-next.2
|
|
14
|
+
- @backstage/plugin-events-node@0.3.9-next.2
|
|
15
|
+
- @backstage/config@1.2.0
|
|
16
|
+
- @backstage/errors@1.2.4
|
|
17
|
+
- @backstage/types@1.1.1
|
|
18
|
+
|
|
19
|
+
## 0.4.5-next.1
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies
|
|
24
|
+
- @backstage/backend-plugin-api@0.7.1-next.1
|
|
25
|
+
- @backstage/backend-app-api@0.8.1-next.1
|
|
26
|
+
- @backstage/backend-defaults@0.4.2-next.1
|
|
27
|
+
- @backstage/config@1.2.0
|
|
28
|
+
- @backstage/errors@1.2.4
|
|
29
|
+
- @backstage/types@1.1.1
|
|
30
|
+
- @backstage/plugin-auth-node@0.4.18-next.1
|
|
31
|
+
- @backstage/plugin-events-node@0.3.9-next.1
|
|
32
|
+
|
|
3
33
|
## 0.4.5-next.0
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/dist/index.cjs.js
CHANGED
|
@@ -1675,7 +1675,7 @@ function createPluginsForOrphanModules(features) {
|
|
|
1675
1675
|
const pluginIds = /* @__PURE__ */ new Set();
|
|
1676
1676
|
const modulePluginIds = /* @__PURE__ */ new Set();
|
|
1677
1677
|
for (const feature of features) {
|
|
1678
|
-
if (
|
|
1678
|
+
if (isInternalBackendRegistrations(feature)) {
|
|
1679
1679
|
const registrations = feature.getRegistrations();
|
|
1680
1680
|
for (const registration of registrations) {
|
|
1681
1681
|
if (registration.type === "plugin") {
|
|
@@ -1704,17 +1704,7 @@ function createExtensionPointTestModules(features, extensionPointTuples) {
|
|
|
1704
1704
|
return [];
|
|
1705
1705
|
}
|
|
1706
1706
|
const registrations = features.flatMap((feature) => {
|
|
1707
|
-
if (feature
|
|
1708
|
-
throw new Error(
|
|
1709
|
-
`Failed to add feature, invalid type '${feature.$$type}'`
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
if (isInternalBackendFeature(feature)) {
|
|
1713
|
-
if (feature.version !== "v1") {
|
|
1714
|
-
throw new Error(
|
|
1715
|
-
`Failed to add feature, invalid version '${feature.version}'`
|
|
1716
|
-
);
|
|
1717
|
-
}
|
|
1707
|
+
if (isInternalBackendRegistrations(feature)) {
|
|
1718
1708
|
return feature.getRegistrations();
|
|
1719
1709
|
}
|
|
1720
1710
|
return [];
|
|
@@ -1883,8 +1873,24 @@ function registerTestHooks() {
|
|
|
1883
1873
|
});
|
|
1884
1874
|
}
|
|
1885
1875
|
registerTestHooks();
|
|
1886
|
-
function
|
|
1887
|
-
|
|
1876
|
+
function toInternalBackendFeature(feature) {
|
|
1877
|
+
if (feature.$$type !== "@backstage/BackendFeature") {
|
|
1878
|
+
throw new Error(`Invalid BackendFeature, bad type '${feature.$$type}'`);
|
|
1879
|
+
}
|
|
1880
|
+
const internal = feature;
|
|
1881
|
+
if (internal.version !== "v1") {
|
|
1882
|
+
throw new Error(
|
|
1883
|
+
`Invalid BackendFeature, bad version '${internal.version}'`
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
return internal;
|
|
1887
|
+
}
|
|
1888
|
+
function isInternalBackendRegistrations(feature) {
|
|
1889
|
+
const internal = toInternalBackendFeature(feature);
|
|
1890
|
+
if (internal.featureType === "registrations") {
|
|
1891
|
+
return true;
|
|
1892
|
+
}
|
|
1893
|
+
return "getRegistrations" in internal;
|
|
1888
1894
|
}
|
|
1889
1895
|
|
|
1890
1896
|
class Node {
|
|
@@ -2084,7 +2090,19 @@ function createPluginMetadataServiceFactory(pluginId) {
|
|
|
2084
2090
|
}
|
|
2085
2091
|
class ServiceRegistry {
|
|
2086
2092
|
static create(factories) {
|
|
2087
|
-
const
|
|
2093
|
+
const factoryMap = /* @__PURE__ */ new Map();
|
|
2094
|
+
for (const factory of factories) {
|
|
2095
|
+
if (factory.service.multiton) {
|
|
2096
|
+
const existing = factoryMap.get(factory.service.id) ?? [];
|
|
2097
|
+
factoryMap.set(
|
|
2098
|
+
factory.service.id,
|
|
2099
|
+
existing.concat(toInternalServiceFactory(factory))
|
|
2100
|
+
);
|
|
2101
|
+
} else {
|
|
2102
|
+
factoryMap.set(factory.service.id, [toInternalServiceFactory(factory)]);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
const registry = new ServiceRegistry(factoryMap);
|
|
2088
2106
|
registry.checkForCircularDeps();
|
|
2089
2107
|
return registry;
|
|
2090
2108
|
}
|
|
@@ -2095,17 +2113,15 @@ class ServiceRegistry {
|
|
|
2095
2113
|
#addedFactoryIds = /* @__PURE__ */ new Set();
|
|
2096
2114
|
#instantiatedFactories = /* @__PURE__ */ new Set();
|
|
2097
2115
|
constructor(factories) {
|
|
2098
|
-
this.#providedFactories =
|
|
2099
|
-
factories.map((sf) => [sf.service.id, toInternalServiceFactory(sf)])
|
|
2100
|
-
);
|
|
2116
|
+
this.#providedFactories = factories;
|
|
2101
2117
|
this.#loadedDefaultFactories = /* @__PURE__ */ new Map();
|
|
2102
2118
|
this.#implementations = /* @__PURE__ */ new Map();
|
|
2103
2119
|
}
|
|
2104
2120
|
#resolveFactory(ref, pluginId) {
|
|
2105
2121
|
if (ref.id === backendPluginApi.coreServices.pluginMetadata.id) {
|
|
2106
|
-
return Promise.resolve(
|
|
2122
|
+
return Promise.resolve([
|
|
2107
2123
|
toInternalServiceFactory(createPluginMetadataServiceFactory(pluginId))
|
|
2108
|
-
);
|
|
2124
|
+
]);
|
|
2109
2125
|
}
|
|
2110
2126
|
let resolvedFactory = this.#providedFactories.get(ref.id);
|
|
2111
2127
|
const { __defaultFactory: defaultFactory } = ref;
|
|
@@ -2120,13 +2136,16 @@ class ServiceRegistry {
|
|
|
2120
2136
|
);
|
|
2121
2137
|
this.#loadedDefaultFactories.set(defaultFactory, loadedFactory);
|
|
2122
2138
|
}
|
|
2123
|
-
resolvedFactory = loadedFactory.
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2139
|
+
resolvedFactory = loadedFactory.then(
|
|
2140
|
+
(factory) => [factory],
|
|
2141
|
+
(error) => {
|
|
2142
|
+
throw new Error(
|
|
2143
|
+
`Failed to instantiate service '${ref.id}' because the default factory loader threw an error, ${errors.stringifyError(
|
|
2144
|
+
error
|
|
2145
|
+
)}`
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
);
|
|
2130
2149
|
}
|
|
2131
2150
|
return Promise.resolve(resolvedFactory);
|
|
2132
2151
|
}
|
|
@@ -2138,6 +2157,9 @@ class ServiceRegistry {
|
|
|
2138
2157
|
if (this.#providedFactories.get(ref.id)) {
|
|
2139
2158
|
return false;
|
|
2140
2159
|
}
|
|
2160
|
+
if (ref.multiton) {
|
|
2161
|
+
return false;
|
|
2162
|
+
}
|
|
2141
2163
|
return !ref.__defaultFactory;
|
|
2142
2164
|
});
|
|
2143
2165
|
if (missingDeps.length) {
|
|
@@ -2149,13 +2171,13 @@ class ServiceRegistry {
|
|
|
2149
2171
|
}
|
|
2150
2172
|
checkForCircularDeps() {
|
|
2151
2173
|
const graph = DependencyGraph.fromIterable(
|
|
2152
|
-
Array.from(this.#providedFactories).map(
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
)
|
|
2174
|
+
Array.from(this.#providedFactories).map(([serviceId, factories]) => ({
|
|
2175
|
+
value: serviceId,
|
|
2176
|
+
provides: [serviceId],
|
|
2177
|
+
consumes: factories.flatMap(
|
|
2178
|
+
(factory) => Object.values(factory.deps).map((d) => d.id)
|
|
2179
|
+
)
|
|
2180
|
+
}))
|
|
2159
2181
|
);
|
|
2160
2182
|
const circularDependencies = Array.from(graph.detectCircularDependencies());
|
|
2161
2183
|
if (circularDependencies.length) {
|
|
@@ -2171,21 +2193,28 @@ class ServiceRegistry {
|
|
|
2171
2193
|
`The ${backendPluginApi.coreServices.pluginMetadata.id} service cannot be overridden`
|
|
2172
2194
|
);
|
|
2173
2195
|
}
|
|
2174
|
-
if (this.#addedFactoryIds.has(factoryId)) {
|
|
2175
|
-
throw new Error(
|
|
2176
|
-
`Duplicate service implementations provided for ${factoryId}`
|
|
2177
|
-
);
|
|
2178
|
-
}
|
|
2179
2196
|
if (this.#instantiatedFactories.has(factoryId)) {
|
|
2180
2197
|
throw new Error(
|
|
2181
2198
|
`Unable to set service factory with id ${factoryId}, service has already been instantiated`
|
|
2182
2199
|
);
|
|
2183
2200
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2201
|
+
if (factory.service.multiton) {
|
|
2202
|
+
const newFactories = (this.#providedFactories.get(factoryId) ?? []).concat(toInternalServiceFactory(factory));
|
|
2203
|
+
this.#providedFactories.set(factoryId, newFactories);
|
|
2204
|
+
} else {
|
|
2205
|
+
if (this.#addedFactoryIds.has(factoryId)) {
|
|
2206
|
+
throw new Error(
|
|
2207
|
+
`Duplicate service implementations provided for ${factoryId}`
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
this.#addedFactoryIds.add(factoryId);
|
|
2211
|
+
this.#providedFactories.set(factoryId, [
|
|
2212
|
+
toInternalServiceFactory(factory)
|
|
2213
|
+
]);
|
|
2214
|
+
}
|
|
2186
2215
|
}
|
|
2187
2216
|
async initializeEagerServicesWithScope(scope, pluginId = "root") {
|
|
2188
|
-
for (const factory of this.#providedFactories.values()) {
|
|
2217
|
+
for (const [factory] of this.#providedFactories.values()) {
|
|
2189
2218
|
if (factory.service.scope === scope) {
|
|
2190
2219
|
if (scope === "root" && factory.initialization !== "lazy") {
|
|
2191
2220
|
await this.get(factory.service, pluginId);
|
|
@@ -2197,72 +2226,80 @@ class ServiceRegistry {
|
|
|
2197
2226
|
}
|
|
2198
2227
|
get(ref, pluginId) {
|
|
2199
2228
|
this.#instantiatedFactories.add(ref.id);
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2229
|
+
const resolvedFactory = this.#resolveFactory(ref, pluginId);
|
|
2230
|
+
if (!resolvedFactory) {
|
|
2231
|
+
return ref.multiton ? Promise.resolve([]) : void 0;
|
|
2232
|
+
}
|
|
2233
|
+
return resolvedFactory.then((factories) => {
|
|
2234
|
+
return Promise.all(
|
|
2235
|
+
factories.map((factory) => {
|
|
2236
|
+
if (factory.service.scope === "root") {
|
|
2237
|
+
let existing = this.#rootServiceImplementations.get(factory);
|
|
2238
|
+
if (!existing) {
|
|
2239
|
+
this.#checkForMissingDeps(factory, pluginId);
|
|
2240
|
+
const rootDeps = new Array();
|
|
2241
|
+
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
|
2242
|
+
if (serviceRef.scope !== "root") {
|
|
2243
|
+
throw new Error(
|
|
2244
|
+
`Failed to instantiate 'root' scoped service '${ref.id}' because it depends on '${serviceRef.scope}' scoped service '${serviceRef.id}'.`
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
const target = this.get(serviceRef, pluginId);
|
|
2248
|
+
rootDeps.push(target.then((impl) => [name, impl]));
|
|
2249
|
+
}
|
|
2250
|
+
existing = Promise.all(rootDeps).then(
|
|
2251
|
+
(entries) => factory.factory(Object.fromEntries(entries), void 0)
|
|
2210
2252
|
);
|
|
2253
|
+
this.#rootServiceImplementations.set(factory, existing);
|
|
2211
2254
|
}
|
|
2212
|
-
|
|
2213
|
-
rootDeps.push(target.then((impl) => [name, impl]));
|
|
2255
|
+
return existing;
|
|
2214
2256
|
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2257
|
+
let implementation = this.#implementations.get(factory);
|
|
2258
|
+
if (!implementation) {
|
|
2259
|
+
this.#checkForMissingDeps(factory, pluginId);
|
|
2260
|
+
const rootDeps = new Array();
|
|
2261
|
+
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
|
2262
|
+
if (serviceRef.scope === "root") {
|
|
2263
|
+
const target = this.get(serviceRef, pluginId);
|
|
2264
|
+
rootDeps.push(target.then((impl) => [name, impl]));
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
implementation = {
|
|
2268
|
+
context: Promise.all(rootDeps).then(
|
|
2269
|
+
(entries) => factory.createRootContext?.(Object.fromEntries(entries))
|
|
2270
|
+
).catch((error) => {
|
|
2271
|
+
const cause = errors.stringifyError(error);
|
|
2272
|
+
throw new Error(
|
|
2273
|
+
`Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`
|
|
2274
|
+
);
|
|
2275
|
+
}),
|
|
2276
|
+
byPlugin: /* @__PURE__ */ new Map()
|
|
2277
|
+
};
|
|
2278
|
+
this.#implementations.set(factory, implementation);
|
|
2230
2279
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
(
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
(entries) => factory.factory(Object.fromEntries(entries), context)
|
|
2255
|
-
)
|
|
2256
|
-
).catch((error) => {
|
|
2257
|
-
const cause = errors.stringifyError(error);
|
|
2258
|
-
throw new Error(
|
|
2259
|
-
`Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`
|
|
2260
|
-
);
|
|
2261
|
-
});
|
|
2262
|
-
implementation.byPlugin.set(pluginId, result);
|
|
2263
|
-
}
|
|
2264
|
-
return result;
|
|
2265
|
-
});
|
|
2280
|
+
let result = implementation.byPlugin.get(pluginId);
|
|
2281
|
+
if (!result) {
|
|
2282
|
+
const allDeps = new Array();
|
|
2283
|
+
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
|
2284
|
+
const target = this.get(serviceRef, pluginId);
|
|
2285
|
+
allDeps.push(target.then((impl) => [name, impl]));
|
|
2286
|
+
}
|
|
2287
|
+
result = implementation.context.then(
|
|
2288
|
+
(context) => Promise.all(allDeps).then(
|
|
2289
|
+
(entries) => factory.factory(Object.fromEntries(entries), context)
|
|
2290
|
+
)
|
|
2291
|
+
).catch((error) => {
|
|
2292
|
+
const cause = errors.stringifyError(error);
|
|
2293
|
+
throw new Error(
|
|
2294
|
+
`Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`
|
|
2295
|
+
);
|
|
2296
|
+
});
|
|
2297
|
+
implementation.byPlugin.set(pluginId, result);
|
|
2298
|
+
}
|
|
2299
|
+
return result;
|
|
2300
|
+
})
|
|
2301
|
+
);
|
|
2302
|
+
}).then((results) => ref.multiton ? results : results[0]);
|
|
2266
2303
|
}
|
|
2267
2304
|
}
|
|
2268
2305
|
|
|
@@ -2308,7 +2345,8 @@ class ServiceFactoryTester {
|
|
|
2308
2345
|
*/
|
|
2309
2346
|
async getSubject(...args) {
|
|
2310
2347
|
const [pluginId] = args;
|
|
2311
|
-
|
|
2348
|
+
const instance = this.#registry.get(this.#subject, pluginId ?? "test");
|
|
2349
|
+
return instance;
|
|
2312
2350
|
}
|
|
2313
2351
|
/**
|
|
2314
2352
|
* Return the service instance for any of the provided dependencies or built-in services.
|