@backstage/frontend-app-api 0.4.0-next.3 → 0.4.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,46 @@
1
1
  # @backstage/frontend-app-api
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e539735: Updated core extension structure to make space for the sign-in page by adding `core.router`.
8
+ - 44735df: Removed `featureLoader` from `createApp`, `features` instead accepts both `FrontendFeature` and `CreateAppFeatureLoader`
9
+ - af7bc3e: Switched all core extensions to instead use the namespace `'app'`.
10
+ - ea06590: The app no longer provides the `AppContext` from `@backstage/core-plugin-api`. Components that require this context to be available should use the `compatWrapper` helper from `@backstage/core-compat-api`.
11
+
12
+ ### Patch Changes
13
+
14
+ - 5eb6b8a: Added the nav logo extension for customization of sidebar logo
15
+ - aeb8008: Add support for translation extensions.
16
+ - 1f12fb7: Create a core components extension that allows adopters to override core app components such as `Progress`, `BootErrorPage`, `NotFoundErrorPage` and `ErrorBoundaryFallback`.
17
+ - a379243: Leverage the new `FrontendFeature` type to simplify interfaces
18
+ - 60d6eb5: Removed `@backstage/plugin-graphiql` dependency.
19
+ - b7adf24: Use the new plugin type for error boundary components.
20
+ - 5970928: Collect and register feature flags from plugins and extension overrides.
21
+ - 9ad4039: Bringing over apis from core-plugin-api
22
+ - 8f5d6c1: Updates to match the new extension input wrapping.
23
+ - c35036b: A `configLoader` passed to `createApp` now returns an object, to make room for future expansion
24
+ - f27ee7d: Migrate analytics route tracker component.
25
+ - b8cb780: Added `createSpecializedApp`, which is a synchronous version of `createApp` where config and features already need to be loaded.
26
+ - c36e0b9: Renamed `AppRouteBinder` to `CreateAppRouteBinder`
27
+ - cb4197a: Forward ` node`` instead of `extensionId` to resolved extension inputs.
28
+ - 8837a96: Updates to match the introduction of `ExtensionDefinition` and new extension ID naming patterns.
29
+ - a5a0473: Updates to provide `node` to extension factories instead of `id` and `source`.
30
+ - 5cdf2b3: Updated usage of `Extension` and `ExtensionDefinition` as they are now opaque.
31
+ - f9ef632: Updates to match the new `coreExtensionData` structure.
32
+ - f1183b7: Renamed the `component` option of `createComponentExtension` to `loader`.
33
+ - Updated dependencies
34
+ - @backstage/core-plugin-api@1.8.1
35
+ - @backstage/frontend-plugin-api@0.4.0
36
+ - @backstage/core-components@0.13.9
37
+ - @backstage/theme@0.5.0
38
+ - @backstage/core-app-api@1.11.2
39
+ - @backstage/config@1.1.1
40
+ - @backstage/errors@1.2.3
41
+ - @backstage/types@1.1.1
42
+ - @backstage/version-bridge@1.0.7
43
+
3
44
  ## 0.4.0-next.3
4
45
 
5
46
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ExternalRouteRef, RouteRef, SubRouteRef, ExtensionDataRef, BackstagePlugin, ExtensionOverrides } from '@backstage/frontend-plugin-api';
1
+ import { ExternalRouteRef, RouteRef, SubRouteRef, ExtensionDataRef, FrontendFeature } from '@backstage/frontend-plugin-api';
2
2
  import { JSX } from 'react';
3
3
  import { Config } from '@backstage/config';
4
4
  import { ConfigApi } from '@backstage/core-plugin-api';
@@ -37,7 +37,7 @@ type TargetRouteMap<ExternalRoutes extends {
37
37
  *
38
38
  * @public
39
39
  */
40
- type AppRouteBinder = <TExternalRoutes extends {
40
+ type CreateAppRouteBinder = <TExternalRoutes extends {
41
41
  [name: string]: ExternalRouteRef;
42
42
  }>(externalRoutes: TExternalRoutes, targetRoutes: PartialKeys<TargetRouteMap<TExternalRoutes>, KeysWithType<TExternalRoutes, ExternalRouteRef<any, true>>>) => void;
43
43
 
@@ -57,32 +57,51 @@ interface ExtensionTree {
57
57
  declare function createExtensionTree(options: {
58
58
  config: Config;
59
59
  }): ExtensionTree;
60
+ /**
61
+ * A source of dynamically loaded frontend features.
62
+ *
63
+ * @public
64
+ */
65
+ interface CreateAppFeatureLoader {
66
+ /**
67
+ * Returns name of this loader. suitable for showing to users.
68
+ */
69
+ getLoaderName(): string;
70
+ /**
71
+ * Loads a number of features dynamically.
72
+ */
73
+ load(options: {
74
+ config: ConfigApi;
75
+ }): Promise<{
76
+ features: FrontendFeature[];
77
+ }>;
78
+ }
60
79
  /** @public */
61
80
  declare function createApp(options?: {
62
- features?: (BackstagePlugin | ExtensionOverrides)[];
63
- configLoader?: () => Promise<ConfigApi>;
81
+ features?: (FrontendFeature | CreateAppFeatureLoader)[];
82
+ configLoader?: () => Promise<{
83
+ config: ConfigApi;
84
+ }>;
64
85
  bindRoutes?(context: {
65
- bind: AppRouteBinder;
86
+ bind: CreateAppRouteBinder;
66
87
  }): void;
67
- featureLoader?: (ctx: {
68
- config: ConfigApi;
69
- }) => Promise<(BackstagePlugin | ExtensionOverrides)[]>;
70
88
  }): {
71
89
  createRoot(): JSX.Element;
72
90
  };
73
91
  /**
74
92
  * Synchronous version of {@link createApp}, expecting all features and
75
93
  * config to have been loaded already.
94
+ *
76
95
  * @public
77
96
  */
78
97
  declare function createSpecializedApp(options?: {
79
- features?: (BackstagePlugin | ExtensionOverrides)[];
98
+ features?: FrontendFeature[];
80
99
  config?: ConfigApi;
81
100
  bindRoutes?(context: {
82
- bind: AppRouteBinder;
101
+ bind: CreateAppRouteBinder;
83
102
  }): void;
84
103
  }): {
85
104
  createRoot(): JSX.Element;
86
105
  };
87
106
 
88
- export { AppRouteBinder, ExtensionTree, ExtensionTreeNode, createApp, createExtensionTree, createSpecializedApp };
107
+ export { CreateAppFeatureLoader, CreateAppRouteBinder, ExtensionTree, ExtensionTreeNode, createApp, createExtensionTree, createSpecializedApp };
package/dist/index.esm.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import React, { useMemo, useState, useEffect, createContext, useContext } from 'react';
2
2
  import { ConfigReader } from '@backstage/config';
3
- import { createExtension, createExtensionInput, coreExtensionData, createTranslationExtension, useComponentRef, coreComponentRefs, useRouteRef, createThemeExtension, createComponentExtension, AnalyticsContext, useAnalytics, appTreeApiRef, componentsApiRef } from '@backstage/frontend-plugin-api';
3
+ import { createExtension, createExtensionInput, createApiExtension, createThemeExtension, createComponentExtension, createTranslationExtension, coreExtensionData, useComponentRef, coreComponentRefs, createNavItemExtension, createNavLogoExtension, useRouteRef, AnalyticsContext, useAnalytics, createSignInPageExtension, appTreeApiRef, componentsApiRef } from '@backstage/frontend-plugin-api';
4
4
  import { useRoutes, BrowserRouter, useInRouterContext, MemoryRouter, matchRoutes, generatePath, useLocation, Route } from 'react-router-dom';
5
5
  import { SidebarPage, sidebarConfig, Sidebar, SidebarDivider, useSidebarOpenState, Link, SidebarItem, Progress, ErrorPage, ErrorPanel } from '@backstage/core-components';
6
6
  import { makeStyles, Button as Button$1 } from '@material-ui/core';
7
- import { useApi, appThemeApiRef, FeatureFlagState, createApiFactory, discoveryApiRef, configApiRef, alertApiRef, analyticsApiRef, errorApiRef, storageApiRef, fetchApiRef, identityApiRef, oauthRequestApiRef, googleAuthApiRef, microsoftAuthApiRef, githubAuthApiRef, oktaAuthApiRef, gitlabAuthApiRef, oneloginAuthApiRef, bitbucketAuthApiRef, bitbucketServerAuthApiRef, atlassianAuthApiRef, createApiRef, attachComponentData, featureFlagsApiRef } from '@backstage/core-plugin-api';
7
+ import { useApi, appThemeApiRef, FeatureFlagState, createApiFactory, discoveryApiRef, configApiRef, alertApiRef, analyticsApiRef, errorApiRef, storageApiRef, fetchApiRef, identityApiRef, oauthRequestApiRef, googleAuthApiRef, microsoftAuthApiRef, githubAuthApiRef, oktaAuthApiRef, gitlabAuthApiRef, oneloginAuthApiRef, bitbucketAuthApiRef, bitbucketServerAuthApiRef, atlassianAuthApiRef, attachComponentData, featureFlagsApiRef } from '@backstage/core-plugin-api';
8
8
  import { UrlPatternDiscovery, AlertApiForwarder, NoOpAnalyticsApi, ErrorAlerter, ErrorApiForwarder, UnhandledErrorForwarder, WebStorage, createFetchApi, FetchMiddlewares, OAuthRequestManager, GoogleAuth, MicrosoftAuth, GithubAuth, OktaAuth, GitlabAuth, OneLoginAuth, BitbucketAuth, BitbucketServerAuth, AtlassianAuth, ApiFactoryRegistry, AppThemeSelector, ApiResolver, ApiProvider } from '@backstage/core-app-api';
9
9
  import useObservable from 'react-use/lib/useObservable';
10
10
  import ObservableImpl from 'zen-observable';
@@ -38,20 +38,21 @@ import LightIcon from '@material-ui/icons/WbSunny';
38
38
  import { getOrCreateGlobalSingleton, createVersionedContext, createVersionedValueMap } from '@backstage/version-bridge';
39
39
  import { appLanguageApiRef, translationApiRef } from '@backstage/core-plugin-api/alpha';
40
40
  import mapValues from 'lodash/mapValues';
41
+ import { stringifyError } from '@backstage/errors';
41
42
 
42
43
  const Core = createExtension({
43
- namespace: "core",
44
+ namespace: "app",
44
45
  attachTo: { id: "root", input: "default" },
45
46
  // ignored
46
47
  inputs: {
47
48
  apis: createExtensionInput({
48
- api: coreExtensionData.apiFactory
49
+ api: createApiExtension.factoryDataRef
49
50
  }),
50
51
  themes: createExtensionInput({
51
- theme: coreExtensionData.theme
52
+ theme: createThemeExtension.themeDataRef
52
53
  }),
53
54
  components: createExtensionInput({
54
- component: coreExtensionData.component
55
+ component: createComponentExtension.componentDataRef
55
56
  }),
56
57
  translations: createExtensionInput({
57
58
  translation: createTranslationExtension.translationDataRef
@@ -74,9 +75,9 @@ const Core = createExtension({
74
75
  });
75
76
 
76
77
  const CoreRoutes = createExtension({
77
- namespace: "core",
78
+ namespace: "app",
78
79
  name: "routes",
79
- attachTo: { id: "core/layout", input: "content" },
80
+ attachTo: { id: "app/layout", input: "content" },
80
81
  inputs: {
81
82
  routes: createExtensionInput({
82
83
  path: coreExtensionData.routePath,
@@ -111,9 +112,9 @@ const CoreRoutes = createExtension({
111
112
  });
112
113
 
113
114
  const CoreLayout = createExtension({
114
- namespace: "core",
115
+ namespace: "app",
115
116
  name: "layout",
116
- attachTo: { id: "core/router", input: "children" },
117
+ attachTo: { id: "app/router", input: "children" },
117
118
  inputs: {
118
119
  nav: createExtensionInput(
119
120
  {
@@ -220,16 +221,16 @@ const SidebarNavItem = (props) => {
220
221
  return /* @__PURE__ */ React.createElement(SidebarItem, { to, icon: Icon, text: title });
221
222
  };
222
223
  const CoreNav = createExtension({
223
- namespace: "core",
224
+ namespace: "app",
224
225
  name: "nav",
225
- attachTo: { id: "core/layout", input: "nav" },
226
+ attachTo: { id: "app/layout", input: "nav" },
226
227
  inputs: {
227
228
  items: createExtensionInput({
228
- target: coreExtensionData.navTarget
229
+ target: createNavItemExtension.targetDataRef
229
230
  }),
230
231
  logos: createExtensionInput(
231
232
  {
232
- elements: coreExtensionData.logoElements
233
+ elements: createNavLogoExtension.logoElementsDataRef
233
234
  },
234
235
  {
235
236
  singleton: true,
@@ -1065,10 +1066,40 @@ registerDefaults_fn = function(internalRef) {
1065
1066
  };
1066
1067
  let I18nextTranslationApi = _I18nextTranslationApi;
1067
1068
 
1069
+ function toInternalExtensionDefinition(overrides) {
1070
+ const internal = overrides;
1071
+ if (internal.$$type !== "@backstage/ExtensionDefinition") {
1072
+ throw new Error(
1073
+ `Invalid extension definition instance, bad type '${internal.$$type}'`
1074
+ );
1075
+ }
1076
+ if (internal.version !== "v1") {
1077
+ throw new Error(
1078
+ `Invalid extension definition instance, bad version '${internal.version}'`
1079
+ );
1080
+ }
1081
+ return internal;
1082
+ }
1083
+
1084
+ function toInternalExtension(overrides) {
1085
+ const internal = overrides;
1086
+ if (internal.$$type !== "@backstage/Extension") {
1087
+ throw new Error(
1088
+ `Invalid extension instance, bad type '${internal.$$type}'`
1089
+ );
1090
+ }
1091
+ if (internal.version !== "v1") {
1092
+ throw new Error(
1093
+ `Invalid extension instance, bad version '${internal.version}'`
1094
+ );
1095
+ }
1096
+ return internal;
1097
+ }
1068
1098
  function resolveExtensionDefinition(definition, context) {
1069
1099
  var _a;
1070
- const { name, kind, namespace: _, ...rest } = definition;
1071
- const namespace = (_a = definition.namespace) != null ? _a : context == null ? void 0 : context.namespace;
1100
+ const internalDefinition = toInternalExtensionDefinition(definition);
1101
+ const { name, kind, namespace: _, ...rest } = internalDefinition;
1102
+ const namespace = (_a = internalDefinition.namespace) != null ? _a : context == null ? void 0 : context.namespace;
1072
1103
  const namePart = name && namespace ? `${namespace}/${name}` : namespace || name;
1073
1104
  if (!namePart) {
1074
1105
  throw new Error(
@@ -1077,8 +1108,9 @@ function resolveExtensionDefinition(definition, context) {
1077
1108
  }
1078
1109
  return {
1079
1110
  ...rest,
1080
- id: kind ? `${kind}:${namePart}` : namePart,
1081
- $$type: "@backstage/Extension"
1111
+ $$type: "@backstage/Extension",
1112
+ version: "v1",
1113
+ id: kind ? `${kind}:${namePart}` : namePart
1082
1114
  };
1083
1115
  }
1084
1116
 
@@ -1988,40 +2020,47 @@ function resolveAppNodeSpecs(options) {
1988
2020
  );
1989
2021
  }
1990
2022
  const configuredExtensions = [
1991
- ...pluginExtensions.map(({ source, ...extension }) => ({
1992
- extension,
1993
- params: {
1994
- source,
1995
- attachTo: extension.attachTo,
1996
- disabled: extension.disabled,
1997
- config: void 0
1998
- }
1999
- })),
2000
- ...builtinExtensions.map((extension) => ({
2001
- extension,
2002
- params: {
2003
- source: void 0,
2004
- attachTo: extension.attachTo,
2005
- disabled: extension.disabled,
2006
- config: void 0
2007
- }
2008
- }))
2023
+ ...pluginExtensions.map(({ source, ...extension }) => {
2024
+ const internalExtension = toInternalExtension(extension);
2025
+ return {
2026
+ extension: internalExtension,
2027
+ params: {
2028
+ source,
2029
+ attachTo: internalExtension.attachTo,
2030
+ disabled: internalExtension.disabled,
2031
+ config: void 0
2032
+ }
2033
+ };
2034
+ }),
2035
+ ...builtinExtensions.map((extension) => {
2036
+ const internalExtension = toInternalExtension(extension);
2037
+ return {
2038
+ extension: internalExtension,
2039
+ params: {
2040
+ source: void 0,
2041
+ attachTo: internalExtension.attachTo,
2042
+ disabled: internalExtension.disabled,
2043
+ config: void 0
2044
+ }
2045
+ };
2046
+ })
2009
2047
  ];
2010
2048
  for (const extension of overrideExtensions) {
2049
+ const internalExtension = toInternalExtension(extension);
2011
2050
  const index = configuredExtensions.findIndex(
2012
2051
  (e) => e.extension.id === extension.id
2013
2052
  );
2014
2053
  if (index !== -1) {
2015
- configuredExtensions[index].extension = extension;
2016
- configuredExtensions[index].params.attachTo = extension.attachTo;
2017
- configuredExtensions[index].params.disabled = extension.disabled;
2054
+ configuredExtensions[index].extension = internalExtension;
2055
+ configuredExtensions[index].params.attachTo = internalExtension.attachTo;
2056
+ configuredExtensions[index].params.disabled = internalExtension.disabled;
2018
2057
  } else {
2019
2058
  configuredExtensions.push({
2020
- extension,
2059
+ extension: internalExtension,
2021
2060
  params: {
2022
2061
  source: void 0,
2023
- attachTo: extension.attachTo,
2024
- disabled: extension.disabled,
2062
+ attachTo: internalExtension.attachTo,
2063
+ disabled: internalExtension.disabled,
2025
2064
  config: void 0
2026
2065
  }
2027
2066
  });
@@ -2163,13 +2202,14 @@ function createAppNodeInstance(options) {
2163
2202
  );
2164
2203
  }
2165
2204
  try {
2166
- const namedOutputs = extension.factory({
2205
+ const internalExtension = toInternalExtension(extension);
2206
+ const namedOutputs = internalExtension.factory({
2167
2207
  node,
2168
2208
  config: parsedConfig,
2169
- inputs: resolveInputs(extension.inputs, attachments)
2209
+ inputs: resolveInputs(internalExtension.inputs, attachments)
2170
2210
  });
2171
2211
  for (const [name, output] of Object.entries(namedOutputs)) {
2172
- const ref = extension.output[name];
2212
+ const ref = internalExtension.output[name];
2173
2213
  if (!ref) {
2174
2214
  throw new Error(`unknown output provided via '${name}'`);
2175
2215
  }
@@ -2227,12 +2267,12 @@ function instantiateAppNodeTree(rootNode) {
2227
2267
 
2228
2268
  function createAppTree(options) {
2229
2269
  const tree = resolveAppTree(
2230
- "core",
2270
+ "app",
2231
2271
  resolveAppNodeSpecs({
2232
2272
  features: options.features,
2233
2273
  builtinExtensions: options.builtinExtensions,
2234
2274
  parameters: readAppExtensionsConfig(options.config),
2235
- forbidden: /* @__PURE__ */ new Set(["core"])
2275
+ forbidden: /* @__PURE__ */ new Set(["app"])
2236
2276
  })
2237
2277
  );
2238
2278
  instantiateAppNodeTree(tree.root);
@@ -2241,15 +2281,15 @@ function createAppTree(options) {
2241
2281
 
2242
2282
  const DefaultProgressComponent = createComponentExtension({
2243
2283
  ref: coreComponentRefs.progress,
2244
- component: { sync: () => components.Progress }
2284
+ loader: { sync: () => components.Progress }
2245
2285
  });
2246
2286
  const DefaultNotFoundErrorPageComponent = createComponentExtension({
2247
2287
  ref: coreComponentRefs.notFoundErrorPage,
2248
- component: { sync: () => components.NotFoundErrorPage }
2288
+ loader: { sync: () => components.NotFoundErrorPage }
2249
2289
  });
2250
2290
  const DefaultErrorBoundaryComponent = createComponentExtension({
2251
2291
  ref: coreComponentRefs.errorBoundaryFallback,
2252
- component: {
2292
+ loader: {
2253
2293
  sync: () => (props) => {
2254
2294
  const { plugin, error, resetError } = props;
2255
2295
  const title = `Error in ${plugin == null ? void 0 : plugin.id}`;
@@ -2260,38 +2300,6 @@ const DefaultErrorBoundaryComponent = createComponentExtension({
2260
2300
 
2261
2301
  const InternalAppContext = createContext(void 0);
2262
2302
 
2263
- getOrCreateGlobalSingleton(
2264
- "core-plugin-api:analytics-tracker-events",
2265
- () => ({
2266
- mostRecentGatheredNavigation: void 0,
2267
- mostRecentRoutableExtensionRender: void 0,
2268
- beforeUnloadRegistered: false
2269
- })
2270
- );
2271
-
2272
- createApiRef({ id: "core.app-tree" });
2273
-
2274
- createApiRef({
2275
- id: "core.components"
2276
- });
2277
-
2278
- createApiRef({
2279
- id: "core.analytics"
2280
- });
2281
-
2282
- function createExtensionDataRef(id) {
2283
- return {
2284
- id,
2285
- $$type: "@backstage/ExtensionDataRef",
2286
- config: {},
2287
- optional() {
2288
- return { ...this, config: { ...this.config, optional: true } };
2289
- }
2290
- };
2291
- }
2292
-
2293
- const signInPageComponentDataRef = createExtensionDataRef("core.signInPage");
2294
-
2295
2303
  const getExtensionContext = (pathname, routes) => {
2296
2304
  var _a, _b;
2297
2305
  try {
@@ -2360,13 +2368,13 @@ const RouteTracker = ({
2360
2368
  };
2361
2369
 
2362
2370
  const CoreRouter = createExtension({
2363
- namespace: "core",
2371
+ namespace: "app",
2364
2372
  name: "router",
2365
- attachTo: { id: "core", input: "root" },
2373
+ attachTo: { id: "app", input: "root" },
2366
2374
  inputs: {
2367
2375
  signInPage: createExtensionInput(
2368
2376
  {
2369
- component: signInPageComponentDataRef
2377
+ component: createSignInPageExtension.componentDataRef
2370
2378
  },
2371
2379
  { singleton: true, optional: true }
2372
2380
  ),
@@ -2492,6 +2500,7 @@ class DefaultComponentsApi {
2492
2500
  }
2493
2501
  _components = new WeakMap();
2494
2502
 
2503
+ const DefaultApis = apis.map((factory) => createApiExtension({ factory }));
2495
2504
  const builtinExtensions = [
2496
2505
  Core,
2497
2506
  CoreRouter,
@@ -2502,7 +2511,8 @@ const builtinExtensions = [
2502
2511
  DefaultErrorBoundaryComponent,
2503
2512
  DefaultNotFoundErrorPageComponent,
2504
2513
  LightTheme,
2505
- DarkTheme
2514
+ DarkTheme,
2515
+ ...DefaultApis
2506
2516
  ].map((def) => resolveExtensionDefinition(def));
2507
2517
  function createExtensionTree(options) {
2508
2518
  const features = getAvailableFeatures(options.config);
@@ -2529,7 +2539,7 @@ function createExtensionTree(options) {
2529
2539
  return (_c = (_b = (_a = tree.nodes.get(id)) == null ? void 0 : _a.edges.attachments.get(inputName)) == null ? void 0 : _b.map(convertNode).filter((node) => Boolean(node))) != null ? _c : [];
2530
2540
  },
2531
2541
  getRootRoutes() {
2532
- return this.getExtensionAttachments("core/routes", "routes").map((node) => {
2542
+ return this.getExtensionAttachments("app/routes", "routes").map((node) => {
2533
2543
  const path = node.getData(coreExtensionData.routePath);
2534
2544
  const element = node.getData(coreExtensionData.reactElement);
2535
2545
  const routeRef = node.getData(coreExtensionData.routeRef);
@@ -2548,8 +2558,8 @@ function createExtensionTree(options) {
2548
2558
  const location = useRouteRef(props.routeRef);
2549
2559
  return /* @__PURE__ */ React.createElement(SidebarItem, { icon: props.icon, to: location(), text: props.title });
2550
2560
  };
2551
- return this.getExtensionAttachments("core/nav", "items").map((node, index) => {
2552
- const target = node.getData(coreExtensionData.navTarget);
2561
+ return this.getExtensionAttachments("app/nav", "items").map((node, index) => {
2562
+ const target = node.getData(createNavItemExtension.targetDataRef);
2553
2563
  if (!target) {
2554
2564
  return null;
2555
2565
  }
@@ -2582,19 +2592,31 @@ function deduplicateFeatures(allFeatures) {
2582
2592
  }
2583
2593
  function createApp(options) {
2584
2594
  async function appLoader() {
2585
- var _a, _b, _c, _d, _e;
2586
- const config = (_b = await ((_a = options == null ? void 0 : options.configLoader) == null ? void 0 : _a.call(options))) != null ? _b : ConfigReader.fromConfigs(
2595
+ var _a, _b, _c;
2596
+ const config = (_b = await ((_a = options == null ? void 0 : options.configLoader) == null ? void 0 : _a.call(options).then((c) => c.config))) != null ? _b : ConfigReader.fromConfigs(
2587
2597
  overrideBaseUrlConfigs(defaultConfigLoaderSync())
2588
2598
  );
2589
2599
  const discoveredFeatures = getAvailableFeatures(config);
2590
- const loadedFeatures = (_d = await ((_c = options == null ? void 0 : options.featureLoader) == null ? void 0 : _c.call(options, { config }))) != null ? _d : [];
2600
+ const providedFeatures = [];
2601
+ for (const entry of (_c = options == null ? void 0 : options.features) != null ? _c : []) {
2602
+ if ("load" in entry) {
2603
+ try {
2604
+ const result = await entry.load({ config });
2605
+ providedFeatures.push(...result.features);
2606
+ } catch (e) {
2607
+ throw new Error(
2608
+ `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError(
2609
+ e
2610
+ )}`
2611
+ );
2612
+ }
2613
+ } else {
2614
+ providedFeatures.push(entry);
2615
+ }
2616
+ }
2591
2617
  const app = createSpecializedApp({
2592
2618
  config,
2593
- features: [
2594
- ...discoveredFeatures,
2595
- ...loadedFeatures,
2596
- ...(_e = options == null ? void 0 : options.features) != null ? _e : []
2597
- ],
2619
+ features: [...discoveredFeatures, ...providedFeatures],
2598
2620
  bindRoutes: options == null ? void 0 : options.bindRoutes
2599
2621
  }).createRoot();
2600
2622
  return { default: () => app };
@@ -2662,11 +2684,11 @@ function createApiHolder(tree, configApi, appIdentityProxy) {
2662
2684
  const factoryRegistry = new ApiFactoryRegistry();
2663
2685
  const pluginApis = (_b = (_a = tree.root.edges.attachments.get("apis")) == null ? void 0 : _a.map((e) => {
2664
2686
  var _a2;
2665
- return (_a2 = e.instance) == null ? void 0 : _a2.getData(coreExtensionData.apiFactory);
2687
+ return (_a2 = e.instance) == null ? void 0 : _a2.getData(createApiExtension.factoryDataRef);
2666
2688
  }).filter((x) => !!x)) != null ? _b : [];
2667
2689
  const themeExtensions = (_d = (_c = tree.root.edges.attachments.get("themes")) == null ? void 0 : _c.map((e) => {
2668
2690
  var _a2;
2669
- return (_a2 = e.instance) == null ? void 0 : _a2.getData(coreExtensionData.theme);
2691
+ return (_a2 = e.instance) == null ? void 0 : _a2.getData(createThemeExtension.themeDataRef);
2670
2692
  }).filter((x) => !!x)) != null ? _d : [];
2671
2693
  const translationResources = (_f = (_e = tree.root.edges.attachments.get("translations")) == null ? void 0 : _e.map(
2672
2694
  (e) => {
@@ -2676,7 +2698,7 @@ function createApiHolder(tree, configApi, appIdentityProxy) {
2676
2698
  ).filter(
2677
2699
  (x) => !!x
2678
2700
  )) != null ? _f : [];
2679
- for (const factory of [...apis, ...pluginApis]) {
2701
+ for (const factory of pluginApis) {
2680
2702
  factoryRegistry.register("default", factory);
2681
2703
  }
2682
2704
  factoryRegistry.register("default", {
@@ -2698,7 +2720,7 @@ function createApiHolder(tree, configApi, appIdentityProxy) {
2698
2720
  });
2699
2721
  const componentsExtensions = (_h = (_g = tree.root.edges.attachments.get("components")) == null ? void 0 : _g.map((e) => {
2700
2722
  var _a2;
2701
- return (_a2 = e.instance) == null ? void 0 : _a2.getData(coreExtensionData.component);
2723
+ return (_a2 = e.instance) == null ? void 0 : _a2.getData(createComponentExtension.componentDataRef);
2702
2724
  }).filter((x) => !!x)) != null ? _h : [];
2703
2725
  const componentsMap = componentsExtensions.reduce(
2704
2726
  (components, component) => component ? components.set(component.ref, component == null ? void 0 : component.impl) : components,
@@ -2738,13 +2760,6 @@ function createApiHolder(tree, configApi, appIdentityProxy) {
2738
2760
  resources: translationResources
2739
2761
  })
2740
2762
  });
2741
- for (const factory of apis) {
2742
- if (!factoryRegistry.register("app", factory)) {
2743
- throw new Error(
2744
- `Duplicate or forbidden API factory for ${factory.api} in app`
2745
- );
2746
- }
2747
- }
2748
2763
  ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis());
2749
2764
  return new ApiResolver(factoryRegistry);
2750
2765
  }