@backstage/frontend-defaults 0.2.0-next.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @backstage/frontend-defaults
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4823831: Introduced a `createFrontendFeatureLoader()` function, as well as a `FrontendFeatureLoader` interface, to gather several frontend plugins, modules or feature loaders in a single exported entrypoint and load them, possibly asynchronously. This new feature, very similar to the `createBackendFeatureLoader()` already available on the backend, supersedes the previous `CreateAppFeatureLoader` type which has been deprecated.
8
+ - 8250ffe: **BREAKING**: Dropped support for the removed opaque `@backstage/ExtensionOverrides` and `@backstage/BackstagePlugin` types.
9
+
10
+ ### Patch Changes
11
+
12
+ - 4d18b55: It's now possible to provide a middleware that wraps all extension factories by passing an `extensionFactoryMiddleware` to either `createApp()` or `createSpecializedApp()`.
13
+ - abcdf44: Internal refactor to match updated `createSpecializedApp`.
14
+ - e3f19db: Feature discovery and resolution logic used in `createApp` is now exposed via the `discoverAvailableFeatures` and `resolveAsyncFeatures` functions respectively.
15
+ - Updated dependencies
16
+ - @backstage/frontend-app-api@0.11.0
17
+ - @backstage/frontend-plugin-api@0.10.0
18
+ - @backstage/plugin-app@0.1.7
19
+ - @backstage/config@1.3.2
20
+ - @backstage/errors@1.2.7
21
+
3
22
  ## 0.2.0-next.2
4
23
 
5
24
  ### Minor Changes
@@ -17,14 +17,14 @@ function createApp(options) {
17
17
  const config = await options?.configLoader?.().then((c) => c.config) ?? ConfigReader.fromConfigs(
18
18
  overrideBaseUrlConfigs(defaultConfigLoaderSync())
19
19
  );
20
- const { features: discoveredFeatures } = discoverAvailableFeatures(config);
21
- const { features: providedFeatures } = await resolveAsyncFeatures({
20
+ const { features: discoveredFeaturesAndLoaders } = discoverAvailableFeatures(config);
21
+ const { features: loadedFeatures } = await resolveAsyncFeatures({
22
22
  config,
23
- features: options?.features
23
+ features: [...discoveredFeaturesAndLoaders, ...options?.features ?? []]
24
24
  });
25
25
  const app = createSpecializedApp({
26
26
  config,
27
- features: [appPlugin, ...discoveredFeatures, ...providedFeatures],
27
+ features: [appPlugin, ...loadedFeatures],
28
28
  bindRoutes: options?.bindRoutes,
29
29
  extensionFactoryMiddleware: options?.extensionFactoryMiddleware
30
30
  });
@@ -1 +1 @@
1
- {"version":3,"file":"createApp.esm.js","sources":["../src/createApp.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { JSX, ReactNode } from 'react';\nimport {\n ConfigApi,\n coreExtensionData,\n ExtensionFactoryMiddleware,\n} from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseUrlConfigs';\nimport { ConfigReader } from '@backstage/config';\nimport {\n CreateAppRouteBinder,\n FrontendFeature,\n createSpecializedApp,\n} from '@backstage/frontend-app-api';\nimport appPlugin from '@backstage/plugin-app';\nimport { discoverAvailableFeatures } from './discovery';\nimport { resolveAsyncFeatures } from './resolution';\n\n/**\n * A source of dynamically loaded frontend features.\n *\n * @public\n */\nexport interface CreateAppFeatureLoader {\n /**\n * Returns name of this loader. suitable for showing to users.\n */\n getLoaderName(): string;\n\n /**\n * Loads a number of features dynamically.\n */\n load(options: { config: ConfigApi }): Promise<{\n features: FrontendFeature[];\n }>;\n}\n\n/**\n * Options for {@link createApp}.\n *\n * @public\n */\nexport interface CreateAppOptions {\n features?: (FrontendFeature | CreateAppFeatureLoader)[];\n configLoader?: () => Promise<{ config: ConfigApi }>;\n bindRoutes?(context: { bind: CreateAppRouteBinder }): void;\n /**\n * The component to render while loading the app (waiting for config, features, etc)\n *\n * Is the text \"Loading...\" by default.\n * If set to \"null\" then no loading fallback component is rendered. *\n */\n loadingComponent?: ReactNode;\n extensionFactoryMiddleware?:\n | ExtensionFactoryMiddleware\n | ExtensionFactoryMiddleware[];\n}\n\n/**\n * Creates a new Backstage frontend app instance. See https://backstage.io/docs/frontend-system/building-apps/index\n *\n * @public\n */\nexport function createApp(options?: CreateAppOptions): {\n createRoot(): JSX.Element;\n} {\n let suspenseFallback = options?.loadingComponent;\n if (suspenseFallback === undefined) {\n suspenseFallback = 'Loading...';\n }\n\n async function appLoader() {\n const config =\n (await options?.configLoader?.().then(c => c.config)) ??\n ConfigReader.fromConfigs(\n overrideBaseUrlConfigs(defaultConfigLoaderSync()),\n );\n\n const { features: discoveredFeatures } = discoverAvailableFeatures(config);\n const { features: providedFeatures } = await resolveAsyncFeatures({\n config,\n features: options?.features,\n });\n\n const app = createSpecializedApp({\n config,\n features: [appPlugin, ...discoveredFeatures, ...providedFeatures],\n bindRoutes: options?.bindRoutes,\n extensionFactoryMiddleware: options?.extensionFactoryMiddleware,\n });\n\n const rootEl = app.tree.root.instance!.getData(\n coreExtensionData.reactElement,\n );\n\n return { default: () => rootEl };\n }\n\n return {\n createRoot() {\n const LazyApp = React.lazy(appLoader);\n return (\n <React.Suspense fallback={suspenseFallback}>\n <LazyApp />\n </React.Suspense>\n );\n },\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;AAiFO,SAAS,UAAU,OAExB,EAAA;AACA,EAAA,IAAI,mBAAmB,OAAS,EAAA,gBAAA;AAChC,EAAA,IAAI,qBAAqB,KAAW,CAAA,EAAA;AAClC,IAAmB,gBAAA,GAAA,YAAA;AAAA;AAGrB,EAAA,eAAe,SAAY,GAAA;AACzB,IAAM,MAAA,MAAA,GACH,MAAM,OAAA,EAAS,YAAe,IAAA,CAAE,KAAK,CAAK,CAAA,KAAA,CAAA,CAAE,MAAM,CAAA,IACnD,YAAa,CAAA,WAAA;AAAA,MACX,sBAAA,CAAuB,yBAAyB;AAAA,KAClD;AAEF,IAAA,MAAM,EAAE,QAAA,EAAU,kBAAmB,EAAA,GAAI,0BAA0B,MAAM,CAAA;AACzE,IAAA,MAAM,EAAE,QAAA,EAAU,gBAAiB,EAAA,GAAI,MAAM,oBAAqB,CAAA;AAAA,MAChE,MAAA;AAAA,MACA,UAAU,OAAS,EAAA;AAAA,KACpB,CAAA;AAED,IAAA,MAAM,MAAM,oBAAqB,CAAA;AAAA,MAC/B,MAAA;AAAA,MACA,UAAU,CAAC,SAAA,EAAW,GAAG,kBAAA,EAAoB,GAAG,gBAAgB,CAAA;AAAA,MAChE,YAAY,OAAS,EAAA,UAAA;AAAA,MACrB,4BAA4B,OAAS,EAAA;AAAA,KACtC,CAAA;AAED,IAAA,MAAM,MAAS,GAAA,GAAA,CAAI,IAAK,CAAA,IAAA,CAAK,QAAU,CAAA,OAAA;AAAA,MACrC,iBAAkB,CAAA;AAAA,KACpB;AAEA,IAAO,OAAA,EAAE,OAAS,EAAA,MAAM,MAAO,EAAA;AAAA;AAGjC,EAAO,OAAA;AAAA,IACL,UAAa,GAAA;AACX,MAAM,MAAA,OAAA,GAAU,KAAM,CAAA,IAAA,CAAK,SAAS,CAAA;AACpC,MACE,uBAAA,KAAA,CAAA,aAAA,CAAC,MAAM,QAAN,EAAA,EAAe,UAAU,gBACxB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,aAAQ,CACX,CAAA;AAAA;AAEJ,GACF;AACF;;;;"}
1
+ {"version":3,"file":"createApp.esm.js","sources":["../src/createApp.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { JSX, ReactNode } from 'react';\nimport {\n ConfigApi,\n coreExtensionData,\n ExtensionFactoryMiddleware,\n FrontendFeature,\n FrontendFeatureLoader,\n} from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseUrlConfigs';\nimport { ConfigReader } from '@backstage/config';\nimport {\n CreateAppRouteBinder,\n createSpecializedApp,\n} from '@backstage/frontend-app-api';\nimport appPlugin from '@backstage/plugin-app';\nimport { discoverAvailableFeatures } from './discovery';\nimport { resolveAsyncFeatures } from './resolution';\n\n/**\n * A source of dynamically loaded frontend features.\n *\n * @public\n * @deprecated Use the {@link @backstage/frontend-plugin-api#createFrontendFeatureLoader} function instead.\n */\nexport interface CreateAppFeatureLoader {\n /**\n * Returns name of this loader. suitable for showing to users.\n */\n getLoaderName(): string;\n\n /**\n * Loads a number of features dynamically.\n */\n load(options: { config: ConfigApi }): Promise<{\n features: FrontendFeature[];\n }>;\n}\n\n/**\n * Options for {@link createApp}.\n *\n * @public\n */\nexport interface CreateAppOptions {\n features?: (\n | FrontendFeature\n | FrontendFeatureLoader\n | CreateAppFeatureLoader\n )[];\n configLoader?: () => Promise<{ config: ConfigApi }>;\n bindRoutes?(context: { bind: CreateAppRouteBinder }): void;\n /**\n * The component to render while loading the app (waiting for config, features, etc)\n *\n * Is the text \"Loading...\" by default.\n * If set to \"null\" then no loading fallback component is rendered. *\n */\n loadingComponent?: ReactNode;\n extensionFactoryMiddleware?:\n | ExtensionFactoryMiddleware\n | ExtensionFactoryMiddleware[];\n}\n\n/**\n * Creates a new Backstage frontend app instance. See https://backstage.io/docs/frontend-system/building-apps/index\n *\n * @public\n */\nexport function createApp(options?: CreateAppOptions): {\n createRoot(): JSX.Element;\n} {\n let suspenseFallback = options?.loadingComponent;\n if (suspenseFallback === undefined) {\n suspenseFallback = 'Loading...';\n }\n\n async function appLoader() {\n const config =\n (await options?.configLoader?.().then(c => c.config)) ??\n ConfigReader.fromConfigs(\n overrideBaseUrlConfigs(defaultConfigLoaderSync()),\n );\n\n const { features: discoveredFeaturesAndLoaders } =\n discoverAvailableFeatures(config);\n const { features: loadedFeatures } = await resolveAsyncFeatures({\n config,\n features: [...discoveredFeaturesAndLoaders, ...(options?.features ?? [])],\n });\n\n const app = createSpecializedApp({\n config,\n features: [appPlugin, ...loadedFeatures],\n bindRoutes: options?.bindRoutes,\n extensionFactoryMiddleware: options?.extensionFactoryMiddleware,\n });\n\n const rootEl = app.tree.root.instance!.getData(\n coreExtensionData.reactElement,\n );\n\n return { default: () => rootEl };\n }\n\n return {\n createRoot() {\n const LazyApp = React.lazy(appLoader);\n return (\n <React.Suspense fallback={suspenseFallback}>\n <LazyApp />\n </React.Suspense>\n );\n },\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;AAuFO,SAAS,UAAU,OAExB,EAAA;AACA,EAAA,IAAI,mBAAmB,OAAS,EAAA,gBAAA;AAChC,EAAA,IAAI,qBAAqB,KAAW,CAAA,EAAA;AAClC,IAAmB,gBAAA,GAAA,YAAA;AAAA;AAGrB,EAAA,eAAe,SAAY,GAAA;AACzB,IAAM,MAAA,MAAA,GACH,MAAM,OAAA,EAAS,YAAe,IAAA,CAAE,KAAK,CAAK,CAAA,KAAA,CAAA,CAAE,MAAM,CAAA,IACnD,YAAa,CAAA,WAAA;AAAA,MACX,sBAAA,CAAuB,yBAAyB;AAAA,KAClD;AAEF,IAAA,MAAM,EAAE,QAAA,EAAU,4BAA6B,EAAA,GAC7C,0BAA0B,MAAM,CAAA;AAClC,IAAA,MAAM,EAAE,QAAA,EAAU,cAAe,EAAA,GAAI,MAAM,oBAAqB,CAAA;AAAA,MAC9D,MAAA;AAAA,MACA,QAAA,EAAU,CAAC,GAAG,4BAAA,EAA8B,GAAI,OAAS,EAAA,QAAA,IAAY,EAAG;AAAA,KACzE,CAAA;AAED,IAAA,MAAM,MAAM,oBAAqB,CAAA;AAAA,MAC/B,MAAA;AAAA,MACA,QAAU,EAAA,CAAC,SAAW,EAAA,GAAG,cAAc,CAAA;AAAA,MACvC,YAAY,OAAS,EAAA,UAAA;AAAA,MACrB,4BAA4B,OAAS,EAAA;AAAA,KACtC,CAAA;AAED,IAAA,MAAM,MAAS,GAAA,GAAA,CAAI,IAAK,CAAA,IAAA,CAAK,QAAU,CAAA,OAAA;AAAA,MACrC,iBAAkB,CAAA;AAAA,KACpB;AAEA,IAAO,OAAA,EAAE,OAAS,EAAA,MAAM,MAAO,EAAA;AAAA;AAGjC,EAAO,OAAA;AAAA,IACL,UAAa,GAAA;AACX,MAAM,MAAA,OAAA,GAAU,KAAM,CAAA,IAAA,CAAK,SAAS,CAAA;AACpC,MACE,uBAAA,KAAA,CAAA,aAAA,CAAC,MAAM,QAAN,EAAA,EAAe,UAAU,gBACxB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,aAAQ,CACX,CAAA;AAAA;AAEJ,GACF;AACF;;;;"}
@@ -1,4 +1,5 @@
1
1
  import { ConfigReader } from '@backstage/config';
2
+ import { isBackstageFeatureLoader } from './resolution.esm.js';
2
3
 
3
4
  function readPackageDetectionConfig(config) {
4
5
  const packages = config.getOptional("app.experimental.packages");
@@ -42,7 +43,7 @@ function discoverAvailableFeatures(config) {
42
43
  return false;
43
44
  }
44
45
  return true;
45
- }).map((m) => m.default).filter(isBackstageFeature) ?? []
46
+ }).map((m) => m.default).filter(isFeatureOrLoader) ?? []
46
47
  };
47
48
  }
48
49
  function isBackstageFeature(obj) {
@@ -51,6 +52,9 @@ function isBackstageFeature(obj) {
51
52
  }
52
53
  return false;
53
54
  }
55
+ function isFeatureOrLoader(obj) {
56
+ return isBackstageFeature(obj) || isBackstageFeatureLoader(obj);
57
+ }
54
58
 
55
59
  export { discoverAvailableFeatures };
56
60
  //# sourceMappingURL=discovery.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.esm.js","sources":["../src/discovery.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config, ConfigReader } from '@backstage/config';\nimport { FrontendFeature } from '@backstage/frontend-app-api';\n\ninterface DiscoveryGlobal {\n modules: Array<{ name: string; export?: string; default: unknown }>;\n}\n\nfunction readPackageDetectionConfig(config: Config) {\n const packages = config.getOptional('app.experimental.packages');\n if (packages === undefined || packages === null) {\n return undefined;\n }\n\n if (typeof packages === 'string') {\n if (packages !== 'all') {\n throw new Error(\n `Invalid app.experimental.packages mode, got '${packages}', expected 'all'`,\n );\n }\n return {};\n }\n\n if (typeof packages !== 'object' || Array.isArray(packages)) {\n throw new Error(\n \"Invalid config at 'app.experimental.packages', expected object\",\n );\n }\n const packagesConfig = new ConfigReader(\n packages,\n 'app.experimental.packages',\n );\n\n return {\n include: packagesConfig.getOptionalStringArray('include'),\n exclude: packagesConfig.getOptionalStringArray('exclude'),\n };\n}\n\n/**\n * @public\n */\nexport function discoverAvailableFeatures(config: Config): {\n features: FrontendFeature[];\n} {\n const discovered = (\n window as { '__@backstage/discovered__'?: DiscoveryGlobal }\n )['__@backstage/discovered__'];\n\n const detection = readPackageDetectionConfig(config);\n if (!detection) {\n return { features: [] };\n }\n\n return {\n features:\n discovered?.modules\n .filter(({ name }) => {\n if (detection.exclude?.includes(name)) {\n return false;\n }\n if (detection.include && !detection.include.includes(name)) {\n return false;\n }\n return true;\n })\n .map(m => m.default)\n .filter(isBackstageFeature) ?? [],\n };\n}\n\nfunction isBackstageFeature(obj: unknown): obj is FrontendFeature {\n if (obj !== null && typeof obj === 'object' && '$$type' in obj) {\n return (\n obj.$$type === '@backstage/FrontendPlugin' ||\n obj.$$type === '@backstage/FrontendModule'\n );\n }\n return false;\n}\n"],"names":[],"mappings":";;AAuBA,SAAS,2BAA2B,MAAgB,EAAA;AAClD,EAAM,MAAA,QAAA,GAAW,MAAO,CAAA,WAAA,CAAY,2BAA2B,CAAA;AAC/D,EAAI,IAAA,QAAA,KAAa,KAAa,CAAA,IAAA,QAAA,KAAa,IAAM,EAAA;AAC/C,IAAO,OAAA,KAAA,CAAA;AAAA;AAGT,EAAI,IAAA,OAAO,aAAa,QAAU,EAAA;AAChC,IAAA,IAAI,aAAa,KAAO,EAAA;AACtB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,gDAAgD,QAAQ,CAAA,iBAAA;AAAA,OAC1D;AAAA;AAEF,IAAA,OAAO,EAAC;AAAA;AAGV,EAAA,IAAI,OAAO,QAAa,KAAA,QAAA,IAAY,KAAM,CAAA,OAAA,CAAQ,QAAQ,CAAG,EAAA;AAC3D,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA;AAEF,EAAA,MAAM,iBAAiB,IAAI,YAAA;AAAA,IACzB,QAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAO,OAAA;AAAA,IACL,OAAA,EAAS,cAAe,CAAA,sBAAA,CAAuB,SAAS,CAAA;AAAA,IACxD,OAAA,EAAS,cAAe,CAAA,sBAAA,CAAuB,SAAS;AAAA,GAC1D;AACF;AAKO,SAAS,0BAA0B,MAExC,EAAA;AACA,EAAM,MAAA,UAAA,GACJ,OACA,2BAA2B,CAAA;AAE7B,EAAM,MAAA,SAAA,GAAY,2BAA2B,MAAM,CAAA;AACnD,EAAA,IAAI,CAAC,SAAW,EAAA;AACd,IAAO,OAAA,EAAE,QAAU,EAAA,EAAG,EAAA;AAAA;AAGxB,EAAO,OAAA;AAAA,IACL,UACE,UAAY,EAAA,OAAA,CACT,OAAO,CAAC,EAAE,MAAW,KAAA;AACpB,MAAA,IAAI,SAAU,CAAA,OAAA,EAAS,QAAS,CAAA,IAAI,CAAG,EAAA;AACrC,QAAO,OAAA,KAAA;AAAA;AAET,MAAA,IAAI,UAAU,OAAW,IAAA,CAAC,UAAU,OAAQ,CAAA,QAAA,CAAS,IAAI,CAAG,EAAA;AAC1D,QAAO,OAAA,KAAA;AAAA;AAET,MAAO,OAAA,IAAA;AAAA,KACR,CACA,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,CAClB,CAAA,MAAA,CAAO,kBAAkB,CAAA,IAAK;AAAC,GACtC;AACF;AAEA,SAAS,mBAAmB,GAAsC,EAAA;AAChE,EAAA,IAAI,QAAQ,IAAQ,IAAA,OAAO,GAAQ,KAAA,QAAA,IAAY,YAAY,GAAK,EAAA;AAC9D,IAAA,OACE,GAAI,CAAA,MAAA,KAAW,2BACf,IAAA,GAAA,CAAI,MAAW,KAAA,2BAAA;AAAA;AAGnB,EAAO,OAAA,KAAA;AACT;;;;"}
1
+ {"version":3,"file":"discovery.esm.js","sources":["../src/discovery.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config, ConfigReader } from '@backstage/config';\nimport {\n FrontendFeature,\n FrontendFeatureLoader,\n} from '@backstage/frontend-plugin-api';\nimport { isBackstageFeatureLoader } from './resolution';\n\ninterface DiscoveryGlobal {\n modules: Array<{ name: string; export?: string; default: unknown }>;\n}\n\nfunction readPackageDetectionConfig(config: Config) {\n const packages = config.getOptional('app.experimental.packages');\n if (packages === undefined || packages === null) {\n return undefined;\n }\n\n if (typeof packages === 'string') {\n if (packages !== 'all') {\n throw new Error(\n `Invalid app.experimental.packages mode, got '${packages}', expected 'all'`,\n );\n }\n return {};\n }\n\n if (typeof packages !== 'object' || Array.isArray(packages)) {\n throw new Error(\n \"Invalid config at 'app.experimental.packages', expected object\",\n );\n }\n const packagesConfig = new ConfigReader(\n packages,\n 'app.experimental.packages',\n );\n\n return {\n include: packagesConfig.getOptionalStringArray('include'),\n exclude: packagesConfig.getOptionalStringArray('exclude'),\n };\n}\n\n/**\n * @public\n */\nexport function discoverAvailableFeatures(config: Config): {\n features: (FrontendFeature | FrontendFeatureLoader)[];\n} {\n const discovered = (\n window as { '__@backstage/discovered__'?: DiscoveryGlobal }\n )['__@backstage/discovered__'];\n\n const detection = readPackageDetectionConfig(config);\n if (!detection) {\n return { features: [] };\n }\n\n return {\n features:\n discovered?.modules\n .filter(({ name }) => {\n if (detection.exclude?.includes(name)) {\n return false;\n }\n if (detection.include && !detection.include.includes(name)) {\n return false;\n }\n return true;\n })\n .map(m => m.default)\n .filter(isFeatureOrLoader) ?? [],\n };\n}\n\nfunction isBackstageFeature(obj: unknown): obj is FrontendFeature {\n if (obj !== null && typeof obj === 'object' && '$$type' in obj) {\n return (\n obj.$$type === '@backstage/FrontendPlugin' ||\n obj.$$type === '@backstage/FrontendModule'\n );\n }\n return false;\n}\n\nfunction isFeatureOrLoader(\n obj: unknown,\n): obj is FrontendFeature | FrontendFeatureLoader {\n return isBackstageFeature(obj) || isBackstageFeatureLoader(obj);\n}\n"],"names":[],"mappings":";;;AA2BA,SAAS,2BAA2B,MAAgB,EAAA;AAClD,EAAM,MAAA,QAAA,GAAW,MAAO,CAAA,WAAA,CAAY,2BAA2B,CAAA;AAC/D,EAAI,IAAA,QAAA,KAAa,KAAa,CAAA,IAAA,QAAA,KAAa,IAAM,EAAA;AAC/C,IAAO,OAAA,KAAA,CAAA;AAAA;AAGT,EAAI,IAAA,OAAO,aAAa,QAAU,EAAA;AAChC,IAAA,IAAI,aAAa,KAAO,EAAA;AACtB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,gDAAgD,QAAQ,CAAA,iBAAA;AAAA,OAC1D;AAAA;AAEF,IAAA,OAAO,EAAC;AAAA;AAGV,EAAA,IAAI,OAAO,QAAa,KAAA,QAAA,IAAY,KAAM,CAAA,OAAA,CAAQ,QAAQ,CAAG,EAAA;AAC3D,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA;AAEF,EAAA,MAAM,iBAAiB,IAAI,YAAA;AAAA,IACzB,QAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAO,OAAA;AAAA,IACL,OAAA,EAAS,cAAe,CAAA,sBAAA,CAAuB,SAAS,CAAA;AAAA,IACxD,OAAA,EAAS,cAAe,CAAA,sBAAA,CAAuB,SAAS;AAAA,GAC1D;AACF;AAKO,SAAS,0BAA0B,MAExC,EAAA;AACA,EAAM,MAAA,UAAA,GACJ,OACA,2BAA2B,CAAA;AAE7B,EAAM,MAAA,SAAA,GAAY,2BAA2B,MAAM,CAAA;AACnD,EAAA,IAAI,CAAC,SAAW,EAAA;AACd,IAAO,OAAA,EAAE,QAAU,EAAA,EAAG,EAAA;AAAA;AAGxB,EAAO,OAAA;AAAA,IACL,UACE,UAAY,EAAA,OAAA,CACT,OAAO,CAAC,EAAE,MAAW,KAAA;AACpB,MAAA,IAAI,SAAU,CAAA,OAAA,EAAS,QAAS,CAAA,IAAI,CAAG,EAAA;AACrC,QAAO,OAAA,KAAA;AAAA;AAET,MAAA,IAAI,UAAU,OAAW,IAAA,CAAC,UAAU,OAAQ,CAAA,QAAA,CAAS,IAAI,CAAG,EAAA;AAC1D,QAAO,OAAA,KAAA;AAAA;AAET,MAAO,OAAA,IAAA;AAAA,KACR,CACA,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,CAClB,CAAA,MAAA,CAAO,iBAAiB,CAAA,IAAK;AAAC,GACrC;AACF;AAEA,SAAS,mBAAmB,GAAsC,EAAA;AAChE,EAAA,IAAI,QAAQ,IAAQ,IAAA,OAAO,GAAQ,KAAA,QAAA,IAAY,YAAY,GAAK,EAAA;AAC9D,IAAA,OACE,GAAI,CAAA,MAAA,KAAW,2BACf,IAAA,GAAA,CAAI,MAAW,KAAA,2BAAA;AAAA;AAGnB,EAAO,OAAA,KAAA;AACT;AAEA,SAAS,kBACP,GACgD,EAAA;AAChD,EAAA,OAAO,kBAAmB,CAAA,GAAG,CAAK,IAAA,wBAAA,CAAyB,GAAG,CAAA;AAChE;;;;"}
@@ -0,0 +1,22 @@
1
+ function isInternalFrontendFeatureLoader(opaque) {
2
+ if (opaque.$$type === "@backstage/FrontendFeatureLoader") {
3
+ toInternalFrontendFeatureLoader(opaque);
4
+ return true;
5
+ }
6
+ return false;
7
+ }
8
+ function toInternalFrontendFeatureLoader(plugin) {
9
+ const internal = plugin;
10
+ if (internal.$$type !== "@backstage/FrontendFeatureLoader") {
11
+ throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`);
12
+ }
13
+ if (internal.version !== "v1") {
14
+ throw new Error(
15
+ `Invalid plugin instance, bad version '${internal.version}'`
16
+ );
17
+ }
18
+ return internal;
19
+ }
20
+
21
+ export { isInternalFrontendFeatureLoader, toInternalFrontendFeatureLoader };
22
+ //# sourceMappingURL=createFrontendFeatureLoader.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createFrontendFeatureLoader.esm.js","sources":["../../../../../frontend-plugin-api/src/wiring/createFrontendFeatureLoader.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConfigApi } from '../apis/definitions';\nimport { describeParentCallSite } from '../routing/describeParentCallSite';\nimport { FrontendFeature } from './types';\n\n/** @public */\nexport interface CreateFrontendFeatureLoaderOptions {\n loader(deps: {\n config: ConfigApi;\n }):\n | Iterable<\n | FrontendFeature\n | FrontendFeatureLoader\n | Promise<{ default: FrontendFeature | FrontendFeatureLoader }>\n >\n | Promise<\n Iterable<\n | FrontendFeature\n | FrontendFeatureLoader\n | Promise<{ default: FrontendFeature | FrontendFeatureLoader }>\n >\n >\n | AsyncIterable<\n | FrontendFeature\n | FrontendFeatureLoader\n | { default: FrontendFeature | FrontendFeatureLoader }\n >;\n}\n\n/** @public */\nexport interface FrontendFeatureLoader {\n readonly $$type: '@backstage/FrontendFeatureLoader';\n}\n\n/** @internal */\nexport interface InternalFrontendFeatureLoader extends FrontendFeatureLoader {\n readonly version: 'v1';\n readonly description: string;\n readonly loader: (deps: {\n config: ConfigApi;\n }) => Promise<(FrontendFeature | FrontendFeatureLoader)[]>;\n}\n\n/** @public */\nexport function createFrontendFeatureLoader(\n options: CreateFrontendFeatureLoaderOptions,\n): FrontendFeatureLoader {\n const description = `created at '${describeParentCallSite()}'`;\n return {\n $$type: '@backstage/FrontendFeatureLoader',\n version: 'v1',\n description,\n toString() {\n return `FeatureLoader{description=${description}}`;\n },\n async loader(deps: {\n config: ConfigApi;\n }): Promise<(FrontendFeature | FrontendFeatureLoader)[]> {\n const it = await options.loader(deps);\n const result = new Array<FrontendFeature | FrontendFeatureLoader>();\n for await (const item of it) {\n if (isFeatureOrLoader(item)) {\n result.push(item);\n } else if ('default' in item) {\n result.push(item.default);\n } else {\n throw new Error(`Invalid item \"${item}\"`);\n }\n }\n return result;\n },\n } as InternalFrontendFeatureLoader;\n}\n\n/** @internal */\nexport function isInternalFrontendFeatureLoader(opaque: {\n $$type: string;\n}): opaque is InternalFrontendFeatureLoader {\n if (opaque.$$type === '@backstage/FrontendFeatureLoader') {\n // Make sure we throw if invalid\n toInternalFrontendFeatureLoader(opaque as FrontendFeatureLoader);\n return true;\n }\n return false;\n}\n\n/** @internal */\nexport function toInternalFrontendFeatureLoader(\n plugin: FrontendFeatureLoader,\n): InternalFrontendFeatureLoader {\n const internal = plugin as InternalFrontendFeatureLoader;\n if (internal.$$type !== '@backstage/FrontendFeatureLoader') {\n throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`);\n }\n if (internal.version !== 'v1') {\n throw new Error(\n `Invalid plugin instance, bad version '${internal.version}'`,\n );\n }\n return internal;\n}\n\nfunction isFeatureOrLoader(\n obj: unknown,\n): obj is FrontendFeature | FrontendFeatureLoader {\n if (obj !== null && typeof obj === 'object' && '$$type' in obj) {\n return (\n obj.$$type === '@backstage/FrontendPlugin' ||\n obj.$$type === '@backstage/FrontendModule' ||\n obj.$$type === '@backstage/FrontendFeatureLoader'\n );\n }\n return false;\n}\n"],"names":[],"mappings":"AA0FO,SAAS,gCAAgC,MAEJ,EAAA;AAC1C,EAAI,IAAA,MAAA,CAAO,WAAW,kCAAoC,EAAA;AAExD,IAAA,+BAAA,CAAgC,MAA+B,CAAA;AAC/D,IAAO,OAAA,IAAA;AAAA;AAET,EAAO,OAAA,KAAA;AACT;AAGO,SAAS,gCACd,MAC+B,EAAA;AAC/B,EAAA,MAAM,QAAW,GAAA,MAAA;AACjB,EAAI,IAAA,QAAA,CAAS,WAAW,kCAAoC,EAAA;AAC1D,IAAA,MAAM,IAAI,KAAA,CAAM,CAAsC,mCAAA,EAAA,QAAA,CAAS,MAAM,CAAG,CAAA,CAAA,CAAA;AAAA;AAE1E,EAAI,IAAA,QAAA,CAAS,YAAY,IAAM,EAAA;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,SAAS,OAAO,CAAA,CAAA;AAAA,KAC3D;AAAA;AAEF,EAAO,OAAA,QAAA;AACT;;;;"}
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import React, { ReactNode, JSX } from 'react';
2
- import { ConfigApi, ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api';
3
- import { FrontendFeature, CreateAppRouteBinder } from '@backstage/frontend-app-api';
2
+ import { ConfigApi, FrontendFeature, FrontendFeatureLoader, ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api';
3
+ import { CreateAppRouteBinder } from '@backstage/frontend-app-api';
4
4
  import { Config } from '@backstage/config';
5
5
 
6
6
  /**
7
7
  * A source of dynamically loaded frontend features.
8
8
  *
9
9
  * @public
10
+ * @deprecated Use the {@link @backstage/frontend-plugin-api#createFrontendFeatureLoader} function instead.
10
11
  */
11
12
  interface CreateAppFeatureLoader {
12
13
  /**
@@ -28,7 +29,7 @@ interface CreateAppFeatureLoader {
28
29
  * @public
29
30
  */
30
31
  interface CreateAppOptions {
31
- features?: (FrontendFeature | CreateAppFeatureLoader)[];
32
+ features?: (FrontendFeature | FrontendFeatureLoader | CreateAppFeatureLoader)[];
32
33
  configLoader?: () => Promise<{
33
34
  config: ConfigApi;
34
35
  }>;
@@ -81,13 +82,13 @@ declare function createPublicSignInApp(options?: CreateAppOptions): {
81
82
  * @public
82
83
  */
83
84
  declare function discoverAvailableFeatures(config: Config): {
84
- features: FrontendFeature[];
85
+ features: (FrontendFeature | FrontendFeatureLoader)[];
85
86
  };
86
87
 
87
88
  /** @public */
88
89
  declare function resolveAsyncFeatures(options: {
89
90
  config: Config;
90
- features?: (FrontendFeature | CreateAppFeatureLoader)[];
91
+ features?: (FrontendFeature | FrontendFeatureLoader | CreateAppFeatureLoader)[];
91
92
  }): Promise<{
92
93
  features: FrontendFeature[];
93
94
  }>;
@@ -1,25 +1,64 @@
1
1
  import { stringifyError } from '@backstage/errors';
2
+ import { isInternalFrontendFeatureLoader } from './frontend-plugin-api/src/wiring/createFrontendFeatureLoader.esm.js';
2
3
 
3
4
  async function resolveAsyncFeatures(options) {
4
5
  const features = [];
5
- for (const entry of options.features ?? []) {
6
- if ("load" in entry) {
6
+ for (const item of options?.features ?? []) {
7
+ if ("load" in item) {
7
8
  try {
8
- const result = await entry.load({ config: options.config });
9
+ const result = await item.load({ config: options.config });
9
10
  features.push(...result.features);
10
11
  } catch (e) {
11
12
  throw new Error(
12
- `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError(
13
+ `Failed to read frontend features from loader '${item.getLoaderName()}', ${stringifyError(
13
14
  e
14
15
  )}`
15
16
  );
16
17
  }
17
18
  } else {
18
- features.push(entry);
19
+ features.push(item);
19
20
  }
20
21
  }
21
- return { features };
22
+ const loadedFeatures = [];
23
+ const alreadyMetFeatureLoaders = [];
24
+ const maxRecursionDepth = 5;
25
+ async function applyFeatureLoaders(featuresOrLoaders, recursionDepth) {
26
+ if (featuresOrLoaders.length === 0) {
27
+ return;
28
+ }
29
+ for (const featureOrLoader of featuresOrLoaders) {
30
+ if (isBackstageFeatureLoader(featureOrLoader)) {
31
+ if (alreadyMetFeatureLoaders.some((l) => l === featureOrLoader)) {
32
+ continue;
33
+ }
34
+ if (isInternalFrontendFeatureLoader(featureOrLoader)) {
35
+ if (recursionDepth > maxRecursionDepth) {
36
+ throw new Error(
37
+ `Maximum feature loading recursion depth (${maxRecursionDepth}) reached for the feature loader ${featureOrLoader.description}`
38
+ );
39
+ }
40
+ alreadyMetFeatureLoaders.push(featureOrLoader);
41
+ let result;
42
+ try {
43
+ result = await featureOrLoader.loader({ config: options.config });
44
+ } catch (e) {
45
+ throw new Error(
46
+ `Failed to read frontend features from loader ${featureOrLoader.description}: ${stringifyError(e)}`
47
+ );
48
+ }
49
+ await applyFeatureLoaders(result, recursionDepth + 1);
50
+ }
51
+ } else {
52
+ loadedFeatures.push(featureOrLoader);
53
+ }
54
+ }
55
+ }
56
+ await applyFeatureLoaders(features, 1);
57
+ return { features: loadedFeatures };
58
+ }
59
+ function isBackstageFeatureLoader(obj) {
60
+ return obj !== null && typeof obj === "object" && "$$type" in obj && obj.$$type === "@backstage/FrontendFeatureLoader";
22
61
  }
23
62
 
24
- export { resolveAsyncFeatures };
63
+ export { isBackstageFeatureLoader, resolveAsyncFeatures };
25
64
  //# sourceMappingURL=resolution.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolution.esm.js","sources":["../src/resolution.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { stringifyError } from '@backstage/errors';\nimport { FrontendFeature } from '@backstage/frontend-app-api';\nimport { CreateAppFeatureLoader } from './createApp';\n\n/** @public */\nexport async function resolveAsyncFeatures(options: {\n config: Config;\n features?: (FrontendFeature | CreateAppFeatureLoader)[];\n}): Promise<{ features: FrontendFeature[] }> {\n const features = [];\n for (const entry of options.features ?? []) {\n if ('load' in entry) {\n try {\n const result = await entry.load({ config: options.config });\n features.push(...result.features);\n } catch (e) {\n throw new Error(\n `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError(\n e,\n )}`,\n );\n }\n } else {\n features.push(entry);\n }\n }\n return { features };\n}\n"],"names":[],"mappings":";;AAsBA,eAAsB,qBAAqB,OAGE,EAAA;AAC3C,EAAA,MAAM,WAAW,EAAC;AAClB,EAAA,KAAA,MAAW,KAAS,IAAA,OAAA,CAAQ,QAAY,IAAA,EAAI,EAAA;AAC1C,IAAA,IAAI,UAAU,KAAO,EAAA;AACnB,MAAI,IAAA;AACF,QAAM,MAAA,MAAA,GAAS,MAAM,KAAM,CAAA,IAAA,CAAK,EAAE,MAAQ,EAAA,OAAA,CAAQ,QAAQ,CAAA;AAC1D,QAAS,QAAA,CAAA,IAAA,CAAK,GAAG,MAAA,CAAO,QAAQ,CAAA;AAAA,eACzB,CAAG,EAAA;AACV,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAiD,8CAAA,EAAA,KAAA,CAAM,aAAc,EAAC,CAAM,GAAA,EAAA,cAAA;AAAA,YAC1E;AAAA,WACD,CAAA;AAAA,SACH;AAAA;AACF,KACK,MAAA;AACL,MAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AAAA;AACrB;AAEF,EAAA,OAAO,EAAE,QAAS,EAAA;AACpB;;;;"}
1
+ {"version":3,"file":"resolution.esm.js","sources":["../src/resolution.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { stringifyError } from '@backstage/errors';\nimport {\n FrontendFeature,\n FrontendFeatureLoader,\n} from '@backstage/frontend-plugin-api';\nimport { CreateAppFeatureLoader } from './createApp';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isInternalFrontendFeatureLoader } from '../../frontend-plugin-api/src/wiring/createFrontendFeatureLoader';\n\n/** @public */\nexport async function resolveAsyncFeatures(options: {\n config: Config;\n features?: (\n | FrontendFeature\n | FrontendFeatureLoader\n | CreateAppFeatureLoader\n )[];\n}): Promise<{ features: FrontendFeature[] }> {\n const features: (FrontendFeature | FrontendFeatureLoader)[] = [];\n\n // Separate deprecated CreateAppFeatureLoader elements from the frontend features,\n // and manage the deprecated elements first.\n for (const item of options?.features ?? []) {\n if ('load' in item) {\n try {\n const result = await item.load({ config: options.config });\n features.push(...result.features);\n } catch (e) {\n throw new Error(\n `Failed to read frontend features from loader '${item.getLoaderName()}', ${stringifyError(\n e,\n )}`,\n );\n }\n } else {\n features.push(item);\n }\n }\n\n const loadedFeatures: FrontendFeature[] = [];\n const alreadyMetFeatureLoaders: FrontendFeatureLoader[] = [];\n const maxRecursionDepth = 5;\n\n async function applyFeatureLoaders(\n featuresOrLoaders: (FrontendFeature | FrontendFeatureLoader)[],\n recursionDepth: number,\n ) {\n if (featuresOrLoaders.length === 0) {\n return;\n }\n\n for (const featureOrLoader of featuresOrLoaders) {\n if (isBackstageFeatureLoader(featureOrLoader)) {\n if (alreadyMetFeatureLoaders.some(l => l === featureOrLoader)) {\n continue;\n }\n if (isInternalFrontendFeatureLoader(featureOrLoader)) {\n if (recursionDepth > maxRecursionDepth) {\n throw new Error(\n `Maximum feature loading recursion depth (${maxRecursionDepth}) reached for the feature loader ${featureOrLoader.description}`,\n );\n }\n alreadyMetFeatureLoaders.push(featureOrLoader);\n let result: (FrontendFeature | FrontendFeatureLoader)[];\n try {\n result = await featureOrLoader.loader({ config: options.config });\n } catch (e) {\n throw new Error(\n `Failed to read frontend features from loader ${\n featureOrLoader.description\n }: ${stringifyError(e)}`,\n );\n }\n await applyFeatureLoaders(result, recursionDepth + 1);\n }\n } else {\n loadedFeatures.push(featureOrLoader);\n }\n }\n }\n\n await applyFeatureLoaders(features, 1);\n\n return { features: loadedFeatures };\n}\n\nexport function isBackstageFeatureLoader(\n obj: unknown,\n): obj is FrontendFeatureLoader {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n '$$type' in obj &&\n obj.$$type === '@backstage/FrontendFeatureLoader'\n );\n}\n"],"names":[],"mappings":";;;AA2BA,eAAsB,qBAAqB,OAOE,EAAA;AAC3C,EAAA,MAAM,WAAwD,EAAC;AAI/D,EAAA,KAAA,MAAW,IAAQ,IAAA,OAAA,EAAS,QAAY,IAAA,EAAI,EAAA;AAC1C,IAAA,IAAI,UAAU,IAAM,EAAA;AAClB,MAAI,IAAA;AACF,QAAM,MAAA,MAAA,GAAS,MAAM,IAAK,CAAA,IAAA,CAAK,EAAE,MAAQ,EAAA,OAAA,CAAQ,QAAQ,CAAA;AACzD,QAAS,QAAA,CAAA,IAAA,CAAK,GAAG,MAAA,CAAO,QAAQ,CAAA;AAAA,eACzB,CAAG,EAAA;AACV,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAiD,8CAAA,EAAA,IAAA,CAAK,aAAc,EAAC,CAAM,GAAA,EAAA,cAAA;AAAA,YACzE;AAAA,WACD,CAAA;AAAA,SACH;AAAA;AACF,KACK,MAAA;AACL,MAAA,QAAA,CAAS,KAAK,IAAI,CAAA;AAAA;AACpB;AAGF,EAAA,MAAM,iBAAoC,EAAC;AAC3C,EAAA,MAAM,2BAAoD,EAAC;AAC3D,EAAA,MAAM,iBAAoB,GAAA,CAAA;AAE1B,EAAe,eAAA,mBAAA,CACb,mBACA,cACA,EAAA;AACA,IAAI,IAAA,iBAAA,CAAkB,WAAW,CAAG,EAAA;AAClC,MAAA;AAAA;AAGF,IAAA,KAAA,MAAW,mBAAmB,iBAAmB,EAAA;AAC/C,MAAI,IAAA,wBAAA,CAAyB,eAAe,CAAG,EAAA;AAC7C,QAAA,IAAI,wBAAyB,CAAA,IAAA,CAAK,CAAK,CAAA,KAAA,CAAA,KAAM,eAAe,CAAG,EAAA;AAC7D,UAAA;AAAA;AAEF,QAAI,IAAA,+BAAA,CAAgC,eAAe,CAAG,EAAA;AACpD,UAAA,IAAI,iBAAiB,iBAAmB,EAAA;AACtC,YAAA,MAAM,IAAI,KAAA;AAAA,cACR,CAA4C,yCAAA,EAAA,iBAAiB,CAAoC,iCAAA,EAAA,eAAA,CAAgB,WAAW,CAAA;AAAA,aAC9H;AAAA;AAEF,UAAA,wBAAA,CAAyB,KAAK,eAAe,CAAA;AAC7C,UAAI,IAAA,MAAA;AACJ,UAAI,IAAA;AACF,YAAA,MAAA,GAAS,MAAM,eAAgB,CAAA,MAAA,CAAO,EAAE,MAAQ,EAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,mBACzD,CAAG,EAAA;AACV,YAAA,MAAM,IAAI,KAAA;AAAA,cACR,gDACE,eAAgB,CAAA,WAClB,CAAK,EAAA,EAAA,cAAA,CAAe,CAAC,CAAC,CAAA;AAAA,aACxB;AAAA;AAEF,UAAM,MAAA,mBAAA,CAAoB,MAAQ,EAAA,cAAA,GAAiB,CAAC,CAAA;AAAA;AACtD,OACK,MAAA;AACL,QAAA,cAAA,CAAe,KAAK,eAAe,CAAA;AAAA;AACrC;AACF;AAGF,EAAM,MAAA,mBAAA,CAAoB,UAAU,CAAC,CAAA;AAErC,EAAO,OAAA,EAAE,UAAU,cAAe,EAAA;AACpC;AAEO,SAAS,yBACd,GAC8B,EAAA;AAC9B,EACE,OAAA,GAAA,KAAQ,QACR,OAAO,GAAA,KAAQ,YACf,QAAY,IAAA,GAAA,IACZ,IAAI,MAAW,KAAA,kCAAA;AAEnB;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/frontend-defaults",
3
- "version": "0.2.0-next.2",
3
+ "version": "0.2.0",
4
4
  "backstage": {
5
5
  "role": "web-library"
6
6
  },
@@ -31,17 +31,17 @@
31
31
  "test": "backstage-cli package test"
32
32
  },
33
33
  "dependencies": {
34
- "@backstage/config": "1.3.2",
35
- "@backstage/errors": "1.2.7",
36
- "@backstage/frontend-app-api": "0.11.0-next.2",
37
- "@backstage/frontend-plugin-api": "0.10.0-next.2",
38
- "@backstage/plugin-app": "0.1.7-next.2",
34
+ "@backstage/config": "^1.3.2",
35
+ "@backstage/errors": "^1.2.7",
36
+ "@backstage/frontend-app-api": "^0.11.0",
37
+ "@backstage/frontend-plugin-api": "^0.10.0",
38
+ "@backstage/plugin-app": "^0.1.7",
39
39
  "@react-hookz/web": "^24.0.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@backstage/cli": "0.31.0-next.1",
43
- "@backstage/core-plugin-api": "1.10.4",
44
- "@backstage/test-utils": "1.7.6-next.0",
42
+ "@backstage/cli": "^0.31.0",
43
+ "@backstage/core-plugin-api": "^1.10.5",
44
+ "@backstage/test-utils": "^1.7.6",
45
45
  "@testing-library/jest-dom": "^6.0.0",
46
46
  "@testing-library/react": "^16.0.0",
47
47
  "@types/react": "^18.0.0",