@backstage/backend-dynamic-feature-service 0.6.2-next.2 → 0.7.0-next.0

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,63 @@
1
1
  # @backstage/backend-dynamic-feature-service
2
2
 
3
+ ## 0.7.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 10f693c: **BREAKING** Removed support for the legacy backend, please migrate to the new backend system
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/backend-defaults@0.9.1-next.0
13
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
14
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
15
+ - @backstage/backend-plugin-api@1.3.1-next.0
16
+ - @backstage/cli-node@0.2.13
17
+ - @backstage/config-loader@1.10.0
18
+ - @backstage/plugin-auth-node@0.6.3-next.0
19
+ - @backstage/plugin-events-backend@0.5.2-next.0
20
+ - @backstage/plugin-events-node@0.4.11-next.0
21
+ - @backstage/plugin-permission-node@0.9.2-next.0
22
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
23
+ - @backstage/backend-openapi-utils@0.5.3-next.0
24
+ - @backstage/cli-common@0.1.15
25
+ - @backstage/config@1.3.2
26
+ - @backstage/errors@1.2.7
27
+ - @backstage/types@1.2.1
28
+ - @backstage/plugin-app-node@0.1.33-next.0
29
+ - @backstage/plugin-permission-common@0.8.4
30
+ - @backstage/plugin-search-common@1.2.17
31
+
32
+ ## 0.6.2
33
+
34
+ ### Patch Changes
35
+
36
+ - 3bee3c3: The new package `frontend-dynamic-features-loader` provides a frontend feature loader that dynamically
37
+ loads frontend features based on the new frontend system and exposed as module federation remotes.
38
+ This new frontend feature loader works hand-in-hand with a new server of frontend plugin module federation
39
+ remotes, which is added as part of backend dynamic feature service in package `@backstage/backend-dynamic-feature-service`.
40
+ - Updated dependencies
41
+ - @backstage/backend-defaults@0.9.0
42
+ - @backstage/plugin-catalog-backend@1.32.1
43
+ - @backstage/plugin-scaffolder-node@0.8.1
44
+ - @backstage/backend-plugin-api@1.3.0
45
+ - @backstage/plugin-auth-node@0.6.2
46
+ - @backstage/plugin-events-backend@0.5.1
47
+ - @backstage/plugin-permission-node@0.9.1
48
+ - @backstage/plugin-search-backend-node@1.3.10
49
+ - @backstage/backend-openapi-utils@0.5.2
50
+ - @backstage/cli-common@0.1.15
51
+ - @backstage/cli-node@0.2.13
52
+ - @backstage/config@1.3.2
53
+ - @backstage/config-loader@1.10.0
54
+ - @backstage/errors@1.2.7
55
+ - @backstage/types@1.2.1
56
+ - @backstage/plugin-app-node@0.1.32
57
+ - @backstage/plugin-events-node@0.4.10
58
+ - @backstage/plugin-permission-common@0.8.4
59
+ - @backstage/plugin-search-common@1.2.17
60
+
3
61
  ## 0.6.2-next.2
4
62
 
5
63
  ### Patch Changes
@@ -6,6 +6,7 @@ var pluginScanner = require('../scanner/plugin-scanner.cjs.js');
6
6
  var schemas = require('../schemas/schemas.cjs.js');
7
7
  var frontend = require('../schemas/frontend.cjs.js');
8
8
  var rootLogger = require('../schemas/rootLogger.cjs.js');
9
+ var frontendRemotesServer = require('../server/frontendRemotesServer.cjs.js');
9
10
 
10
11
  const dynamicPluginsFeatureLoaderWithOptions = (options) => backendPluginApi.createBackendFeatureLoader({
11
12
  deps: {
@@ -25,6 +26,7 @@ const dynamicPluginsFeatureLoaderWithOptions = (options) => backendPluginApi.cre
25
26
  yield* [
26
27
  rootLogger.dynamicPluginsRootLoggerServiceFactory(rootLoggerOptions),
27
28
  frontend.dynamicPluginsFrontendSchemas,
29
+ frontendRemotesServer.frontendRemotesServerService,
28
30
  pluginManager.dynamicPluginsFeatureDiscoveryLoader
29
31
  ];
30
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"features.cjs.js","sources":["../../src/features/features.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 {\n coreServices,\n createBackendFeatureLoader,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport {\n DynamicPluginsFactoryOptions,\n dynamicPluginsFeatureDiscoveryLoader,\n dynamicPluginsServiceFactory,\n} from '../manager';\nimport { configKey } from '../scanner/plugin-scanner';\nimport {\n DynamicPluginsRootLoggerFactoryOptions,\n DynamicPluginsSchemasOptions,\n dynamicPluginsFrontendSchemas,\n dynamicPluginsRootLoggerServiceFactory,\n dynamicPluginsSchemasServiceFactory,\n} from '../schemas';\n\n/**\n * @public\n */\nexport type DynamicPluginsFeatureLoaderOptions = DynamicPluginsFactoryOptions &\n DynamicPluginsSchemasOptions & {\n logger?: (config?: Config) => DynamicPluginsRootLoggerFactoryOptions;\n };\n\nconst dynamicPluginsFeatureLoaderWithOptions = (\n options?: DynamicPluginsFeatureLoaderOptions,\n) =>\n createBackendFeatureLoader({\n deps: {\n config: coreServices.rootConfig,\n },\n *loader({ config }) {\n const dynamicPluginsEnabled = config.has(configKey);\n\n let rootLoggerOptions: DynamicPluginsRootLoggerFactoryOptions = {};\n if (options?.logger) {\n rootLoggerOptions = options.logger(config);\n }\n\n yield* [\n dynamicPluginsSchemasServiceFactory(options),\n dynamicPluginsServiceFactory(options),\n ];\n if (dynamicPluginsEnabled) {\n yield* [\n dynamicPluginsRootLoggerServiceFactory(rootLoggerOptions),\n dynamicPluginsFrontendSchemas,\n dynamicPluginsFeatureDiscoveryLoader,\n ];\n }\n },\n });\n\n/**\n * A backend feature loader that fully enable backend dynamic plugins.\n * More precisely it:\n * - adds the dynamic plugins root service (typically depended upon by plugins),\n * - adds additional required features to allow supporting dynamic plugins config schemas\n * in the frontend application and the backend root logger,\n * - uses the dynamic plugins service to discover and expose dynamic plugins as features.\n *\n * @public\n *\n * @example\n * Using the `dynamicPluginsFeatureLoader` loader in a backend instance:\n * ```ts\n * //...\n * import { createBackend } from '@backstage/backend-defaults';\n * import { dynamicPluginsFeatureLoader } from '@backstage/backend-dynamic-feature-service';\n *\n * const backend = createBackend();\n * backend.add(dynamicPluginsFeatureLoader);\n * //...\n * backend.start();\n * ```\n *\n * @example\n * Passing options to the `dynamicPluginsFeatureLoader` loader in a backend instance:\n * ```ts\n * //...\n * import { createBackend } from '@backstage/backend-defaults';\n * import { dynamicPluginsFeatureLoader } from '@backstage/backend-dynamic-feature-service';\n * import { myCustomModuleLoader } from './myCustomModuleLoader';\n * import { myCustomSchemaLocator } from './myCustomSchemaLocator';\n * import { myConfiguredLoggerOptions } from './myConfiguredLoggerOptions';\n *\n * const backend = createBackend();\n * backend.add(dynamicPluginsFeatureLoader({\n * moduleLoader: myCustomModuleLoader,\n * schemaLocator: myCustomSchemaLocator,\n * logger: (config) => myConfiguredLoggerOptions(config),\n * }));\n * //...\n * backend.start();\n * ```\n */\nexport const dynamicPluginsFeatureLoader = Object.assign(\n dynamicPluginsFeatureLoaderWithOptions,\n dynamicPluginsFeatureLoaderWithOptions(),\n);\n"],"names":["createBackendFeatureLoader","coreServices","configKey","dynamicPluginsSchemasServiceFactory","dynamicPluginsServiceFactory","dynamicPluginsRootLoggerServiceFactory","dynamicPluginsFrontendSchemas","dynamicPluginsFeatureDiscoveryLoader"],"mappings":";;;;;;;;;AA2CA,MAAM,sCAAA,GAAyC,CAC7C,OAAA,KAEAA,2CAA2B,CAAA;AAAA,EACzB,IAAM,EAAA;AAAA,IACJ,QAAQC,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,CAAC,MAAA,CAAO,EAAE,MAAA,EAAU,EAAA;AAClB,IAAM,MAAA,qBAAA,GAAwB,MAAO,CAAA,GAAA,CAAIC,uBAAS,CAAA;AAElD,IAAA,IAAI,oBAA4D,EAAC;AACjE,IAAA,IAAI,SAAS,MAAQ,EAAA;AACnB,MAAoB,iBAAA,GAAA,OAAA,CAAQ,OAAO,MAAM,CAAA;AAAA;AAG3C,IAAO,OAAA;AAAA,MACLC,4CAAoC,OAAO,CAAA;AAAA,MAC3CC,2CAA6B,OAAO;AAAA,KACtC;AACA,IAAA,IAAI,qBAAuB,EAAA;AACzB,MAAO,OAAA;AAAA,QACLC,kDAAuC,iBAAiB,CAAA;AAAA,QACxDC,sCAAA;AAAA,QACAC;AAAA,OACF;AAAA;AACF;AAEJ,CAAC,CAAA;AA6CI,MAAM,8BAA8B,MAAO,CAAA,MAAA;AAAA,EAChD,sCAAA;AAAA,EACA,sCAAuC;AACzC;;;;"}
1
+ {"version":3,"file":"features.cjs.js","sources":["../../src/features/features.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 {\n coreServices,\n createBackendFeatureLoader,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport {\n DynamicPluginsFactoryOptions,\n dynamicPluginsFeatureDiscoveryLoader,\n dynamicPluginsServiceFactory,\n} from '../manager';\nimport { configKey } from '../scanner/plugin-scanner';\nimport {\n DynamicPluginsRootLoggerFactoryOptions,\n DynamicPluginsSchemasOptions,\n dynamicPluginsFrontendSchemas,\n dynamicPluginsRootLoggerServiceFactory,\n dynamicPluginsSchemasServiceFactory,\n} from '../schemas';\nimport { frontendRemotesServerService } from '../server/frontendRemotesServer';\n\n/**\n * @public\n */\nexport type DynamicPluginsFeatureLoaderOptions = DynamicPluginsFactoryOptions &\n DynamicPluginsSchemasOptions & {\n logger?: (config?: Config) => DynamicPluginsRootLoggerFactoryOptions;\n };\n\nconst dynamicPluginsFeatureLoaderWithOptions = (\n options?: DynamicPluginsFeatureLoaderOptions,\n) =>\n createBackendFeatureLoader({\n deps: {\n config: coreServices.rootConfig,\n },\n *loader({ config }) {\n const dynamicPluginsEnabled = config.has(configKey);\n\n let rootLoggerOptions: DynamicPluginsRootLoggerFactoryOptions = {};\n if (options?.logger) {\n rootLoggerOptions = options.logger(config);\n }\n\n yield* [\n dynamicPluginsSchemasServiceFactory(options),\n dynamicPluginsServiceFactory(options),\n ];\n if (dynamicPluginsEnabled) {\n yield* [\n dynamicPluginsRootLoggerServiceFactory(rootLoggerOptions),\n dynamicPluginsFrontendSchemas,\n frontendRemotesServerService,\n dynamicPluginsFeatureDiscoveryLoader,\n ];\n }\n },\n });\n\n/**\n * A backend feature loader that fully enable backend dynamic plugins.\n * More precisely it:\n * - adds the dynamic plugins root service (typically depended upon by plugins),\n * - adds additional required features to allow supporting dynamic plugins config schemas\n * in the frontend application and the backend root logger,\n * - uses the dynamic plugins service to discover and expose dynamic plugins as features.\n *\n * @public\n *\n * @example\n * Using the `dynamicPluginsFeatureLoader` loader in a backend instance:\n * ```ts\n * //...\n * import { createBackend } from '@backstage/backend-defaults';\n * import { dynamicPluginsFeatureLoader } from '@backstage/backend-dynamic-feature-service';\n *\n * const backend = createBackend();\n * backend.add(dynamicPluginsFeatureLoader);\n * //...\n * backend.start();\n * ```\n *\n * @example\n * Passing options to the `dynamicPluginsFeatureLoader` loader in a backend instance:\n * ```ts\n * //...\n * import { createBackend } from '@backstage/backend-defaults';\n * import { dynamicPluginsFeatureLoader } from '@backstage/backend-dynamic-feature-service';\n * import { myCustomModuleLoader } from './myCustomModuleLoader';\n * import { myCustomSchemaLocator } from './myCustomSchemaLocator';\n * import { myConfiguredLoggerOptions } from './myConfiguredLoggerOptions';\n *\n * const backend = createBackend();\n * backend.add(dynamicPluginsFeatureLoader({\n * moduleLoader: myCustomModuleLoader,\n * schemaLocator: myCustomSchemaLocator,\n * logger: (config) => myConfiguredLoggerOptions(config),\n * }));\n * //...\n * backend.start();\n * ```\n */\nexport const dynamicPluginsFeatureLoader = Object.assign(\n dynamicPluginsFeatureLoaderWithOptions,\n dynamicPluginsFeatureLoaderWithOptions(),\n);\n"],"names":["createBackendFeatureLoader","coreServices","configKey","dynamicPluginsSchemasServiceFactory","dynamicPluginsServiceFactory","dynamicPluginsRootLoggerServiceFactory","dynamicPluginsFrontendSchemas","frontendRemotesServerService","dynamicPluginsFeatureDiscoveryLoader"],"mappings":";;;;;;;;;;AA4CA,MAAM,sCAAA,GAAyC,CAC7C,OAAA,KAEAA,2CAA2B,CAAA;AAAA,EACzB,IAAM,EAAA;AAAA,IACJ,QAAQC,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,CAAC,MAAA,CAAO,EAAE,MAAA,EAAU,EAAA;AAClB,IAAM,MAAA,qBAAA,GAAwB,MAAO,CAAA,GAAA,CAAIC,uBAAS,CAAA;AAElD,IAAA,IAAI,oBAA4D,EAAC;AACjE,IAAA,IAAI,SAAS,MAAQ,EAAA;AACnB,MAAoB,iBAAA,GAAA,OAAA,CAAQ,OAAO,MAAM,CAAA;AAAA;AAG3C,IAAO,OAAA;AAAA,MACLC,4CAAoC,OAAO,CAAA;AAAA,MAC3CC,2CAA6B,OAAO;AAAA,KACtC;AACA,IAAA,IAAI,qBAAuB,EAAA;AACzB,MAAO,OAAA;AAAA,QACLC,kDAAuC,iBAAiB,CAAA;AAAA,QACxDC,sCAAA;AAAA,QACAC,kDAAA;AAAA,QACAC;AAAA,OACF;AAAA;AACF;AAEJ,CAAC,CAAA;AA6CI,MAAM,8BAA8B,MAAO,CAAA,MAAA;AAAA,EAChD,sCAAA;AAAA,EACA,sCAAuC;AACzC;;;;"}
package/dist/index.cjs.js CHANGED
@@ -7,6 +7,7 @@ var schemas = require('./schemas/schemas.cjs.js');
7
7
  var frontend = require('./schemas/frontend.cjs.js');
8
8
  var rootLogger = require('./schemas/rootLogger.cjs.js');
9
9
  var features = require('./features/features.cjs.js');
10
+ var frontendRemotesServer = require('./server/frontendRemotesServer.cjs.js');
10
11
 
11
12
 
12
13
 
@@ -21,4 +22,5 @@ exports.dynamicPluginsSchemasServiceFactory = schemas.dynamicPluginsSchemasServi
21
22
  exports.dynamicPluginsFrontendSchemas = frontend.dynamicPluginsFrontendSchemas;
22
23
  exports.dynamicPluginsRootLoggerServiceFactory = rootLogger.dynamicPluginsRootLoggerServiceFactory;
23
24
  exports.dynamicPluginsFeatureLoader = features.dynamicPluginsFeatureLoader;
25
+ exports.dynamicPluginsFrontendServiceRef = frontendRemotesServer.dynamicPluginsFrontendServiceRef;
24
26
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -1,18 +1,10 @@
1
1
  import { BackstagePackageJson, PackageRole, PackagePlatform } from '@backstage/cli-node';
2
2
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
3
- import { LoggerService, DatabaseService, UrlReaderService, DiscoveryService, SchedulerService, BackendFeature, SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
4
- import { Logger } from 'winston';
3
+ import { LoggerService, BackendFeature } from '@backstage/backend-plugin-api';
5
4
  import { Config } from '@backstage/config';
6
- import { Router } from 'express';
7
- import { IdentityApi } from '@backstage/plugin-auth-node';
8
- import { PermissionEvaluator } from '@backstage/plugin-permission-common';
9
- import { EventBroker, EventsService, HttpPostIngressOptions } from '@backstage/plugin-events-node';
10
- import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
11
- import { TemplateAction } from '@backstage/plugin-scaffolder-node';
12
- import { IndexBuilder } from '@backstage/plugin-search-backend-node';
13
- import { PermissionPolicy } from '@backstage/plugin-permission-node';
14
5
  import { ConfigSchema } from '@backstage/config-loader';
15
6
  import { WinstonLoggerOptions } from '@backstage/backend-defaults/rootLogger';
7
+ import { JsonObject } from '@backstage/types';
16
8
 
17
9
  /**
18
10
  * @public
@@ -58,31 +50,6 @@ declare class CommonJSModuleLoader implements ModuleLoader {
58
50
  load(packagePath: string): Promise<any>;
59
51
  }
60
52
 
61
- /**
62
- * @public
63
- *
64
- * @deprecated
65
- *
66
- * Support for the legacy backend system will be removed in the future.
67
- *
68
- * When adding a legacy plugin installer entrypoint in your plugin,
69
- * you should always take the opportunity to also implement support
70
- * for the new backend system if not already done.
71
- *
72
- */
73
- type LegacyPluginEnvironment = {
74
- logger: Logger;
75
- database: DatabaseService;
76
- config: Config;
77
- reader: UrlReaderService;
78
- discovery: DiscoveryService;
79
- permissions: PermissionEvaluator;
80
- scheduler: SchedulerService;
81
- identity: IdentityApi;
82
- eventBroker: EventBroker;
83
- events: EventsService;
84
- pluginProvider: BackendPluginProvider;
85
- };
86
53
  /**
87
54
  * @public
88
55
  */
@@ -138,7 +105,7 @@ interface BackendDynamicPlugin extends BaseDynamicPlugin {
138
105
  /**
139
106
  * @public
140
107
  */
141
- type BackendDynamicPluginInstaller = LegacyBackendPluginInstaller | NewBackendPluginInstaller;
108
+ type BackendDynamicPluginInstaller = NewBackendPluginInstaller;
142
109
  /**
143
110
  * @public
144
111
  */
@@ -146,31 +113,6 @@ interface NewBackendPluginInstaller {
146
113
  kind: 'new';
147
114
  install(): BackendFeature | BackendFeature[];
148
115
  }
149
- /**
150
- * @public
151
- * @deprecated
152
- *
153
- * Support for the legacy backend system will be removed in the future.
154
- *
155
- * When adding a legacy plugin installer entrypoint in your plugin,
156
- * you should always take the opportunity to also implement support
157
- * for the new backend system if not already done.
158
- *
159
- */
160
- interface LegacyBackendPluginInstaller {
161
- kind: 'legacy';
162
- router?: {
163
- pluginID: string;
164
- createPlugin(env: LegacyPluginEnvironment): Promise<Router>;
165
- };
166
- catalog?(builder: CatalogBuilder, env: LegacyPluginEnvironment): void;
167
- scaffolder?(env: LegacyPluginEnvironment): TemplateAction<any>[];
168
- search?(indexBuilder: IndexBuilder, schedule: SchedulerServiceTaskRunner, env: LegacyPluginEnvironment): void;
169
- events?(env: LegacyPluginEnvironment): HttpPostIngressOptions[];
170
- permissions?: {
171
- policy?: PermissionPolicy;
172
- };
173
- }
174
116
  /**
175
117
  * @public
176
118
  */
@@ -334,4 +276,87 @@ type DynamicPluginsFeatureLoaderOptions = DynamicPluginsFactoryOptions & Dynamic
334
276
  */
335
277
  declare const dynamicPluginsFeatureLoader: ((options?: DynamicPluginsFeatureLoaderOptions) => _backstage_backend_plugin_api.BackendFeature) & _backstage_backend_plugin_api.BackendFeature;
336
278
 
337
- export { type BackendDynamicPlugin, type BackendDynamicPluginInstaller, type BackendPluginProvider, type BaseDynamicPlugin, CommonJSModuleLoader, type CommonJSModuleLoaderOptions, type DynamicPlugin, DynamicPluginManager, type DynamicPluginManagerOptions, type DynamicPluginProvider, type DynamicPluginsFactoryOptions, type DynamicPluginsFeatureLoaderOptions, type DynamicPluginsRootLoggerFactoryOptions, type DynamicPluginsSchemasOptions, type DynamicPluginsSchemasService, type FrontendDynamicPlugin, type FrontendPluginProvider, type LegacyBackendPluginInstaller, type LegacyPluginEnvironment, type ModuleLoader, type NewBackendPluginInstaller, type ScannedPluginManifest, type ScannedPluginPackage, dynamicPluginsFeatureDiscoveryLoader, dynamicPluginsFeatureLoader, dynamicPluginsFrontendSchemas, dynamicPluginsRootLoggerServiceFactory, dynamicPluginsSchemasServiceFactory, dynamicPluginsServiceFactory, dynamicPluginsServiceFactoryWithOptions, dynamicPluginsServiceRef, isBackendDynamicPluginInstaller };
279
+ /**
280
+ * Definition of a frontend plugin Module Federation remote served by the backend
281
+ * @public
282
+ */
283
+ interface RemoteInfo {
284
+ /**
285
+ * Name of the module federation remote
286
+ */
287
+ name: string;
288
+ /**
289
+ * Remote entry, either the remote manifest file, or the remote entry Javascript file.
290
+ */
291
+ entry: string;
292
+ entryGlobalName?: string;
293
+ shareScope?: string;
294
+ type?: RemoteInfoTypeEnum;
295
+ }
296
+ /**
297
+ * @public
298
+ */
299
+ type RemoteInfoTypeEnum = 'var' | 'module' | 'assign' | 'assign-properties' | 'this' | 'window' | 'self' | 'global' | 'commonjs' | 'commonjs2' | 'commonjs-module' | 'commonjs-static' | 'amd' | 'amd-require' | 'umd' | 'umd2' | 'jsonp' | 'system';
300
+
301
+ /**
302
+ *
303
+ * @public
304
+ * */
305
+ type AdditionalRemoteInfo = Omit<RemoteInfo, 'name' | 'entry'>;
306
+ /**
307
+ *
308
+ * @public
309
+ * */
310
+ type FrontendRemoteResolver = {
311
+ /**
312
+ * Relative path to the module federation assets folder from thr root folder of the plugin package.
313
+ * Default value is `dist`.
314
+ */
315
+ assetsPathFromPackage?: string;
316
+ /**
317
+ * File name of the module federation manifest inside the module federation assets folder.
318
+ * Default value is `mf-manifest.json`.
319
+ */
320
+ manifestFileName?: string;
321
+ /**
322
+ * Type of the remote entry returned in the RemoteInfo for this remote.
323
+ * Default value is `manifest`.
324
+ */
325
+ getRemoteEntryType?: (manifestContent: JsonObject) => 'manifest' | 'javascript';
326
+ /**
327
+ * Additional module federation fields, which might be required if the remote entry type is 'javascript'.
328
+ */
329
+ getAdditionaRemoteInfo?: (manifestContent: JsonObject) => AdditionalRemoteInfo;
330
+ /**
331
+ * Overrides the list of exposed modules. By default the exposed modules are read from the manifest file.
332
+ */
333
+ overrideExposedModules?: (exposedModules: string[], manifestContent: JsonObject) => string[];
334
+ /**
335
+ * Customizes the manifest before returning it as the remote entry.
336
+ */
337
+ customizeManifest?: (content: JsonObject) => JsonObject;
338
+ };
339
+ /**
340
+ *
341
+ * @public
342
+ * */
343
+ type FrontendRemoteResolverProvider = {
344
+ for(pluginName: string, pluginPackagePath: string): Partial<FrontendRemoteResolver> | undefined;
345
+ };
346
+ /**
347
+ *
348
+ * @public
349
+ * */
350
+ interface DynamicPluginsFrontendRemotesService {
351
+ setResolverProvider(provider: FrontendRemoteResolverProvider): void;
352
+ }
353
+ /**
354
+ * A service that serves the frontend module federation remotes,
355
+ * and allows a plugin to customize the way remotes are served,
356
+ * by setting a ResolverProvider.
357
+ *
358
+ * @public
359
+ */
360
+ declare const dynamicPluginsFrontendServiceRef: _backstage_backend_plugin_api.ServiceRef<DynamicPluginsFrontendRemotesService, "root", "singleton">;
361
+
362
+ export { type AdditionalRemoteInfo, type BackendDynamicPlugin, type BackendDynamicPluginInstaller, type BackendPluginProvider, type BaseDynamicPlugin, CommonJSModuleLoader, type CommonJSModuleLoaderOptions, type DynamicPlugin, DynamicPluginManager, type DynamicPluginManagerOptions, type DynamicPluginProvider, type DynamicPluginsFactoryOptions, type DynamicPluginsFeatureLoaderOptions, type DynamicPluginsFrontendRemotesService, type DynamicPluginsRootLoggerFactoryOptions, type DynamicPluginsSchemasOptions, type DynamicPluginsSchemasService, type FrontendDynamicPlugin, type FrontendPluginProvider, type FrontendRemoteResolver, type FrontendRemoteResolverProvider, type ModuleLoader, type NewBackendPluginInstaller, type RemoteInfo, type RemoteInfoTypeEnum, type ScannedPluginManifest, type ScannedPluginPackage, dynamicPluginsFeatureDiscoveryLoader, dynamicPluginsFeatureLoader, dynamicPluginsFrontendSchemas, dynamicPluginsFrontendServiceRef, dynamicPluginsRootLoggerServiceFactory, dynamicPluginsSchemasServiceFactory, dynamicPluginsServiceFactory, dynamicPluginsServiceFactoryWithOptions, dynamicPluginsServiceRef, isBackendDynamicPluginInstaller };
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs.js","sources":["../../src/manager/types.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 { Logger } from 'winston';\nimport { Config } from '@backstage/config';\nimport { Router } from 'express';\nimport { IdentityApi } from '@backstage/plugin-auth-node';\nimport { PermissionEvaluator } from '@backstage/plugin-permission-common';\nimport {\n EventBroker,\n EventsService,\n HttpPostIngressOptions,\n} from '@backstage/plugin-events-node';\n\nimport {\n BackendFeature,\n UrlReaderService,\n SchedulerService,\n SchedulerServiceTaskRunner,\n DatabaseService,\n DiscoveryService,\n} from '@backstage/backend-plugin-api';\nimport { PackagePlatform, PackageRole } from '@backstage/cli-node';\nimport { CatalogBuilder } from '@backstage/plugin-catalog-backend';\nimport { TemplateAction } from '@backstage/plugin-scaffolder-node';\nimport { IndexBuilder } from '@backstage/plugin-search-backend-node';\nimport { PermissionPolicy } from '@backstage/plugin-permission-node';\nimport { ScannedPluginPackage } from '../scanner';\n\n/**\n * @public\n *\n * @deprecated\n *\n * Support for the legacy backend system will be removed in the future.\n *\n * When adding a legacy plugin installer entrypoint in your plugin,\n * you should always take the opportunity to also implement support\n * for the new backend system if not already done.\n *\n */\nexport type LegacyPluginEnvironment = {\n logger: Logger;\n database: DatabaseService;\n config: Config;\n reader: UrlReaderService;\n discovery: DiscoveryService;\n permissions: PermissionEvaluator;\n scheduler: SchedulerService;\n identity: IdentityApi;\n eventBroker: EventBroker;\n events: EventsService;\n pluginProvider: BackendPluginProvider;\n};\n\n/**\n * @public\n */\nexport interface DynamicPluginProvider\n extends FrontendPluginProvider,\n BackendPluginProvider {\n plugins(options?: { includeFailed?: boolean }): DynamicPlugin[];\n getScannedPackage(plugin: DynamicPlugin): ScannedPluginPackage;\n}\n\n/**\n * @public\n */\nexport interface BackendPluginProvider {\n backendPlugins(options?: { includeFailed?: boolean }): BackendDynamicPlugin[];\n}\n\n/**\n * @public\n */\nexport interface FrontendPluginProvider {\n frontendPlugins(options?: {\n includeFailed?: boolean;\n }): FrontendDynamicPlugin[];\n}\n\n/**\n * @public\n */\nexport interface BaseDynamicPlugin {\n name: string;\n version: string;\n role: PackageRole;\n platform: PackagePlatform;\n failure?: string;\n}\n\n/**\n * @public\n */\nexport type DynamicPlugin = FrontendDynamicPlugin | BackendDynamicPlugin;\n\n/**\n * @public\n */\nexport interface FrontendDynamicPlugin extends BaseDynamicPlugin {\n platform: 'web';\n}\n\n/**\n * @public\n */\nexport interface BackendDynamicPlugin extends BaseDynamicPlugin {\n platform: 'node';\n installer?: BackendDynamicPluginInstaller;\n}\n\n/**\n * @public\n */\nexport type BackendDynamicPluginInstaller =\n | LegacyBackendPluginInstaller\n | NewBackendPluginInstaller;\n\n/**\n * @public\n */\nexport interface NewBackendPluginInstaller {\n kind: 'new';\n\n install(): BackendFeature | BackendFeature[];\n}\n\n/**\n * @public\n * @deprecated\n *\n * Support for the legacy backend system will be removed in the future.\n *\n * When adding a legacy plugin installer entrypoint in your plugin,\n * you should always take the opportunity to also implement support\n * for the new backend system if not already done.\n *\n */\nexport interface LegacyBackendPluginInstaller {\n kind: 'legacy';\n\n router?: {\n pluginID: string;\n createPlugin(env: LegacyPluginEnvironment): Promise<Router>;\n };\n\n catalog?(builder: CatalogBuilder, env: LegacyPluginEnvironment): void;\n scaffolder?(env: LegacyPluginEnvironment): TemplateAction<any>[];\n search?(\n indexBuilder: IndexBuilder,\n schedule: SchedulerServiceTaskRunner,\n env: LegacyPluginEnvironment,\n ): void;\n events?(env: LegacyPluginEnvironment): HttpPostIngressOptions[];\n permissions?: {\n policy?: PermissionPolicy;\n };\n}\n\n/**\n * @public\n */\nexport function isBackendDynamicPluginInstaller(\n obj: any,\n): obj is BackendDynamicPluginInstaller {\n return (\n obj !== undefined &&\n 'kind' in obj &&\n (obj.kind === 'new' || obj.kind === 'legacy')\n );\n}\n"],"names":[],"mappings":";;AAgLO,SAAS,gCACd,GACsC,EAAA;AACtC,EACE,OAAA,GAAA,KAAQ,UACR,MAAU,IAAA,GAAA,KACT,IAAI,IAAS,KAAA,KAAA,IAAS,IAAI,IAAS,KAAA,QAAA,CAAA;AAExC;;;;"}
1
+ {"version":3,"file":"types.cjs.js","sources":["../../src/manager/types.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 { BackendFeature } from '@backstage/backend-plugin-api';\nimport { PackagePlatform, PackageRole } from '@backstage/cli-node';\nimport { ScannedPluginPackage } from '../scanner';\n\n/**\n * @public\n */\nexport interface DynamicPluginProvider\n extends FrontendPluginProvider,\n BackendPluginProvider {\n plugins(options?: { includeFailed?: boolean }): DynamicPlugin[];\n getScannedPackage(plugin: DynamicPlugin): ScannedPluginPackage;\n}\n\n/**\n * @public\n */\nexport interface BackendPluginProvider {\n backendPlugins(options?: { includeFailed?: boolean }): BackendDynamicPlugin[];\n}\n\n/**\n * @public\n */\nexport interface FrontendPluginProvider {\n frontendPlugins(options?: {\n includeFailed?: boolean;\n }): FrontendDynamicPlugin[];\n}\n\n/**\n * @public\n */\nexport interface BaseDynamicPlugin {\n name: string;\n version: string;\n role: PackageRole;\n platform: PackagePlatform;\n failure?: string;\n}\n\n/**\n * @public\n */\nexport type DynamicPlugin = FrontendDynamicPlugin | BackendDynamicPlugin;\n\n/**\n * @public\n */\nexport interface FrontendDynamicPlugin extends BaseDynamicPlugin {\n platform: 'web';\n}\n\n/**\n * @public\n */\nexport interface BackendDynamicPlugin extends BaseDynamicPlugin {\n platform: 'node';\n installer?: BackendDynamicPluginInstaller;\n}\n\n/**\n * @public\n */\nexport type BackendDynamicPluginInstaller = NewBackendPluginInstaller;\n\n/**\n * @public\n */\nexport interface NewBackendPluginInstaller {\n kind: 'new';\n\n install(): BackendFeature | BackendFeature[];\n}\n\n/**\n * @public\n */\nexport function isBackendDynamicPluginInstaller(\n obj: any,\n): obj is BackendDynamicPluginInstaller {\n return (\n obj !== undefined &&\n 'kind' in obj &&\n (obj.kind === 'new' || obj.kind === 'legacy')\n );\n}\n"],"names":[],"mappings":";;AA8FO,SAAS,gCACd,GACsC,EAAA;AACtC,EACE,OAAA,GAAA,KAAQ,UACR,MAAU,IAAA,GAAA,KACT,IAAI,IAAS,KAAA,KAAA,IAAS,IAAI,IAAS,KAAA,QAAA,CAAA;AAExC;;;;"}
@@ -0,0 +1,213 @@
1
+ 'use strict';
2
+
3
+ var backendOpenapiUtils = require('@backstage/backend-openapi-utils');
4
+
5
+ const spec = {
6
+ openapi: "3.0.3",
7
+ info: {
8
+ title: ".backstage/dynamic-features",
9
+ version: "1",
10
+ description: "The Backstage backend plugin that serves the frontend plugins module federation manifests and assets",
11
+ license: {
12
+ name: "Apache-2.0",
13
+ url: "http://www.apache.org/licenses/LICENSE-2.0.html"
14
+ },
15
+ contact: {}
16
+ },
17
+ servers: [
18
+ {
19
+ url: "/"
20
+ }
21
+ ],
22
+ components: {
23
+ examples: {},
24
+ headers: {},
25
+ parameters: {},
26
+ requestBodies: {},
27
+ responses: {
28
+ RemotesResponse: {
29
+ description: "List of Module Federation Remotes exposed by the backend",
30
+ content: {
31
+ "application/json": {
32
+ schema: {
33
+ $ref: "#/components/schemas/Remotes"
34
+ }
35
+ }
36
+ }
37
+ },
38
+ ErrorResponse: {
39
+ description: "An error response from the backend.",
40
+ content: {
41
+ "application/json": {
42
+ schema: {
43
+ $ref: "#/components/schemas/Error"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ },
49
+ schemas: {
50
+ Remotes: {
51
+ type: "array",
52
+ items: {
53
+ $ref: "#/components/schemas/Remote"
54
+ }
55
+ },
56
+ Remote: {
57
+ description: "Definition of a frontend plugin Module Federation remote served by the backend",
58
+ type: "object",
59
+ properties: {
60
+ packageName: {
61
+ description: "Name of the package exposed through this Module Federation remote",
62
+ type: "string"
63
+ },
64
+ remoteInfo: {
65
+ $ref: "#/components/schemas/RemoteInfo"
66
+ },
67
+ exposedModules: {
68
+ description: "Names of modules exposed by this module federation remote",
69
+ type: "array",
70
+ items: {
71
+ type: "string"
72
+ }
73
+ }
74
+ },
75
+ required: ["packageName", "remoteInfo", "exposedModules"]
76
+ },
77
+ RemoteInfo: {
78
+ description: "Definition of a frontend plugin Module Federation remote served by the backend",
79
+ externalDocs: {
80
+ url: "https://module-federation.io/guide/basic/runtime.html#init"
81
+ },
82
+ type: "object",
83
+ properties: {
84
+ name: {
85
+ description: "Name of the module federation remote",
86
+ type: "string"
87
+ },
88
+ entry: {
89
+ description: "Remote entry, either the remote manifest file, or the remote entry Javascript file.",
90
+ type: "string"
91
+ },
92
+ entryGlobalName: {
93
+ type: "string"
94
+ },
95
+ shareScope: {
96
+ type: "string"
97
+ },
98
+ type: {
99
+ type: "string",
100
+ enum: [
101
+ "var",
102
+ "module",
103
+ "assign",
104
+ "assign-properties",
105
+ "this",
106
+ "window",
107
+ "self",
108
+ "global",
109
+ "commonjs",
110
+ "commonjs2",
111
+ "commonjs-module",
112
+ "commonjs-static",
113
+ "amd",
114
+ "amd-require",
115
+ "umd",
116
+ "umd2",
117
+ "jsonp",
118
+ "system"
119
+ ]
120
+ }
121
+ },
122
+ required: ["name", "entry"]
123
+ },
124
+ Error: {
125
+ type: "object",
126
+ properties: {
127
+ error: {
128
+ type: "object",
129
+ properties: {
130
+ name: {
131
+ type: "string"
132
+ },
133
+ message: {
134
+ type: "string"
135
+ },
136
+ stack: {
137
+ type: "string"
138
+ },
139
+ code: {
140
+ type: "string"
141
+ }
142
+ },
143
+ required: ["name", "message"]
144
+ },
145
+ request: {
146
+ type: "object",
147
+ properties: {
148
+ method: {
149
+ type: "string"
150
+ },
151
+ url: {
152
+ type: "string"
153
+ }
154
+ },
155
+ required: ["method", "url"]
156
+ },
157
+ response: {
158
+ type: "object",
159
+ properties: {
160
+ statusCode: {
161
+ type: "number"
162
+ }
163
+ },
164
+ required: ["statusCode"]
165
+ }
166
+ },
167
+ required: ["error", "response"],
168
+ additionalProperties: {}
169
+ }
170
+ },
171
+ securitySchemes: {
172
+ JWT: {
173
+ type: "http",
174
+ scheme: "bearer",
175
+ bearerFormat: "JWT"
176
+ }
177
+ }
178
+ },
179
+ paths: {
180
+ "/remotes": {
181
+ get: {
182
+ operationId: "GetRemotes",
183
+ description: "Get the Module Federation remote definitions.",
184
+ responses: {
185
+ "200": {
186
+ $ref: "#/components/responses/RemotesResponse"
187
+ },
188
+ "400": {
189
+ $ref: "#/components/responses/ErrorResponse"
190
+ },
191
+ default: {
192
+ $ref: "#/components/responses/ErrorResponse"
193
+ }
194
+ },
195
+ security: [
196
+ {},
197
+ {
198
+ JWT: []
199
+ }
200
+ ],
201
+ parameters: []
202
+ }
203
+ }
204
+ }
205
+ };
206
+ const createOpenApiRouter = async (options) => backendOpenapiUtils.createValidatedOpenApiRouterFromGeneratedEndpointMap(
207
+ spec,
208
+ options
209
+ );
210
+
211
+ exports.createOpenApiRouter = createOpenApiRouter;
212
+ exports.spec = spec;
213
+ //# sourceMappingURL=router.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.cjs.js","sources":["../../../../src/schema/openapi/generated/router.ts"],"sourcesContent":["/*\n * Copyright 2025 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\n// ******************************************************************\n// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *\n// ******************************************************************\nimport { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils';\nimport { EndpointMap } from './';\n\nexport const spec = {\n openapi: '3.0.3',\n info: {\n title: '.backstage/dynamic-features',\n version: '1',\n description:\n 'The Backstage backend plugin that serves the frontend plugins module federation manifests and assets',\n license: {\n name: 'Apache-2.0',\n url: 'http://www.apache.org/licenses/LICENSE-2.0.html',\n },\n contact: {},\n },\n servers: [\n {\n url: '/',\n },\n ],\n components: {\n examples: {},\n headers: {},\n parameters: {},\n requestBodies: {},\n responses: {\n RemotesResponse: {\n description: 'List of Module Federation Remotes exposed by the backend',\n content: {\n 'application/json': {\n schema: {\n $ref: '#/components/schemas/Remotes',\n },\n },\n },\n },\n ErrorResponse: {\n description: 'An error response from the backend.',\n content: {\n 'application/json': {\n schema: {\n $ref: '#/components/schemas/Error',\n },\n },\n },\n },\n },\n schemas: {\n Remotes: {\n type: 'array',\n items: {\n $ref: '#/components/schemas/Remote',\n },\n },\n Remote: {\n description:\n 'Definition of a frontend plugin Module Federation remote served by the backend',\n type: 'object',\n properties: {\n packageName: {\n description:\n 'Name of the package exposed through this Module Federation remote',\n type: 'string',\n },\n remoteInfo: {\n $ref: '#/components/schemas/RemoteInfo',\n },\n exposedModules: {\n description:\n 'Names of modules exposed by this module federation remote',\n type: 'array',\n items: {\n type: 'string',\n },\n },\n },\n required: ['packageName', 'remoteInfo', 'exposedModules'],\n },\n RemoteInfo: {\n description:\n 'Definition of a frontend plugin Module Federation remote served by the backend',\n externalDocs: {\n url: 'https://module-federation.io/guide/basic/runtime.html#init',\n },\n type: 'object',\n properties: {\n name: {\n description: 'Name of the module federation remote',\n type: 'string',\n },\n entry: {\n description:\n 'Remote entry, either the remote manifest file, or the remote entry Javascript file.',\n type: 'string',\n },\n entryGlobalName: {\n type: 'string',\n },\n shareScope: {\n type: 'string',\n },\n type: {\n type: 'string',\n enum: [\n 'var',\n 'module',\n 'assign',\n 'assign-properties',\n 'this',\n 'window',\n 'self',\n 'global',\n 'commonjs',\n 'commonjs2',\n 'commonjs-module',\n 'commonjs-static',\n 'amd',\n 'amd-require',\n 'umd',\n 'umd2',\n 'jsonp',\n 'system',\n ],\n },\n },\n required: ['name', 'entry'],\n },\n Error: {\n type: 'object',\n properties: {\n error: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n message: {\n type: 'string',\n },\n stack: {\n type: 'string',\n },\n code: {\n type: 'string',\n },\n },\n required: ['name', 'message'],\n },\n request: {\n type: 'object',\n properties: {\n method: {\n type: 'string',\n },\n url: {\n type: 'string',\n },\n },\n required: ['method', 'url'],\n },\n response: {\n type: 'object',\n properties: {\n statusCode: {\n type: 'number',\n },\n },\n required: ['statusCode'],\n },\n },\n required: ['error', 'response'],\n additionalProperties: {},\n },\n },\n securitySchemes: {\n JWT: {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n },\n },\n },\n paths: {\n '/remotes': {\n get: {\n operationId: 'GetRemotes',\n description: 'Get the Module Federation remote definitions.',\n responses: {\n '200': {\n $ref: '#/components/responses/RemotesResponse',\n },\n '400': {\n $ref: '#/components/responses/ErrorResponse',\n },\n default: {\n $ref: '#/components/responses/ErrorResponse',\n },\n },\n security: [\n {},\n {\n JWT: [],\n },\n ],\n parameters: [],\n },\n },\n },\n} as const;\nexport const createOpenApiRouter = async (\n options?: Parameters<\n typeof createValidatedOpenApiRouterFromGeneratedEndpointMap\n >['1'],\n) =>\n createValidatedOpenApiRouterFromGeneratedEndpointMap<EndpointMap>(\n spec,\n options,\n );\n"],"names":["createValidatedOpenApiRouterFromGeneratedEndpointMap"],"mappings":";;;;AAsBO,MAAM,IAAO,GAAA;AAAA,EAClB,OAAS,EAAA,OAAA;AAAA,EACT,IAAM,EAAA;AAAA,IACJ,KAAO,EAAA,6BAAA;AAAA,IACP,OAAS,EAAA,GAAA;AAAA,IACT,WACE,EAAA,sGAAA;AAAA,IACF,OAAS,EAAA;AAAA,MACP,IAAM,EAAA,YAAA;AAAA,MACN,GAAK,EAAA;AAAA,KACP;AAAA,IACA,SAAS;AAAC,GACZ;AAAA,EACA,OAAS,EAAA;AAAA,IACP;AAAA,MACE,GAAK,EAAA;AAAA;AACP,GACF;AAAA,EACA,UAAY,EAAA;AAAA,IACV,UAAU,EAAC;AAAA,IACX,SAAS,EAAC;AAAA,IACV,YAAY,EAAC;AAAA,IACb,eAAe,EAAC;AAAA,IAChB,SAAW,EAAA;AAAA,MACT,eAAiB,EAAA;AAAA,QACf,WAAa,EAAA,0DAAA;AAAA,QACb,OAAS,EAAA;AAAA,UACP,kBAAoB,EAAA;AAAA,YAClB,MAAQ,EAAA;AAAA,cACN,IAAM,EAAA;AAAA;AACR;AACF;AACF,OACF;AAAA,MACA,aAAe,EAAA;AAAA,QACb,WAAa,EAAA,qCAAA;AAAA,QACb,OAAS,EAAA;AAAA,UACP,kBAAoB,EAAA;AAAA,YAClB,MAAQ,EAAA;AAAA,cACN,IAAM,EAAA;AAAA;AACR;AACF;AACF;AACF,KACF;AAAA,IACA,OAAS,EAAA;AAAA,MACP,OAAS,EAAA;AAAA,QACP,IAAM,EAAA,OAAA;AAAA,QACN,KAAO,EAAA;AAAA,UACL,IAAM,EAAA;AAAA;AACR,OACF;AAAA,MACA,MAAQ,EAAA;AAAA,QACN,WACE,EAAA,gFAAA;AAAA,QACF,IAAM,EAAA,QAAA;AAAA,QACN,UAAY,EAAA;AAAA,UACV,WAAa,EAAA;AAAA,YACX,WACE,EAAA,mEAAA;AAAA,YACF,IAAM,EAAA;AAAA,WACR;AAAA,UACA,UAAY,EAAA;AAAA,YACV,IAAM,EAAA;AAAA,WACR;AAAA,UACA,cAAgB,EAAA;AAAA,YACd,WACE,EAAA,2DAAA;AAAA,YACF,IAAM,EAAA,OAAA;AAAA,YACN,KAAO,EAAA;AAAA,cACL,IAAM,EAAA;AAAA;AACR;AACF,SACF;AAAA,QACA,QAAU,EAAA,CAAC,aAAe,EAAA,YAAA,EAAc,gBAAgB;AAAA,OAC1D;AAAA,MACA,UAAY,EAAA;AAAA,QACV,WACE,EAAA,gFAAA;AAAA,QACF,YAAc,EAAA;AAAA,UACZ,GAAK,EAAA;AAAA,SACP;AAAA,QACA,IAAM,EAAA,QAAA;AAAA,QACN,UAAY,EAAA;AAAA,UACV,IAAM,EAAA;AAAA,YACJ,WAAa,EAAA,sCAAA;AAAA,YACb,IAAM,EAAA;AAAA,WACR;AAAA,UACA,KAAO,EAAA;AAAA,YACL,WACE,EAAA,qFAAA;AAAA,YACF,IAAM,EAAA;AAAA,WACR;AAAA,UACA,eAAiB,EAAA;AAAA,YACf,IAAM,EAAA;AAAA,WACR;AAAA,UACA,UAAY,EAAA;AAAA,YACV,IAAM,EAAA;AAAA,WACR;AAAA,UACA,IAAM,EAAA;AAAA,YACJ,IAAM,EAAA,QAAA;AAAA,YACN,IAAM,EAAA;AAAA,cACJ,KAAA;AAAA,cACA,QAAA;AAAA,cACA,QAAA;AAAA,cACA,mBAAA;AAAA,cACA,MAAA;AAAA,cACA,QAAA;AAAA,cACA,MAAA;AAAA,cACA,QAAA;AAAA,cACA,UAAA;AAAA,cACA,WAAA;AAAA,cACA,iBAAA;AAAA,cACA,iBAAA;AAAA,cACA,KAAA;AAAA,cACA,aAAA;AAAA,cACA,KAAA;AAAA,cACA,MAAA;AAAA,cACA,OAAA;AAAA,cACA;AAAA;AACF;AACF,SACF;AAAA,QACA,QAAA,EAAU,CAAC,MAAA,EAAQ,OAAO;AAAA,OAC5B;AAAA,MACA,KAAO,EAAA;AAAA,QACL,IAAM,EAAA,QAAA;AAAA,QACN,UAAY,EAAA;AAAA,UACV,KAAO,EAAA;AAAA,YACL,IAAM,EAAA,QAAA;AAAA,YACN,UAAY,EAAA;AAAA,cACV,IAAM,EAAA;AAAA,gBACJ,IAAM,EAAA;AAAA,eACR;AAAA,cACA,OAAS,EAAA;AAAA,gBACP,IAAM,EAAA;AAAA,eACR;AAAA,cACA,KAAO,EAAA;AAAA,gBACL,IAAM,EAAA;AAAA,eACR;AAAA,cACA,IAAM,EAAA;AAAA,gBACJ,IAAM,EAAA;AAAA;AACR,aACF;AAAA,YACA,QAAA,EAAU,CAAC,MAAA,EAAQ,SAAS;AAAA,WAC9B;AAAA,UACA,OAAS,EAAA;AAAA,YACP,IAAM,EAAA,QAAA;AAAA,YACN,UAAY,EAAA;AAAA,cACV,MAAQ,EAAA;AAAA,gBACN,IAAM,EAAA;AAAA,eACR;AAAA,cACA,GAAK,EAAA;AAAA,gBACH,IAAM,EAAA;AAAA;AACR,aACF;AAAA,YACA,QAAA,EAAU,CAAC,QAAA,EAAU,KAAK;AAAA,WAC5B;AAAA,UACA,QAAU,EAAA;AAAA,YACR,IAAM,EAAA,QAAA;AAAA,YACN,UAAY,EAAA;AAAA,cACV,UAAY,EAAA;AAAA,gBACV,IAAM,EAAA;AAAA;AACR,aACF;AAAA,YACA,QAAA,EAAU,CAAC,YAAY;AAAA;AACzB,SACF;AAAA,QACA,QAAA,EAAU,CAAC,OAAA,EAAS,UAAU,CAAA;AAAA,QAC9B,sBAAsB;AAAC;AACzB,KACF;AAAA,IACA,eAAiB,EAAA;AAAA,MACf,GAAK,EAAA;AAAA,QACH,IAAM,EAAA,MAAA;AAAA,QACN,MAAQ,EAAA,QAAA;AAAA,QACR,YAAc,EAAA;AAAA;AAChB;AACF,GACF;AAAA,EACA,KAAO,EAAA;AAAA,IACL,UAAY,EAAA;AAAA,MACV,GAAK,EAAA;AAAA,QACH,WAAa,EAAA,YAAA;AAAA,QACb,WAAa,EAAA,+CAAA;AAAA,QACb,SAAW,EAAA;AAAA,UACT,KAAO,EAAA;AAAA,YACL,IAAM,EAAA;AAAA,WACR;AAAA,UACA,KAAO,EAAA;AAAA,YACL,IAAM,EAAA;AAAA,WACR;AAAA,UACA,OAAS,EAAA;AAAA,YACP,IAAM,EAAA;AAAA;AACR,SACF;AAAA,QACA,QAAU,EAAA;AAAA,UACR,EAAC;AAAA,UACD;AAAA,YACE,KAAK;AAAC;AACR,SACF;AAAA,QACA,YAAY;AAAC;AACf;AACF;AAEJ;AACa,MAAA,mBAAA,GAAsB,OACjC,OAIA,KAAAA,wEAAA;AAAA,EACE,IAAA;AAAA,EACA;AACF;;;;;"}
@@ -96,7 +96,7 @@ const dynamicPluginsSchemasServiceFactory = Object.assign(
96
96
  dynamicPluginsSchemasServiceFactoryWithOptions,
97
97
  dynamicPluginsSchemasServiceFactoryWithOptions()
98
98
  );
99
- async function gatherDynamicPluginsSchemas(packages, logger, schemaLocator = () => path__namespace.join("dist", "configSchema.json")) {
99
+ async function gatherDynamicPluginsSchemas(packages, logger, schemaLocator = () => path__namespace.join("dist", ".config-schema.json")) {
100
100
  const allSchemas = {};
101
101
  for (const pluginPackage of packages) {
102
102
  let schemaLocation = schemaLocator(pluginPackage);
@@ -107,13 +107,18 @@ async function gatherDynamicPluginsSchemas(packages, logger, schemaLocator = ()
107
107
  if (!await fs__default.default.pathExists(schemaLocation)) {
108
108
  continue;
109
109
  }
110
- const serialized = await fs__default.default.readJson(schemaLocation);
110
+ let serialized = await fs__default.default.readJson(schemaLocation);
111
111
  if (!serialized) {
112
112
  continue;
113
113
  }
114
114
  if (lodash.isEmpty(serialized)) {
115
115
  continue;
116
116
  }
117
+ if (serialized?.backstageConfigSchemaVersion === 1) {
118
+ serialized = configLoader.mergeConfigSchemas(
119
+ (serialized?.schemas).map((_) => _.value)
120
+ );
121
+ }
117
122
  if (!serialized?.$schema || serialized?.type !== "object") {
118
123
  logger.error(
119
124
  `Serialized configuration schema is invalid for plugin ${pluginPackage.manifest.name}`
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.cjs.js","sources":["../../src/schemas/schemas.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 { ScannedPluginPackage } from '../scanner/types';\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { findPaths } from '@backstage/cli-common';\n\nimport fs from 'fs-extra';\nimport * as path from 'path';\nimport * as url from 'url';\nimport { isEmpty } from 'lodash';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { PluginScanner } from '../scanner/plugin-scanner';\nimport { ConfigSchema, loadConfigSchema } from '@backstage/config-loader';\nimport { dynamicPluginsFeatureLoader } from '../features';\n\n/**\n *\n * @public\n * */\nexport interface DynamicPluginsSchemasService {\n addDynamicPluginsSchemas(configSchema: ConfigSchema): Promise<{\n schema: ConfigSchema;\n }>;\n}\n\n/**\n * A service that provides the config schemas of scanned dynamic plugins.\n *\n * @public\n */\nexport const dynamicPluginsSchemasServiceRef =\n createServiceRef<DynamicPluginsSchemasService>({\n id: 'core.dynamicplugins.schemas',\n scope: 'root',\n });\n\n/**\n * @public\n */\nexport interface DynamicPluginsSchemasOptions {\n /**\n * Function that returns the path to the Json schema file for a given scanned plugin package.\n * The path is either absolute, or relative to the plugin package root directory.\n *\n * Default behavior is to look for the `dist/configSchema.json` relative path.\n *\n * @param pluginPackage - The scanned plugin package.\n * @returns the absolute or plugin-relative path to the Json schema file.\n */\n schemaLocator?: (pluginPackage: ScannedPluginPackage) => string;\n}\n\nconst dynamicPluginsSchemasServiceFactoryWithOptions = (\n options?: DynamicPluginsSchemasOptions,\n) =>\n createServiceFactory({\n service: dynamicPluginsSchemasServiceRef,\n deps: {\n config: coreServices.rootConfig,\n },\n factory({ config }) {\n let additionalSchemas: { [context: string]: JsonObject } | undefined;\n\n return {\n async addDynamicPluginsSchemas(configSchema: ConfigSchema): Promise<{\n schema: ConfigSchema;\n }> {\n if (!additionalSchemas) {\n const logger = {\n ...console,\n child() {\n return this;\n },\n };\n\n const scanner = PluginScanner.create({\n config,\n logger,\n // eslint-disable-next-line no-restricted-syntax\n backstageRoot: findPaths(__dirname).targetRoot,\n preferAlpha: true,\n });\n\n const { packages } = await scanner.scanRoot();\n\n additionalSchemas = await gatherDynamicPluginsSchemas(\n packages,\n logger,\n options?.schemaLocator,\n );\n }\n\n const serialized = configSchema.serialize();\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n const schemas = serialized.schemas as {\n value: JsonObject;\n path: string;\n }[];\n\n schemas.push(\n ...Object.keys(additionalSchemas).map(context => {\n return {\n path: context,\n value: additionalSchemas![context],\n };\n }),\n );\n serialized.schemas = schemas;\n return {\n schema: await loadConfigSchema({\n serialized,\n }),\n };\n },\n };\n },\n });\n\n/**\n * @public\n * @deprecated Use {@link dynamicPluginsFeatureLoader} instead, which gathers all services and features required for dynamic plugins.\n */\nexport const dynamicPluginsSchemasServiceFactory = Object.assign(\n dynamicPluginsSchemasServiceFactoryWithOptions,\n dynamicPluginsSchemasServiceFactoryWithOptions(),\n);\n\n/** @internal */\nasync function gatherDynamicPluginsSchemas(\n packages: ScannedPluginPackage[],\n logger: LoggerService,\n schemaLocator: (pluginPackage: ScannedPluginPackage) => string = () =>\n path.join('dist', 'configSchema.json'),\n): Promise<{ [context: string]: JsonObject }> {\n const allSchemas: { [context: string]: JsonObject } = {};\n\n for (const pluginPackage of packages) {\n let schemaLocation = schemaLocator(pluginPackage);\n\n if (!path.isAbsolute(schemaLocation)) {\n const pluginLocation = url.fileURLToPath(pluginPackage.location);\n schemaLocation = path.resolve(pluginLocation, schemaLocation);\n }\n\n if (!(await fs.pathExists(schemaLocation))) {\n continue;\n }\n\n const serialized = await fs.readJson(schemaLocation);\n if (!serialized) {\n continue;\n }\n\n if (isEmpty(serialized)) {\n continue;\n }\n\n if (!serialized?.$schema || serialized?.type !== 'object') {\n logger.error(\n `Serialized configuration schema is invalid for plugin ${pluginPackage.manifest.name}`,\n );\n continue;\n }\n\n allSchemas[schemaLocation] = serialized;\n }\n\n return allSchemas;\n}\n"],"names":["createServiceRef","createServiceFactory","coreServices","PluginScanner","findPaths","loadConfigSchema","path","url","fs","isEmpty"],"mappings":"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"}
1
+ {"version":3,"file":"schemas.cjs.js","sources":["../../src/schemas/schemas.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 { ScannedPluginPackage } from '../scanner/types';\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { findPaths } from '@backstage/cli-common';\n\nimport fs from 'fs-extra';\nimport * as path from 'path';\nimport * as url from 'url';\nimport { isEmpty } from 'lodash';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { PluginScanner } from '../scanner/plugin-scanner';\nimport {\n ConfigSchema,\n loadConfigSchema,\n mergeConfigSchemas,\n} from '@backstage/config-loader';\nimport { dynamicPluginsFeatureLoader } from '../features';\n\n/**\n *\n * @public\n * */\nexport interface DynamicPluginsSchemasService {\n addDynamicPluginsSchemas(configSchema: ConfigSchema): Promise<{\n schema: ConfigSchema;\n }>;\n}\n\n/**\n * A service that provides the config schemas of scanned dynamic plugins.\n *\n * @public\n */\nexport const dynamicPluginsSchemasServiceRef =\n createServiceRef<DynamicPluginsSchemasService>({\n id: 'core.dynamicplugins.schemas',\n scope: 'root',\n });\n\n/**\n * @public\n */\nexport interface DynamicPluginsSchemasOptions {\n /**\n * Function that returns the path to the Json schema file for a given scanned plugin package.\n * The path is either absolute, or relative to the plugin package root directory.\n *\n * Default behavior is to look for the `dist/configSchema.json` relative path.\n *\n * @param pluginPackage - The scanned plugin package.\n * @returns the absolute or plugin-relative path to the Json schema file.\n */\n schemaLocator?: (pluginPackage: ScannedPluginPackage) => string;\n}\n\nconst dynamicPluginsSchemasServiceFactoryWithOptions = (\n options?: DynamicPluginsSchemasOptions,\n) =>\n createServiceFactory({\n service: dynamicPluginsSchemasServiceRef,\n deps: {\n config: coreServices.rootConfig,\n },\n factory({ config }) {\n let additionalSchemas: { [context: string]: JsonObject } | undefined;\n\n return {\n async addDynamicPluginsSchemas(configSchema: ConfigSchema): Promise<{\n schema: ConfigSchema;\n }> {\n if (!additionalSchemas) {\n const logger = {\n ...console,\n child() {\n return this;\n },\n };\n\n const scanner = PluginScanner.create({\n config,\n logger,\n // eslint-disable-next-line no-restricted-syntax\n backstageRoot: findPaths(__dirname).targetRoot,\n preferAlpha: true,\n });\n\n const { packages } = await scanner.scanRoot();\n\n additionalSchemas = await gatherDynamicPluginsSchemas(\n packages,\n logger,\n options?.schemaLocator,\n );\n }\n\n const serialized = configSchema.serialize();\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n const schemas = serialized.schemas as {\n value: JsonObject;\n path: string;\n }[];\n\n schemas.push(\n ...Object.keys(additionalSchemas).map(context => {\n return {\n path: context,\n value: additionalSchemas![context],\n };\n }),\n );\n serialized.schemas = schemas;\n return {\n schema: await loadConfigSchema({\n serialized,\n }),\n };\n },\n };\n },\n });\n\n/**\n * @public\n * @deprecated Use {@link dynamicPluginsFeatureLoader} instead, which gathers all services and features required for dynamic plugins.\n */\nexport const dynamicPluginsSchemasServiceFactory = Object.assign(\n dynamicPluginsSchemasServiceFactoryWithOptions,\n dynamicPluginsSchemasServiceFactoryWithOptions(),\n);\n\n/** @internal */\nasync function gatherDynamicPluginsSchemas(\n packages: ScannedPluginPackage[],\n logger: LoggerService,\n schemaLocator: (pluginPackage: ScannedPluginPackage) => string = () =>\n path.join('dist', '.config-schema.json'),\n): Promise<{ [context: string]: JsonObject }> {\n const allSchemas: { [context: string]: JsonObject } = {};\n\n for (const pluginPackage of packages) {\n let schemaLocation = schemaLocator(pluginPackage);\n\n if (!path.isAbsolute(schemaLocation)) {\n const pluginLocation = url.fileURLToPath(pluginPackage.location);\n schemaLocation = path.resolve(pluginLocation, schemaLocation);\n }\n\n if (!(await fs.pathExists(schemaLocation))) {\n continue;\n }\n\n let serialized = await fs.readJson(schemaLocation);\n if (!serialized) {\n continue;\n }\n\n if (isEmpty(serialized)) {\n continue;\n }\n\n if (serialized?.backstageConfigSchemaVersion === 1) {\n serialized = mergeConfigSchemas(\n (serialized?.schemas as JsonObject[]).map(_ => _.value as any),\n );\n }\n\n if (!serialized?.$schema || serialized?.type !== 'object') {\n logger.error(\n `Serialized configuration schema is invalid for plugin ${pluginPackage.manifest.name}`,\n );\n continue;\n }\n\n allSchemas[schemaLocation] = serialized;\n }\n\n return allSchemas;\n}\n"],"names":["createServiceRef","createServiceFactory","coreServices","PluginScanner","findPaths","loadConfigSchema","path","url","fs","isEmpty","mergeConfigSchemas"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDO,MAAM,kCACXA,iCAA+C,CAAA;AAAA,EAC7C,EAAI,EAAA,6BAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAC;AAkBH,MAAM,8CAAA,GAAiD,CACrD,OAAA,KAEAC,qCAAqB,CAAA;AAAA,EACnB,OAAS,EAAA,+BAAA;AAAA,EACT,IAAM,EAAA;AAAA,IACJ,QAAQC,6BAAa,CAAA;AAAA,GACvB;AAAA,EACA,OAAA,CAAQ,EAAE,MAAA,EAAU,EAAA;AAClB,IAAI,IAAA,iBAAA;AAEJ,IAAO,OAAA;AAAA,MACL,MAAM,yBAAyB,YAE5B,EAAA;AACD,QAAA,IAAI,CAAC,iBAAmB,EAAA;AACtB,UAAA,MAAM,MAAS,GAAA;AAAA,YACb,GAAG,OAAA;AAAA,YACH,KAAQ,GAAA;AACN,cAAO,OAAA,IAAA;AAAA;AACT,WACF;AAEA,UAAM,MAAA,OAAA,GAAUC,4BAAc,MAAO,CAAA;AAAA,YACnC,MAAA;AAAA,YACA,MAAA;AAAA;AAAA,YAEA,aAAA,EAAeC,mBAAU,CAAA,SAAS,CAAE,CAAA,UAAA;AAAA,YACpC,WAAa,EAAA;AAAA,WACd,CAAA;AAED,UAAA,MAAM,EAAE,QAAA,EAAa,GAAA,MAAM,QAAQ,QAAS,EAAA;AAE5C,UAAA,iBAAA,GAAoB,MAAM,2BAAA;AAAA,YACxB,QAAA;AAAA,YACA,MAAA;AAAA,YACA,OAAS,EAAA;AAAA,WACX;AAAA;AAGF,QAAM,MAAA,UAAA,GAAa,aAAa,SAAU,EAAA;AAC1C,QAAI,IAAA,UAAA,EAAY,iCAAiC,CAAG,EAAA;AAClD,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,MAAM,UAAU,UAAW,CAAA,OAAA;AAK3B,QAAQ,OAAA,CAAA,IAAA;AAAA,UACN,GAAG,MAAO,CAAA,IAAA,CAAK,iBAAiB,CAAA,CAAE,IAAI,CAAW,OAAA,KAAA;AAC/C,YAAO,OAAA;AAAA,cACL,IAAM,EAAA,OAAA;AAAA,cACN,KAAA,EAAO,kBAAmB,OAAO;AAAA,aACnC;AAAA,WACD;AAAA,SACH;AACA,QAAA,UAAA,CAAW,OAAU,GAAA,OAAA;AACrB,QAAO,OAAA;AAAA,UACL,MAAA,EAAQ,MAAMC,6BAAiB,CAAA;AAAA,YAC7B;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACF;AAAA;AAEJ,CAAC,CAAA;AAMI,MAAM,sCAAsC,MAAO,CAAA,MAAA;AAAA,EACxD,8CAAA;AAAA,EACA,8CAA+C;AACjD;AAGA,eAAe,2BAAA,CACb,UACA,MACA,EAAA,aAAA,GAAiE,MAC/DC,eAAK,CAAA,IAAA,CAAK,MAAQ,EAAA,qBAAqB,CACG,EAAA;AAC5C,EAAA,MAAM,aAAgD,EAAC;AAEvD,EAAA,KAAA,MAAW,iBAAiB,QAAU,EAAA;AACpC,IAAI,IAAA,cAAA,GAAiB,cAAc,aAAa,CAAA;AAEhD,IAAA,IAAI,CAACA,eAAA,CAAK,UAAW,CAAA,cAAc,CAAG,EAAA;AACpC,MAAA,MAAM,cAAiB,GAAAC,cAAA,CAAI,aAAc,CAAA,aAAA,CAAc,QAAQ,CAAA;AAC/D,MAAiB,cAAA,GAAAD,eAAA,CAAK,OAAQ,CAAA,cAAA,EAAgB,cAAc,CAAA;AAAA;AAG9D,IAAA,IAAI,CAAE,MAAME,mBAAG,CAAA,UAAA,CAAW,cAAc,CAAI,EAAA;AAC1C,MAAA;AAAA;AAGF,IAAA,IAAI,UAAa,GAAA,MAAMA,mBAAG,CAAA,QAAA,CAAS,cAAc,CAAA;AACjD,IAAA,IAAI,CAAC,UAAY,EAAA;AACf,MAAA;AAAA;AAGF,IAAI,IAAAC,cAAA,CAAQ,UAAU,CAAG,EAAA;AACvB,MAAA;AAAA;AAGF,IAAI,IAAA,UAAA,EAAY,iCAAiC,CAAG,EAAA;AAClD,MAAa,UAAA,GAAAC,+BAAA;AAAA,QAAA,CACV,UAAY,EAAA,OAAA,EAAyB,GAAI,CAAA,CAAA,CAAA,KAAK,EAAE,KAAY;AAAA,OAC/D;AAAA;AAGF,IAAA,IAAI,CAAC,UAAA,EAAY,OAAW,IAAA,UAAA,EAAY,SAAS,QAAU,EAAA;AACzD,MAAO,MAAA,CAAA,KAAA;AAAA,QACL,CAAA,sDAAA,EAAyD,aAAc,CAAA,QAAA,CAAS,IAAI,CAAA;AAAA,OACtF;AACA,MAAA;AAAA;AAGF,IAAA,UAAA,CAAW,cAAc,CAAI,GAAA,UAAA;AAAA;AAG/B,EAAO,OAAA,UAAA;AACT;;;;;"}
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var router$1 = require('./router.cjs.js');
5
+ var backendDynamicFeatureService = require('@backstage/backend-dynamic-feature-service');
6
+ var router = require('../schema/openapi/generated/router.cjs.js');
7
+ var sdk = require('@module-federation/sdk');
8
+
9
+ const dynamicPluginsFrontendServiceRef = backendPluginApi.createServiceRef({
10
+ id: "core.dynamicplugins.frontendRemotes",
11
+ scope: "root"
12
+ });
13
+ const frontendRemotesServerService = backendPluginApi.createServiceFactory({
14
+ service: dynamicPluginsFrontendServiceRef,
15
+ deps: {
16
+ logger: backendPluginApi.coreServices.rootLogger,
17
+ rootHttpRouter: backendPluginApi.coreServices.rootHttpRouter,
18
+ config: backendPluginApi.coreServices.rootConfig,
19
+ dynamicPlugins: backendDynamicFeatureService.dynamicPluginsServiceRef,
20
+ lifecycle: backendPluginApi.coreServices.rootLifecycle
21
+ },
22
+ async factory({ logger, rootHttpRouter, config, dynamicPlugins, lifecycle }) {
23
+ const resolvers = {
24
+ default: {
25
+ assetsPathFromPackage: "dist",
26
+ manifestFileName: sdk.ManifestFileName,
27
+ getRemoteEntryType: () => "manifest"
28
+ },
29
+ provider: void 0
30
+ };
31
+ lifecycle.addStartupHook(async () => {
32
+ rootHttpRouter.use(
33
+ `/${router.spec.info.title}`,
34
+ await router$1.createRouter({
35
+ logger,
36
+ config,
37
+ dynamicPlugins,
38
+ resolvers
39
+ })
40
+ );
41
+ });
42
+ return {
43
+ setResolverProvider(resolver) {
44
+ logger.info("Setting resolver provider");
45
+ if (resolvers.provider) {
46
+ throw new Error(
47
+ "Attempted to install a frontend remote resolver provider twice"
48
+ );
49
+ }
50
+ resolvers.provider = resolver;
51
+ }
52
+ };
53
+ }
54
+ });
55
+
56
+ exports.dynamicPluginsFrontendServiceRef = dynamicPluginsFrontendServiceRef;
57
+ exports.frontendRemotesServerService = frontendRemotesServerService;
58
+ //# sourceMappingURL=frontendRemotesServer.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontendRemotesServer.cjs.js","sources":["../../src/server/frontendRemotesServer.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 */\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './router';\nimport { dynamicPluginsServiceRef } from '@backstage/backend-dynamic-feature-service';\nimport { spec } from '../schema/openapi';\nimport { ManifestFileName } from '@module-federation/sdk';\nimport { RemoteInfo } from '../schema/openapi/generated/models';\nimport { JsonObject } from '@backstage/types';\n\n/**\n *\n * @public\n * */\nexport type AdditionalRemoteInfo = Omit<RemoteInfo, 'name' | 'entry'>;\n\n/**\n *\n * @public\n * */\nexport type FrontendRemoteResolver = {\n /**\n * Relative path to the module federation assets folder from thr root folder of the plugin package.\n * Default value is `dist`.\n */\n assetsPathFromPackage?: string;\n\n /**\n * File name of the module federation manifest inside the module federation assets folder.\n * Default value is `mf-manifest.json`.\n */\n manifestFileName?: string;\n\n /**\n * Type of the remote entry returned in the RemoteInfo for this remote.\n * Default value is `manifest`.\n */\n getRemoteEntryType?: (\n manifestContent: JsonObject,\n ) => 'manifest' | 'javascript';\n\n /**\n * Additional module federation fields, which might be required if the remote entry type is 'javascript'.\n */\n getAdditionaRemoteInfo?: (\n manifestContent: JsonObject,\n ) => AdditionalRemoteInfo;\n\n /**\n * Overrides the list of exposed modules. By default the exposed modules are read from the manifest file.\n */\n overrideExposedModules?: (\n exposedModules: string[],\n manifestContent: JsonObject,\n ) => string[];\n\n /**\n * Customizes the manifest before returning it as the remote entry.\n */\n customizeManifest?: (content: JsonObject) => JsonObject;\n};\n\n/**\n *\n * @public\n * */\nexport type FrontendRemoteResolverProvider = {\n for(\n pluginName: string,\n pluginPackagePath: string,\n ): Partial<FrontendRemoteResolver> | undefined;\n};\n\n/**\n *\n * @public\n * */\nexport interface DynamicPluginsFrontendRemotesService {\n setResolverProvider(provider: FrontendRemoteResolverProvider): void;\n}\n\n/**\n * A service that serves the frontend module federation remotes,\n * and allows a plugin to customize the way remotes are served,\n * by setting a ResolverProvider.\n *\n * @public\n */\nexport const dynamicPluginsFrontendServiceRef =\n createServiceRef<DynamicPluginsFrontendRemotesService>({\n id: 'core.dynamicplugins.frontendRemotes',\n scope: 'root',\n });\n\nexport type FrontendRemoteResolvers = {\n default: FrontendRemoteResolver &\n Required<\n Pick<\n FrontendRemoteResolver,\n 'assetsPathFromPackage' | 'manifestFileName' | 'getRemoteEntryType'\n >\n >;\n provider?: FrontendRemoteResolverProvider;\n};\n\nexport const frontendRemotesServerService = createServiceFactory({\n service: dynamicPluginsFrontendServiceRef,\n deps: {\n logger: coreServices.rootLogger,\n rootHttpRouter: coreServices.rootHttpRouter,\n config: coreServices.rootConfig,\n dynamicPlugins: dynamicPluginsServiceRef,\n lifecycle: coreServices.rootLifecycle,\n },\n async factory({ logger, rootHttpRouter, config, dynamicPlugins, lifecycle }) {\n const resolvers: FrontendRemoteResolvers = {\n default: {\n assetsPathFromPackage: 'dist',\n manifestFileName: ManifestFileName,\n getRemoteEntryType: () => 'manifest',\n },\n provider: undefined,\n };\n\n lifecycle.addStartupHook(async () => {\n rootHttpRouter.use(\n `/${spec.info.title}`,\n await createRouter({\n logger,\n config,\n dynamicPlugins,\n resolvers,\n }),\n );\n });\n\n return {\n setResolverProvider(resolver) {\n logger.info('Setting resolver provider');\n if (resolvers.provider) {\n throw new Error(\n 'Attempted to install a frontend remote resolver provider twice',\n );\n }\n resolvers.provider = resolver;\n },\n };\n },\n});\n"],"names":["createServiceRef","createServiceFactory","coreServices","dynamicPluginsServiceRef","ManifestFileName","spec","createRouter"],"mappings":";;;;;;;;AAyGO,MAAM,mCACXA,iCAAuD,CAAA;AAAA,EACrD,EAAI,EAAA,qCAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAC;AAaI,MAAM,+BAA+BC,qCAAqB,CAAA;AAAA,EAC/D,OAAS,EAAA,gCAAA;AAAA,EACT,IAAM,EAAA;AAAA,IACJ,QAAQC,6BAAa,CAAA,UAAA;AAAA,IACrB,gBAAgBA,6BAAa,CAAA,cAAA;AAAA,IAC7B,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,cAAgB,EAAAC,qDAAA;AAAA,IAChB,WAAWD,6BAAa,CAAA;AAAA,GAC1B;AAAA,EACA,MAAM,QAAQ,EAAE,MAAA,EAAQ,gBAAgB,MAAQ,EAAA,cAAA,EAAgB,WAAa,EAAA;AAC3E,IAAA,MAAM,SAAqC,GAAA;AAAA,MACzC,OAAS,EAAA;AAAA,QACP,qBAAuB,EAAA,MAAA;AAAA,QACvB,gBAAkB,EAAAE,oBAAA;AAAA,QAClB,oBAAoB,MAAM;AAAA,OAC5B;AAAA,MACA,QAAU,EAAA,KAAA;AAAA,KACZ;AAEA,IAAA,SAAA,CAAU,eAAe,YAAY;AACnC,MAAe,cAAA,CAAA,GAAA;AAAA,QACb,CAAA,CAAA,EAAIC,WAAK,CAAA,IAAA,CAAK,KAAK,CAAA,CAAA;AAAA,QACnB,MAAMC,qBAAa,CAAA;AAAA,UACjB,MAAA;AAAA,UACA,MAAA;AAAA,UACA,cAAA;AAAA,UACA;AAAA,SACD;AAAA,OACH;AAAA,KACD,CAAA;AAED,IAAO,OAAA;AAAA,MACL,oBAAoB,QAAU,EAAA;AAC5B,QAAA,MAAA,CAAO,KAAK,2BAA2B,CAAA;AACvC,QAAA,IAAI,UAAU,QAAU,EAAA;AACtB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA;AAEF,QAAA,SAAA,CAAU,QAAW,GAAA,QAAA;AAAA;AACvB,KACF;AAAA;AAEJ,CAAC;;;;;"}
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+
3
+ var express = require('express');
4
+ var router = require('../schema/openapi/generated/router.cjs.js');
5
+ var fs = require('fs');
6
+ var path = require('path');
7
+ var url = require('url');
8
+
9
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
+
11
+ function _interopNamespaceCompat(e) {
12
+ if (e && typeof e === 'object' && 'default' in e) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
30
+ var fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
31
+ var path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
32
+ var url__namespace = /*#__PURE__*/_interopNamespaceCompat(url);
33
+
34
+ async function createRouter({
35
+ logger,
36
+ config,
37
+ dynamicPlugins,
38
+ resolvers
39
+ }) {
40
+ const externalBaseUrl = `${config.getString("backend.baseUrl")}/${router.spec.info.title}`;
41
+ const typedRouter = await router.createOpenApiRouter();
42
+ const frontendPluginRemotes = [];
43
+ const { default: defaultResolver, provider: resolverProvider } = resolvers;
44
+ for (const plugin of dynamicPlugins.frontendPlugins()) {
45
+ try {
46
+ const pluginScannedPackage = dynamicPlugins.getScannedPackage(plugin);
47
+ const pluginScannedPackagePath = path__namespace.resolve(
48
+ url__namespace.fileURLToPath(pluginScannedPackage.location)
49
+ );
50
+ const providedResolver = resolverProvider?.for(
51
+ plugin.name,
52
+ pluginScannedPackagePath
53
+ );
54
+ const assetsPath = path__namespace.resolve(
55
+ pluginScannedPackagePath,
56
+ providedResolver?.assetsPathFromPackage ?? defaultResolver.assetsPathFromPackage
57
+ );
58
+ const manifestFileName = providedResolver?.manifestFileName ?? defaultResolver.manifestFileName;
59
+ const manifestLocation = path__namespace.resolve(assetsPath, manifestFileName);
60
+ if (!fs__namespace.existsSync(manifestLocation)) {
61
+ logger.error(
62
+ `Could not find manifest '${manifestLocation}' for frontend plugin ${plugin.name}@${plugin.version}`
63
+ );
64
+ continue;
65
+ }
66
+ let manifest;
67
+ try {
68
+ manifest = JSON.parse(fs__namespace.readFileSync(manifestLocation).toString());
69
+ } catch (error) {
70
+ logger.error(
71
+ `Dynamic frontend plugin manifest '${manifestLocation}' could not be parsed for plugin ${plugin.name}@${plugin.version}`
72
+ );
73
+ continue;
74
+ }
75
+ if (!manifest.name || typeof manifest.name !== "string") {
76
+ logger.error(
77
+ `Error in manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}: module name not found`
78
+ );
79
+ continue;
80
+ }
81
+ if (!manifest.metaData || typeof manifest.metaData !== "object" || !("remoteEntry" in manifest.metaData) || !manifest.metaData.remoteEntry || typeof manifest.metaData.remoteEntry !== "object" || !("name" in manifest.metaData.remoteEntry) || typeof manifest.metaData.remoteEntry.name !== "string") {
82
+ logger.error(
83
+ `Could not find remote entry asset in the manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}`
84
+ );
85
+ continue;
86
+ }
87
+ if (!manifest.exposes || !Array.isArray(manifest.exposes) || !manifest.exposes.every(
88
+ (i) => i !== null && typeof i === "object" && "name" in i
89
+ )) {
90
+ logger.error(
91
+ `Could not find the exposes field in the manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}`
92
+ );
93
+ continue;
94
+ }
95
+ const getAdditionalRemoteInfo = providedResolver?.getAdditionaRemoteInfo ?? defaultResolver.getAdditionaRemoteInfo;
96
+ const getRemoteEntryType = providedResolver?.getRemoteEntryType ?? defaultResolver.getRemoteEntryType;
97
+ const remoteEntryType = getRemoteEntryType(manifest);
98
+ let remoteEntryAsset = manifestFileName;
99
+ if (remoteEntryType === "javascript") {
100
+ remoteEntryAsset = manifest.metaData.remoteEntry.name;
101
+ }
102
+ const remoteEntryAssetLocation = path__namespace.resolve(
103
+ assetsPath,
104
+ remoteEntryAsset
105
+ );
106
+ if (!fs__namespace.existsSync(remoteEntryAssetLocation)) {
107
+ logger.error(
108
+ `Could not find remote entry asset '${remoteEntryAssetLocation}' for frontend plugin ${plugin.name}@${plugin.version}`
109
+ );
110
+ continue;
111
+ }
112
+ const remoteAssetsPrefix = `/remotes/${plugin.name}`;
113
+ const remoteEntryPath = `${remoteAssetsPrefix}/${remoteEntryAsset}`;
114
+ const overrideExposedModules = providedResolver?.overrideExposedModules ?? defaultResolver.overrideExposedModules;
115
+ const exposedModules = manifest.exposes.map((e) => e.name);
116
+ frontendPluginRemotes.push({
117
+ packageName: plugin.name,
118
+ remoteInfo: {
119
+ name: manifest.name,
120
+ entry: `${externalBaseUrl}${remoteEntryPath}`,
121
+ ...getAdditionalRemoteInfo?.(manifest)
122
+ },
123
+ exposedModules: overrideExposedModules?.(exposedModules, manifest) ?? exposedModules
124
+ });
125
+ const customizeManifest = providedResolver?.customizeManifest ?? defaultResolver.customizeManifest;
126
+ if (remoteEntryType === "manifest" && customizeManifest) {
127
+ const customizedContent = customizeManifest(manifest);
128
+ typedRouter.use(`${remoteEntryPath}`, (_, res) => {
129
+ res.json(customizedContent);
130
+ });
131
+ }
132
+ typedRouter.use(remoteAssetsPrefix, express__default.default.static(assetsPath));
133
+ logger.info(
134
+ `Exposed dynamic frontend plugin '${plugin.name}' from '${assetsPath}' `
135
+ );
136
+ } catch (error) {
137
+ logger.error(
138
+ `Unexpected error when exposing dynamic frontend plugin '${plugin.name}@${plugin.version}'`,
139
+ error
140
+ );
141
+ continue;
142
+ }
143
+ }
144
+ logger.info(`/remotes => ${JSON.stringify(frontendPluginRemotes)}`);
145
+ typedRouter.get("/remotes", (_, res) => {
146
+ res.status(200).json(frontendPluginRemotes);
147
+ });
148
+ return typedRouter;
149
+ }
150
+
151
+ exports.createRouter = createRouter;
152
+ //# sourceMappingURL=router.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.cjs.js","sources":["../../src/server/router.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 {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport express from 'express';\nimport { createOpenApiRouter, spec } from '../schema/openapi';\nimport { DynamicPluginProvider } from '@backstage/backend-dynamic-feature-service';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as url from 'url';\nimport { FrontendRemoteResolvers } from './frontendRemotesServer';\nimport { Remote } from '../schema/openapi/generated/models';\nimport { JsonObject } from '@backstage/types';\n\nexport async function createRouter({\n logger,\n config,\n dynamicPlugins,\n resolvers,\n}: {\n logger: LoggerService;\n config: RootConfigService;\n dynamicPlugins: DynamicPluginProvider;\n resolvers: FrontendRemoteResolvers;\n}): Promise<express.Router> {\n const externalBaseUrl = `${config.getString('backend.baseUrl')}/${\n spec.info.title\n }`;\n\n const typedRouter = await createOpenApiRouter();\n\n const frontendPluginRemotes: Remote[] = [];\n\n const { default: defaultResolver, provider: resolverProvider } = resolvers;\n for (const plugin of dynamicPlugins.frontendPlugins()) {\n try {\n const pluginScannedPackage = dynamicPlugins.getScannedPackage(plugin);\n const pluginScannedPackagePath = path.resolve(\n url.fileURLToPath(pluginScannedPackage.location),\n );\n const providedResolver = resolverProvider?.for(\n plugin.name,\n pluginScannedPackagePath,\n );\n\n const assetsPath = path.resolve(\n pluginScannedPackagePath,\n providedResolver?.assetsPathFromPackage ??\n defaultResolver.assetsPathFromPackage,\n );\n\n const manifestFileName =\n providedResolver?.manifestFileName ?? defaultResolver.manifestFileName;\n const manifestLocation = path.resolve(assetsPath, manifestFileName);\n if (!fs.existsSync(manifestLocation)) {\n logger.error(\n `Could not find manifest '${manifestLocation}' for frontend plugin ${plugin.name}@${plugin.version}`,\n );\n continue;\n }\n\n let manifest: JsonObject;\n try {\n manifest = JSON.parse(fs.readFileSync(manifestLocation).toString());\n } catch (error) {\n logger.error(\n `Dynamic frontend plugin manifest '${manifestLocation}' could not be parsed for plugin ${plugin.name}@${plugin.version}`,\n );\n continue;\n }\n\n if (!manifest.name || typeof manifest.name !== 'string') {\n logger.error(\n `Error in manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}: module name not found`,\n );\n continue;\n }\n if (\n !manifest.metaData ||\n typeof manifest.metaData !== 'object' ||\n !('remoteEntry' in manifest.metaData) ||\n !manifest.metaData.remoteEntry ||\n typeof manifest.metaData.remoteEntry !== 'object' ||\n !('name' in manifest.metaData.remoteEntry) ||\n typeof manifest.metaData.remoteEntry.name !== 'string'\n ) {\n logger.error(\n `Could not find remote entry asset in the manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}`,\n );\n continue;\n }\n\n if (\n !manifest.exposes ||\n !Array.isArray(manifest.exposes) ||\n !manifest.exposes.every<{ name: string }>(\n (i): i is { name: string } =>\n i !== null && typeof i === 'object' && 'name' in i,\n )\n ) {\n logger.error(\n `Could not find the exposes field in the manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}`,\n );\n continue;\n }\n\n const getAdditionalRemoteInfo =\n providedResolver?.getAdditionaRemoteInfo ??\n defaultResolver.getAdditionaRemoteInfo;\n const getRemoteEntryType =\n providedResolver?.getRemoteEntryType ??\n defaultResolver.getRemoteEntryType;\n const remoteEntryType = getRemoteEntryType(manifest);\n\n let remoteEntryAsset = manifestFileName;\n if (remoteEntryType === 'javascript') {\n remoteEntryAsset = manifest.metaData.remoteEntry.name;\n }\n\n const remoteEntryAssetLocation = path.resolve(\n assetsPath,\n remoteEntryAsset,\n );\n if (!fs.existsSync(remoteEntryAssetLocation)) {\n logger.error(\n `Could not find remote entry asset '${remoteEntryAssetLocation}' for frontend plugin ${plugin.name}@${plugin.version}`,\n );\n continue;\n }\n\n const remoteAssetsPrefix = `/remotes/${plugin.name}`;\n const remoteEntryPath = `${remoteAssetsPrefix}/${remoteEntryAsset}`;\n\n const overrideExposedModules =\n providedResolver?.overrideExposedModules ??\n defaultResolver.overrideExposedModules;\n\n const exposedModules = manifest.exposes.map(e => e.name);\n\n frontendPluginRemotes.push({\n packageName: plugin.name,\n remoteInfo: {\n name: manifest.name,\n entry: `${externalBaseUrl}${remoteEntryPath}`,\n ...getAdditionalRemoteInfo?.(manifest),\n },\n exposedModules:\n overrideExposedModules?.(exposedModules, manifest) ?? exposedModules,\n });\n\n const customizeManifest =\n providedResolver?.customizeManifest ??\n defaultResolver.customizeManifest;\n if (remoteEntryType === 'manifest' && customizeManifest) {\n const customizedContent = customizeManifest(manifest);\n typedRouter.use(`${remoteEntryPath}`, (_, res) => {\n res.json(customizedContent);\n });\n }\n typedRouter.use(remoteAssetsPrefix, express.static(assetsPath));\n logger.info(\n `Exposed dynamic frontend plugin '${plugin.name}' from '${assetsPath}' `,\n );\n } catch (error) {\n logger.error(\n `Unexpected error when exposing dynamic frontend plugin '${plugin.name}@${plugin.version}'`,\n error,\n );\n continue;\n }\n }\n\n logger.info(`/remotes => ${JSON.stringify(frontendPluginRemotes)}`);\n typedRouter.get('/remotes', (_, res) => {\n res.status(200).json(frontendPluginRemotes);\n });\n\n return typedRouter;\n}\n"],"names":["spec","createOpenApiRouter","path","url","fs","express"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-dynamic-feature-service",
3
- "version": "0.6.2-next.2",
3
+ "version": "0.7.0-next.0",
4
4
  "description": "Backstage dynamic feature service",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -42,6 +42,9 @@
42
42
  "scripts": {
43
43
  "build": "backstage-cli package build",
44
44
  "clean": "backstage-cli package clean",
45
+ "diff": "backstage-repo-tools package schema openapi diff",
46
+ "fuzz": "backstage-repo-tools package schema openapi fuzz --exclude-checks response_schema_conformance",
47
+ "generate": "backstage-repo-tools package schema openapi generate --server --client-package packages/frontend-dynamic-feature-loader",
45
48
  "lint": "backstage-cli package lint",
46
49
  "prepack": "backstage-cli package prepack",
47
50
  "postpack": "backstage-cli package postpack",
@@ -49,38 +52,43 @@
49
52
  "test": "backstage-cli package test"
50
53
  },
51
54
  "dependencies": {
52
- "@backstage/backend-defaults": "0.9.0-next.2",
53
- "@backstage/backend-plugin-api": "1.2.1",
55
+ "@backstage/backend-defaults": "0.9.1-next.0",
56
+ "@backstage/backend-openapi-utils": "0.5.3-next.0",
57
+ "@backstage/backend-plugin-api": "1.3.1-next.0",
54
58
  "@backstage/cli-common": "0.1.15",
55
59
  "@backstage/cli-node": "0.2.13",
56
60
  "@backstage/config": "1.3.2",
57
61
  "@backstage/config-loader": "1.10.0",
58
62
  "@backstage/errors": "1.2.7",
59
- "@backstage/plugin-app-node": "0.1.31",
60
- "@backstage/plugin-auth-node": "0.6.1",
61
- "@backstage/plugin-catalog-backend": "1.32.1-next.1",
62
- "@backstage/plugin-events-backend": "0.5.0",
63
- "@backstage/plugin-events-node": "0.4.9",
63
+ "@backstage/plugin-app-node": "0.1.33-next.0",
64
+ "@backstage/plugin-auth-node": "0.6.3-next.0",
65
+ "@backstage/plugin-catalog-backend": "1.32.2-next.0",
66
+ "@backstage/plugin-events-backend": "0.5.2-next.0",
67
+ "@backstage/plugin-events-node": "0.4.11-next.0",
64
68
  "@backstage/plugin-permission-common": "0.8.4",
65
- "@backstage/plugin-permission-node": "0.9.0",
66
- "@backstage/plugin-scaffolder-node": "0.8.1-next.1",
67
- "@backstage/plugin-search-backend-node": "1.3.9",
69
+ "@backstage/plugin-permission-node": "0.9.2-next.0",
70
+ "@backstage/plugin-scaffolder-node": "0.8.2-next.0",
71
+ "@backstage/plugin-search-backend-node": "1.3.11-next.0",
68
72
  "@backstage/plugin-search-common": "1.2.17",
69
73
  "@backstage/types": "1.2.1",
70
74
  "@manypkg/get-packages": "^1.1.3",
71
- "@types/express": "^4.17.6",
75
+ "@module-federation/sdk": "^0.9.0",
72
76
  "chokidar": "^3.5.3",
73
77
  "express": "^4.17.1",
78
+ "express-promise-router": "^4.1.0",
74
79
  "fs-extra": "^11.2.0",
75
80
  "lodash": "^4.17.21",
76
81
  "winston": "^3.2.1"
77
82
  },
78
83
  "devDependencies": {
79
- "@backstage/backend-app-api": "1.2.1",
80
- "@backstage/backend-test-utils": "1.3.2-next.2",
81
- "@backstage/cli": "0.32.0-next.2",
82
- "@backstage/plugin-app-backend": "0.5.0",
84
+ "@backstage/backend-app-api": "1.2.3-next.0",
85
+ "@backstage/backend-test-utils": "1.5.0-next.0",
86
+ "@backstage/cli": "0.32.1-next.0",
87
+ "@backstage/plugin-app-backend": "0.5.2-next.0",
88
+ "@backstage/repo-tools": "0.13.3-next.0",
89
+ "@types/express": "^4.17.6",
83
90
  "triple-beam": "^1.4.1",
84
91
  "wait-for-expect": "^3.0.2"
85
- }
92
+ },
93
+ "configSchema": "config.d.ts"
86
94
  }