Merge pull request #28076 from davidfestal/new-frontend-dynamic-features
Dynamic frontend features based on module federation and the new frontend system
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
---
|
||||
'@backstage/backend-dynamic-feature-service': patch
|
||||
'@backstage/frontend-dynamic-feature-loader': minor
|
||||
'@backstage/frontend-defaults': patch
|
||||
---
|
||||
|
||||
The new package `frontend-dynamic-features-loader` provides a frontend feature loader that dynamically
|
||||
loads frontend features based on the new frontend system and exposed as module federation remotes.
|
||||
This new frontend feature loader works hand-in-hand with a new server of frontend plugin module federation
|
||||
remotes, which is added as part of backend dynamic feature service in package `@backstage/backend-dynamic-feature-service`.
|
||||
@@ -38,6 +38,9 @@
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"diff": "backstage-repo-tools package schema openapi diff",
|
||||
"fuzz": "backstage-repo-tools package schema openapi fuzz --exclude-checks response_schema_conformance",
|
||||
"generate": "backstage-repo-tools package schema openapi generate --server --client-package packages/frontend-dynamic-feature-loader",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
@@ -46,6 +49,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-defaults": "workspace:^",
|
||||
"@backstage/backend-openapi-utils": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/cli-common": "workspace:^",
|
||||
"@backstage/cli-node": "workspace:^",
|
||||
@@ -64,9 +68,11 @@
|
||||
"@backstage/plugin-search-common": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@module-federation/sdk": "^0.9.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"chokidar": "^3.5.3",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"winston": "^3.2.1"
|
||||
@@ -76,7 +82,9 @@
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-app-backend": "workspace:^",
|
||||
"@backstage/repo-tools": "workspace:^",
|
||||
"triple-beam": "^1.4.1",
|
||||
"wait-for-expect": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { EventsService } from '@backstage/plugin-events-node';
|
||||
import { HttpPostIngressOptions } from '@backstage/plugin-events-node';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
import { IndexBuilder } from '@backstage/plugin-search-backend-node';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { Logger } from 'winston';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { PackagePlatform } from '@backstage/cli-node';
|
||||
@@ -31,6 +32,9 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { UrlReaderService } from '@backstage/backend-plugin-api';
|
||||
import { WinstonLoggerOptions } from '@backstage/backend-defaults/rootLogger';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AdditionalRemoteInfo = Omit<RemoteInfo, 'name' | 'entry'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BackendDynamicPlugin extends BaseDynamicPlugin {
|
||||
// (undocumented)
|
||||
@@ -158,9 +162,22 @@ export type DynamicPluginsFeatureLoaderOptions = DynamicPluginsFactoryOptions &
|
||||
logger?: (config?: Config) => DynamicPluginsRootLoggerFactoryOptions;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DynamicPluginsFrontendRemotesService {
|
||||
// (undocumented)
|
||||
setResolverProvider(provider: FrontendRemoteResolverProvider): void;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const dynamicPluginsFrontendSchemas: BackendFeature;
|
||||
|
||||
// @public
|
||||
export const dynamicPluginsFrontendServiceRef: ServiceRef<
|
||||
DynamicPluginsFrontendRemotesService,
|
||||
'root',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type DynamicPluginsRootLoggerFactoryOptions = Omit<
|
||||
WinstonLoggerOptions,
|
||||
@@ -224,6 +241,31 @@ export interface FrontendPluginProvider {
|
||||
}): FrontendDynamicPlugin[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type FrontendRemoteResolver = {
|
||||
assetsPathFromPackage?: string;
|
||||
manifestFileName?: string;
|
||||
getRemoteEntryType?: (
|
||||
manifestContent: JsonObject,
|
||||
) => 'manifest' | 'javascript';
|
||||
getAdditionaRemoteInfo?: (
|
||||
manifestContent: JsonObject,
|
||||
) => AdditionalRemoteInfo;
|
||||
overrideExposedModules?: (
|
||||
exposedModules: string[],
|
||||
manifestContent: JsonObject,
|
||||
) => string[];
|
||||
customizeManifest?: (content: JsonObject) => JsonObject;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type FrontendRemoteResolverProvider = {
|
||||
for(
|
||||
pluginName: string,
|
||||
pluginPackagePath: string,
|
||||
): Partial<FrontendRemoteResolver> | undefined;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function isBackendDynamicPluginInstaller(
|
||||
obj: any,
|
||||
@@ -291,6 +333,39 @@ export interface NewBackendPluginInstaller {
|
||||
kind: 'new';
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface RemoteInfo {
|
||||
entry: string;
|
||||
// (undocumented)
|
||||
entryGlobalName?: string;
|
||||
name: string;
|
||||
// (undocumented)
|
||||
shareScope?: string;
|
||||
// (undocumented)
|
||||
type?: RemoteInfoTypeEnum;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type RemoteInfoTypeEnum =
|
||||
| 'var'
|
||||
| 'module'
|
||||
| 'assign'
|
||||
| 'assign-properties'
|
||||
| 'this'
|
||||
| 'window'
|
||||
| 'self'
|
||||
| 'global'
|
||||
| 'commonjs'
|
||||
| 'commonjs2'
|
||||
| 'commonjs-module'
|
||||
| 'commonjs-static'
|
||||
| 'amd'
|
||||
| 'amd-require'
|
||||
| 'umd'
|
||||
| 'umd2'
|
||||
| 'jsonp'
|
||||
| 'system';
|
||||
|
||||
// @public (undocumented)
|
||||
export type ScannedPluginManifest = BackstagePackageJson &
|
||||
Required<Pick<BackstagePackageJson, 'main'>> &
|
||||
|
||||
+2
-21
@@ -31,27 +31,8 @@ const testPlugin = backendPluginApi.createBackendPlugin({
|
||||
metadata,
|
||||
}) {
|
||||
logger.info("This secret value should be hidden by the dynamic-plugin-aware logger: AVerySecretValue");
|
||||
const externalBaseUrl = await discovery.getExternalBaseUrl(metadata.getId());
|
||||
const router = express.Router();
|
||||
const frontendPluginsIndexPath = privateDep.frontendPluginsIndexPath;
|
||||
const frontendPluginManifests = Object.fromEntries(dynamicPlugins.frontendPlugins().map(fp => {
|
||||
const pluginScannedPackage = dynamicPlugins.getScannedPackage(fp);
|
||||
const pkgDistLocation = path.resolve(
|
||||
url.fileURLToPath(pluginScannedPackage.location),
|
||||
'dist',
|
||||
);
|
||||
router.use(`/${frontendPluginsIndexPath}/${fp.name}`, express.static(pkgDistLocation))
|
||||
return [fp.name, `${externalBaseUrl}/${frontendPluginsIndexPath}/${fp.name}/mf-manifest.json`]
|
||||
}));
|
||||
router.get(`/${frontendPluginsIndexPath}`, (req, res) => {
|
||||
res.status(200).json(frontendPluginManifests);
|
||||
});
|
||||
http.use(router);
|
||||
http.addAuthPolicy({
|
||||
path: `/`,
|
||||
allow: 'unauthenticated',
|
||||
});
|
||||
|
||||
const messageFromPrivateDep = privateDep.message;
|
||||
logger.info(messageFromPrivateDep);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,6 +2,6 @@
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
const frontendPluginsIndexPath = 'frontend-plugins';
|
||||
const message = 'This message comes from a plugin private dependency';
|
||||
|
||||
exports.frontendPluginsIndexPath = frontendPluginsIndexPath;
|
||||
exports.message = message;
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"schemas": [
|
||||
{
|
||||
"packageName": "plugin-test-dynamic",
|
||||
"path": "./test-dynamic",
|
||||
"value": {
|
||||
"type": "object",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"test-frontend": {
|
||||
"type": "object",
|
||||
"required": ["frontendValue"],
|
||||
"properties": {
|
||||
"frontendValue": {
|
||||
"type": "string",
|
||||
"visibility": "frontend"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"backstageConfigSchemaVersion": 1
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"id": "backstage__plugin_test",
|
||||
"name": "backstage__plugin_test",
|
||||
"metaData": {
|
||||
"name": "backstage__plugin_test",
|
||||
"type": "app",
|
||||
"buildInfo": {
|
||||
"buildVersion": "0.0.0",
|
||||
"buildName": "@backstage/plugin-test"
|
||||
},
|
||||
"remoteEntry": {
|
||||
"name": "remoteEntry.js",
|
||||
"path": "",
|
||||
"type": "global"
|
||||
},
|
||||
"types": {
|
||||
"path": "",
|
||||
"name": "",
|
||||
"zip": "",
|
||||
"api": ""
|
||||
},
|
||||
"globalName": "backstage__plugin_test",
|
||||
"pluginVersion": "0.0.0",
|
||||
"publicPath": "auto"
|
||||
},
|
||||
"shared": [],
|
||||
"remotes": [],
|
||||
"exposes": [
|
||||
{
|
||||
"name": "."
|
||||
},
|
||||
{
|
||||
"name": "alpha"
|
||||
}
|
||||
]
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
(function doNothing() {})();
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"schemas": [
|
||||
{
|
||||
"packageName": "plugin-test-dynamic",
|
||||
"path": "./test-dynamic",
|
||||
"value": {
|
||||
"type": "object",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"test-frontend": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"frontendValue"
|
||||
],
|
||||
"properties": {
|
||||
"frontendValue": {
|
||||
"type": "string",
|
||||
"visibility": "frontend"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"backstageConfigSchemaVersion": 1
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"type": "object",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"test-frontend": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"frontendValue"
|
||||
],
|
||||
"properties": {
|
||||
"frontendValue": {
|
||||
"type": "string",
|
||||
"visibility": "frontend"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"id": "backstage__plugin_test",
|
||||
"name": "backstage__plugin_test",
|
||||
"metaData": {
|
||||
"name": "backstage__plugin_test",
|
||||
"type": "app",
|
||||
"buildInfo": {
|
||||
"buildVersion": "0.0.0",
|
||||
"buildName": "@backstage/plugin-test"
|
||||
},
|
||||
"remoteEntry": {
|
||||
"name": "remoteEntry-not-found.js",
|
||||
"path": "",
|
||||
"type": "global"
|
||||
},
|
||||
"types": {
|
||||
"path": "",
|
||||
"name": "",
|
||||
"zip": "",
|
||||
"api": ""
|
||||
},
|
||||
"globalName": "backstage__plugin_test",
|
||||
"pluginVersion": "0.0.0",
|
||||
"publicPath": "auto"
|
||||
},
|
||||
"shared": [],
|
||||
"remotes": [],
|
||||
"exposes": [{
|
||||
"name": "."
|
||||
},{
|
||||
"name": "alpha"
|
||||
}]
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"id": "backstage__plugin_test",
|
||||
"metaData": {
|
||||
"name": "backstage__plugin_test",
|
||||
"type": "app",
|
||||
"buildInfo": {
|
||||
"buildVersion": "0.0.0",
|
||||
"buildName": "@backstage/plugin-test"
|
||||
},
|
||||
"remoteEntry": {
|
||||
"name": "remoteEntry.js",
|
||||
"path": "",
|
||||
"type": "global"
|
||||
},
|
||||
"types": {
|
||||
"path": "",
|
||||
"name": "",
|
||||
"zip": "",
|
||||
"api": ""
|
||||
},
|
||||
"globalName": "backstage__plugin_test",
|
||||
"pluginVersion": "0.0.0",
|
||||
"publicPath": "auto"
|
||||
},
|
||||
"shared": [],
|
||||
"remotes": [],
|
||||
"exposes": [{
|
||||
"name": "."
|
||||
},{
|
||||
"name": "alpha"
|
||||
}]
|
||||
}
|
||||
+5
-1
@@ -25,5 +25,9 @@
|
||||
},
|
||||
"shared": [],
|
||||
"remotes": [],
|
||||
"exposes": []
|
||||
"exposes": [{
|
||||
"name": "."
|
||||
},{
|
||||
"name": "alpha"
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ import * as url from 'url';
|
||||
import { MESSAGE } from 'triple-beam';
|
||||
import { overridePackagePathResolution } from '@backstage/backend-plugin-api/testUtils';
|
||||
import { ScannedPluginPackage } from '../scanner';
|
||||
import {
|
||||
dynamicPluginsFrontendServiceRef,
|
||||
FrontendRemoteResolverProvider,
|
||||
} from '../server/frontendRemotesServer';
|
||||
|
||||
// these can get a bit slow in CI
|
||||
jest.setTimeout(60_000);
|
||||
@@ -117,6 +121,9 @@ describe('dynamicPluginsFeatureLoader', () => {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
@@ -161,6 +168,9 @@ describe('dynamicPluginsFeatureLoader', () => {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
@@ -211,6 +221,9 @@ Require stack:
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
@@ -250,6 +263,9 @@ Require stack:
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
customLogLabel: 'a very nice label',
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
@@ -289,6 +305,9 @@ Require stack:
|
||||
'test-backend': {
|
||||
secretValue: 'AVerySecretValue',
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
@@ -342,6 +361,9 @@ Require stack:
|
||||
'test-frontend': {
|
||||
frontendValue: 'AFrontendValue',
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
@@ -371,52 +393,6 @@ Require stack:
|
||||
</head>`);
|
||||
});
|
||||
|
||||
it('should access the module federation assets of the frontend plugin through the backend plugin', async () => {
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
moduleLoader: logger =>
|
||||
jestFreeTypescriptAwareModuleLoader({ logger }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const list = await fetch(
|
||||
`http://localhost:${server.port()}/api/test/frontend-plugins`,
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
expect(await list.json()).toEqual({
|
||||
'plugin-test-dynamic': `http://localhost:${server.port()}/api/test/frontend-plugins/plugin-test-dynamic/mf-manifest.json`,
|
||||
});
|
||||
|
||||
const manifest = await fetch(
|
||||
`http://localhost:${server.port()}/api/test/frontend-plugins/plugin-test-dynamic/mf-manifest.json`,
|
||||
);
|
||||
expect(manifest.ok).toBe(true);
|
||||
expect(await manifest.json()).toMatchObject({
|
||||
exposes: [],
|
||||
id: 'backstage__plugin_test',
|
||||
name: 'backstage__plugin_test',
|
||||
metaData: {
|
||||
buildInfo: {
|
||||
buildName: '@backstage/plugin-test',
|
||||
buildVersion: '0.0.0',
|
||||
},
|
||||
globalName: 'backstage__plugin_test',
|
||||
name: 'backstage__plugin_test',
|
||||
pluginVersion: '0.0.0',
|
||||
publicPath: 'auto',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should load a backend plugin from the alpha package first', async () => {
|
||||
const dynamicPLuginsLister = new DynamicPluginLister();
|
||||
const mockedTransport = new MockedTransport();
|
||||
@@ -431,6 +407,9 @@ Require stack:
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootForAlpha,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
@@ -485,4 +464,385 @@ Require stack:
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('module federation support', () => {
|
||||
const createRemoteProviderPlugin = (
|
||||
provider: FrontendRemoteResolverProvider,
|
||||
) =>
|
||||
createBackendPlugin({
|
||||
pluginId: 'test-remote-provider',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
frontendRemotes: dynamicPluginsFrontendServiceRef,
|
||||
},
|
||||
async init({ frontendRemotes }) {
|
||||
frontendRemotes.setResolverProvider(provider);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
it('should access the module federation assets of the frontend plugin through the backend plugin', async () => {
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
moduleLoader: logger =>
|
||||
jestFreeTypescriptAwareModuleLoader({ logger }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const list = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes`,
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
expect(await list.json()).toEqual([
|
||||
{
|
||||
packageName: 'plugin-test-dynamic',
|
||||
exposedModules: ['.', 'alpha'],
|
||||
remoteInfo: {
|
||||
name: 'backstage__plugin_test',
|
||||
entry:
|
||||
'http://localhost:0/.backstage/dynamic-features/remotes/plugin-test-dynamic/mf-manifest.json',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const manifest = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes/plugin-test-dynamic/mf-manifest.json`,
|
||||
);
|
||||
expect(manifest.ok).toBe(true);
|
||||
expect(await manifest.json()).toMatchObject({
|
||||
exposes: [{ name: '.' }, { name: 'alpha' }],
|
||||
id: 'backstage__plugin_test',
|
||||
name: 'backstage__plugin_test',
|
||||
metaData: {
|
||||
buildInfo: {
|
||||
buildName: '@backstage/plugin-test',
|
||||
buildVersion: '0.0.0',
|
||||
},
|
||||
globalName: 'backstage__plugin_test',
|
||||
name: 'backstage__plugin_test',
|
||||
pluginVersion: '0.0.0',
|
||||
publicPath: 'auto',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow overriding the module federation assets folder, return the Javascript remote entry, the exposed modules and additional remote intry info fields', async () => {
|
||||
const mockedTransport = new MockedTransport();
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
createRemoteProviderPlugin({
|
||||
for: () => ({
|
||||
assetsPathFromPackage: 'dist-alternate',
|
||||
getRemoteEntryType: () => 'javascript',
|
||||
getAdditionaRemoteInfo: manifest => ({
|
||||
type: (manifest as any).metaData.remoteEntry.type,
|
||||
}),
|
||||
overrideExposedModules: exposedModules =>
|
||||
exposedModules.filter(name => name !== 'alpha'),
|
||||
}),
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
moduleLoader: logger =>
|
||||
jestFreeTypescriptAwareModuleLoader({ logger }),
|
||||
logger: () => ({
|
||||
transports: [mockedTransport],
|
||||
format: winston.format.simple(),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const list = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes`,
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
expect(await list.json()).toEqual([
|
||||
{
|
||||
exposedModules: ['.'],
|
||||
packageName: 'plugin-test-dynamic',
|
||||
remoteInfo: {
|
||||
entry:
|
||||
'http://localhost:0/.backstage/dynamic-features/remotes/plugin-test-dynamic/remoteEntry.js',
|
||||
name: 'backstage__plugin_test',
|
||||
type: 'global',
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(mockedTransport.logs).toContainEqual(
|
||||
expect.stringMatching(
|
||||
`Exposed dynamic frontend plugin 'plugin-test-dynamic' from '.*/dist-alternate' `,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow customizing the module federation manifest when returning it as the remote entry', async () => {
|
||||
const mockedTransport = new MockedTransport();
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
createRemoteProviderPlugin({
|
||||
for: () => ({
|
||||
customizeManifest(manifest) {
|
||||
(manifest as any).metaData.publicPath =
|
||||
'https://some-cdn-url-where-module-federation-assets-are-mirrored';
|
||||
return manifest;
|
||||
},
|
||||
}),
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
moduleLoader: logger =>
|
||||
jestFreeTypescriptAwareModuleLoader({ logger }),
|
||||
logger: () => ({
|
||||
transports: [mockedTransport],
|
||||
format: winston.format.simple(),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const list = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes`,
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
expect(await list.json()).toEqual([
|
||||
{
|
||||
packageName: 'plugin-test-dynamic',
|
||||
exposedModules: ['.', 'alpha'],
|
||||
remoteInfo: {
|
||||
name: 'backstage__plugin_test',
|
||||
entry:
|
||||
'http://localhost:0/.backstage/dynamic-features/remotes/plugin-test-dynamic/mf-manifest.json',
|
||||
},
|
||||
},
|
||||
]);
|
||||
const manifest = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes/plugin-test-dynamic/mf-manifest.json`,
|
||||
);
|
||||
expect(manifest.ok).toBe(true);
|
||||
expect(await manifest.json()).toMatchObject({
|
||||
exposes: [{ name: '.' }, { name: 'alpha' }],
|
||||
id: 'backstage__plugin_test',
|
||||
name: 'backstage__plugin_test',
|
||||
metaData: {
|
||||
buildInfo: {
|
||||
buildName: '@backstage/plugin-test',
|
||||
buildVersion: '0.0.0',
|
||||
},
|
||||
globalName: 'backstage__plugin_test',
|
||||
name: 'backstage__plugin_test',
|
||||
pluginVersion: '0.0.0',
|
||||
publicPath:
|
||||
'https://some-cdn-url-where-module-federation-assets-are-mirrored',
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockedTransport.logs).toContainEqual(
|
||||
expect.stringMatching(
|
||||
`Exposed dynamic frontend plugin 'plugin-test-dynamic' from '.*/dist' `,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log an error and skip the plugin if the module federation manifest does not contain a valid name', async () => {
|
||||
const mockedTransport = new MockedTransport();
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
createRemoteProviderPlugin({
|
||||
for: () => ({
|
||||
manifestFileName: 'mf-manifest-without-name.json',
|
||||
}),
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
moduleLoader: logger =>
|
||||
jestFreeTypescriptAwareModuleLoader({ logger }),
|
||||
logger: () => ({
|
||||
transports: [mockedTransport],
|
||||
format: winston.format.simple(),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const list = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes`,
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
expect(await list.json()).toEqual([]);
|
||||
expect(mockedTransport.logs).toContainEqual(
|
||||
expect.stringMatching(
|
||||
"error: Error in manifest '.*' for plugin .*@.*: module name not found",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log an error and skip the plugin if a resolver provider triggers an error', async () => {
|
||||
const mockedTransport = new MockedTransport();
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
createRemoteProviderPlugin({
|
||||
for() {
|
||||
throw new Error('Resolver provider error');
|
||||
},
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
moduleLoader: logger =>
|
||||
jestFreeTypescriptAwareModuleLoader({ logger }),
|
||||
logger: () => ({
|
||||
transports: [mockedTransport],
|
||||
format: winston.format.simple(),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const list = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes`,
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
expect(await list.json()).toEqual([]);
|
||||
expect(mockedTransport.logs).toContainEqual(
|
||||
expect.stringMatching(
|
||||
"error: Unexpected error when exposing dynamic frontend plugin 'plugin-test-dynamic@0.0.0' Resolver provider error",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log an error and skip the plugin if the module federation manifest is not found', async () => {
|
||||
const mockedTransport = new MockedTransport();
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
createRemoteProviderPlugin({
|
||||
for: () => ({
|
||||
manifestFileName: 'mf-manifest-not-found.json',
|
||||
}),
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
moduleLoader: logger =>
|
||||
jestFreeTypescriptAwareModuleLoader({ logger }),
|
||||
logger: () => ({
|
||||
transports: [mockedTransport],
|
||||
format: winston.format.simple(),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const list = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes`,
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
expect(await list.json()).toEqual([]);
|
||||
expect(mockedTransport.logs).toContainEqual(
|
||||
expect.stringMatching(
|
||||
"error: Could not find manifest '.*/dist/mf-manifest-not-found.json' for frontend plugin plugin-test-dynamic@0.0.0",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log an error and skip the plugin if the module federation remote entry asset is not found', async () => {
|
||||
const mockedTransport = new MockedTransport();
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
dynamicPlugins: {
|
||||
rootDirectory: dynamicPluginsRootDirectory,
|
||||
},
|
||||
backend: {
|
||||
baseUrl: `http://localhost:0`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
createRemoteProviderPlugin({
|
||||
for: () => ({
|
||||
manifestFileName: 'mf-manifest-with-wrong-remote-entry.json',
|
||||
getRemoteEntryType: () => 'javascript',
|
||||
}),
|
||||
}),
|
||||
dynamicPluginsFeatureLoader({
|
||||
moduleLoader: logger =>
|
||||
jestFreeTypescriptAwareModuleLoader({ logger }),
|
||||
logger: () => ({
|
||||
transports: [mockedTransport],
|
||||
format: winston.format.simple(),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const list = await fetch(
|
||||
`http://localhost:${server.port()}/.backstage/dynamic-features/remotes`,
|
||||
);
|
||||
expect(list.ok).toBe(true);
|
||||
expect(await list.json()).toEqual([]);
|
||||
expect(mockedTransport.logs).toContainEqual(
|
||||
expect.stringMatching(
|
||||
"error: Could not find remote entry asset '.*/dist/remoteEntry-not-found.js' for frontend plugin plugin-test-dynamic@0.0.0",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
dynamicPluginsRootLoggerServiceFactory,
|
||||
dynamicPluginsSchemasServiceFactory,
|
||||
} from '../schemas';
|
||||
import { frontendRemotesServerService } from '../server/frontendRemotesServer';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -64,6 +65,7 @@ const dynamicPluginsFeatureLoaderWithOptions = (
|
||||
yield* [
|
||||
dynamicPluginsRootLoggerServiceFactory(rootLoggerOptions),
|
||||
dynamicPluginsFrontendSchemas,
|
||||
frontendRemotesServerService,
|
||||
dynamicPluginsFeatureDiscoveryLoader,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -19,3 +19,8 @@ export * from './scanner';
|
||||
export * from './manager';
|
||||
export * from './schemas';
|
||||
export * from './features';
|
||||
export * from './server';
|
||||
export {
|
||||
type RemoteInfo,
|
||||
type RemoteInfoTypeEnum,
|
||||
} from './schema/openapi/generated/models';
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: .backstage/dynamic-features
|
||||
version: '1'
|
||||
description: The Backstage backend plugin that serves the frontend plugins module federation manifests and assets
|
||||
license:
|
||||
name: Apache-2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
contact: {}
|
||||
servers:
|
||||
- url: /
|
||||
components:
|
||||
examples: {}
|
||||
headers: {}
|
||||
parameters: {}
|
||||
requestBodies: {}
|
||||
responses:
|
||||
RemotesResponse:
|
||||
description: List of Module Federation Remotes exposed by the backend
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Remotes'
|
||||
|
||||
ErrorResponse:
|
||||
description: An error response from the backend.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
schemas:
|
||||
Remotes:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Remote'
|
||||
|
||||
Remote:
|
||||
description: Definition of a frontend plugin Module Federation remote served by the backend
|
||||
type: object
|
||||
properties:
|
||||
packageName:
|
||||
description: Name of the package exposed through this Module Federation remote
|
||||
type: string
|
||||
remoteInfo:
|
||||
$ref: '#/components/schemas/RemoteInfo'
|
||||
|
||||
exposedModules:
|
||||
description: Names of modules exposed by this module federation remote
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- packageName
|
||||
- remoteInfo
|
||||
- exposedModules
|
||||
|
||||
RemoteInfo:
|
||||
description: Definition of a frontend plugin Module Federation remote served by the backend
|
||||
externalDocs:
|
||||
url: https://module-federation.io/guide/basic/runtime.html#init
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: Name of the module federation remote
|
||||
type: string
|
||||
entry:
|
||||
description: Remote entry, either the remote manifest file, or the remote entry Javascript file.
|
||||
type: string
|
||||
entryGlobalName:
|
||||
type: string
|
||||
shareScope:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
[
|
||||
'var',
|
||||
'module',
|
||||
'assign',
|
||||
'assign-properties',
|
||||
'this',
|
||||
'window',
|
||||
'self',
|
||||
'global',
|
||||
'commonjs',
|
||||
'commonjs2',
|
||||
'commonjs-module',
|
||||
'commonjs-static',
|
||||
'amd',
|
||||
'amd-require',
|
||||
'umd',
|
||||
'umd2',
|
||||
'jsonp',
|
||||
'system',
|
||||
]
|
||||
required:
|
||||
- 'name'
|
||||
- 'entry'
|
||||
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
stack:
|
||||
type: string
|
||||
code:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- message
|
||||
request:
|
||||
type: object
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- method
|
||||
- url
|
||||
response:
|
||||
type: object
|
||||
properties:
|
||||
statusCode:
|
||||
type: number
|
||||
required:
|
||||
- statusCode
|
||||
required:
|
||||
- error
|
||||
- response
|
||||
additionalProperties: {}
|
||||
securitySchemes:
|
||||
JWT:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
paths:
|
||||
/remotes:
|
||||
get:
|
||||
operationId: GetRemotes
|
||||
description: Get the Module Federation remote definitions.
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/RemotesResponse'
|
||||
'400':
|
||||
$ref: '#/components/responses/ErrorResponse'
|
||||
default:
|
||||
$ref: '#/components/responses/ErrorResponse'
|
||||
security:
|
||||
- {}
|
||||
- JWT: []
|
||||
parameters: []
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { Remote } from '../models/Remote.model';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type GetRemotes = {
|
||||
response: Array<Remote> | Error | Error;
|
||||
};
|
||||
|
||||
export type EndpointMap = {
|
||||
'#get|/remotes': GetRemotes;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
export * from './Api.server';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './apis';
|
||||
export * from './router';
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ErrorError {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
code?: string;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ErrorRequest {
|
||||
method: string;
|
||||
url: string;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ErrorResponse {
|
||||
statusCode: number;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { ErrorError } from '../models/ErrorError.model';
|
||||
import { ErrorRequest } from '../models/ErrorRequest.model';
|
||||
import { ErrorResponse } from '../models/ErrorResponse.model';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ModelError {
|
||||
[key: string]: any;
|
||||
|
||||
error: ErrorError;
|
||||
request?: ErrorRequest;
|
||||
response: ErrorResponse;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { RemoteInfo } from '../models/RemoteInfo.model';
|
||||
|
||||
/**
|
||||
* Definition of a frontend plugin Module Federation remote served by the backend
|
||||
* @public
|
||||
*/
|
||||
export interface Remote {
|
||||
/**
|
||||
* Name of the package exposed through this Module Federation remote
|
||||
*/
|
||||
packageName: string;
|
||||
remoteInfo: RemoteInfo;
|
||||
/**
|
||||
* Names of modules exposed by this module federation remote
|
||||
*/
|
||||
exposedModules: Array<string>;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
/**
|
||||
* Definition of a frontend plugin Module Federation remote served by the backend
|
||||
* @public
|
||||
*/
|
||||
export interface RemoteInfo {
|
||||
/**
|
||||
* Name of the module federation remote
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Remote entry, either the remote manifest file, or the remote entry Javascript file.
|
||||
*/
|
||||
entry: string;
|
||||
entryGlobalName?: string;
|
||||
shareScope?: string;
|
||||
type?: RemoteInfoTypeEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type RemoteInfoTypeEnum =
|
||||
| 'var'
|
||||
| 'module'
|
||||
| 'assign'
|
||||
| 'assign-properties'
|
||||
| 'this'
|
||||
| 'window'
|
||||
| 'self'
|
||||
| 'global'
|
||||
| 'commonjs'
|
||||
| 'commonjs2'
|
||||
| 'commonjs-module'
|
||||
| 'commonjs-static'
|
||||
| 'amd'
|
||||
| 'amd-require'
|
||||
| 'umd'
|
||||
| 'umd2'
|
||||
| 'jsonp'
|
||||
| 'system';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from '../models/ErrorError.model';
|
||||
export * from '../models/ErrorRequest.model';
|
||||
export * from '../models/ErrorResponse.model';
|
||||
export * from '../models/ModelError.model';
|
||||
export * from '../models/Remote.model';
|
||||
export * from '../models/RemoteInfo.model';
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils';
|
||||
import { EndpointMap } from './';
|
||||
|
||||
export const spec = {
|
||||
openapi: '3.0.3',
|
||||
info: {
|
||||
title: '.backstage/dynamic-features',
|
||||
version: '1',
|
||||
description:
|
||||
'The Backstage backend plugin that serves the frontend plugins module federation manifests and assets',
|
||||
license: {
|
||||
name: 'Apache-2.0',
|
||||
url: 'http://www.apache.org/licenses/LICENSE-2.0.html',
|
||||
},
|
||||
contact: {},
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: '/',
|
||||
},
|
||||
],
|
||||
components: {
|
||||
examples: {},
|
||||
headers: {},
|
||||
parameters: {},
|
||||
requestBodies: {},
|
||||
responses: {
|
||||
RemotesResponse: {
|
||||
description: 'List of Module Federation Remotes exposed by the backend',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Remotes',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ErrorResponse: {
|
||||
description: 'An error response from the backend.',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
Remotes: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/Remote',
|
||||
},
|
||||
},
|
||||
Remote: {
|
||||
description:
|
||||
'Definition of a frontend plugin Module Federation remote served by the backend',
|
||||
type: 'object',
|
||||
properties: {
|
||||
packageName: {
|
||||
description:
|
||||
'Name of the package exposed through this Module Federation remote',
|
||||
type: 'string',
|
||||
},
|
||||
remoteInfo: {
|
||||
$ref: '#/components/schemas/RemoteInfo',
|
||||
},
|
||||
exposedModules: {
|
||||
description:
|
||||
'Names of modules exposed by this module federation remote',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['packageName', 'remoteInfo', 'exposedModules'],
|
||||
},
|
||||
RemoteInfo: {
|
||||
description:
|
||||
'Definition of a frontend plugin Module Federation remote served by the backend',
|
||||
externalDocs: {
|
||||
url: 'https://module-federation.io/guide/basic/runtime.html#init',
|
||||
},
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
description: 'Name of the module federation remote',
|
||||
type: 'string',
|
||||
},
|
||||
entry: {
|
||||
description:
|
||||
'Remote entry, either the remote manifest file, or the remote entry Javascript file.',
|
||||
type: 'string',
|
||||
},
|
||||
entryGlobalName: {
|
||||
type: 'string',
|
||||
},
|
||||
shareScope: {
|
||||
type: 'string',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'var',
|
||||
'module',
|
||||
'assign',
|
||||
'assign-properties',
|
||||
'this',
|
||||
'window',
|
||||
'self',
|
||||
'global',
|
||||
'commonjs',
|
||||
'commonjs2',
|
||||
'commonjs-module',
|
||||
'commonjs-static',
|
||||
'amd',
|
||||
'amd-require',
|
||||
'umd',
|
||||
'umd2',
|
||||
'jsonp',
|
||||
'system',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: ['name', 'entry'],
|
||||
},
|
||||
Error: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
stack: {
|
||||
type: 'string',
|
||||
},
|
||||
code: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['name', 'message'],
|
||||
},
|
||||
request: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
method: {
|
||||
type: 'string',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['method', 'url'],
|
||||
},
|
||||
response: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
statusCode: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
required: ['statusCode'],
|
||||
},
|
||||
},
|
||||
required: ['error', 'response'],
|
||||
additionalProperties: {},
|
||||
},
|
||||
},
|
||||
securitySchemes: {
|
||||
JWT: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
},
|
||||
},
|
||||
},
|
||||
paths: {
|
||||
'/remotes': {
|
||||
get: {
|
||||
operationId: 'GetRemotes',
|
||||
description: 'Get the Module Federation remote definitions.',
|
||||
responses: {
|
||||
'200': {
|
||||
$ref: '#/components/responses/RemotesResponse',
|
||||
},
|
||||
'400': {
|
||||
$ref: '#/components/responses/ErrorResponse',
|
||||
},
|
||||
default: {
|
||||
$ref: '#/components/responses/ErrorResponse',
|
||||
},
|
||||
},
|
||||
security: [
|
||||
{},
|
||||
{
|
||||
JWT: [],
|
||||
},
|
||||
],
|
||||
parameters: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
export const createOpenApiRouter = async (
|
||||
options?: Parameters<
|
||||
typeof createValidatedOpenApiRouterFromGeneratedEndpointMap
|
||||
>['1'],
|
||||
) =>
|
||||
createValidatedOpenApiRouterFromGeneratedEndpointMap<EndpointMap>(
|
||||
spec,
|
||||
options,
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './generated';
|
||||
@@ -29,7 +29,11 @@ import { isEmpty } from 'lodash';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { PluginScanner } from '../scanner/plugin-scanner';
|
||||
import { ConfigSchema, loadConfigSchema } from '@backstage/config-loader';
|
||||
import {
|
||||
ConfigSchema,
|
||||
loadConfigSchema,
|
||||
mergeConfigSchemas,
|
||||
} from '@backstage/config-loader';
|
||||
import { dynamicPluginsFeatureLoader } from '../features';
|
||||
|
||||
/**
|
||||
@@ -153,7 +157,7 @@ async function gatherDynamicPluginsSchemas(
|
||||
packages: ScannedPluginPackage[],
|
||||
logger: LoggerService,
|
||||
schemaLocator: (pluginPackage: ScannedPluginPackage) => string = () =>
|
||||
path.join('dist', 'configSchema.json'),
|
||||
path.join('dist', '.config-schema.json'),
|
||||
): Promise<{ [context: string]: JsonObject }> {
|
||||
const allSchemas: { [context: string]: JsonObject } = {};
|
||||
|
||||
@@ -169,7 +173,7 @@ async function gatherDynamicPluginsSchemas(
|
||||
continue;
|
||||
}
|
||||
|
||||
const serialized = await fs.readJson(schemaLocation);
|
||||
let serialized = await fs.readJson(schemaLocation);
|
||||
if (!serialized) {
|
||||
continue;
|
||||
}
|
||||
@@ -178,6 +182,12 @@ async function gatherDynamicPluginsSchemas(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (serialized?.backstageConfigSchemaVersion === 1) {
|
||||
serialized = mergeConfigSchemas(
|
||||
(serialized?.schemas as JsonObject[]).map(_ => _.value as any),
|
||||
);
|
||||
}
|
||||
|
||||
if (!serialized?.$schema || serialized?.type !== 'object') {
|
||||
logger.error(
|
||||
`Serialized configuration schema is invalid for plugin ${pluginPackage.manifest.name}`,
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createRouter } from './router';
|
||||
import { dynamicPluginsServiceRef } from '@backstage/backend-dynamic-feature-service';
|
||||
import { spec } from '../schema/openapi';
|
||||
import { ManifestFileName } from '@module-federation/sdk';
|
||||
import { RemoteInfo } from '../schema/openapi/generated/models';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
* */
|
||||
export type AdditionalRemoteInfo = Omit<RemoteInfo, 'name' | 'entry'>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
* */
|
||||
export type FrontendRemoteResolver = {
|
||||
/**
|
||||
* Relative path to the module federation assets folder from thr root folder of the plugin package.
|
||||
* Default value is `dist`.
|
||||
*/
|
||||
assetsPathFromPackage?: string;
|
||||
|
||||
/**
|
||||
* File name of the module federation manifest inside the module federation assets folder.
|
||||
* Default value is `mf-manifest.json`.
|
||||
*/
|
||||
manifestFileName?: string;
|
||||
|
||||
/**
|
||||
* Type of the remote entry returned in the RemoteInfo for this remote.
|
||||
* Default value is `manifest`.
|
||||
*/
|
||||
getRemoteEntryType?: (
|
||||
manifestContent: JsonObject,
|
||||
) => 'manifest' | 'javascript';
|
||||
|
||||
/**
|
||||
* Additional module federation fields, which might be required if the remote entry type is 'javascript'.
|
||||
*/
|
||||
getAdditionaRemoteInfo?: (
|
||||
manifestContent: JsonObject,
|
||||
) => AdditionalRemoteInfo;
|
||||
|
||||
/**
|
||||
* Overrides the list of exposed modules. By default the exposed modules are read from the manifest file.
|
||||
*/
|
||||
overrideExposedModules?: (
|
||||
exposedModules: string[],
|
||||
manifestContent: JsonObject,
|
||||
) => string[];
|
||||
|
||||
/**
|
||||
* Customizes the manifest before returning it as the remote entry.
|
||||
*/
|
||||
customizeManifest?: (content: JsonObject) => JsonObject;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
* */
|
||||
export type FrontendRemoteResolverProvider = {
|
||||
for(
|
||||
pluginName: string,
|
||||
pluginPackagePath: string,
|
||||
): Partial<FrontendRemoteResolver> | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
* */
|
||||
export interface DynamicPluginsFrontendRemotesService {
|
||||
setResolverProvider(provider: FrontendRemoteResolverProvider): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A service that serves the frontend module federation remotes,
|
||||
* and allows a plugin to customize the way remotes are served,
|
||||
* by setting a ResolverProvider.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const dynamicPluginsFrontendServiceRef =
|
||||
createServiceRef<DynamicPluginsFrontendRemotesService>({
|
||||
id: 'core.dynamicplugins.frontendRemotes',
|
||||
scope: 'root',
|
||||
});
|
||||
|
||||
export type FrontendRemoteResolvers = {
|
||||
default: FrontendRemoteResolver &
|
||||
Required<
|
||||
Pick<
|
||||
FrontendRemoteResolver,
|
||||
'assetsPathFromPackage' | 'manifestFileName' | 'getRemoteEntryType'
|
||||
>
|
||||
>;
|
||||
provider?: FrontendRemoteResolverProvider;
|
||||
};
|
||||
|
||||
export const frontendRemotesServerService = createServiceFactory({
|
||||
service: dynamicPluginsFrontendServiceRef,
|
||||
deps: {
|
||||
logger: coreServices.rootLogger,
|
||||
rootHttpRouter: coreServices.rootHttpRouter,
|
||||
config: coreServices.rootConfig,
|
||||
dynamicPlugins: dynamicPluginsServiceRef,
|
||||
lifecycle: coreServices.rootLifecycle,
|
||||
},
|
||||
async factory({ logger, rootHttpRouter, config, dynamicPlugins, lifecycle }) {
|
||||
const resolvers: FrontendRemoteResolvers = {
|
||||
default: {
|
||||
assetsPathFromPackage: 'dist',
|
||||
manifestFileName: ManifestFileName,
|
||||
getRemoteEntryType: () => 'manifest',
|
||||
},
|
||||
provider: undefined,
|
||||
};
|
||||
|
||||
lifecycle.addStartupHook(async () => {
|
||||
rootHttpRouter.use(
|
||||
`/${spec.info.title}`,
|
||||
await createRouter({
|
||||
logger,
|
||||
config,
|
||||
dynamicPlugins,
|
||||
resolvers,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
setResolverProvider(resolver) {
|
||||
logger.info('Setting resolver provider');
|
||||
if (resolvers.provider) {
|
||||
throw new Error(
|
||||
'Attempted to install a frontend remote resolver provider twice',
|
||||
);
|
||||
}
|
||||
resolvers.provider = resolver;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { dynamicPluginsFrontendServiceRef } from './frontendRemotesServer';
|
||||
|
||||
export type {
|
||||
DynamicPluginsFrontendRemotesService,
|
||||
FrontendRemoteResolver,
|
||||
FrontendRemoteResolverProvider,
|
||||
AdditionalRemoteInfo,
|
||||
} from './frontendRemotesServer';
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import express from 'express';
|
||||
import { createOpenApiRouter, spec } from '../schema/openapi';
|
||||
import { DynamicPluginProvider } from '@backstage/backend-dynamic-feature-service';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import { FrontendRemoteResolvers } from './frontendRemotesServer';
|
||||
import { Remote } from '../schema/openapi/generated/models';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
export async function createRouter({
|
||||
logger,
|
||||
config,
|
||||
dynamicPlugins,
|
||||
resolvers,
|
||||
}: {
|
||||
logger: LoggerService;
|
||||
config: RootConfigService;
|
||||
dynamicPlugins: DynamicPluginProvider;
|
||||
resolvers: FrontendRemoteResolvers;
|
||||
}): Promise<express.Router> {
|
||||
const externalBaseUrl = `${config.getString('backend.baseUrl')}/${
|
||||
spec.info.title
|
||||
}`;
|
||||
|
||||
const typedRouter = await createOpenApiRouter();
|
||||
|
||||
const frontendPluginRemotes: Remote[] = [];
|
||||
|
||||
const { default: defaultResolver, provider: resolverProvider } = resolvers;
|
||||
for (const plugin of dynamicPlugins.frontendPlugins()) {
|
||||
try {
|
||||
const pluginScannedPackage = dynamicPlugins.getScannedPackage(plugin);
|
||||
const pluginScannedPackagePath = path.resolve(
|
||||
url.fileURLToPath(pluginScannedPackage.location),
|
||||
);
|
||||
const providedResolver = resolverProvider?.for(
|
||||
plugin.name,
|
||||
pluginScannedPackagePath,
|
||||
);
|
||||
|
||||
const assetsPath = path.resolve(
|
||||
pluginScannedPackagePath,
|
||||
providedResolver?.assetsPathFromPackage ??
|
||||
defaultResolver.assetsPathFromPackage,
|
||||
);
|
||||
|
||||
const manifestFileName =
|
||||
providedResolver?.manifestFileName ?? defaultResolver.manifestFileName;
|
||||
const manifestLocation = path.resolve(assetsPath, manifestFileName);
|
||||
if (!fs.existsSync(manifestLocation)) {
|
||||
logger.error(
|
||||
`Could not find manifest '${manifestLocation}' for frontend plugin ${plugin.name}@${plugin.version}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let manifest: JsonObject;
|
||||
try {
|
||||
manifest = JSON.parse(fs.readFileSync(manifestLocation).toString());
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Dynamic frontend plugin manifest '${manifestLocation}' could not be parsed for plugin ${plugin.name}@${plugin.version}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!manifest.name || typeof manifest.name !== 'string') {
|
||||
logger.error(
|
||||
`Error in manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}: module name not found`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
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'
|
||||
) {
|
||||
logger.error(
|
||||
`Could not find remote entry asset in the manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!manifest.exposes ||
|
||||
!Array.isArray(manifest.exposes) ||
|
||||
!manifest.exposes.every<{ name: string }>(
|
||||
(i): i is { name: string } =>
|
||||
i !== null && typeof i === 'object' && 'name' in i,
|
||||
)
|
||||
) {
|
||||
logger.error(
|
||||
`Could not find the exposes field in the manifest '${manifestLocation}' for plugin ${plugin.name}@${plugin.version}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const getAdditionalRemoteInfo =
|
||||
providedResolver?.getAdditionaRemoteInfo ??
|
||||
defaultResolver.getAdditionaRemoteInfo;
|
||||
const getRemoteEntryType =
|
||||
providedResolver?.getRemoteEntryType ??
|
||||
defaultResolver.getRemoteEntryType;
|
||||
const remoteEntryType = getRemoteEntryType(manifest);
|
||||
|
||||
let remoteEntryAsset = manifestFileName;
|
||||
if (remoteEntryType === 'javascript') {
|
||||
remoteEntryAsset = manifest.metaData.remoteEntry.name;
|
||||
}
|
||||
|
||||
const remoteEntryAssetLocation = path.resolve(
|
||||
assetsPath,
|
||||
remoteEntryAsset,
|
||||
);
|
||||
if (!fs.existsSync(remoteEntryAssetLocation)) {
|
||||
logger.error(
|
||||
`Could not find remote entry asset '${remoteEntryAssetLocation}' for frontend plugin ${plugin.name}@${plugin.version}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const remoteAssetsPrefix = `/remotes/${plugin.name}`;
|
||||
const remoteEntryPath = `${remoteAssetsPrefix}/${remoteEntryAsset}`;
|
||||
|
||||
const overrideExposedModules =
|
||||
providedResolver?.overrideExposedModules ??
|
||||
defaultResolver.overrideExposedModules;
|
||||
|
||||
const exposedModules = manifest.exposes.map(e => e.name);
|
||||
|
||||
frontendPluginRemotes.push({
|
||||
packageName: plugin.name,
|
||||
remoteInfo: {
|
||||
name: manifest.name,
|
||||
entry: `${externalBaseUrl}${remoteEntryPath}`,
|
||||
...getAdditionalRemoteInfo?.(manifest),
|
||||
},
|
||||
exposedModules:
|
||||
overrideExposedModules?.(exposedModules, manifest) ?? exposedModules,
|
||||
});
|
||||
|
||||
const customizeManifest =
|
||||
providedResolver?.customizeManifest ??
|
||||
defaultResolver.customizeManifest;
|
||||
if (remoteEntryType === 'manifest' && customizeManifest) {
|
||||
const customizedContent = customizeManifest(manifest);
|
||||
typedRouter.use(`${remoteEntryPath}`, (_, res) => {
|
||||
res.json(customizedContent);
|
||||
});
|
||||
}
|
||||
typedRouter.use(remoteAssetsPrefix, express.static(assetsPath));
|
||||
logger.info(
|
||||
`Exposed dynamic frontend plugin '${plugin.name}' from '${assetsPath}' `,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Unexpected error when exposing dynamic frontend plugin '${plugin.name}@${plugin.version}'`,
|
||||
error,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`/remotes => ${JSON.stringify(frontendPluginRemotes)}`);
|
||||
typedRouter.get('/remotes', (_, res) => {
|
||||
res.status(200).json(frontendPluginRemotes);
|
||||
});
|
||||
|
||||
return typedRouter;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
|
||||
rules: {
|
||||
'@backstage/no-top-level-material-ui-4-imports': 'error',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
# @backstage/frontend-dynamic-feature-loader
|
||||
|
||||
Backstage frontend feature loader to load new frontend system plugins exposed as module federation remotes.
|
||||
The frontend feature loader provided in this package works hand-in-hand with the server of frontend plugin module federation remotes server which is part of backend dynamic feature service in package `@backstage/backend-dynamic-feature-service`.
|
||||
|
||||
**NOTE: The [new frontend system](https://backstage.io/docs/frontend-system/) that this package is relaying upon is in alpha, and we do not yet recommend using it for production deployments**
|
||||
|
||||
## Usage
|
||||
|
||||
- To enable this loader, you should:
|
||||
|
||||
- Enable the backend dynamic features in your backend application, as explained in the [`backend-dynamic-feature-service` README.md file](../backend-dynamic-feature-service/README.md#how-it-works)
|
||||
- Add the frontend feature loader to the list of features when creating the frontend application:
|
||||
|
||||
```typescript
|
||||
const app = createApp({
|
||||
features: [...someOtherFeatures, dynamicFrontendFeaturesLoader()],
|
||||
});
|
||||
```
|
||||
|
||||
## How to add a frontend plugin for dynamic loading
|
||||
|
||||
Adding a frontend plugin (with new frontend system support, possibly in alpha support), is straightforward and consists in:
|
||||
|
||||
- building the frontend plugin with the `frontend-dynamic-container` role, which enables the module federation support, and packages the plugin as a module remote
|
||||
- copying the frontend package folder, with the `dist` folder generated during the build, to the dynamic plugins root folder of the Backstage installation (defined by the `dynamicPlugins.rootDirectory` configuration value, which is usually set as `dynamic-plugins-root`).
|
||||
|
||||
So from a frontend plugin package folder, you would use the following command:
|
||||
|
||||
```bash
|
||||
yarn build --role frontend-dynamic-container && cp -R $(pwd) <target backstage>/dynamic-plugins-root/
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: backstage-frontend-dynamic-feature-loader
|
||||
title: '@backstage/frontend-dynamic-feature-loader'
|
||||
description: Backstage frontend feature loader to load new frontend system plugins exposed as module federation remotes.
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-web-library
|
||||
owner: maintainers
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
/**
|
||||
* @visibility frontend
|
||||
*/
|
||||
dynamicPlugins?: {};
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# Knip report
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "@backstage/frontend-dynamic-feature-loader",
|
||||
"version": "0.0.0",
|
||||
"backstage": {
|
||||
"role": "web-library"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/frontend-dynamic-feature-loader"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"sideEffects": false,
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"files": [
|
||||
"config.d.ts",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"start": "backstage-cli package start",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@module-federation/enhanced": "^0.9.0",
|
||||
"@module-federation/runtime": "^0.9.0",
|
||||
"@module-federation/sdk": "^0.9.0",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"uri-template": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"msw": "^1.0.0",
|
||||
"react": "^18.0.2",
|
||||
"react-dom": "^18.0.2",
|
||||
"react-router-dom": "^6.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "^6.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
## API Report File for "@backstage/frontend-dynamic-feature-loader"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
|
||||
import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api';
|
||||
import { ShareStrategy } from '@module-federation/runtime/types';
|
||||
import { UserOptions } from '@module-federation/runtime/types';
|
||||
|
||||
// @public
|
||||
export function dynamicFrontendFeaturesLoader(
|
||||
options?: DynamicFrontendFeaturesLoaderOptions,
|
||||
): FrontendFeatureLoader;
|
||||
|
||||
// @public (undocumented)
|
||||
export type DynamicFrontendFeaturesLoaderOptions = {
|
||||
moduleFederation: {
|
||||
shared?: UserOptions['shared'];
|
||||
shareStrategy?: ShareStrategy;
|
||||
plugins?: Array<FederationRuntimePlugin>;
|
||||
};
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core API used by Backstage frontend apps.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './loader';
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
FederationRuntimePlugin,
|
||||
init,
|
||||
loadRemote,
|
||||
} from '@module-federation/enhanced/runtime';
|
||||
import { Module } from '@module-federation/sdk';
|
||||
import { DefaultApiClient, Remote } from './schema/openapi';
|
||||
import {
|
||||
FrontendFeature,
|
||||
FrontendFeatureLoader,
|
||||
createFrontendFeatureLoader,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { ShareStrategy, UserOptions } from '@module-federation/runtime/types';
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type DynamicFrontendFeaturesLoaderOptions = {
|
||||
/**
|
||||
* Additional module federation arguments for the Module Federation runtime initialization.
|
||||
*/
|
||||
moduleFederation: {
|
||||
shared?: UserOptions['shared'];
|
||||
shareStrategy?: ShareStrategy;
|
||||
plugins?: Array<FederationRuntimePlugin>;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* A function providing a loader of frontend features exposed as module federation remotes
|
||||
* from the backend dynamic features service.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function dynamicFrontendFeaturesLoader(
|
||||
options?: DynamicFrontendFeaturesLoaderOptions,
|
||||
): FrontendFeatureLoader {
|
||||
return createFrontendFeatureLoader({
|
||||
async loader({ config }) {
|
||||
const dynamicPLuginsConfig = config.getOptionalConfig('dynamicPlugins');
|
||||
if (!dynamicPLuginsConfig) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function error(message: string, err: unknown) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`${message}: ${
|
||||
err instanceof Error ? err.toString() : JSON.stringify(err)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const backendBaseUrl = config.getString('backend.baseUrl');
|
||||
|
||||
const appPackageName =
|
||||
config.getOptionalString('app.packageName') ?? 'app';
|
||||
let frontendPluginRemotes: Array<Remote>;
|
||||
try {
|
||||
const apiClient = new DefaultApiClient({
|
||||
discoveryApi: {
|
||||
getBaseUrl: async rootPath => `${backendBaseUrl}/${rootPath}`,
|
||||
},
|
||||
fetchApi: {
|
||||
fetch(input) {
|
||||
return global.fetch(input);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await apiClient.getRemotes({});
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status} - ${response.statusText}`);
|
||||
}
|
||||
frontendPluginRemotes = await response.json();
|
||||
} catch (err) {
|
||||
error(
|
||||
`Failed fetching module federation configuration of dynamic frontend plugins`,
|
||||
err,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
init({
|
||||
...options?.moduleFederation,
|
||||
name: appPackageName
|
||||
.replaceAll('@', '')
|
||||
.replaceAll('/', '__')
|
||||
.replaceAll('-', '_'),
|
||||
remotes: frontendPluginRemotes.map(remote => ({
|
||||
alias: remote.packageName,
|
||||
...remote.remoteInfo,
|
||||
})),
|
||||
});
|
||||
} catch (err) {
|
||||
error(`Failed initializing module federation`, err);
|
||||
return [];
|
||||
}
|
||||
|
||||
const features = (
|
||||
await Promise.all(
|
||||
frontendPluginRemotes.map(async remote => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(
|
||||
`Loading dynamic plugin '${remote.packageName}' from '${remote.remoteInfo.entry}'`,
|
||||
);
|
||||
|
||||
const moduleFeatures = await Promise.all(
|
||||
remote.exposedModules.map(async exposedModuleName => {
|
||||
const remoteModuleName =
|
||||
exposedModuleName === '.'
|
||||
? remote.remoteInfo.name
|
||||
: `${remote.remoteInfo.name}/${exposedModuleName}`;
|
||||
let module: Module;
|
||||
try {
|
||||
module = await loadRemote<Module>(remoteModuleName);
|
||||
} catch (err) {
|
||||
error(
|
||||
`Failed loading remote module '${remoteModuleName}' of dynamic plugin '${remote.packageName}'`,
|
||||
err,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
if (!module) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`Skipping empty dynamic plugin remote module '${remoteModuleName}'.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(
|
||||
`Remote module '${remoteModuleName}' of dynamic plugin '${remote.packageName}' loaded from ${remote.remoteInfo.entry}`,
|
||||
);
|
||||
const defaultEntry = module.default;
|
||||
if (!isLoadable(defaultEntry)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(
|
||||
`Skipping dynamic plugin remote module '${remote}' since it doesn't export a new 'FrontendFeature' as default export.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return defaultEntry;
|
||||
}),
|
||||
);
|
||||
return moduleFeatures;
|
||||
}),
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
.filter((feature): feature is FrontendFeature => feature !== undefined);
|
||||
|
||||
return [...features];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function isLoadable(obj: unknown): obj is FrontendFeature {
|
||||
if (obj !== null && typeof obj === 'object' && '$$type' in obj) {
|
||||
return (
|
||||
obj.$$type === '@backstage/FrontendPlugin' ||
|
||||
obj.$$type === '@backstage/FrontendModule'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { DiscoveryApi } from '../types/discovery';
|
||||
import { FetchApi } from '../types/fetch';
|
||||
import crossFetch from 'cross-fetch';
|
||||
import { pluginId } from '../pluginId';
|
||||
import * as parser from 'uri-template';
|
||||
import { Remote } from '../models/Remote.model';
|
||||
|
||||
/**
|
||||
* Wraps the Response type to convey a type on the json call.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TypedResponse<T> = Omit<Response, 'json'> & {
|
||||
json: () => Promise<T>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options you can pass into a request for additional information.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface RequestOptions {
|
||||
token?: string;
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type GetRemotes = {};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class DefaultApiClient {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
constructor(options: {
|
||||
discoveryApi: { getBaseUrl(pluginId: string): Promise<string> };
|
||||
fetchApi?: { fetch: typeof fetch };
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.fetchApi = options.fetchApi || { fetch: crossFetch };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Module Federation remote definitions.
|
||||
*/
|
||||
public async getRemotes(
|
||||
// @ts-ignore
|
||||
request: GetRemotes,
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<Array<Remote>>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/remotes`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({});
|
||||
|
||||
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './Api.client';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './apis';
|
||||
export * from './models';
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ErrorError {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
code?: string;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ErrorRequest {
|
||||
method: string;
|
||||
url: string;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ErrorResponse {
|
||||
statusCode: number;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { ErrorError } from '../models/ErrorError.model';
|
||||
import { ErrorRequest } from '../models/ErrorRequest.model';
|
||||
import { ErrorResponse } from '../models/ErrorResponse.model';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ModelError {
|
||||
[key: string]: any;
|
||||
|
||||
error: ErrorError;
|
||||
request?: ErrorRequest;
|
||||
response: ErrorResponse;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
import { RemoteInfo } from '../models/RemoteInfo.model';
|
||||
|
||||
/**
|
||||
* Definition of a frontend plugin Module Federation remote served by the backend
|
||||
* @public
|
||||
*/
|
||||
export interface Remote {
|
||||
/**
|
||||
* Name of the package exposed through this Module Federation remote
|
||||
*/
|
||||
packageName: string;
|
||||
remoteInfo: RemoteInfo;
|
||||
/**
|
||||
* Names of modules exposed by this module federation remote
|
||||
*/
|
||||
exposedModules: Array<string>;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
/**
|
||||
* Definition of a frontend plugin Module Federation remote served by the backend
|
||||
* @public
|
||||
*/
|
||||
export interface RemoteInfo {
|
||||
/**
|
||||
* Name of the module federation remote
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Remote entry, either the remote manifest file, or the remote entry Javascript file.
|
||||
*/
|
||||
entry: string;
|
||||
entryGlobalName?: string;
|
||||
shareScope?: string;
|
||||
type?: RemoteInfoTypeEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type RemoteInfoTypeEnum =
|
||||
| 'var'
|
||||
| 'module'
|
||||
| 'assign'
|
||||
| 'assign-properties'
|
||||
| 'this'
|
||||
| 'window'
|
||||
| 'self'
|
||||
| 'global'
|
||||
| 'commonjs'
|
||||
| 'commonjs2'
|
||||
| 'commonjs-module'
|
||||
| 'commonjs-static'
|
||||
| 'amd'
|
||||
| 'amd-require'
|
||||
| 'umd'
|
||||
| 'umd2'
|
||||
| 'jsonp'
|
||||
| 'system';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from '../models/ErrorError.model';
|
||||
export * from '../models/ErrorRequest.model';
|
||||
export * from '../models/ErrorResponse.model';
|
||||
export * from '../models/ModelError.model';
|
||||
export * from '../models/Remote.model';
|
||||
export * from '../models/RemoteInfo.model';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const pluginId = '.backstage/dynamic-features';
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of the DiscoveryApi, to avoid importing core-plugin-api.
|
||||
*/
|
||||
export type DiscoveryApi = {
|
||||
getBaseUrl(pluginId: string): Promise<string>;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of FetchApi, to avoid importing core-plugin-api.
|
||||
*/
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './generated';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -77,6 +77,7 @@ const roleRules = [
|
||||
'@backstage/core-compat-api',
|
||||
'@backstage/dev-utils',
|
||||
'@backstage/frontend-defaults',
|
||||
'@backstage/frontend-dynamic-feature-loader',
|
||||
'@backstage/frontend-app-api',
|
||||
'@backstage/frontend-test-utils',
|
||||
'@backstage/test-utils',
|
||||
|
||||
@@ -3656,6 +3656,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@backstage/backend-app-api": "workspace:^"
|
||||
"@backstage/backend-defaults": "workspace:^"
|
||||
"@backstage/backend-openapi-utils": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
@@ -3675,11 +3676,14 @@ __metadata:
|
||||
"@backstage/plugin-scaffolder-node": "workspace:^"
|
||||
"@backstage/plugin-search-backend-node": "workspace:^"
|
||||
"@backstage/plugin-search-common": "workspace:^"
|
||||
"@backstage/repo-tools": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@manypkg/get-packages": "npm:^1.1.3"
|
||||
"@module-federation/sdk": "npm:^0.9.0"
|
||||
"@types/express": "npm:^4.17.6"
|
||||
chokidar: "npm:^3.5.3"
|
||||
express: "npm:^4.17.1"
|
||||
express-promise-router: "npm:^4.1.0"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
lodash: "npm:^4.17.21"
|
||||
triple-beam: "npm:^1.4.1"
|
||||
@@ -4097,7 +4101,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/core-app-api@npm:^1.16.0":
|
||||
"@backstage/core-app-api@npm:^1.15.5, @backstage/core-app-api@npm:^1.16.0":
|
||||
version: 1.16.0
|
||||
resolution: "@backstage/core-app-api@npm:1.16.0"
|
||||
dependencies:
|
||||
@@ -4169,13 +4173,12 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/core-compat-api@npm:^0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "@backstage/core-compat-api@npm:0.4.0"
|
||||
"@backstage/core-compat-api@npm:^0.3.6":
|
||||
version: 0.3.6
|
||||
resolution: "@backstage/core-compat-api@npm:0.3.6"
|
||||
dependencies:
|
||||
"@backstage/core-plugin-api": "npm:^1.10.5"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.10.0"
|
||||
"@backstage/plugin-catalog-react": "npm:^1.16.0"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.4"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.9.5"
|
||||
"@backstage/version-bridge": "npm:^1.0.11"
|
||||
lodash: "npm:^4.17.21"
|
||||
peerDependencies:
|
||||
@@ -4186,7 +4189,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/6b29f14dadc5dd5c055d063cb12ff901e2ff4458eb606489fc917e029afc3bb81b5292cf701227cedf35c3df25dc85ad1c60ca20ef3b8f75aa41d40a7f5580ee
|
||||
checksum: 10/befd4cb7108672ff0c3f4433e39bf5557e43a755fdf658de7b4a161977efb69b9b8a129930b141b02ea7bca5f71d70353d415d7d89f9a440afd6b4fa28c9a8cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4330,12 +4333,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@backstage/core-components@npm:^0.17.0":
|
||||
version: 0.17.0
|
||||
resolution: "@backstage/core-components@npm:0.17.0"
|
||||
"@backstage/core-components@npm:^0.16.4":
|
||||
version: 0.16.4
|
||||
resolution: "@backstage/core-components@npm:0.16.4"
|
||||
dependencies:
|
||||
"@backstage/config": "npm:^1.3.2"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.5"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.4"
|
||||
"@backstage/errors": "npm:^1.2.7"
|
||||
"@backstage/theme": "npm:^0.6.4"
|
||||
"@backstage/version-bridge": "npm:^1.0.11"
|
||||
@@ -4380,7 +4383,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/b48f7a3a6df469f26eb033f2f59da36dcf56bb52ba7298b7dba05d9132c90f8ab5b58dd247a8f9c1b652b91138358fbae482654465550c74780a32fe04bf6e7e
|
||||
checksum: 10/472f4a17edc740cec15041612068320114b0128d6917d6968db8b435c3f4c2f002be939978b29efb12ce32c3d660b28e9de051ac1104c14f4eae2b6129c5ab49
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4462,7 +4465,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.5, @backstage/core-plugin-api@npm:^1.8.2":
|
||||
"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.4, @backstage/core-plugin-api@npm:^1.10.5, @backstage/core-plugin-api@npm:^1.8.2":
|
||||
version: 1.10.5
|
||||
resolution: "@backstage/core-plugin-api@npm:1.10.5"
|
||||
dependencies:
|
||||
@@ -4616,16 +4619,16 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/frontend-app-api@npm:^0.11.0":
|
||||
version: 0.11.0
|
||||
resolution: "@backstage/frontend-app-api@npm:0.11.0"
|
||||
"@backstage/frontend-app-api@npm:^0.10.5":
|
||||
version: 0.10.5
|
||||
resolution: "@backstage/frontend-app-api@npm:0.10.5"
|
||||
dependencies:
|
||||
"@backstage/config": "npm:^1.3.2"
|
||||
"@backstage/core-app-api": "npm:^1.16.0"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.5"
|
||||
"@backstage/core-app-api": "npm:^1.15.5"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.4"
|
||||
"@backstage/errors": "npm:^1.2.7"
|
||||
"@backstage/frontend-defaults": "npm:^0.2.0"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.10.0"
|
||||
"@backstage/frontend-defaults": "npm:^0.1.6"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.9.5"
|
||||
"@backstage/types": "npm:^1.2.1"
|
||||
"@backstage/version-bridge": "npm:^1.0.11"
|
||||
lodash: "npm:^4.17.21"
|
||||
@@ -4638,7 +4641,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/5522ddd487766ea44288d5626f269e42ad7d84902a5a257fd3962d2b2409743cf1b5de135e15915ba568bf2b12ec25c814ece077c87e1436c67e63998b1b9652
|
||||
checksum: 10/55837eeacbc3bac8b9e684c63d8784f56d5adc76d20e415f1e1a30c7fb2014b8407c7e30bdd9823a5f2c9c63ce793271b5f0997c5057359a4c5e73b7ba346afb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4676,15 +4679,15 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/frontend-defaults@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "@backstage/frontend-defaults@npm:0.2.0"
|
||||
"@backstage/frontend-defaults@npm:^0.1.6":
|
||||
version: 0.1.6
|
||||
resolution: "@backstage/frontend-defaults@npm:0.1.6"
|
||||
dependencies:
|
||||
"@backstage/config": "npm:^1.3.2"
|
||||
"@backstage/errors": "npm:^1.2.7"
|
||||
"@backstage/frontend-app-api": "npm:^0.11.0"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.10.0"
|
||||
"@backstage/plugin-app": "npm:^0.1.7"
|
||||
"@backstage/frontend-app-api": "npm:^0.10.5"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.9.5"
|
||||
"@backstage/plugin-app": "npm:^0.1.6"
|
||||
"@react-hookz/web": "npm:^24.0.0"
|
||||
peerDependencies:
|
||||
"@types/react": ^17.0.0 || ^18.0.0
|
||||
@@ -4694,7 +4697,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/65decef0c603ededc36791db771f82c35f6b8c283a65d6b81ad098175290ce8d125d22127968820d29dcbc69c4241b7e703013c5091a1c433bfaf86eaad482ea
|
||||
checksum: 10/2e387815586c436c5c24a116f0294dddcf1a5bc0a84b8dd73bdb9fc797fd5aca2aeaa77b4da3908f48e4b18a356751d9241473bfbe4951edf56138e0a62994e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4728,12 +4731,43 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/frontend-plugin-api@npm:^0.10.0":
|
||||
version: 0.10.0
|
||||
resolution: "@backstage/frontend-plugin-api@npm:0.10.0"
|
||||
"@backstage/frontend-dynamic-feature-loader@workspace:packages/frontend-dynamic-feature-loader":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/frontend-dynamic-feature-loader@workspace:packages/frontend-dynamic-feature-loader"
|
||||
dependencies:
|
||||
"@backstage/core-components": "npm:^0.17.0"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.5"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@module-federation/enhanced": "npm:^0.9.0"
|
||||
"@module-federation/runtime": "npm:^0.9.0"
|
||||
"@module-federation/sdk": "npm:^0.9.0"
|
||||
"@testing-library/jest-dom": "npm:^6.0.0"
|
||||
"@testing-library/react": "npm:^16.0.0"
|
||||
"@types/react": "npm:^18.0.0"
|
||||
cross-fetch: "npm:^4.0.0"
|
||||
msw: "npm:^1.0.0"
|
||||
react: "npm:^18.0.2"
|
||||
react-dom: "npm:^18.0.2"
|
||||
react-router-dom: "npm:^6.3.0"
|
||||
uri-template: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
"@types/react": ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
react-router-dom: ^6.3.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/frontend-plugin-api@npm:^0.9.5":
|
||||
version: 0.9.5
|
||||
resolution: "@backstage/frontend-plugin-api@npm:0.9.5"
|
||||
dependencies:
|
||||
"@backstage/core-components": "npm:^0.16.4"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.4"
|
||||
"@backstage/types": "npm:^1.2.1"
|
||||
"@backstage/version-bridge": "npm:^1.0.11"
|
||||
"@material-ui/core": "npm:^4.12.4"
|
||||
@@ -4748,7 +4782,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/9f52031e65d38087da1ad8d3117fdb2c54dbde20326f32c7cfebd755af6d28d881c9e57b5f9c17f4920fb40eea90b3b5b48b4cfc95f7dd2a3359866544ad5411
|
||||
checksum: 10/91a55d7d12545aa8041b64c2443e566775c9c6cca62e6b8bd5d3ac5eb4c8f2d28a8c7bb602c1389537be4ce61d0bf93e2ddeddd4e2fa3ae691817a3bb55fabab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4786,15 +4820,15 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/frontend-test-utils@npm:^0.3.0":
|
||||
version: 0.3.0
|
||||
resolution: "@backstage/frontend-test-utils@npm:0.3.0"
|
||||
"@backstage/frontend-test-utils@npm:^0.2.6":
|
||||
version: 0.2.6
|
||||
resolution: "@backstage/frontend-test-utils@npm:0.2.6"
|
||||
dependencies:
|
||||
"@backstage/config": "npm:^1.3.2"
|
||||
"@backstage/frontend-app-api": "npm:^0.11.0"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.10.0"
|
||||
"@backstage/plugin-app": "npm:^0.1.7"
|
||||
"@backstage/test-utils": "npm:^1.7.6"
|
||||
"@backstage/frontend-app-api": "npm:^0.10.5"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.9.5"
|
||||
"@backstage/plugin-app": "npm:^0.1.6"
|
||||
"@backstage/test-utils": "npm:^1.7.5"
|
||||
"@backstage/types": "npm:^1.2.1"
|
||||
"@backstage/version-bridge": "npm:^1.0.11"
|
||||
zod: "npm:^3.22.4"
|
||||
@@ -4807,7 +4841,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/d141510fd32dc585677e1e1d7e0962b7656fd3ba7c214ef6341da39771e6fc41f5a1ca593ccfe660017fab15c90e7f7166c501618cb4c21d076ea98f101039dc
|
||||
checksum: 10/0fd0c43c85f6a28c23840ac44064d9294340e892bb6e316d66afc683b05eaadebc9415a32a3f98fb81d4e8090b9496255e10ed519bb74757e089ab1d70794174
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4860,7 +4894,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/integration-react@npm:^1.1.24, @backstage/integration-react@npm:^1.2.5":
|
||||
"@backstage/integration-react@npm:^1.1.24, @backstage/integration-react@npm:^1.2.4":
|
||||
version: 1.2.5
|
||||
resolution: "@backstage/integration-react@npm:1.2.5"
|
||||
dependencies:
|
||||
@@ -5091,17 +5125,16 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-app@npm:^0.1.7":
|
||||
version: 0.1.7
|
||||
resolution: "@backstage/plugin-app@npm:0.1.7"
|
||||
"@backstage/plugin-app@npm:^0.1.6":
|
||||
version: 0.1.6
|
||||
resolution: "@backstage/plugin-app@npm:0.1.6"
|
||||
dependencies:
|
||||
"@backstage/core-components": "npm:^0.17.0"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.5"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.10.0"
|
||||
"@backstage/integration-react": "npm:^1.2.5"
|
||||
"@backstage/plugin-permission-react": "npm:^0.4.32"
|
||||
"@backstage/core-components": "npm:^0.16.4"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.4"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.9.5"
|
||||
"@backstage/integration-react": "npm:^1.2.4"
|
||||
"@backstage/plugin-permission-react": "npm:^0.4.31"
|
||||
"@backstage/theme": "npm:^0.6.4"
|
||||
"@backstage/types": "npm:^1.2.1"
|
||||
"@material-ui/core": "npm:^4.9.13"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:^4.0.0-alpha.61"
|
||||
@@ -5114,7 +5147,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/29819de2c81dbeb787e485d43b8ccb475d42fb4e071ce2eab6e0a814e35f5d6f14deb1266082326dd3b07005136a34b780a14af3aed7efae6f0fff4ae28bc9cb
|
||||
checksum: 10/6ba4de2ba60b95366f71b1b301c234a3d7ff34356db006696e8cae15b8c1ba9e3f31e17acf205cc74f2faa068b55ecdc535b1c087ae3a97c90915841192efee6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6276,22 +6309,22 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-catalog-react@npm:^1.14.0, @backstage/plugin-catalog-react@npm:^1.16.0, @backstage/plugin-catalog-react@npm:^1.9.3":
|
||||
version: 1.16.0
|
||||
resolution: "@backstage/plugin-catalog-react@npm:1.16.0"
|
||||
"@backstage/plugin-catalog-react@npm:^1.14.0, @backstage/plugin-catalog-react@npm:^1.9.3":
|
||||
version: 1.15.2
|
||||
resolution: "@backstage/plugin-catalog-react@npm:1.15.2"
|
||||
dependencies:
|
||||
"@backstage/catalog-client": "npm:^1.9.1"
|
||||
"@backstage/catalog-model": "npm:^1.7.3"
|
||||
"@backstage/core-compat-api": "npm:^0.4.0"
|
||||
"@backstage/core-components": "npm:^0.17.0"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.5"
|
||||
"@backstage/core-compat-api": "npm:^0.3.6"
|
||||
"@backstage/core-components": "npm:^0.16.4"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.4"
|
||||
"@backstage/errors": "npm:^1.2.7"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.10.0"
|
||||
"@backstage/frontend-test-utils": "npm:^0.3.0"
|
||||
"@backstage/integration-react": "npm:^1.2.5"
|
||||
"@backstage/frontend-plugin-api": "npm:^0.9.5"
|
||||
"@backstage/frontend-test-utils": "npm:^0.2.6"
|
||||
"@backstage/integration-react": "npm:^1.2.4"
|
||||
"@backstage/plugin-catalog-common": "npm:^1.1.3"
|
||||
"@backstage/plugin-permission-common": "npm:^0.8.4"
|
||||
"@backstage/plugin-permission-react": "npm:^0.4.32"
|
||||
"@backstage/plugin-permission-react": "npm:^0.4.31"
|
||||
"@backstage/types": "npm:^1.2.1"
|
||||
"@backstage/version-bridge": "npm:^1.0.11"
|
||||
"@material-ui/core": "npm:^4.12.2"
|
||||
@@ -6313,7 +6346,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/0149b99ede5d563cd81dfa1d657cf332af2cbb3e6ff2e88244c4b3c59e2109264c62e3a6903ee471596a3c709294b81fe2da83513d79690704fb37f6cf67d305
|
||||
checksum: 10/379f934cc871a101714675bbe6aa51a4b0a831731dfac807004b74c804b107f2def033b53bb56c10c7bda1b16d07a014c00b374fccad7677e3f9b29be51fc354
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7381,7 +7414,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-permission-react@npm:^0.4.32":
|
||||
"@backstage/plugin-permission-react@npm:^0.4.31, @backstage/plugin-permission-react@npm:^0.4.32":
|
||||
version: 0.4.32
|
||||
resolution: "@backstage/plugin-permission-react@npm:0.4.32"
|
||||
dependencies:
|
||||
@@ -8804,7 +8837,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/test-utils@npm:^1.7.6":
|
||||
"@backstage/test-utils@npm:^1.7.5":
|
||||
version: 1.7.6
|
||||
resolution: "@backstage/test-utils@npm:1.7.6"
|
||||
dependencies:
|
||||
@@ -12032,39 +12065,39 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/bridge-react-webpack-plugin@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.9.0"
|
||||
"@module-federation/bridge-react-webpack-plugin@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
"@types/semver": "npm:7.5.8"
|
||||
semver: "npm:7.6.3"
|
||||
checksum: 10/234b022407b1d84790aecc32f2b825ad22c7ebfa541914367270f307b0962194511460d6ace0b7c76bcf9aaf6dc4d09c3021f4cb73ec6568412d31023bf9c93f
|
||||
checksum: 10/4ff197741b1bdccf8f9e2236781e5ce3ef434e4207c5462b4b95b044c4c57a3ab3dd4dce48490d9fde5bd06fb855b9918841c7d04306726ee224d3e288074091
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/data-prefetch@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/data-prefetch@npm:0.9.0"
|
||||
"@module-federation/data-prefetch@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/data-prefetch@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/runtime": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
"@module-federation/runtime": "npm:0.9.1"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
fs-extra: "npm:9.1.0"
|
||||
peerDependencies:
|
||||
react: ">=16.9.0"
|
||||
react-dom: ">=16.9.0"
|
||||
checksum: 10/d8cfef2e3427326048e17a686e3d2df8df7f6913170ddd03fff93af29bd03e68ab26f8b2587bec99357e2fb3fbed457f81e3ba6e8af98deacd744850869c1516
|
||||
checksum: 10/a3c0e8d77f4d06e3851d041175ef3f55d47ff4069a526b9ac773c8aba71a520a0490653c87cdcf3761e96f9eb9ea16d45026f3aafdf0851c34ff4db6d71ec113
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/dts-plugin@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/dts-plugin@npm:0.9.0"
|
||||
"@module-federation/dts-plugin@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/dts-plugin@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/error-codes": "npm:0.9.0"
|
||||
"@module-federation/managers": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
"@module-federation/third-party-dts-extractor": "npm:0.9.0"
|
||||
"@module-federation/error-codes": "npm:0.9.1"
|
||||
"@module-federation/managers": "npm:0.9.1"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
"@module-federation/third-party-dts-extractor": "npm:0.9.1"
|
||||
adm-zip: "npm:^0.5.10"
|
||||
ansi-colors: "npm:^4.1.3"
|
||||
axios: "npm:^1.7.4"
|
||||
@@ -12083,24 +12116,24 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
vue-tsc:
|
||||
optional: true
|
||||
checksum: 10/539d277dd7c769ec9edde394fe3e9dddb38533c6fde0ac0fef0346d1229795a1b2199c4cc73e457b0ac2480c3548814e01d9fa47e12327fe97e3f6486300002d
|
||||
checksum: 10/e9fd11b150456f2621636587d181f6456fe5cd60a0720843fe1310e350c3ff8ebef02ea87d1bf40dc04a4b4cd65cfb2e9007ca1c20e4e5664142b7c670c4a57d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/enhanced@npm:^0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/enhanced@npm:0.9.0"
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/enhanced@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/bridge-react-webpack-plugin": "npm:0.9.0"
|
||||
"@module-federation/data-prefetch": "npm:0.9.0"
|
||||
"@module-federation/dts-plugin": "npm:0.9.0"
|
||||
"@module-federation/error-codes": "npm:0.9.0"
|
||||
"@module-federation/inject-external-runtime-core-plugin": "npm:0.9.0"
|
||||
"@module-federation/managers": "npm:0.9.0"
|
||||
"@module-federation/manifest": "npm:0.9.0"
|
||||
"@module-federation/rspack": "npm:0.9.0"
|
||||
"@module-federation/runtime-tools": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
"@module-federation/bridge-react-webpack-plugin": "npm:0.9.1"
|
||||
"@module-federation/data-prefetch": "npm:0.9.1"
|
||||
"@module-federation/dts-plugin": "npm:0.9.1"
|
||||
"@module-federation/error-codes": "npm:0.9.1"
|
||||
"@module-federation/inject-external-runtime-core-plugin": "npm:0.9.1"
|
||||
"@module-federation/managers": "npm:0.9.1"
|
||||
"@module-federation/manifest": "npm:0.9.1"
|
||||
"@module-federation/rspack": "npm:0.9.1"
|
||||
"@module-federation/runtime-tools": "npm:0.9.1"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
btoa: "npm:^1.2.1"
|
||||
upath: "npm:2.0.1"
|
||||
peerDependencies:
|
||||
@@ -12114,7 +12147,7 @@ __metadata:
|
||||
optional: true
|
||||
webpack:
|
||||
optional: true
|
||||
checksum: 10/9cca13bf8118ed3e11a54adb1006dc5a55437c0140d1edff3a8969c0ed90c07a268f42360e47f90070a32cdfedc340129b3b4e50143ca846c3b8e4ee5880523e
|
||||
checksum: 10/a7955711f37ba02a18f3570289dfd8fbed30511b82fcf593e56808559cfd49d8852028aa67950db5e45617d0917536d067d85847892e62c20b68ba6ab40e17c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12125,57 +12158,57 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/error-codes@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/error-codes@npm:0.9.0"
|
||||
checksum: 10/9ebe1bbf3ed61b1d9ec3643d6743884ab5b44290fe5f1cb080ef3f514f1bf9c4cd39309fa3733697030f2468577c6f5159ec0a12d1cb69c7842aaee11159a647
|
||||
"@module-federation/error-codes@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/error-codes@npm:0.9.1"
|
||||
checksum: 10/545aecc606a506ee47f061835e0eaa41b8d1b02f6bf71b36ec9ae85a1b0370af1f7b7cf92a8f52c3c4b35da858653244316de5ab06bea5dac5b92995467631cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/inject-external-runtime-core-plugin@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/inject-external-runtime-core-plugin@npm:0.9.0"
|
||||
"@module-federation/inject-external-runtime-core-plugin@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/inject-external-runtime-core-plugin@npm:0.9.1"
|
||||
peerDependencies:
|
||||
"@module-federation/runtime-tools": 0.9.0
|
||||
checksum: 10/7821cc6bc2cd865ec4b0496c1c53e009611e1bf7571ca6be7e852e79de418df3952ce02b02f7a6773d833638bccbc2b3b853ebd22869a6abdaec717dc5996a8a
|
||||
"@module-federation/runtime-tools": 0.9.1
|
||||
checksum: 10/931eef6292c278450fc8cdb017073fa0b721796461eee12a254fc60a88a2f17e91395b12371f2c8b3b25b4056fc2dd2b76d1022e679be7353947c3d797e75ea5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/managers@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/managers@npm:0.9.0"
|
||||
"@module-federation/managers@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/managers@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
find-pkg: "npm:2.0.0"
|
||||
fs-extra: "npm:9.1.0"
|
||||
checksum: 10/64f8430d6beff7b76e0fe1eaead080bd42a8565bcd3c80b7dcbfba69578c8407ee725785aee847c11e3c3a28c5defb30a39780cb319dca3140c8296c5af0fcfe
|
||||
checksum: 10/32c1666244ba98644ab6eccdc844415d1aece1c105f6ba2ff17a3836866e7cdb2f61e556cb5a2b506b898ec709951f318d397f4d153efeff377067237968462f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/manifest@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/manifest@npm:0.9.0"
|
||||
"@module-federation/manifest@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/manifest@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/dts-plugin": "npm:0.9.0"
|
||||
"@module-federation/managers": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
"@module-federation/dts-plugin": "npm:0.9.1"
|
||||
"@module-federation/managers": "npm:0.9.1"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
chalk: "npm:3.0.0"
|
||||
find-pkg: "npm:2.0.0"
|
||||
checksum: 10/4902b945845fc3a0d1cffb33227fadc235ed02f62e436bb90dfb4c45b32f508e3692ccc4799733d8439314b8588ef14e597ef11927eea74b6ecd3878924e6ad6
|
||||
checksum: 10/539b86bd5388296fb35a34c7b732b92788600ab6625d53f11588ad67c3a603ac3c1974541d9cf04db2d24635be1610414c29388e849d96bbb812cad3b1c79425
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/rspack@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/rspack@npm:0.9.0"
|
||||
"@module-federation/rspack@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/rspack@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/bridge-react-webpack-plugin": "npm:0.9.0"
|
||||
"@module-federation/dts-plugin": "npm:0.9.0"
|
||||
"@module-federation/inject-external-runtime-core-plugin": "npm:0.9.0"
|
||||
"@module-federation/managers": "npm:0.9.0"
|
||||
"@module-federation/manifest": "npm:0.9.0"
|
||||
"@module-federation/runtime-tools": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
"@module-federation/bridge-react-webpack-plugin": "npm:0.9.1"
|
||||
"@module-federation/dts-plugin": "npm:0.9.1"
|
||||
"@module-federation/inject-external-runtime-core-plugin": "npm:0.9.1"
|
||||
"@module-federation/managers": "npm:0.9.1"
|
||||
"@module-federation/manifest": "npm:0.9.1"
|
||||
"@module-federation/runtime-tools": "npm:0.9.1"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
peerDependencies:
|
||||
"@rspack/core": ">=0.7"
|
||||
typescript: ^4.9.0 || ^5.0.0
|
||||
@@ -12185,7 +12218,7 @@ __metadata:
|
||||
optional: true
|
||||
vue-tsc:
|
||||
optional: true
|
||||
checksum: 10/3c67889f1f4114ea2f0180f3ba5f2e9cbbc687590d6ec07a20638655b4041a1874bafbe685a93fcb8132c9ad67ace7fe6cde474c70562f8c4fb9af9d9de5aa62
|
||||
checksum: 10/e6deb7236ccc66d7f0475a09916ec714c9855847bfd3d3ec6cdbee991016a222a22f6ae3fd14d71351d55e22e8949140f37ce9ce79dece5416731c6698726096
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12199,13 +12232,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/runtime-core@npm:0.6.21":
|
||||
version: 0.6.21
|
||||
resolution: "@module-federation/runtime-core@npm:0.6.21"
|
||||
"@module-federation/runtime-core@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/runtime-core@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/error-codes": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
checksum: 10/6cff7180b7fd36f37188bc8605861f709ffdb89a1d4600c6d60c6fa4fb44672927f855fbae973e7d0b73f4d1df3408e96af6f616890225d5b4463e48afc2d1f1
|
||||
"@module-federation/error-codes": "npm:0.9.1"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
checksum: 10/6f9edbe23013395d7896fc2a24cb4055bc78df5a335f090e079df951835c1cf91c567228f19879eee3fddb0b34128abd0b50feaca1cf3fb2828c7b9bacc22169
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12219,13 +12252,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/runtime-tools@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/runtime-tools@npm:0.9.0"
|
||||
"@module-federation/runtime-tools@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/runtime-tools@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/runtime": "npm:0.9.0"
|
||||
"@module-federation/webpack-bundler-runtime": "npm:0.9.0"
|
||||
checksum: 10/c92c4614745ded2bbc43edcadb6d661fc392efdc8b0dc72e1f8e34763d3dd326f4b986d4db7ae8e911d6422e5f2cc212d052bd57067e9c4d52a7a572cbdf914c
|
||||
"@module-federation/runtime": "npm:0.9.1"
|
||||
"@module-federation/webpack-bundler-runtime": "npm:0.9.1"
|
||||
checksum: 10/9436e814a4ab72839b8aed1aa097f32cf7d4d49728dd863c9ce6dc4fb149fe49da6dc9cdeb97814f0023cc91489c9aca06cbfdf9fb4e43d20498ab6ee78c95dd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12240,14 +12273,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/runtime@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/runtime@npm:0.9.0"
|
||||
"@module-federation/runtime@npm:0.9.1, @module-federation/runtime@npm:^0.9.0":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/runtime@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/error-codes": "npm:0.9.0"
|
||||
"@module-federation/runtime-core": "npm:0.6.21"
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
checksum: 10/4bed92de413925fa66ffac9f176a72a83b47266830b9a7fe6bbc2e3eea226110455a43d737b1476b314c2a7b38564debe7e1dd6d066a0c6724f8c3be07b2eeef
|
||||
"@module-federation/error-codes": "npm:0.9.1"
|
||||
"@module-federation/runtime-core": "npm:0.9.1"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
checksum: 10/71eb1c3e81b307ebfe06c43ce70bfa56b217e9dfbed27f0b4235d3d0d05cc0fe2eb1dc57fffb3260ed9e0239257ef117ae13924c642b5ff97d9c65bdf48206fe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12258,23 +12291,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/sdk@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/sdk@npm:0.9.0"
|
||||
dependencies:
|
||||
isomorphic-rslog: "npm:0.0.7"
|
||||
checksum: 10/031db27afc3ee552dd052f12c8b3d6d967b6590768ebd9b5052c37047453261b9d3bb68e325bdf018f9db88e0a7276bdfeaf82b5cef332f4dd1d5e4a91add0f4
|
||||
"@module-federation/sdk@npm:0.9.1, @module-federation/sdk@npm:^0.9.0":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/sdk@npm:0.9.1"
|
||||
checksum: 10/ea0320feff328a05405e65503d1df28e46a9ad17ef99b77f1428db5c89efbadd2a76ec82d99a54ac1d21286cee6d9ddbba881a9c46748dfe8abdb11e9afef7da
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/third-party-dts-extractor@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/third-party-dts-extractor@npm:0.9.0"
|
||||
"@module-federation/third-party-dts-extractor@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/third-party-dts-extractor@npm:0.9.1"
|
||||
dependencies:
|
||||
find-pkg: "npm:2.0.0"
|
||||
fs-extra: "npm:9.1.0"
|
||||
resolve: "npm:1.22.8"
|
||||
checksum: 10/accece159cded982499f239b8e7cc7410249ee91b28584652aebb7cc13d07b540939fa9af7ac2609ca20c4b7d2bd113d7120ad535722a3178c404bf19b8ed048
|
||||
checksum: 10/68c70c79573cd927212d95879aa7b35490519d0ec9f58dd264c9d61e22fea8d452e45a52a94155128d8378e5cdcaecb229707b5b0f4e4c0d53ae96e2fbac366a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12288,13 +12319,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@module-federation/webpack-bundler-runtime@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@module-federation/webpack-bundler-runtime@npm:0.9.0"
|
||||
"@module-federation/webpack-bundler-runtime@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@module-federation/webpack-bundler-runtime@npm:0.9.1"
|
||||
dependencies:
|
||||
"@module-federation/runtime": "npm:0.9.0"
|
||||
"@module-federation/sdk": "npm:0.9.0"
|
||||
checksum: 10/8199fa4b5b088e717ac6cd43bd4d2038099d3660568cf79b9e376a395906c40dfe02095f1e1c2c083e3195326a7648dc05ea495701c91eee1fac0e64246cf783
|
||||
"@module-federation/runtime": "npm:0.9.1"
|
||||
"@module-federation/sdk": "npm:0.9.1"
|
||||
checksum: 10/430cac0a770b3c46bc195088eb4c1892e1e29a69238dbe72423d64b2b67050afeca2b5026b1a30659b4fe8d9faa038ff97cceba7e2ddf6193b93763f270d9df6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -33563,13 +33594,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isomorphic-rslog@npm:0.0.7":
|
||||
version: 0.0.7
|
||||
resolution: "isomorphic-rslog@npm:0.0.7"
|
||||
checksum: 10/cb6f03a64bdbb3972c22429a4fcdff25505ceca2bf09ca2cebc1c8086b2580f67d4dd04daba79d7dd642af099deea1eb884667ddf183bd3192b67faaf86438cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isomorphic-textencoder@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "isomorphic-textencoder@npm:1.0.1"
|
||||
|
||||
Reference in New Issue
Block a user