@backstage/frontend-plugin-api 0.14.0-next.2 → 0.14.1

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/analytics/useAnalytics.esm.js +1 -0
  3. package/dist/analytics/useAnalytics.esm.js.map +1 -1
  4. package/dist/apis/definitions/IconsApi.esm.js.map +1 -1
  5. package/dist/apis/definitions/PluginHeaderActionsApi.esm.js +11 -0
  6. package/dist/apis/definitions/PluginHeaderActionsApi.esm.js.map +1 -0
  7. package/dist/blueprints/PageBlueprint.esm.js +97 -9
  8. package/dist/blueprints/PageBlueprint.esm.js.map +1 -1
  9. package/dist/blueprints/PluginHeaderActionBlueprint.esm.js +52 -0
  10. package/dist/blueprints/PluginHeaderActionBlueprint.esm.js.map +1 -0
  11. package/dist/blueprints/SubPageBlueprint.esm.js +66 -0
  12. package/dist/blueprints/SubPageBlueprint.esm.js.map +1 -0
  13. package/dist/components/ExtensionBoundary.esm.js +1 -0
  14. package/dist/components/ExtensionBoundary.esm.js.map +1 -1
  15. package/dist/components/PageLayout.esm.js +99 -0
  16. package/dist/components/PageLayout.esm.js.map +1 -0
  17. package/dist/components/createSwappableComponent.esm.js +1 -0
  18. package/dist/components/createSwappableComponent.esm.js.map +1 -1
  19. package/dist/frontend-internal/src/wiring/InternalFrontendPlugin.esm.js.map +1 -1
  20. package/dist/index.d.ts +245 -26
  21. package/dist/index.esm.js +4 -0
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/routing/useRouteRef.esm.js +1 -0
  24. package/dist/routing/useRouteRef.esm.js.map +1 -1
  25. package/dist/translation/useTranslationRef.esm.js +1 -0
  26. package/dist/translation/useTranslationRef.esm.js.map +1 -1
  27. package/dist/wiring/coreExtensionData.esm.js +2 -0
  28. package/dist/wiring/coreExtensionData.esm.js.map +1 -1
  29. package/dist/wiring/createFrontendPlugin.esm.js +7 -2
  30. package/dist/wiring/createFrontendPlugin.esm.js.map +1 -1
  31. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"InternalFrontendPlugin.esm.js","sources":["../../../../../frontend-internal/src/wiring/InternalFrontendPlugin.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Extension,\n FeatureFlagConfig,\n OverridableFrontendPlugin,\n} from '@backstage/frontend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueFrontendPlugin = OpaqueType.create<{\n public: OverridableFrontendPlugin;\n versions: {\n readonly version: 'v1';\n readonly extensions: Extension<unknown>[];\n readonly featureFlags: FeatureFlagConfig[];\n readonly infoOptions?: {\n packageJson?: () => Promise<JsonObject>;\n manifest?: () => Promise<JsonObject>;\n };\n };\n}>({\n type: '@backstage/FrontendPlugin',\n versions: ['v1'],\n});\n"],"names":[],"mappings":";;AAwBO,MAAM,oBAAA,GAAuB,WAAW,MAAA,CAW5C;AAAA,EACD,IAAA,EAAM,2BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC;;;;"}
1
+ {"version":3,"file":"InternalFrontendPlugin.esm.js","sources":["../../../../../frontend-internal/src/wiring/InternalFrontendPlugin.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Extension,\n FeatureFlagConfig,\n IconElement,\n OverridableFrontendPlugin,\n} from '@backstage/frontend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueFrontendPlugin = OpaqueType.create<{\n public: OverridableFrontendPlugin;\n versions: {\n readonly version: 'v1';\n readonly title?: string;\n readonly icon?: IconElement;\n readonly extensions: Extension<unknown>[];\n readonly featureFlags: FeatureFlagConfig[];\n readonly infoOptions?: {\n packageJson?: () => Promise<JsonObject>;\n manifest?: () => Promise<JsonObject>;\n };\n };\n}>({\n type: '@backstage/FrontendPlugin',\n versions: ['v1'],\n});\n"],"names":[],"mappings":";;AAyBO,MAAM,oBAAA,GAAuB,WAAW,MAAA,CAa5C;AAAA,EACD,IAAA,EAAM,2BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC;;;;"}
package/dist/index.d.ts CHANGED
@@ -39,6 +39,37 @@ declare const AnalyticsContext: (options: {
39
39
  children: ReactNode;
40
40
  }) => react_jsx_runtime.JSX.Element;
41
41
 
42
+ /**
43
+ * IconComponent is the common icon type used throughout Backstage when
44
+ * working with and rendering generic icons, including the app system icons.
45
+ *
46
+ * @remarks
47
+ *
48
+ * The type is based on SvgIcon from Material UI, but we do not want the plugin-api
49
+ * package to have a dependency on Material UI, nor do we want the props to be as broad
50
+ * as the SvgIconProps interface.
51
+ *
52
+ * If you have the need to forward additional props from SvgIconProps, you can
53
+ * open an issue or submit a PR to the main Backstage repo. When doing so please
54
+ * also describe your use-case and reasoning of the addition.
55
+ *
56
+ * @public
57
+ * @deprecated Use {@link IconElement} instead, passing `<MyIcon />` rather than `MyIcon`.
58
+ */
59
+ type IconComponent = ComponentType<{
60
+ fontSize?: 'medium' | 'large' | 'small' | 'inherit';
61
+ }>;
62
+ /**
63
+ * The type used for icon elements throughout Backstage.
64
+ *
65
+ * @remarks
66
+ *
67
+ * Icons should be exactly 24x24 pixels in size.
68
+ *
69
+ * @public
70
+ */
71
+ type IconElement = JSX$1.Element | null;
72
+
42
73
  /**
43
74
  * Catch-all type for route params.
44
75
  *
@@ -80,6 +111,8 @@ declare function createRouteRef<TParams extends {
80
111
  /** @public */
81
112
  declare const coreExtensionData: {
82
113
  title: _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<string, "core.title", {}>;
114
+ /** An icon element for the extension. Should be exactly 24x24 pixels. */
115
+ icon: _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<IconElement, "core.icon", {}>;
83
116
  reactElement: _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<JSX$1.Element, "core.reactElement", {}>;
84
117
  routePath: _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<string, "core.routing.path", {}>;
85
118
  routeRef: _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {}>;
@@ -564,7 +597,15 @@ interface OverridableFrontendPlugin<TRoutes extends {
564
597
  }> extends FrontendPlugin<TRoutes, TExternalRoutes> {
565
598
  getExtension<TId extends keyof TExtensionMap>(id: TId): OverridableExtensionDefinition<TExtensionMap[TId]['T']>;
566
599
  withOverrides(options: {
567
- extensions: Array<ExtensionDefinition>;
600
+ extensions?: Array<ExtensionDefinition>;
601
+ /**
602
+ * Overrides the display title of the plugin.
603
+ */
604
+ title?: string;
605
+ /**
606
+ * Overrides the display icon of the plugin.
607
+ */
608
+ icon?: IconElement;
568
609
  /**
569
610
  * Overrides the original info loaders of the plugin one by one.
570
611
  */
@@ -592,6 +633,15 @@ interface FrontendPlugin<TRoutes extends {
592
633
  * @deprecated Use `pluginId` instead.
593
634
  */
594
635
  readonly id: string;
636
+ /**
637
+ * The display title of the plugin, used in page headers and navigation.
638
+ * Falls back to the plugin ID if not provided.
639
+ */
640
+ readonly title?: string;
641
+ /**
642
+ * The display icon of the plugin, used in page headers and navigation.
643
+ */
644
+ readonly icon?: IconElement;
595
645
  readonly routes: TRoutes;
596
646
  readonly externalRoutes: TExternalRoutes;
597
647
  /**
@@ -606,6 +656,15 @@ interface PluginOptions<TId extends string, TRoutes extends {
606
656
  [name in string]: ExternalRouteRef;
607
657
  }, TExtensions extends readonly ExtensionDefinition[]> {
608
658
  pluginId: TId;
659
+ /**
660
+ * The display title of the plugin, used in page headers and navigation.
661
+ * Falls back to the plugin ID if not provided.
662
+ */
663
+ title?: string;
664
+ /**
665
+ * The display icon of the plugin, used in page headers and navigation.
666
+ */
667
+ icon?: IconElement;
609
668
  routes?: TRoutes;
610
669
  externalRoutes?: TExternalRoutes;
611
670
  extensions?: TExtensions;
@@ -670,7 +729,9 @@ type ExtensionFactoryMiddleware = (originalFactory: (contextOverrides?: {
670
729
  config?: JsonObject;
671
730
  }) => Iterable<ExtensionDataValue<any, any>>;
672
731
  /** @public */
673
- type FrontendFeature = FrontendPlugin | FrontendModule;
732
+ type FrontendFeature = (Omit<FrontendPlugin, 'pluginId'> & {
733
+ pluginId?: string;
734
+ }) | FrontendModule;
674
735
 
675
736
  /**
676
737
  * A function used to define a parameter mapping function in order to facilitate
@@ -1406,26 +1467,6 @@ declare function createApiFactory<Api, Impl extends Api, Deps extends {
1406
1467
  */
1407
1468
  declare function createApiFactory<Api, Impl extends Api>(api: ApiRef<Api>, instance: Impl): ApiFactory<Api, Impl, {}>;
1408
1469
 
1409
- /**
1410
- * IconComponent is the common icon type used throughout Backstage when
1411
- * working with and rendering generic icons, including the app system icons.
1412
- *
1413
- * @remarks
1414
- *
1415
- * The type is based on SvgIcon from Material UI, but we do not want the plugin-api
1416
- * package to have a dependency on Material UI, nor do we want the props to be as broad
1417
- * as the SvgIconProps interface.
1418
- *
1419
- * If you have the need to forward additional props from SvgIconProps, you can
1420
- * open an issue or submit a PR to the main Backstage repo. When doing so please
1421
- * also describe your use-case and reasoning of the addition.
1422
- *
1423
- * @public
1424
- */
1425
- type IconComponent = ComponentType<{
1426
- fontSize?: 'medium' | 'large' | 'small' | 'inherit';
1427
- }>;
1428
-
1429
1470
  /**
1430
1471
  * This file contains declarations for common interfaces of auth-related APIs.
1431
1472
  * The declarations should be used to signal which type of authentication and
@@ -2003,6 +2044,40 @@ declare const ErrorDisplay: {
2003
2044
  ref: _backstage_frontend_plugin_api.SwappableComponentRef<ErrorDisplayProps, ErrorDisplayProps>;
2004
2045
  };
2005
2046
 
2047
+ /**
2048
+ * Tab configuration for page navigation
2049
+ * @public
2050
+ */
2051
+ interface PageTab {
2052
+ id: string;
2053
+ label: string;
2054
+ icon?: IconElement;
2055
+ href: string;
2056
+ }
2057
+ /**
2058
+ * Props for the PageLayout component
2059
+ * @public
2060
+ */
2061
+ interface PageLayoutProps {
2062
+ title?: string;
2063
+ icon?: IconElement;
2064
+ noHeader?: boolean;
2065
+ headerActions?: Array<JSX.Element | null>;
2066
+ tabs?: PageTab[];
2067
+ children?: ReactNode;
2068
+ }
2069
+ /**
2070
+ * Swappable component for laying out page content with header and navigation.
2071
+ * The default implementation uses plain HTML elements.
2072
+ * Apps can override this with a custom implementation (e.g., using \@backstage/ui).
2073
+ *
2074
+ * @public
2075
+ */
2076
+ declare const PageLayout: {
2077
+ (props: PageLayoutProps): JSX.Element | null;
2078
+ ref: _backstage_frontend_plugin_api.SwappableComponentRef<PageLayoutProps, PageLayoutProps>;
2079
+ };
2080
+
2006
2081
  /**
2007
2082
  * API for looking up components based on component refs.
2008
2083
  *
@@ -2266,6 +2341,13 @@ declare const fetchApiRef: ApiRef<FetchApi>;
2266
2341
  * @public
2267
2342
  */
2268
2343
  interface IconsApi {
2344
+ /**
2345
+ * Look up an icon element by key.
2346
+ */
2347
+ icon(key: string): IconElement | undefined;
2348
+ /**
2349
+ * @deprecated Use {@link IconsApi.icon} instead.
2350
+ */
2269
2351
  getIcon(key: string): IconComponent | undefined;
2270
2352
  listIconKeys(): string[];
2271
2353
  }
@@ -3168,6 +3250,30 @@ type TranslationApi = {
3168
3250
  */
3169
3251
  declare const translationApiRef: ApiRef<TranslationApi>;
3170
3252
 
3253
+ /**
3254
+ * API for retrieving plugin-scoped header actions.
3255
+ *
3256
+ * @remarks
3257
+ *
3258
+ * Header actions are provided via
3259
+ * {@link @backstage/frontend-plugin-api#PluginHeaderActionBlueprint}
3260
+ * and automatically scoped to the providing plugin.
3261
+ *
3262
+ * @public
3263
+ */
3264
+ type PluginHeaderActionsApi = {
3265
+ /**
3266
+ * Returns the header actions for a given plugin.
3267
+ */
3268
+ getPluginHeaderActions(pluginId: string): Array<JSX$1.Element | null>;
3269
+ };
3270
+ /**
3271
+ * The `ApiRef` of {@link PluginHeaderActionsApi}.
3272
+ *
3273
+ * @public
3274
+ */
3275
+ declare const pluginHeaderActionsApiRef: _backstage_frontend_plugin_api.ApiRef<PluginHeaderActionsApi>;
3276
+
3171
3277
  /**
3172
3278
  * Gets a pre-configured analytics tracker.
3173
3279
  *
@@ -3264,7 +3370,7 @@ declare const NavItemBlueprint: _backstage_frontend_plugin_api.ExtensionBlueprin
3264
3370
  }>;
3265
3371
 
3266
3372
  /**
3267
- * Createx extensions that are routable React page components.
3373
+ * Creates extensions that are routable React page components.
3268
3374
  *
3269
3375
  * @public
3270
3376
  */
@@ -3276,21 +3382,134 @@ declare const PageBlueprint: _backstage_frontend_plugin_api.ExtensionBlueprint<{
3276
3382
  */
3277
3383
  defaultPath?: [Error: `Use the 'path' param instead`];
3278
3384
  path: string;
3385
+ title?: string;
3386
+ icon?: IconElement;
3387
+ loader?: () => Promise<JSX$1.Element>;
3388
+ routeRef?: RouteRef;
3389
+ /**
3390
+ * Hide the default plugin page header, making the page fill up all available space.
3391
+ */
3392
+ noHeader?: boolean;
3393
+ };
3394
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
3395
+ optional: true;
3396
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<JSX$1.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.title", {
3397
+ optional: true;
3398
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<IconElement, "core.icon", {
3399
+ optional: true;
3400
+ }>;
3401
+ inputs: {
3402
+ pages: _backstage_frontend_plugin_api.ExtensionInput<_backstage_frontend_plugin_api.ConfigurableExtensionDataRef<JSX$1.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
3403
+ optional: true;
3404
+ }> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<string, "core.title", {
3405
+ optional: true;
3406
+ }> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<IconElement, "core.icon", {
3407
+ optional: true;
3408
+ }>, {
3409
+ singleton: false;
3410
+ optional: false;
3411
+ internal: false;
3412
+ }>;
3413
+ };
3414
+ config: {
3415
+ path: string | undefined;
3416
+ title: string | undefined;
3417
+ };
3418
+ configInput: {
3419
+ title?: string | undefined;
3420
+ path?: string | undefined;
3421
+ };
3422
+ dataRefs: never;
3423
+ }>;
3424
+
3425
+ /**
3426
+ * Creates extensions that are sub-page React components attached to a parent page.
3427
+ * Sub-pages are rendered as tabs within the parent page's header.
3428
+ *
3429
+ * @public
3430
+ * @example
3431
+ * ```tsx
3432
+ * const overviewRouteRef = createRouteRef();
3433
+ *
3434
+ * const mySubPage = SubPageBlueprint.make({
3435
+ * attachTo: { id: 'page:my-plugin', input: 'pages' },
3436
+ * name: 'overview',
3437
+ * params: {
3438
+ * path: 'overview',
3439
+ * title: 'Overview',
3440
+ * routeRef: overviewRouteRef,
3441
+ * loader: () => import('./components/Overview').then(m => <m.Overview />),
3442
+ * },
3443
+ * });
3444
+ * ```
3445
+ */
3446
+ declare const SubPageBlueprint: _backstage_frontend_plugin_api.ExtensionBlueprint<{
3447
+ kind: "sub-page";
3448
+ params: {
3449
+ /**
3450
+ * The path for this sub-page, relative to the parent page. Must **not** start with '/'.
3451
+ *
3452
+ * @example 'overview', 'settings', 'details'
3453
+ */
3454
+ path: string;
3455
+ /**
3456
+ * The title displayed in the tab for this sub-page.
3457
+ */
3458
+ title: string;
3459
+ /**
3460
+ * Optional icon for this sub-page, displayed in the tab.
3461
+ */
3462
+ icon?: IconElement;
3463
+ /**
3464
+ * A function that returns a promise resolving to the React element to render.
3465
+ * This enables lazy loading of the sub-page content.
3466
+ */
3279
3467
  loader: () => Promise<JSX.Element>;
3468
+ /**
3469
+ * Optional route reference for this sub-page.
3470
+ */
3280
3471
  routeRef?: RouteRef;
3281
3472
  };
3282
- output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
3473
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
3474
+ optional: true;
3475
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.title", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<IconElement, "core.icon", {
3283
3476
  optional: true;
3284
3477
  }>;
3285
3478
  inputs: {};
3286
3479
  config: {
3287
3480
  path: string | undefined;
3481
+ title: string | undefined;
3288
3482
  };
3289
3483
  configInput: {
3484
+ title?: string | undefined;
3290
3485
  path?: string | undefined;
3291
3486
  };
3292
3487
  dataRefs: never;
3293
3488
  }>;
3294
3489
 
3295
- export { AnalyticsContext, AnalyticsImplementationBlueprint, ApiBlueprint, AppRootElementBlueprint, ErrorDisplay, ExtensionBoundary, FeatureFlagState, NavItemBlueprint, NotFoundErrorPage, PageBlueprint, Progress, SessionState, alertApiRef, analyticsApiRef, appLanguageApiRef, appThemeApiRef, appTreeApiRef, atlassianAuthApiRef, bitbucketAuthApiRef, bitbucketServerAuthApiRef, configApiRef, coreExtensionData, createApiFactory, createApiRef, createExtension, createExtensionBlueprint, createExtensionBlueprintParams, createExtensionDataRef, createExtensionInput, createExternalRouteRef, createFrontendFeatureLoader, createFrontendModule, createFrontendPlugin, createRouteRef, createSubRouteRef, createSwappableComponent, createTranslationMessages, createTranslationRef, createTranslationResource, dialogApiRef, discoveryApiRef, errorApiRef, featureFlagsApiRef, fetchApiRef, githubAuthApiRef, gitlabAuthApiRef, googleAuthApiRef, iconsApiRef, identityApiRef, microsoftAuthApiRef, oauthRequestApiRef, oktaAuthApiRef, oneloginAuthApiRef, openshiftAuthApiRef, routeResolutionApiRef, storageApiRef, swappableComponentsApiRef, translationApiRef, useAnalytics, useApi, useApiHolder, useAppNode, useRouteRef, useRouteRefParams, useTranslationRef, vmwareCloudAuthApiRef, withApis };
3296
- export type { AlertApi, AlertMessage, AnalyticsApi, AnalyticsContextValue, AnalyticsEvent, AnalyticsEventAttributes, AnalyticsImplementation, AnalyticsImplementationFactory, AnalyticsTracker, AnyApiFactory, AnyApiRef, AnyExtensionDataRef, AnyRouteRefParams, ApiFactory, ApiHolder, ApiRef, ApiRefConfig, AppLanguageApi, AppNode, AppNodeEdges, AppNodeInstance, AppNodeSpec, AppTheme, AppThemeApi, AppTree, AppTreeApi, AuthProviderInfo, AuthRequestOptions, BackstageIdentityApi, BackstageIdentityResponse, BackstageUserIdentity, ConfigApi, ConfigurableExtensionDataRef, CreateExtensionBlueprintOptions, CreateExtensionOptions, CreateFrontendFeatureLoaderOptions, CreateFrontendModuleOptions, CreateSwappableComponentOptions, DialogApi, DialogApiDialog, DiscoveryApi, ErrorApi, ErrorApiError, ErrorApiErrorContext, ErrorDisplayProps, Extension, ExtensionAttachTo, ExtensionAttachToSpec, ExtensionBlueprint, ExtensionBlueprintDefineParams, ExtensionBlueprintParameters, ExtensionBlueprintParams, ExtensionBoundaryProps, ExtensionDataContainer, ExtensionDataRef, ExtensionDataRefToValue, ExtensionDataValue, ExtensionDefinition, ExtensionDefinitionAttachTo, ExtensionDefinitionParameters, ExtensionFactoryMiddleware, ExtensionInput, ExternalRouteRef, FeatureFlag, FeatureFlagConfig, FeatureFlagsApi, FeatureFlagsSaveOptions, FetchApi, FrontendFeature, FrontendFeatureLoader, FrontendModule, FrontendPlugin, FrontendPluginInfo, FrontendPluginInfoOptions, IconComponent, IconsApi, IdentityApi, NotFoundErrorPageProps, OAuthApi, OAuthRequestApi, OAuthRequester, OAuthRequesterOptions, OAuthScope, OpenIdConnectApi, OverridableExtensionDefinition, OverridableFrontendPlugin, PendingOAuthRequest, PluginOptions, PortableSchema, ProfileInfo, ProfileInfoApi, ProgressProps, ResolvedExtensionInput, ResolvedExtensionInputs, RouteFunc, RouteRef, RouteResolutionApi, SessionApi, StorageApi, StorageValueSnapshot, SubRouteRef, SwappableComponentRef, SwappableComponentsApi, TranslationApi, TranslationFunction, TranslationMessages, TranslationMessagesOptions, TranslationRef, TranslationRefOptions, TranslationResource, TranslationResourceOptions, TranslationSnapshot, TypesToApiRefs };
3490
+ /**
3491
+ * Creates extensions that provide plugin-scoped header actions.
3492
+ *
3493
+ * @remarks
3494
+ *
3495
+ * These actions are automatically scoped to the plugin that provides them
3496
+ * and will appear in the header of all pages belonging to that plugin.
3497
+ *
3498
+ * @public
3499
+ */
3500
+ declare const PluginHeaderActionBlueprint: _backstage_frontend_plugin_api.ExtensionBlueprint<{
3501
+ kind: "plugin-header-action";
3502
+ params: (params: {
3503
+ loader: () => Promise<JSX.Element>;
3504
+ }) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<{
3505
+ loader: () => Promise<JSX.Element>;
3506
+ }>;
3507
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}>;
3508
+ inputs: {};
3509
+ config: {};
3510
+ configInput: {};
3511
+ dataRefs: never;
3512
+ }>;
3513
+
3514
+ export { AnalyticsContext, AnalyticsImplementationBlueprint, ApiBlueprint, AppRootElementBlueprint, ErrorDisplay, ExtensionBoundary, FeatureFlagState, NavItemBlueprint, NotFoundErrorPage, PageBlueprint, PageLayout, PluginHeaderActionBlueprint, Progress, SessionState, SubPageBlueprint, alertApiRef, analyticsApiRef, appLanguageApiRef, appThemeApiRef, appTreeApiRef, atlassianAuthApiRef, bitbucketAuthApiRef, bitbucketServerAuthApiRef, configApiRef, coreExtensionData, createApiFactory, createApiRef, createExtension, createExtensionBlueprint, createExtensionBlueprintParams, createExtensionDataRef, createExtensionInput, createExternalRouteRef, createFrontendFeatureLoader, createFrontendModule, createFrontendPlugin, createRouteRef, createSubRouteRef, createSwappableComponent, createTranslationMessages, createTranslationRef, createTranslationResource, dialogApiRef, discoveryApiRef, errorApiRef, featureFlagsApiRef, fetchApiRef, githubAuthApiRef, gitlabAuthApiRef, googleAuthApiRef, iconsApiRef, identityApiRef, microsoftAuthApiRef, oauthRequestApiRef, oktaAuthApiRef, oneloginAuthApiRef, openshiftAuthApiRef, pluginHeaderActionsApiRef, routeResolutionApiRef, storageApiRef, swappableComponentsApiRef, translationApiRef, useAnalytics, useApi, useApiHolder, useAppNode, useRouteRef, useRouteRefParams, useTranslationRef, vmwareCloudAuthApiRef, withApis };
3515
+ export type { AlertApi, AlertMessage, AnalyticsApi, AnalyticsContextValue, AnalyticsEvent, AnalyticsEventAttributes, AnalyticsImplementation, AnalyticsImplementationFactory, AnalyticsTracker, AnyApiFactory, AnyApiRef, AnyExtensionDataRef, AnyRouteRefParams, ApiFactory, ApiHolder, ApiRef, ApiRefConfig, AppLanguageApi, AppNode, AppNodeEdges, AppNodeInstance, AppNodeSpec, AppTheme, AppThemeApi, AppTree, AppTreeApi, AuthProviderInfo, AuthRequestOptions, BackstageIdentityApi, BackstageIdentityResponse, BackstageUserIdentity, ConfigApi, ConfigurableExtensionDataRef, CreateExtensionBlueprintOptions, CreateExtensionOptions, CreateFrontendFeatureLoaderOptions, CreateFrontendModuleOptions, CreateSwappableComponentOptions, DialogApi, DialogApiDialog, DiscoveryApi, ErrorApi, ErrorApiError, ErrorApiErrorContext, ErrorDisplayProps, Extension, ExtensionAttachTo, ExtensionAttachToSpec, ExtensionBlueprint, ExtensionBlueprintDefineParams, ExtensionBlueprintParameters, ExtensionBlueprintParams, ExtensionBoundaryProps, ExtensionDataContainer, ExtensionDataRef, ExtensionDataRefToValue, ExtensionDataValue, ExtensionDefinition, ExtensionDefinitionAttachTo, ExtensionDefinitionParameters, ExtensionFactoryMiddleware, ExtensionInput, ExternalRouteRef, FeatureFlag, FeatureFlagConfig, FeatureFlagsApi, FeatureFlagsSaveOptions, FetchApi, FrontendFeature, FrontendFeatureLoader, FrontendModule, FrontendPlugin, FrontendPluginInfo, FrontendPluginInfoOptions, IconComponent, IconElement, IconsApi, IdentityApi, NotFoundErrorPageProps, OAuthApi, OAuthRequestApi, OAuthRequester, OAuthRequesterOptions, OAuthScope, OpenIdConnectApi, OverridableExtensionDefinition, OverridableFrontendPlugin, PageLayoutProps, PageTab, PendingOAuthRequest, PluginHeaderActionsApi, PluginOptions, PortableSchema, ProfileInfo, ProfileInfoApi, ProgressProps, ResolvedExtensionInput, ResolvedExtensionInputs, RouteFunc, RouteRef, RouteResolutionApi, SessionApi, StorageApi, StorageValueSnapshot, SubRouteRef, SwappableComponentRef, SwappableComponentsApi, TranslationApi, TranslationFunction, TranslationMessages, TranslationMessagesOptions, TranslationRef, TranslationRefOptions, TranslationResource, TranslationResourceOptions, TranslationSnapshot, TypesToApiRefs };
package/dist/index.esm.js CHANGED
@@ -19,6 +19,7 @@ export { routeResolutionApiRef } from './apis/definitions/RouteResolutionApi.esm
19
19
  export { storageApiRef } from './apis/definitions/StorageApi.esm.js';
20
20
  export { analyticsApiRef } from './apis/definitions/AnalyticsApi.esm.js';
21
21
  export { translationApiRef } from './apis/definitions/TranslationApi.esm.js';
22
+ export { pluginHeaderActionsApiRef } from './apis/definitions/PluginHeaderActionsApi.esm.js';
22
23
  export { useApi, useApiHolder, withApis } from './apis/system/useApi.esm.js';
23
24
  export { createApiRef } from './apis/system/ApiRef.esm.js';
24
25
  export { createApiFactory } from './apis/system/helpers.esm.js';
@@ -27,10 +28,13 @@ export { ApiBlueprint } from './blueprints/ApiBlueprint.esm.js';
27
28
  export { AppRootElementBlueprint } from './blueprints/AppRootElementBlueprint.esm.js';
28
29
  export { NavItemBlueprint } from './blueprints/NavItemBlueprint.esm.js';
29
30
  export { PageBlueprint } from './blueprints/PageBlueprint.esm.js';
31
+ export { SubPageBlueprint } from './blueprints/SubPageBlueprint.esm.js';
32
+ export { PluginHeaderActionBlueprint } from './blueprints/PluginHeaderActionBlueprint.esm.js';
30
33
  export { ExtensionBoundary } from './components/ExtensionBoundary.esm.js';
31
34
  export { createSwappableComponent } from './components/createSwappableComponent.esm.js';
32
35
  export { useAppNode } from './components/AppNodeProvider.esm.js';
33
36
  export { ErrorDisplay, NotFoundErrorPage, Progress } from './components/DefaultSwappableComponents.esm.js';
37
+ export { PageLayout } from './components/PageLayout.esm.js';
34
38
  export { createRouteRef } from './routing/RouteRef.esm.js';
35
39
  export { createSubRouteRef } from './routing/SubRouteRef.esm.js';
36
40
  export { createExternalRouteRef } from './routing/ExternalRouteRef.esm.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -19,6 +19,7 @@ import { routeResolutionApiRef } from '../apis/definitions/RouteResolutionApi.es
19
19
  import '../apis/definitions/StorageApi.esm.js';
20
20
  import '../apis/definitions/AnalyticsApi.esm.js';
21
21
  import '../apis/definitions/TranslationApi.esm.js';
22
+ import '../apis/definitions/PluginHeaderActionsApi.esm.js';
22
23
  import { useApi } from '../apis/system/useApi.esm.js';
23
24
 
24
25
  function useRouteRef(routeRef) {
@@ -1 +1 @@
1
- {"version":3,"file":"useRouteRef.esm.js","sources":["../../src/routing/useRouteRef.tsx"],"sourcesContent":["/*\n * Copyright 2020 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 { useMemo } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { AnyRouteRefParams } from './types';\nimport { RouteRef } from './RouteRef';\nimport { SubRouteRef } from './SubRouteRef';\nimport { ExternalRouteRef } from './ExternalRouteRef';\nimport { RouteFunc, routeResolutionApiRef, useApi } from '../apis';\n\n/**\n * React hook for constructing URLs to routes.\n *\n * @remarks\n *\n * See {@link https://backstage.io/docs/plugins/composability#routing-system}\n *\n * @param routeRef - The ref to route that should be converted to URL.\n * @returns A function that will in turn return the concrete URL of the `routeRef`, or `undefined` if the route is not available.\n * @public\n */\nexport function useRouteRef<TParams extends AnyRouteRefParams>(\n routeRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n): RouteFunc<TParams> | undefined {\n const { pathname } = useLocation();\n const routeResolutionApi = useApi(routeResolutionApiRef);\n\n const routeFunc = useMemo(\n () => routeResolutionApi.resolve(routeRef, { sourcePath: pathname }),\n [routeResolutionApi, routeRef, pathname],\n );\n\n return routeFunc;\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmCO,SAAS,YACd,QAAA,EAIgC;AAChC,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,WAAA,EAAY;AACjC,EAAA,MAAM,kBAAA,GAAqB,OAAO,qBAAqB,CAAA;AAEvD,EAAA,MAAM,SAAA,GAAY,OAAA;AAAA,IAChB,MAAM,kBAAA,CAAmB,OAAA,CAAQ,UAAU,EAAE,UAAA,EAAY,UAAU,CAAA;AAAA,IACnE,CAAC,kBAAA,EAAoB,QAAA,EAAU,QAAQ;AAAA,GACzC;AAEA,EAAA,OAAO,SAAA;AACT;;;;"}
1
+ {"version":3,"file":"useRouteRef.esm.js","sources":["../../src/routing/useRouteRef.tsx"],"sourcesContent":["/*\n * Copyright 2020 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 { useMemo } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { AnyRouteRefParams } from './types';\nimport { RouteRef } from './RouteRef';\nimport { SubRouteRef } from './SubRouteRef';\nimport { ExternalRouteRef } from './ExternalRouteRef';\nimport { RouteFunc, routeResolutionApiRef, useApi } from '../apis';\n\n/**\n * React hook for constructing URLs to routes.\n *\n * @remarks\n *\n * See {@link https://backstage.io/docs/plugins/composability#routing-system}\n *\n * @param routeRef - The ref to route that should be converted to URL.\n * @returns A function that will in turn return the concrete URL of the `routeRef`, or `undefined` if the route is not available.\n * @public\n */\nexport function useRouteRef<TParams extends AnyRouteRefParams>(\n routeRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n): RouteFunc<TParams> | undefined {\n const { pathname } = useLocation();\n const routeResolutionApi = useApi(routeResolutionApiRef);\n\n const routeFunc = useMemo(\n () => routeResolutionApi.resolve(routeRef, { sourcePath: pathname }),\n [routeResolutionApi, routeRef, pathname],\n );\n\n return routeFunc;\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAmCO,SAAS,YACd,QAAA,EAIgC;AAChC,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,WAAA,EAAY;AACjC,EAAA,MAAM,kBAAA,GAAqB,OAAO,qBAAqB,CAAA;AAEvD,EAAA,MAAM,SAAA,GAAY,OAAA;AAAA,IAChB,MAAM,kBAAA,CAAmB,OAAA,CAAQ,UAAU,EAAE,UAAA,EAAY,UAAU,CAAA;AAAA,IACnE,CAAC,kBAAA,EAAoB,QAAA,EAAU,QAAQ;AAAA,GACzC;AAEA,EAAA,OAAO,SAAA;AACT;;;;"}
@@ -18,6 +18,7 @@ import '../apis/definitions/RouteResolutionApi.esm.js';
18
18
  import '../apis/definitions/StorageApi.esm.js';
19
19
  import '../apis/definitions/AnalyticsApi.esm.js';
20
20
  import { translationApiRef } from '../apis/definitions/TranslationApi.esm.js';
21
+ import '../apis/definitions/PluginHeaderActionsApi.esm.js';
21
22
  import { useApi } from '../apis/system/useApi.esm.js';
22
23
 
23
24
  const loggedRefs = /* @__PURE__ */ new WeakSet();
@@ -1 +1 @@
1
- {"version":3,"file":"useTranslationRef.esm.js","sources":["../../src/translation/useTranslationRef.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 { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { errorApiRef, useApi } from '../apis';\nimport {\n translationApiRef,\n TranslationFunction,\n TranslationSnapshot,\n} from '../apis/definitions/TranslationApi';\nimport { TranslationRef } from './TranslationRef';\n\n// Make sure we don't fill the logs with loading errors for the same ref\nconst loggedRefs = new WeakSet<TranslationRef<string, {}>>();\n\n/** @public */\nexport const useTranslationRef = <\n TMessages extends { [key in string]: string },\n>(\n translationRef: TranslationRef<string, TMessages>,\n): { t: TranslationFunction<TMessages> } => {\n const errorApi = useApi(errorApiRef);\n const translationApi = useApi(translationApiRef);\n\n const [snapshot, setSnapshot] = useState<TranslationSnapshot<TMessages>>(() =>\n translationApi.getTranslation(translationRef),\n );\n const observable = useMemo(\n () => translationApi.translation$(translationRef),\n [translationApi, translationRef],\n );\n\n const onError = useCallback(\n (error: Error) => {\n if (!loggedRefs.has(translationRef)) {\n const errMsg = `Failed to load translation resource '${translationRef.id}'; caused by ${error}`;\n // eslint-disable-next-line no-console\n console.error(errMsg);\n errorApi.post(new Error(errMsg));\n loggedRefs.add(translationRef);\n }\n },\n [errorApi, translationRef],\n );\n\n useEffect(() => {\n const subscription = observable.subscribe({\n next(next) {\n if (next.ready) {\n setSnapshot(next);\n }\n },\n error(error) {\n onError(error);\n },\n });\n\n return () => {\n subscription.unsubscribe();\n };\n }, [observable, onError]);\n\n // Keep track of if the provided translation ref changes, and in that case update the snapshot\n const initialRenderRef = useRef(true);\n useEffect(() => {\n if (initialRenderRef.current) {\n initialRenderRef.current = false;\n } else {\n setSnapshot(translationApi.getTranslation(translationRef));\n }\n }, [translationApi, translationRef]);\n\n if (!snapshot.ready) {\n throw new Promise<void>(resolve => {\n const subscription = observable.subscribe({\n next(next) {\n if (next.ready) {\n subscription.unsubscribe();\n resolve();\n }\n },\n error(error) {\n subscription.unsubscribe();\n onError(error);\n resolve();\n },\n });\n });\n }\n\n return { t: snapshot.t };\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,UAAA,uBAAiB,OAAA,EAAoC;AAGpD,MAAM,iBAAA,GAAoB,CAG/B,cAAA,KAC0C;AAC1C,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AACnC,EAAA,MAAM,cAAA,GAAiB,OAAO,iBAAiB,CAAA;AAE/C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,QAAA;AAAA,IAAyC,MACvE,cAAA,CAAe,cAAA,CAAe,cAAc;AAAA,GAC9C;AACA,EAAA,MAAM,UAAA,GAAa,OAAA;AAAA,IACjB,MAAM,cAAA,CAAe,YAAA,CAAa,cAAc,CAAA;AAAA,IAChD,CAAC,gBAAgB,cAAc;AAAA,GACjC;AAEA,EAAA,MAAM,OAAA,GAAU,WAAA;AAAA,IACd,CAAC,KAAA,KAAiB;AAChB,MAAA,IAAI,CAAC,UAAA,CAAW,GAAA,CAAI,cAAc,CAAA,EAAG;AACnC,QAAA,MAAM,MAAA,GAAS,CAAA,qCAAA,EAAwC,cAAA,CAAe,EAAE,gBAAgB,KAAK,CAAA,CAAA;AAE7F,QAAA,OAAA,CAAQ,MAAM,MAAM,CAAA;AACpB,QAAA,QAAA,CAAS,IAAA,CAAK,IAAI,KAAA,CAAM,MAAM,CAAC,CAAA;AAC/B,QAAA,UAAA,CAAW,IAAI,cAAc,CAAA;AAAA,MAC/B;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU,cAAc;AAAA,GAC3B;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,YAAA,GAAe,WAAW,SAAA,CAAU;AAAA,MACxC,KAAK,IAAA,EAAM;AACT,QAAA,IAAI,KAAK,KAAA,EAAO;AACd,UAAA,WAAA,CAAY,IAAI,CAAA;AAAA,QAClB;AAAA,MACF,CAAA;AAAA,MACA,MAAM,KAAA,EAAO;AACX,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf;AAAA,KACD,CAAA;AAED,IAAA,OAAO,MAAM;AACX,MAAA,YAAA,CAAa,WAAA,EAAY;AAAA,IAC3B,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,UAAA,EAAY,OAAO,CAAC,CAAA;AAGxB,EAAA,MAAM,gBAAA,GAAmB,OAAO,IAAI,CAAA;AACpC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,MAAA,gBAAA,CAAiB,OAAA,GAAU,KAAA;AAAA,IAC7B,CAAA,MAAO;AACL,MAAA,WAAA,CAAY,cAAA,CAAe,cAAA,CAAe,cAAc,CAAC,CAAA;AAAA,IAC3D;AAAA,EACF,CAAA,EAAG,CAAC,cAAA,EAAgB,cAAc,CAAC,CAAA;AAEnC,EAAA,IAAI,CAAC,SAAS,KAAA,EAAO;AACnB,IAAA,MAAM,IAAI,QAAc,CAAA,OAAA,KAAW;AACjC,MAAA,MAAM,YAAA,GAAe,WAAW,SAAA,CAAU;AAAA,QACxC,KAAK,IAAA,EAAM;AACT,UAAA,IAAI,KAAK,KAAA,EAAO;AACd,YAAA,YAAA,CAAa,WAAA,EAAY;AACzB,YAAA,OAAA,EAAQ;AAAA,UACV;AAAA,QACF,CAAA;AAAA,QACA,MAAM,KAAA,EAAO;AACX,UAAA,YAAA,CAAa,WAAA,EAAY;AACzB,UAAA,OAAA,CAAQ,KAAK,CAAA;AACb,UAAA,OAAA,EAAQ;AAAA,QACV;AAAA,OACD,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,EAAE,CAAA,EAAG,QAAA,CAAS,CAAA,EAAE;AACzB;;;;"}
1
+ {"version":3,"file":"useTranslationRef.esm.js","sources":["../../src/translation/useTranslationRef.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 { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { errorApiRef, useApi } from '../apis';\nimport {\n translationApiRef,\n TranslationFunction,\n TranslationSnapshot,\n} from '../apis/definitions/TranslationApi';\nimport { TranslationRef } from './TranslationRef';\n\n// Make sure we don't fill the logs with loading errors for the same ref\nconst loggedRefs = new WeakSet<TranslationRef<string, {}>>();\n\n/** @public */\nexport const useTranslationRef = <\n TMessages extends { [key in string]: string },\n>(\n translationRef: TranslationRef<string, TMessages>,\n): { t: TranslationFunction<TMessages> } => {\n const errorApi = useApi(errorApiRef);\n const translationApi = useApi(translationApiRef);\n\n const [snapshot, setSnapshot] = useState<TranslationSnapshot<TMessages>>(() =>\n translationApi.getTranslation(translationRef),\n );\n const observable = useMemo(\n () => translationApi.translation$(translationRef),\n [translationApi, translationRef],\n );\n\n const onError = useCallback(\n (error: Error) => {\n if (!loggedRefs.has(translationRef)) {\n const errMsg = `Failed to load translation resource '${translationRef.id}'; caused by ${error}`;\n // eslint-disable-next-line no-console\n console.error(errMsg);\n errorApi.post(new Error(errMsg));\n loggedRefs.add(translationRef);\n }\n },\n [errorApi, translationRef],\n );\n\n useEffect(() => {\n const subscription = observable.subscribe({\n next(next) {\n if (next.ready) {\n setSnapshot(next);\n }\n },\n error(error) {\n onError(error);\n },\n });\n\n return () => {\n subscription.unsubscribe();\n };\n }, [observable, onError]);\n\n // Keep track of if the provided translation ref changes, and in that case update the snapshot\n const initialRenderRef = useRef(true);\n useEffect(() => {\n if (initialRenderRef.current) {\n initialRenderRef.current = false;\n } else {\n setSnapshot(translationApi.getTranslation(translationRef));\n }\n }, [translationApi, translationRef]);\n\n if (!snapshot.ready) {\n throw new Promise<void>(resolve => {\n const subscription = observable.subscribe({\n next(next) {\n if (next.ready) {\n subscription.unsubscribe();\n resolve();\n }\n },\n error(error) {\n subscription.unsubscribe();\n onError(error);\n resolve();\n },\n });\n });\n }\n\n return { t: snapshot.t };\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,UAAA,uBAAiB,OAAA,EAAoC;AAGpD,MAAM,iBAAA,GAAoB,CAG/B,cAAA,KAC0C;AAC1C,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AACnC,EAAA,MAAM,cAAA,GAAiB,OAAO,iBAAiB,CAAA;AAE/C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,QAAA;AAAA,IAAyC,MACvE,cAAA,CAAe,cAAA,CAAe,cAAc;AAAA,GAC9C;AACA,EAAA,MAAM,UAAA,GAAa,OAAA;AAAA,IACjB,MAAM,cAAA,CAAe,YAAA,CAAa,cAAc,CAAA;AAAA,IAChD,CAAC,gBAAgB,cAAc;AAAA,GACjC;AAEA,EAAA,MAAM,OAAA,GAAU,WAAA;AAAA,IACd,CAAC,KAAA,KAAiB;AAChB,MAAA,IAAI,CAAC,UAAA,CAAW,GAAA,CAAI,cAAc,CAAA,EAAG;AACnC,QAAA,MAAM,MAAA,GAAS,CAAA,qCAAA,EAAwC,cAAA,CAAe,EAAE,gBAAgB,KAAK,CAAA,CAAA;AAE7F,QAAA,OAAA,CAAQ,MAAM,MAAM,CAAA;AACpB,QAAA,QAAA,CAAS,IAAA,CAAK,IAAI,KAAA,CAAM,MAAM,CAAC,CAAA;AAC/B,QAAA,UAAA,CAAW,IAAI,cAAc,CAAA;AAAA,MAC/B;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU,cAAc;AAAA,GAC3B;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,YAAA,GAAe,WAAW,SAAA,CAAU;AAAA,MACxC,KAAK,IAAA,EAAM;AACT,QAAA,IAAI,KAAK,KAAA,EAAO;AACd,UAAA,WAAA,CAAY,IAAI,CAAA;AAAA,QAClB;AAAA,MACF,CAAA;AAAA,MACA,MAAM,KAAA,EAAO;AACX,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf;AAAA,KACD,CAAA;AAED,IAAA,OAAO,MAAM;AACX,MAAA,YAAA,CAAa,WAAA,EAAY;AAAA,IAC3B,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,UAAA,EAAY,OAAO,CAAC,CAAA;AAGxB,EAAA,MAAM,gBAAA,GAAmB,OAAO,IAAI,CAAA;AACpC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,MAAA,gBAAA,CAAiB,OAAA,GAAU,KAAA;AAAA,IAC7B,CAAA,MAAO;AACL,MAAA,WAAA,CAAY,cAAA,CAAe,cAAA,CAAe,cAAc,CAAC,CAAA;AAAA,IAC3D;AAAA,EACF,CAAA,EAAG,CAAC,cAAA,EAAgB,cAAc,CAAC,CAAA;AAEnC,EAAA,IAAI,CAAC,SAAS,KAAA,EAAO;AACnB,IAAA,MAAM,IAAI,QAAc,CAAA,OAAA,KAAW;AACjC,MAAA,MAAM,YAAA,GAAe,WAAW,SAAA,CAAU;AAAA,QACxC,KAAK,IAAA,EAAM;AACT,UAAA,IAAI,KAAK,KAAA,EAAO;AACd,YAAA,YAAA,CAAa,WAAA,EAAY;AACzB,YAAA,OAAA,EAAQ;AAAA,UACV;AAAA,QACF,CAAA;AAAA,QACA,MAAM,KAAA,EAAO;AACX,UAAA,YAAA,CAAa,WAAA,EAAY;AACzB,UAAA,OAAA,CAAQ,KAAK,CAAA;AACb,UAAA,OAAA,EAAQ;AAAA,QACV;AAAA,OACD,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,EAAE,CAAA,EAAG,QAAA,CAAS,CAAA,EAAE;AACzB;;;;"}
@@ -2,6 +2,8 @@ import { createExtensionDataRef } from './createExtensionDataRef.esm.js';
2
2
 
3
3
  const coreExtensionData = {
4
4
  title: createExtensionDataRef().with({ id: "core.title" }),
5
+ /** An icon element for the extension. Should be exactly 24x24 pixels. */
6
+ icon: createExtensionDataRef().with({ id: "core.icon" }),
5
7
  reactElement: createExtensionDataRef().with({
6
8
  id: "core.reactElement"
7
9
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"coreExtensionData.esm.js","sources":["../../src/wiring/coreExtensionData.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 { JSX } from 'react';\nimport { RouteRef } from '../routing/RouteRef';\nimport { createExtensionDataRef } from './createExtensionDataRef';\n\n/** @public */\nexport const coreExtensionData = {\n title: createExtensionDataRef<string>().with({ id: 'core.title' }),\n reactElement: createExtensionDataRef<JSX.Element>().with({\n id: 'core.reactElement',\n }),\n routePath: createExtensionDataRef<string>().with({ id: 'core.routing.path' }),\n routeRef: createExtensionDataRef<RouteRef>().with({ id: 'core.routing.ref' }),\n};\n"],"names":[],"mappings":";;AAqBO,MAAM,iBAAA,GAAoB;AAAA,EAC/B,OAAO,sBAAA,EAA+B,CAAE,KAAK,EAAE,EAAA,EAAI,cAAc,CAAA;AAAA,EACjE,YAAA,EAAc,sBAAA,EAAoC,CAAE,IAAA,CAAK;AAAA,IACvD,EAAA,EAAI;AAAA,GACL,CAAA;AAAA,EACD,WAAW,sBAAA,EAA+B,CAAE,KAAK,EAAE,EAAA,EAAI,qBAAqB,CAAA;AAAA,EAC5E,UAAU,sBAAA,EAAiC,CAAE,KAAK,EAAE,EAAA,EAAI,oBAAoB;AAC9E;;;;"}
1
+ {"version":3,"file":"coreExtensionData.esm.js","sources":["../../src/wiring/coreExtensionData.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 { JSX } from 'react';\nimport { IconElement } from '../icons/types';\nimport { RouteRef } from '../routing/RouteRef';\nimport { createExtensionDataRef } from './createExtensionDataRef';\n\n/** @public */\nexport const coreExtensionData = {\n title: createExtensionDataRef<string>().with({ id: 'core.title' }),\n /** An icon element for the extension. Should be exactly 24x24 pixels. */\n icon: createExtensionDataRef<IconElement>().with({ id: 'core.icon' }),\n reactElement: createExtensionDataRef<JSX.Element>().with({\n id: 'core.reactElement',\n }),\n routePath: createExtensionDataRef<string>().with({ id: 'core.routing.path' }),\n routeRef: createExtensionDataRef<RouteRef>().with({ id: 'core.routing.ref' }),\n};\n"],"names":[],"mappings":";;AAsBO,MAAM,iBAAA,GAAoB;AAAA,EAC/B,OAAO,sBAAA,EAA+B,CAAE,KAAK,EAAE,EAAA,EAAI,cAAc,CAAA;AAAA;AAAA,EAEjE,MAAM,sBAAA,EAAoC,CAAE,KAAK,EAAE,EAAA,EAAI,aAAa,CAAA;AAAA,EACpE,YAAA,EAAc,sBAAA,EAAoC,CAAE,IAAA,CAAK;AAAA,IACvD,EAAA,EAAI;AAAA,GACL,CAAA;AAAA,EACD,WAAW,sBAAA,EAA+B,CAAE,KAAK,EAAE,EAAA,EAAI,qBAAqB,CAAA;AAAA,EAC5E,UAAU,sBAAA,EAAiC,CAAE,KAAK,EAAE,EAAA,EAAI,oBAAoB;AAC9E;;;;"}
@@ -39,6 +39,8 @@ function createFrontendPlugin(options) {
39
39
  return OpaqueFrontendPlugin.createInstance("v1", {
40
40
  pluginId,
41
41
  id: pluginId,
42
+ title: options.title,
43
+ icon: options.icon,
42
44
  routes: options.routes ?? {},
43
45
  externalRoutes: options.externalRoutes ?? {},
44
46
  featureFlags: options.featureFlags ?? [],
@@ -63,8 +65,9 @@ function createFrontendPlugin(options) {
63
65
  return `Plugin{id=${pluginId}}`;
64
66
  },
65
67
  withOverrides(overrides) {
68
+ const overrideExtensions = overrides.extensions ?? [];
66
69
  const overriddenExtensionIds = new Set(
67
- overrides.extensions.map(
70
+ overrideExtensions.map(
68
71
  (e) => resolveExtensionDefinition(e, { namespace: pluginId }).id
69
72
  )
70
73
  );
@@ -76,7 +79,9 @@ function createFrontendPlugin(options) {
76
79
  return createFrontendPlugin({
77
80
  ...options,
78
81
  pluginId,
79
- extensions: [...nonOverriddenExtensions, ...overrides.extensions],
82
+ title: overrides.title ?? options.title,
83
+ icon: overrides.icon ?? options.icon,
84
+ extensions: [...nonOverriddenExtensions, ...overrideExtensions],
80
85
  info: {
81
86
  ...options.info,
82
87
  ...overrides.info
@@ -1 +1 @@
1
- {"version":3,"file":"createFrontendPlugin.esm.js","sources":["../../src/wiring/createFrontendPlugin.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 {\n OpaqueExtensionDefinition,\n OpaqueFrontendPlugin,\n} from '@internal/frontend';\nimport {\n ExtensionDefinition,\n OverridableExtensionDefinition,\n} from './createExtension';\nimport {\n Extension,\n resolveExtensionDefinition,\n} from './resolveExtensionDefinition';\nimport { FeatureFlagConfig } from './types';\nimport { MakeSortedExtensionsMap } from './MakeSortedExtensionsMap';\nimport { JsonObject } from '@backstage/types';\nimport { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing';\nimport { ID_PATTERN } from './constants';\n\n/**\n * Information about the plugin.\n *\n * @public\n * @remarks\n *\n * This interface is intended to be extended via [module\n * augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)\n * in order to add fields that are specific to each project.\n *\n * For example, one might add a `slackChannel` field that is read from the\n * opaque manifest file.\n *\n * See the options for `createApp` for more information about how to\n * customize the parsing of manifest files.\n */\nexport interface FrontendPluginInfo {\n /**\n * The name of the package that implements the plugin.\n */\n packageName?: string;\n\n /**\n * The version of the plugin, typically the version of the package.json file.\n */\n version?: string;\n\n /**\n * As short description of the plugin, typically the description field in\n * package.json.\n */\n description?: string;\n\n /**\n * The owner entity references of the plugin.\n */\n ownerEntityRefs?: string[];\n\n /**\n * Links related to the plugin.\n */\n links?: Array<{ title: string; url: string }>;\n}\n\n/**\n * Options for providing information for a plugin.\n *\n * @public\n */\nexport type FrontendPluginInfoOptions = {\n /**\n * A loader function for the package.json file for the plugin.\n */\n packageJson?: () => Promise<{ name: string } & JsonObject>;\n /**\n * A loader function for an opaque manifest file for the plugin.\n */\n manifest?: () => Promise<JsonObject>;\n};\n\n/**\n * A variant of the {@link FrontendPlugin} interface that can also be used to install overrides for the plugin.\n *\n * @public\n */\nexport interface OverridableFrontendPlugin<\n TRoutes extends { [name in string]: RouteRef | SubRouteRef } = {\n [name in string]: RouteRef | SubRouteRef;\n },\n TExternalRoutes extends { [name in string]: ExternalRouteRef } = {\n [name in string]: ExternalRouteRef;\n },\n TExtensionMap extends { [id in string]: ExtensionDefinition } = {\n [id in string]: ExtensionDefinition;\n },\n> extends FrontendPlugin<TRoutes, TExternalRoutes> {\n getExtension<TId extends keyof TExtensionMap>(\n id: TId,\n ): OverridableExtensionDefinition<TExtensionMap[TId]['T']>;\n withOverrides(options: {\n extensions: Array<ExtensionDefinition>;\n\n /**\n * Overrides the original info loaders of the plugin one by one.\n */\n info?: FrontendPluginInfoOptions;\n }): OverridableFrontendPlugin<TRoutes, TExternalRoutes, TExtensionMap>;\n}\n\n/** @public */\nexport interface FrontendPlugin<\n TRoutes extends { [name in string]: RouteRef | SubRouteRef } = {\n [name in string]: RouteRef | SubRouteRef;\n },\n TExternalRoutes extends { [name in string]: ExternalRouteRef } = {\n [name in string]: ExternalRouteRef;\n },\n> {\n readonly $$type: '@backstage/FrontendPlugin';\n /**\n * The plugin ID.\n */\n readonly pluginId: string;\n /**\n * Deprecated alias for `pluginId`.\n *\n * @deprecated Use `pluginId` instead.\n */\n readonly id: string;\n readonly routes: TRoutes;\n readonly externalRoutes: TExternalRoutes;\n\n /**\n * Loads the plugin info.\n */\n info(): Promise<FrontendPluginInfo>;\n}\n\n/** @public */\nexport interface PluginOptions<\n TId extends string,\n TRoutes extends { [name in string]: RouteRef | SubRouteRef },\n TExternalRoutes extends { [name in string]: ExternalRouteRef },\n TExtensions extends readonly ExtensionDefinition[],\n> {\n pluginId: TId;\n routes?: TRoutes;\n externalRoutes?: TExternalRoutes;\n extensions?: TExtensions;\n featureFlags?: FeatureFlagConfig[];\n info?: FrontendPluginInfoOptions;\n}\n\n/**\n * Creates a new plugin that can be installed in a Backstage app.\n *\n * @remarks\n *\n * Every plugin is created with a unique ID and a set of extensions\n * that are installed as part of the plugin.\n *\n * For more information on how plugins work, see the\n * {@link https://backstage.io/docs/frontend-system/building-plugins/index | documentation for plugins}\n * in the frontend system documentation.\n *\n * @example\n *\n * ```tsx\n * import { createFrontendPlugin } from '@backstage/frontend-plugin-api';\n *\n * export const examplePlugin = createFrontendPlugin({\n * pluginId: 'example',\n * extensions: [\n * PageBlueprint.make({\n * path: '/example',\n * loader: () => import('./ExamplePage').then(m => <m.ExamplePage />),\n * }),\n * ],\n * });\n * ```\n *\n * @public\n */\nexport function createFrontendPlugin<\n TId extends string,\n TExtensions extends readonly ExtensionDefinition[],\n TRoutes extends { [name in string]: RouteRef | SubRouteRef } = {},\n TExternalRoutes extends { [name in string]: ExternalRouteRef } = {},\n>(\n options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,\n): OverridableFrontendPlugin<\n TRoutes,\n TExternalRoutes,\n MakeSortedExtensionsMap<TExtensions[number], TId>\n> {\n const pluginId = options.pluginId;\n\n if (!ID_PATTERN.test(pluginId)) {\n // eslint-disable-next-line no-console\n console.warn(\n `WARNING: The pluginId '${pluginId}' will be invalid soon, please change it to match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`,\n );\n }\n\n const extensions = new Array<Extension<any>>();\n const extensionDefinitionsById = new Map<\n string,\n typeof OpaqueExtensionDefinition.TInternal\n >();\n\n for (const def of options.extensions ?? []) {\n const internal = OpaqueExtensionDefinition.toInternal(def);\n const ext = resolveExtensionDefinition(def, { namespace: pluginId });\n extensions.push(ext);\n extensionDefinitionsById.set(ext.id, {\n ...internal,\n namespace: pluginId,\n });\n }\n\n if (extensions.length !== extensionDefinitionsById.size) {\n const extensionIds = extensions.map(e => e.id);\n const duplicates = Array.from(\n new Set(\n extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index),\n ),\n );\n // TODO(Rugvip): This could provide some more information about the kind + name of the extensions\n throw new Error(\n `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join(\n ', ',\n )}`,\n );\n }\n\n return OpaqueFrontendPlugin.createInstance('v1', {\n pluginId,\n id: pluginId,\n routes: options.routes ?? ({} as TRoutes),\n externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes),\n featureFlags: options.featureFlags ?? [],\n extensions: extensions,\n infoOptions: options.info,\n\n // This method is overridden when the plugin instance is installed in an app\n async info() {\n throw new Error(\n `Attempted to load plugin info for plugin '${pluginId}', but the plugin instance is not installed in an app`,\n );\n },\n getExtension(id) {\n const ext = extensionDefinitionsById.get(id);\n if (!ext) {\n throw new Error(\n `Attempted to get non-existent extension '${id}' from plugin '${pluginId}'`,\n );\n }\n return ext;\n },\n toString() {\n return `Plugin{id=${pluginId}}`;\n },\n withOverrides(overrides) {\n const overriddenExtensionIds = new Set(\n overrides.extensions.map(\n e => resolveExtensionDefinition(e, { namespace: pluginId }).id,\n ),\n );\n const nonOverriddenExtensions = (options.extensions ?? []).filter(\n e =>\n !overriddenExtensionIds.has(\n resolveExtensionDefinition(e, { namespace: pluginId }).id,\n ),\n );\n return createFrontendPlugin({\n ...options,\n pluginId,\n extensions: [...nonOverriddenExtensions, ...overrides.extensions],\n info: {\n ...options.info,\n ...overrides.info,\n },\n });\n },\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"createFrontendPlugin.esm.js","sources":["../../src/wiring/createFrontendPlugin.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 {\n OpaqueExtensionDefinition,\n OpaqueFrontendPlugin,\n} from '@internal/frontend';\nimport {\n ExtensionDefinition,\n OverridableExtensionDefinition,\n} from './createExtension';\nimport {\n Extension,\n resolveExtensionDefinition,\n} from './resolveExtensionDefinition';\nimport { FeatureFlagConfig } from './types';\nimport { MakeSortedExtensionsMap } from './MakeSortedExtensionsMap';\nimport { JsonObject } from '@backstage/types';\nimport { IconElement } from '../icons/types';\nimport { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing';\nimport { ID_PATTERN } from './constants';\n\n/**\n * Information about the plugin.\n *\n * @public\n * @remarks\n *\n * This interface is intended to be extended via [module\n * augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)\n * in order to add fields that are specific to each project.\n *\n * For example, one might add a `slackChannel` field that is read from the\n * opaque manifest file.\n *\n * See the options for `createApp` for more information about how to\n * customize the parsing of manifest files.\n */\nexport interface FrontendPluginInfo {\n /**\n * The name of the package that implements the plugin.\n */\n packageName?: string;\n\n /**\n * The version of the plugin, typically the version of the package.json file.\n */\n version?: string;\n\n /**\n * As short description of the plugin, typically the description field in\n * package.json.\n */\n description?: string;\n\n /**\n * The owner entity references of the plugin.\n */\n ownerEntityRefs?: string[];\n\n /**\n * Links related to the plugin.\n */\n links?: Array<{ title: string; url: string }>;\n}\n\n/**\n * Options for providing information for a plugin.\n *\n * @public\n */\nexport type FrontendPluginInfoOptions = {\n /**\n * A loader function for the package.json file for the plugin.\n */\n packageJson?: () => Promise<{ name: string } & JsonObject>;\n /**\n * A loader function for an opaque manifest file for the plugin.\n */\n manifest?: () => Promise<JsonObject>;\n};\n\n/**\n * A variant of the {@link FrontendPlugin} interface that can also be used to install overrides for the plugin.\n *\n * @public\n */\nexport interface OverridableFrontendPlugin<\n TRoutes extends { [name in string]: RouteRef | SubRouteRef } = {\n [name in string]: RouteRef | SubRouteRef;\n },\n TExternalRoutes extends { [name in string]: ExternalRouteRef } = {\n [name in string]: ExternalRouteRef;\n },\n TExtensionMap extends { [id in string]: ExtensionDefinition } = {\n [id in string]: ExtensionDefinition;\n },\n> extends FrontendPlugin<TRoutes, TExternalRoutes> {\n getExtension<TId extends keyof TExtensionMap>(\n id: TId,\n ): OverridableExtensionDefinition<TExtensionMap[TId]['T']>;\n withOverrides(options: {\n extensions?: Array<ExtensionDefinition>;\n\n /**\n * Overrides the display title of the plugin.\n */\n title?: string;\n\n /**\n * Overrides the display icon of the plugin.\n */\n icon?: IconElement;\n\n /**\n * Overrides the original info loaders of the plugin one by one.\n */\n info?: FrontendPluginInfoOptions;\n }): OverridableFrontendPlugin<TRoutes, TExternalRoutes, TExtensionMap>;\n}\n\n/** @public */\nexport interface FrontendPlugin<\n TRoutes extends { [name in string]: RouteRef | SubRouteRef } = {\n [name in string]: RouteRef | SubRouteRef;\n },\n TExternalRoutes extends { [name in string]: ExternalRouteRef } = {\n [name in string]: ExternalRouteRef;\n },\n> {\n readonly $$type: '@backstage/FrontendPlugin';\n /**\n * The plugin ID.\n */\n readonly pluginId: string;\n /**\n * Deprecated alias for `pluginId`.\n *\n * @deprecated Use `pluginId` instead.\n */\n readonly id: string;\n /**\n * The display title of the plugin, used in page headers and navigation.\n * Falls back to the plugin ID if not provided.\n */\n readonly title?: string;\n /**\n * The display icon of the plugin, used in page headers and navigation.\n */\n readonly icon?: IconElement;\n readonly routes: TRoutes;\n readonly externalRoutes: TExternalRoutes;\n\n /**\n * Loads the plugin info.\n */\n info(): Promise<FrontendPluginInfo>;\n}\n\n/** @public */\nexport interface PluginOptions<\n TId extends string,\n TRoutes extends { [name in string]: RouteRef | SubRouteRef },\n TExternalRoutes extends { [name in string]: ExternalRouteRef },\n TExtensions extends readonly ExtensionDefinition[],\n> {\n pluginId: TId;\n /**\n * The display title of the plugin, used in page headers and navigation.\n * Falls back to the plugin ID if not provided.\n */\n title?: string;\n /**\n * The display icon of the plugin, used in page headers and navigation.\n */\n icon?: IconElement;\n routes?: TRoutes;\n externalRoutes?: TExternalRoutes;\n extensions?: TExtensions;\n featureFlags?: FeatureFlagConfig[];\n info?: FrontendPluginInfoOptions;\n}\n\n/**\n * Creates a new plugin that can be installed in a Backstage app.\n *\n * @remarks\n *\n * Every plugin is created with a unique ID and a set of extensions\n * that are installed as part of the plugin.\n *\n * For more information on how plugins work, see the\n * {@link https://backstage.io/docs/frontend-system/building-plugins/index | documentation for plugins}\n * in the frontend system documentation.\n *\n * @example\n *\n * ```tsx\n * import { createFrontendPlugin } from '@backstage/frontend-plugin-api';\n *\n * export const examplePlugin = createFrontendPlugin({\n * pluginId: 'example',\n * extensions: [\n * PageBlueprint.make({\n * path: '/example',\n * loader: () => import('./ExamplePage').then(m => <m.ExamplePage />),\n * }),\n * ],\n * });\n * ```\n *\n * @public\n */\nexport function createFrontendPlugin<\n TId extends string,\n TExtensions extends readonly ExtensionDefinition[],\n TRoutes extends { [name in string]: RouteRef | SubRouteRef } = {},\n TExternalRoutes extends { [name in string]: ExternalRouteRef } = {},\n>(\n options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,\n): OverridableFrontendPlugin<\n TRoutes,\n TExternalRoutes,\n MakeSortedExtensionsMap<TExtensions[number], TId>\n> {\n const pluginId = options.pluginId;\n\n if (!ID_PATTERN.test(pluginId)) {\n // eslint-disable-next-line no-console\n console.warn(\n `WARNING: The pluginId '${pluginId}' will be invalid soon, please change it to match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`,\n );\n }\n\n const extensions = new Array<Extension<any>>();\n const extensionDefinitionsById = new Map<\n string,\n typeof OpaqueExtensionDefinition.TInternal\n >();\n\n for (const def of options.extensions ?? []) {\n const internal = OpaqueExtensionDefinition.toInternal(def);\n const ext = resolveExtensionDefinition(def, { namespace: pluginId });\n extensions.push(ext);\n extensionDefinitionsById.set(ext.id, {\n ...internal,\n namespace: pluginId,\n });\n }\n\n if (extensions.length !== extensionDefinitionsById.size) {\n const extensionIds = extensions.map(e => e.id);\n const duplicates = Array.from(\n new Set(\n extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index),\n ),\n );\n // TODO(Rugvip): This could provide some more information about the kind + name of the extensions\n throw new Error(\n `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join(\n ', ',\n )}`,\n );\n }\n\n return OpaqueFrontendPlugin.createInstance('v1', {\n pluginId,\n id: pluginId,\n title: options.title,\n icon: options.icon,\n routes: options.routes ?? ({} as TRoutes),\n externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes),\n featureFlags: options.featureFlags ?? [],\n extensions: extensions,\n infoOptions: options.info,\n\n // This method is overridden when the plugin instance is installed in an app\n async info() {\n throw new Error(\n `Attempted to load plugin info for plugin '${pluginId}', but the plugin instance is not installed in an app`,\n );\n },\n getExtension(id) {\n const ext = extensionDefinitionsById.get(id);\n if (!ext) {\n throw new Error(\n `Attempted to get non-existent extension '${id}' from plugin '${pluginId}'`,\n );\n }\n return ext;\n },\n toString() {\n return `Plugin{id=${pluginId}}`;\n },\n withOverrides(overrides) {\n const overrideExtensions = overrides.extensions ?? [];\n const overriddenExtensionIds = new Set(\n overrideExtensions.map(\n e => resolveExtensionDefinition(e, { namespace: pluginId }).id,\n ),\n );\n const nonOverriddenExtensions = (options.extensions ?? []).filter(\n e =>\n !overriddenExtensionIds.has(\n resolveExtensionDefinition(e, { namespace: pluginId }).id,\n ),\n );\n return createFrontendPlugin({\n ...options,\n pluginId,\n title: overrides.title ?? options.title,\n icon: overrides.icon ?? options.icon,\n extensions: [...nonOverriddenExtensions, ...overrideExtensions],\n info: {\n ...options.info,\n ...overrides.info,\n },\n });\n },\n });\n}\n"],"names":[],"mappings":";;;;;;;AAkOO,SAAS,qBAMd,OAAA,EAKA;AACA,EAAA,MAAM,WAAW,OAAA,CAAQ,QAAA;AAEzB,EAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,QAAQ,CAAA,EAAG;AAE9B,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN,CAAA,uBAAA,EAA0B,QAAQ,CAAA,8DAAA,EAAiE,UAAU,CAAA,2DAAA;AAAA,KAC/G;AAAA,EACF;AAEA,EAAA,MAAM,UAAA,GAAa,IAAI,KAAA,EAAsB;AAC7C,EAAA,MAAM,wBAAA,uBAA+B,GAAA,EAGnC;AAEF,EAAA,KAAA,MAAW,GAAA,IAAO,OAAA,CAAQ,UAAA,IAAc,EAAC,EAAG;AAC1C,IAAA,MAAM,QAAA,GAAW,yBAAA,CAA0B,UAAA,CAAW,GAAG,CAAA;AACzD,IAAA,MAAM,MAAM,0BAAA,CAA2B,GAAA,EAAK,EAAE,SAAA,EAAW,UAAU,CAAA;AACnE,IAAA,UAAA,CAAW,KAAK,GAAG,CAAA;AACnB,IAAA,wBAAA,CAAyB,GAAA,CAAI,IAAI,EAAA,EAAI;AAAA,MACnC,GAAG,QAAA;AAAA,MACH,SAAA,EAAW;AAAA,KACZ,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,UAAA,CAAW,MAAA,KAAW,wBAAA,CAAyB,IAAA,EAAM;AACvD,IAAA,MAAM,YAAA,GAAe,UAAA,CAAW,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,EAAE,CAAA;AAC7C,IAAA,MAAM,aAAa,KAAA,CAAM,IAAA;AAAA,MACvB,IAAI,GAAA;AAAA,QACF,YAAA,CAAa,OAAO,CAAC,EAAA,EAAI,UAAU,YAAA,CAAa,OAAA,CAAQ,EAAE,CAAA,KAAM,KAAK;AAAA;AACvE,KACF;AAEA,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,QAAA,EAAW,QAAQ,CAAA,iCAAA,EAAoC,UAAA,CAAW,IAAA;AAAA,QAChE;AAAA,OACD,CAAA;AAAA,KACH;AAAA,EACF;AAEA,EAAA,OAAO,oBAAA,CAAqB,eAAe,IAAA,EAAM;AAAA,IAC/C,QAAA;AAAA,IACA,EAAA,EAAI,QAAA;AAAA,IACJ,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,MAAM,OAAA,CAAQ,IAAA;AAAA,IACd,MAAA,EAAQ,OAAA,CAAQ,MAAA,IAAW,EAAC;AAAA,IAC5B,cAAA,EAAgB,OAAA,CAAQ,cAAA,IAAmB,EAAC;AAAA,IAC5C,YAAA,EAAc,OAAA,CAAQ,YAAA,IAAgB,EAAC;AAAA,IACvC,UAAA;AAAA,IACA,aAAa,OAAA,CAAQ,IAAA;AAAA;AAAA,IAGrB,MAAM,IAAA,GAAO;AACX,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,6CAA6C,QAAQ,CAAA,qDAAA;AAAA,OACvD;AAAA,IACF,CAAA;AAAA,IACA,aAAa,EAAA,EAAI;AACf,MAAA,MAAM,GAAA,GAAM,wBAAA,CAAyB,GAAA,CAAI,EAAE,CAAA;AAC3C,MAAA,IAAI,CAAC,GAAA,EAAK;AACR,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,yCAAA,EAA4C,EAAE,CAAA,eAAA,EAAkB,QAAQ,CAAA,CAAA;AAAA,SAC1E;AAAA,MACF;AACA,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAAA,IACA,QAAA,GAAW;AACT,MAAA,OAAO,aAAa,QAAQ,CAAA,CAAA,CAAA;AAAA,IAC9B,CAAA;AAAA,IACA,cAAc,SAAA,EAAW;AACvB,MAAA,MAAM,kBAAA,GAAqB,SAAA,CAAU,UAAA,IAAc,EAAC;AACpD,MAAA,MAAM,yBAAyB,IAAI,GAAA;AAAA,QACjC,kBAAA,CAAmB,GAAA;AAAA,UACjB,OAAK,0BAAA,CAA2B,CAAA,EAAG,EAAE,SAAA,EAAW,QAAA,EAAU,CAAA,CAAE;AAAA;AAC9D,OACF;AACA,MAAA,MAAM,uBAAA,GAAA,CAA2B,OAAA,CAAQ,UAAA,IAAc,EAAC,EAAG,MAAA;AAAA,QACzD,CAAA,CAAA,KACE,CAAC,sBAAA,CAAuB,GAAA;AAAA,UACtB,2BAA2B,CAAA,EAAG,EAAE,SAAA,EAAW,QAAA,EAAU,CAAA,CAAE;AAAA;AACzD,OACJ;AACA,MAAA,OAAO,oBAAA,CAAqB;AAAA,QAC1B,GAAG,OAAA;AAAA,QACH,QAAA;AAAA,QACA,KAAA,EAAO,SAAA,CAAU,KAAA,IAAS,OAAA,CAAQ,KAAA;AAAA,QAClC,IAAA,EAAM,SAAA,CAAU,IAAA,IAAQ,OAAA,CAAQ,IAAA;AAAA,QAChC,UAAA,EAAY,CAAC,GAAG,uBAAA,EAAyB,GAAG,kBAAkB,CAAA;AAAA,QAC9D,IAAA,EAAM;AAAA,UACJ,GAAG,OAAA,CAAQ,IAAA;AAAA,UACX,GAAG,SAAA,CAAU;AAAA;AACf,OACD,CAAA;AAAA,IACH;AAAA,GACD,CAAA;AACH;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/frontend-plugin-api",
3
- "version": "0.14.0-next.2",
3
+ "version": "0.14.1",
4
4
  "backstage": {
5
5
  "role": "web-library"
6
6
  },
@@ -52,18 +52,18 @@
52
52
  "test": "backstage-cli package test"
53
53
  },
54
54
  "dependencies": {
55
- "@backstage/errors": "1.2.7",
56
- "@backstage/types": "1.2.2",
57
- "@backstage/version-bridge": "1.0.12-next.0",
55
+ "@backstage/errors": "^1.2.7",
56
+ "@backstage/types": "^1.2.2",
57
+ "@backstage/version-bridge": "^1.0.12",
58
58
  "zod": "^3.25.76",
59
59
  "zod-to-json-schema": "^3.25.1"
60
60
  },
61
61
  "devDependencies": {
62
- "@backstage/cli": "0.35.4-next.2",
63
- "@backstage/config": "1.3.6",
64
- "@backstage/frontend-app-api": "0.15.0-next.2",
65
- "@backstage/frontend-test-utils": "0.5.0-next.2",
66
- "@backstage/test-utils": "1.7.15-next.2",
62
+ "@backstage/cli": "^0.35.4",
63
+ "@backstage/config": "^1.3.6",
64
+ "@backstage/frontend-app-api": "^0.15.0",
65
+ "@backstage/frontend-test-utils": "^0.5.0",
66
+ "@backstage/test-utils": "^1.7.15",
67
67
  "@testing-library/jest-dom": "^6.0.0",
68
68
  "@testing-library/react": "^16.0.0",
69
69
  "@types/react": "^18.0.0",