@backstage/plugin-app 0.4.0-next.2 → 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,36 @@
1
1
  # @backstage/plugin-app
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ef6916e: Added `SubPageBlueprint` for creating sub-page tabs, `PluginHeaderActionBlueprint` and `PluginHeaderActionsApi` for plugin-scoped header actions, and `PageLayout` as a swappable component. The `PageBlueprint` now supports sub-pages with tabbed navigation, page title, icon, and header actions. Plugins can now specify a `title` and `icon` in `createFrontendPlugin`.
8
+ - 7edb810: **BREAKING**: Extensions created with the following blueprints must now be provided via an override or a module for the `app` plugin. Extensions from other plugins will now trigger a warning in the app and be ignored.
9
+
10
+ - `IconBundleBlueprint`
11
+ - `NavContentBlueprint`
12
+ - `RouterBlueprint`
13
+ - `SignInPageBlueprint`
14
+ - `SwappableComponentBlueprint`
15
+ - `ThemeBlueprint`
16
+ - `TranslationBlueprint`
17
+
18
+ ### Patch Changes
19
+
20
+ - a2133be: Added new `NavContentNavItem`, `NavContentNavItems`, and `navItems` prop to `NavContentComponentProps` for auto-discovering navigation items from page extensions. The new `navItems` collection supports `take(id)` and `rest()` methods for placing specific items in custom sidebar positions, as well as `withComponent(Component)` which returns a `NavContentNavItemsWithComponent` for rendering items directly as elements. The existing `items` prop is now deprecated in favor of `navItems`.
21
+ - a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings.
22
+ - 69d880e: Bump to latest zod to ensure it has the latest features
23
+ - Updated dependencies
24
+ - @backstage/ui@0.12.0
25
+ - @backstage/core-components@0.18.7
26
+ - @backstage/theme@0.7.2
27
+ - @backstage/frontend-plugin-api@0.14.0
28
+ - @backstage/plugin-app-react@0.2.0
29
+ - @backstage/core-plugin-api@1.12.3
30
+ - @backstage/integration-react@1.2.15
31
+ - @backstage/plugin-permission-react@0.4.40
32
+ - @backstage/version-bridge@1.0.12
33
+
3
34
  ## 0.4.0-next.2
4
35
 
5
36
  ### Patch Changes
@@ -0,0 +1,24 @@
1
+ const EMPTY_ACTIONS = new Array();
2
+ class DefaultPluginHeaderActionsApi {
3
+ constructor(actionsByPlugin) {
4
+ this.actionsByPlugin = actionsByPlugin;
5
+ }
6
+ getPluginHeaderActions(pluginId) {
7
+ return this.actionsByPlugin.get(pluginId) ?? EMPTY_ACTIONS;
8
+ }
9
+ static fromActions(actions) {
10
+ const actionsByPlugin = /* @__PURE__ */ new Map();
11
+ for (const action of actions) {
12
+ let pluginActions = actionsByPlugin.get(action.pluginId);
13
+ if (!pluginActions) {
14
+ pluginActions = [];
15
+ actionsByPlugin.set(action.pluginId, pluginActions);
16
+ }
17
+ pluginActions.push(action.element);
18
+ }
19
+ return new DefaultPluginHeaderActionsApi(actionsByPlugin);
20
+ }
21
+ }
22
+
23
+ export { DefaultPluginHeaderActionsApi };
24
+ //# sourceMappingURL=DefaultPluginHeaderActionsApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DefaultPluginHeaderActionsApi.esm.js","sources":["../../../src/apis/PluginHeaderActionsApi/DefaultPluginHeaderActionsApi.tsx"],"sourcesContent":["/*\n * Copyright 2026 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 { type PluginHeaderActionsApi } from '@backstage/frontend-plugin-api';\n\n// Stable reference\nconst EMPTY_ACTIONS = new Array<JSX.Element | null>();\n\ntype ActionInput = {\n element: JSX.Element;\n pluginId: string;\n};\n\n/**\n * Default implementation of PluginHeaderActionsApi.\n *\n * @internal\n */\nexport class DefaultPluginHeaderActionsApi implements PluginHeaderActionsApi {\n constructor(\n private readonly actionsByPlugin: Map<string, Array<JSX.Element | null>>,\n ) {}\n\n getPluginHeaderActions(pluginId: string): Array<JSX.Element | null> {\n return this.actionsByPlugin.get(pluginId) ?? EMPTY_ACTIONS;\n }\n\n static fromActions(\n actions: Array<ActionInput>,\n ): DefaultPluginHeaderActionsApi {\n const actionsByPlugin = new Map<string, Array<JSX.Element | null>>();\n\n for (const action of actions) {\n let pluginActions = actionsByPlugin.get(action.pluginId);\n if (!pluginActions) {\n pluginActions = [];\n actionsByPlugin.set(action.pluginId, pluginActions);\n }\n\n pluginActions.push(action.element);\n }\n\n return new DefaultPluginHeaderActionsApi(actionsByPlugin);\n }\n}\n"],"names":[],"mappings":"AAoBA,MAAM,aAAA,GAAgB,IAAI,KAAA,EAA0B;AAY7C,MAAM,6BAAA,CAAgE;AAAA,EAC3E,YACmB,eAAA,EACjB;AADiB,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AAAA,EAChB;AAAA,EAEH,uBAAuB,QAAA,EAA6C;AAClE,IAAA,OAAO,IAAA,CAAK,eAAA,CAAgB,GAAA,CAAI,QAAQ,CAAA,IAAK,aAAA;AAAA,EAC/C;AAAA,EAEA,OAAO,YACL,OAAA,EAC+B;AAC/B,IAAA,MAAM,eAAA,uBAAsB,GAAA,EAAuC;AAEnE,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,IAAI,aAAA,GAAgB,eAAA,CAAgB,GAAA,CAAI,MAAA,CAAO,QAAQ,CAAA;AACvD,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA,aAAA,GAAgB,EAAC;AACjB,QAAA,eAAA,CAAgB,GAAA,CAAI,MAAA,CAAO,QAAA,EAAU,aAAa,CAAA;AAAA,MACpD;AAEA,MAAA,aAAA,CAAc,IAAA,CAAK,OAAO,OAAO,CAAA;AAAA,IACnC;AAEA,IAAA,OAAO,IAAI,8BAA8B,eAAe,CAAA;AAAA,EAC1D;AACF;;;;"}
@@ -1,31 +1,74 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { createExtension, coreExtensionData, createExtensionInput, NavItemBlueprint, useApi, routeResolutionApiRef } from '@backstage/frontend-plugin-api';
2
+ import { createExtension, coreExtensionData, createExtensionInput, NavItemBlueprint, useApi, appTreeApiRef, routeResolutionApiRef } from '@backstage/frontend-plugin-api';
3
3
  import { NavContentBlueprint } from '@backstage/plugin-app-react';
4
4
  import { Sidebar, SidebarItem } from '@backstage/core-components';
5
5
  import { useMemo } from 'react';
6
6
 
7
+ class NavItemBag {
8
+ #items;
9
+ #index;
10
+ #taken;
11
+ constructor(items, taken) {
12
+ this.#items = items;
13
+ this.#index = new Map(items.map((item) => [item.node.spec.id, item]));
14
+ this.#taken = new Set(taken);
15
+ }
16
+ take(id) {
17
+ const item = this.#index.get(id);
18
+ if (item) {
19
+ this.#taken.add(id);
20
+ }
21
+ return item;
22
+ }
23
+ rest() {
24
+ return this.#items.filter((item) => !this.#taken.has(item.node.spec.id));
25
+ }
26
+ clone() {
27
+ return new NavItemBag(this.#items, this.#taken);
28
+ }
29
+ withComponent(Component) {
30
+ return {
31
+ take: (id) => {
32
+ const item = this.take(id);
33
+ return item ? /* @__PURE__ */ jsx(Component, { ...item }) : null;
34
+ },
35
+ rest: (options) => {
36
+ const items = this.rest();
37
+ if (options?.sortBy === "title") {
38
+ items.sort((a, b) => a.title.localeCompare(b.title));
39
+ }
40
+ return items.map((item) => /* @__PURE__ */ jsx(Component, { ...item }, item.node.spec.id));
41
+ }
42
+ };
43
+ }
44
+ }
7
45
  function DefaultNavContent(props) {
8
- return /* @__PURE__ */ jsx(Sidebar, { children: props.items.map((item, index) => /* @__PURE__ */ jsx(
46
+ const items = props.navItems.rest();
47
+ return /* @__PURE__ */ jsx(Sidebar, { children: items.map((item) => /* @__PURE__ */ jsx(
9
48
  SidebarItem,
10
49
  {
11
- to: item.to,
12
- icon: item.icon,
13
- text: item.text
50
+ to: item.href,
51
+ icon: () => item.icon,
52
+ text: item.title
14
53
  },
15
- index
54
+ item.node.spec.id
16
55
  )) });
17
56
  }
57
+ function tryResolveLink(routeResolutionApi, routeRef) {
58
+ try {
59
+ const link = routeResolutionApi.resolve(routeRef);
60
+ return link?.();
61
+ } catch {
62
+ return void 0;
63
+ }
64
+ }
18
65
  function NavContentRenderer(props) {
66
+ const appTreeApi = useApi(appTreeApiRef);
19
67
  const routeResolutionApi = useApi(routeResolutionApiRef);
20
- const items = useMemo(() => {
21
- return props.items.flatMap((item) => {
68
+ const legacyItems = useMemo(() => {
69
+ return props.legacyNavItems.flatMap((item) => {
22
70
  const link = routeResolutionApi.resolve(item.routeRef);
23
- if (!link) {
24
- console.warn(
25
- `NavItemBlueprint: unable to resolve route ref ${item.routeRef}`
26
- );
27
- return [];
28
- }
71
+ if (!link) return [];
29
72
  return [
30
73
  {
31
74
  to: link(),
@@ -36,8 +79,50 @@ function NavContentRenderer(props) {
36
79
  }
37
80
  ];
38
81
  });
39
- }, [props.items, routeResolutionApi]);
40
- return /* @__PURE__ */ jsx(props.Content, { items });
82
+ }, [props.legacyNavItems, routeResolutionApi]);
83
+ const navItems = useMemo(() => {
84
+ const { tree } = appTreeApi.getTree();
85
+ const routesNode = tree.nodes.get("app/routes");
86
+ if (!routesNode) return new NavItemBag([]);
87
+ const navItemsByRouteRef = new Map(props.legacyNavItems.map((item) => [item.routeRef, item]));
88
+ const pageNodes = routesNode.edges.attachments.get("routes") ?? [];
89
+ const items = pageNodes.flatMap((node) => {
90
+ if (!node.instance || node.spec.disabled) {
91
+ return [];
92
+ }
93
+ const routeRef = node.instance.getData(coreExtensionData.routeRef);
94
+ if (!routeRef) {
95
+ return [];
96
+ }
97
+ const matchingNavItem = navItemsByRouteRef.get(routeRef);
98
+ const resolvedTitle = node.instance.getData(coreExtensionData.title);
99
+ const pluginTitle = node.spec.plugin.title;
100
+ const pluginId = node.spec.plugin.pluginId;
101
+ const hasExplicitPageTitle = resolvedTitle !== void 0 && resolvedTitle !== pluginTitle && resolvedTitle !== pluginId;
102
+ const title = hasExplicitPageTitle ? resolvedTitle : matchingNavItem?.title ?? pluginTitle ?? pluginId;
103
+ const resolvedIcon = node.instance.getData(coreExtensionData.icon);
104
+ const hasExplicitPageIcon = resolvedIcon && !node.spec.plugin.icon;
105
+ const NavItemIcon = matchingNavItem?.icon;
106
+ let icon;
107
+ if (hasExplicitPageIcon) {
108
+ icon = resolvedIcon;
109
+ } else if (NavItemIcon) {
110
+ icon = /* @__PURE__ */ jsx(NavItemIcon, {});
111
+ } else if (resolvedIcon) {
112
+ icon = resolvedIcon;
113
+ }
114
+ if (!title || !icon) {
115
+ return [];
116
+ }
117
+ const to = tryResolveLink(routeResolutionApi, routeRef);
118
+ if (!to) {
119
+ return [];
120
+ }
121
+ return [{ node, href: to, title, icon, routeRef }];
122
+ });
123
+ return new NavItemBag(items);
124
+ }, [appTreeApi, routeResolutionApi, props.legacyNavItems]);
125
+ return /* @__PURE__ */ jsx(props.Content, { navItems, items: legacyItems });
41
126
  }
42
127
  const AppNav = createExtension({
43
128
  name: "nav",
@@ -57,7 +142,7 @@ const AppNav = createExtension({
57
142
  /* @__PURE__ */ jsx(
58
143
  NavContentRenderer,
59
144
  {
60
- items: inputs.items.map(
145
+ legacyNavItems: inputs.items.map(
61
146
  (item) => item.get(NavItemBlueprint.dataRefs.target)
62
147
  ),
63
148
  Content
@@ -1 +1 @@
1
- {"version":3,"file":"AppNav.esm.js","sources":["../../src/extensions/AppNav.tsx"],"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 createExtension,\n coreExtensionData,\n createExtensionInput,\n NavItemBlueprint,\n routeResolutionApiRef,\n IconComponent,\n RouteRef,\n useApi,\n} from '@backstage/frontend-plugin-api';\nimport {\n NavContentBlueprint,\n NavContentComponent,\n NavContentComponentProps,\n} from '@backstage/plugin-app-react';\nimport { Sidebar, SidebarItem } from '@backstage/core-components';\nimport { useMemo } from 'react';\n\nfunction DefaultNavContent(props: NavContentComponentProps) {\n return (\n <Sidebar>\n {props.items.map((item, index) => (\n <SidebarItem\n to={item.to}\n icon={item.icon}\n text={item.text}\n key={index}\n />\n ))}\n </Sidebar>\n );\n}\n\n// This helps defer rendering until the app is being rendered, which is needed\n// because the RouteResolutionApi can't be called until the app has been fully initialized.\nfunction NavContentRenderer(props: {\n Content: NavContentComponent;\n items: Array<{\n title: string;\n icon: IconComponent;\n routeRef: RouteRef<undefined>;\n }>;\n}) {\n const routeResolutionApi = useApi(routeResolutionApiRef);\n\n const items = useMemo(() => {\n return props.items.flatMap(item => {\n const link = routeResolutionApi.resolve(item.routeRef);\n if (!link) {\n // eslint-disable-next-line no-console\n console.warn(\n `NavItemBlueprint: unable to resolve route ref ${item.routeRef}`,\n );\n return [];\n }\n return [\n {\n to: link(),\n text: item.title,\n icon: item.icon,\n title: item.title,\n routeRef: item.routeRef,\n },\n ];\n });\n }, [props.items, routeResolutionApi]);\n\n return <props.Content items={items} />;\n}\n\nexport const AppNav = createExtension({\n name: 'nav',\n attachTo: { id: 'app/layout', input: 'nav' },\n inputs: {\n items: createExtensionInput([NavItemBlueprint.dataRefs.target]),\n content: createExtensionInput([NavContentBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n },\n output: [coreExtensionData.reactElement],\n *factory({ inputs }) {\n const Content =\n inputs.content?.get(NavContentBlueprint.dataRefs.component) ??\n DefaultNavContent;\n\n yield coreExtensionData.reactElement(\n <NavContentRenderer\n items={inputs.items.map(item =>\n item.get(NavItemBlueprint.dataRefs.target),\n )}\n Content={Content}\n />,\n );\n },\n});\n"],"names":[],"mappings":";;;;;;AAkCA,SAAS,kBAAkB,KAAA,EAAiC;AAC1D,EAAA,2BACG,OAAA,EAAA,EACE,QAAA,EAAA,KAAA,CAAM,MAAM,GAAA,CAAI,CAAC,MAAM,KAAA,qBACtB,GAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,IAAI,IAAA,CAAK,EAAA;AAAA,MACT,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,MAAM,IAAA,CAAK;AAAA,KAAA;AAAA,IACN;AAAA,GAER,CAAA,EACH,CAAA;AAEJ;AAIA,SAAS,mBAAmB,KAAA,EAOzB;AACD,EAAA,MAAM,kBAAA,GAAqB,OAAO,qBAAqB,CAAA;AAEvD,EAAA,MAAM,KAAA,GAAQ,QAAQ,MAAM;AAC1B,IAAA,OAAO,KAAA,CAAM,KAAA,CAAM,OAAA,CAAQ,CAAA,IAAA,KAAQ;AACjC,MAAA,MAAM,IAAA,GAAO,kBAAA,CAAmB,OAAA,CAAQ,IAAA,CAAK,QAAQ,CAAA;AACrD,MAAA,IAAI,CAAC,IAAA,EAAM;AAET,QAAA,OAAA,CAAQ,IAAA;AAAA,UACN,CAAA,8CAAA,EAAiD,KAAK,QAAQ,CAAA;AAAA,SAChE;AACA,QAAA,OAAO,EAAC;AAAA,MACV;AACA,MAAA,OAAO;AAAA,QACL;AAAA,UACE,IAAI,IAAA,EAAK;AAAA,UACT,MAAM,IAAA,CAAK,KAAA;AAAA,UACX,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,OAAO,IAAA,CAAK,KAAA;AAAA,UACZ,UAAU,IAAA,CAAK;AAAA;AACjB,OACF;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,CAAC,KAAA,CAAM,KAAA,EAAO,kBAAkB,CAAC,CAAA;AAEpC,EAAA,uBAAO,GAAA,CAAC,KAAA,CAAM,OAAA,EAAN,EAAc,KAAA,EAAc,CAAA;AACtC;AAEO,MAAM,SAAS,eAAA,CAAgB;AAAA,EACpC,IAAA,EAAM,KAAA;AAAA,EACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,KAAA,EAAM;AAAA,EAC3C,MAAA,EAAQ;AAAA,IACN,OAAO,oBAAA,CAAqB,CAAC,gBAAA,CAAiB,QAAA,CAAS,MAAM,CAAC,CAAA;AAAA,IAC9D,SAAS,oBAAA,CAAqB,CAAC,mBAAA,CAAoB,QAAA,CAAS,SAAS,CAAA,EAAG;AAAA,MACtE,SAAA,EAAW,IAAA;AAAA,MACX,QAAA,EAAU,IAAA;AAAA,MACV,QAAA,EAAU;AAAA,KACX;AAAA,GACH;AAAA,EACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,EACvC,CAAC,OAAA,CAAQ,EAAE,MAAA,EAAO,EAAG;AACnB,IAAA,MAAM,UACJ,MAAA,CAAO,OAAA,EAAS,IAAI,mBAAA,CAAoB,QAAA,CAAS,SAAS,CAAA,IAC1D,iBAAA;AAEF,IAAA,MAAM,iBAAA,CAAkB,YAAA;AAAA,sBACtB,GAAA;AAAA,QAAC,kBAAA;AAAA,QAAA;AAAA,UACC,KAAA,EAAO,OAAO,KAAA,CAAM,GAAA;AAAA,YAAI,CAAA,IAAA,KACtB,IAAA,CAAK,GAAA,CAAI,gBAAA,CAAiB,SAAS,MAAM;AAAA,WAC3C;AAAA,UACA;AAAA;AAAA;AACF,KACF;AAAA,EACF;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"AppNav.esm.js","sources":["../../src/extensions/AppNav.tsx"],"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 createExtension,\n coreExtensionData,\n createExtensionInput,\n NavItemBlueprint,\n routeResolutionApiRef,\n appTreeApiRef,\n IconComponent,\n IconElement,\n RouteRef,\n RouteResolutionApi,\n useApi,\n} from '@backstage/frontend-plugin-api';\nimport {\n NavContentBlueprint,\n NavContentComponent,\n NavContentComponentProps,\n NavContentNavItem,\n NavContentNavItems,\n} from '@backstage/plugin-app-react';\nimport { Sidebar, SidebarItem } from '@backstage/core-components';\nimport { useMemo } from 'react';\n\nclass NavItemBag implements NavContentNavItems {\n readonly #items: NavContentNavItem[];\n readonly #index: Map<string, NavContentNavItem>;\n readonly #taken: Set<string>;\n\n constructor(items: NavContentNavItem[], taken?: Iterable<string>) {\n this.#items = items;\n this.#index = new Map(items.map(item => [item.node.spec.id, item]));\n this.#taken = new Set(taken);\n }\n\n take(id: string): NavContentNavItem | undefined {\n const item = this.#index.get(id);\n if (item) {\n this.#taken.add(id);\n }\n return item;\n }\n\n rest(): NavContentNavItem[] {\n return this.#items.filter(item => !this.#taken.has(item.node.spec.id));\n }\n\n clone(): NavContentNavItems {\n return new NavItemBag(this.#items, this.#taken);\n }\n\n withComponent(Component: (props: NavContentNavItem) => JSX.Element) {\n return {\n take: (id: string) => {\n const item = this.take(id);\n return item ? <Component {...item} /> : null;\n },\n rest: (options?: { sortBy?: 'title' }) => {\n const items = this.rest();\n if (options?.sortBy === 'title') {\n items.sort((a, b) => a.title.localeCompare(b.title));\n }\n return items.map(item => (\n <Component key={item.node.spec.id} {...item} />\n ));\n },\n };\n }\n}\n\nfunction DefaultNavContent(props: NavContentComponentProps) {\n const items = props.navItems.rest();\n return (\n <Sidebar>\n {items.map(item => (\n <SidebarItem\n to={item.href}\n icon={() => item.icon}\n text={item.title}\n key={item.node.spec.id}\n />\n ))}\n </Sidebar>\n );\n}\n\n// Tries to resolve a routeRef to a link path, returning undefined if it\n// can't be resolved (e.g. parameterized routes).\nfunction tryResolveLink(\n routeResolutionApi: RouteResolutionApi,\n routeRef: RouteRef,\n): string | undefined {\n try {\n const link = routeResolutionApi.resolve(routeRef);\n return link?.();\n } catch {\n return undefined;\n }\n}\n\n// Defers rendering until the app is fully initialized so that APIs like\n// RouteResolutionApi and AppTreeApi are available.\nfunction NavContentRenderer(props: {\n Content: NavContentComponent;\n legacyNavItems: Array<{\n title: string;\n icon: IconComponent;\n routeRef: RouteRef<undefined>;\n }>;\n}) {\n const appTreeApi = useApi(appTreeApiRef);\n const routeResolutionApi = useApi(routeResolutionApiRef);\n\n // Deprecated items: just resolve nav item routeRefs to paths, no page discovery.\n const legacyItems = useMemo(() => {\n return props.legacyNavItems.flatMap(item => {\n const link = routeResolutionApi.resolve(item.routeRef);\n if (!link) return [];\n return [\n {\n to: link(),\n text: item.title,\n icon: item.icon,\n title: item.title,\n routeRef: item.routeRef,\n },\n ];\n });\n }, [props.legacyNavItems, routeResolutionApi]);\n\n // New navItems: discover pages from the extension tree, merged with nav items.\n const navItems = useMemo(() => {\n const { tree } = appTreeApi.getTree();\n const routesNode = tree.nodes.get('app/routes');\n if (!routesNode) return new NavItemBag([]);\n\n // Index nav items by routeRef for matching against pages\n const navItemsByRouteRef = new Map<\n RouteRef,\n { title: string; icon: IconComponent }\n >(props.legacyNavItems.map(item => [item.routeRef, item]));\n\n const pageNodes = routesNode.edges.attachments.get('routes') ?? [];\n const items = pageNodes.flatMap((node): NavContentNavItem[] => {\n if (!node.instance || node.spec.disabled) {\n return [];\n }\n\n const routeRef = node.instance.getData(coreExtensionData.routeRef);\n if (!routeRef) {\n return [];\n }\n\n const matchingNavItem = navItemsByRouteRef.get(routeRef);\n\n // PageBlueprint resolves title as: config.title ?? params.title ?? plugin.title ?? pluginId\n // We want the priority: page (config/params) -> nav item -> plugin -> pluginId\n const resolvedTitle = node.instance.getData(coreExtensionData.title);\n const pluginTitle = node.spec.plugin.title;\n const pluginId = node.spec.plugin.pluginId;\n const hasExplicitPageTitle =\n resolvedTitle !== undefined &&\n resolvedTitle !== pluginTitle &&\n resolvedTitle !== pluginId;\n const title = hasExplicitPageTitle\n ? resolvedTitle\n : matchingNavItem?.title ?? pluginTitle ?? pluginId;\n\n // PageBlueprint resolves icon as: params.icon ?? plugin.icon\n // We want the priority: page (params) -> nav item -> plugin -> (excluded)\n const resolvedIcon = node.instance.getData(coreExtensionData.icon);\n const hasExplicitPageIcon = resolvedIcon && !node.spec.plugin.icon;\n const NavItemIcon = matchingNavItem?.icon;\n\n let icon: IconElement | undefined;\n if (hasExplicitPageIcon) {\n icon = resolvedIcon;\n } else if (NavItemIcon) {\n icon = <NavItemIcon />;\n } else if (resolvedIcon) {\n icon = resolvedIcon;\n }\n\n if (!title || !icon) {\n return [];\n }\n\n const to = tryResolveLink(routeResolutionApi, routeRef);\n if (!to) {\n return [];\n }\n\n return [{ node, href: to, title, icon, routeRef }];\n });\n\n return new NavItemBag(items);\n }, [appTreeApi, routeResolutionApi, props.legacyNavItems]);\n\n return <props.Content navItems={navItems} items={legacyItems} />;\n}\n\nexport const AppNav = createExtension({\n name: 'nav',\n attachTo: { id: 'app/layout', input: 'nav' },\n inputs: {\n items: createExtensionInput([NavItemBlueprint.dataRefs.target]),\n content: createExtensionInput([NavContentBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n },\n output: [coreExtensionData.reactElement],\n *factory({ inputs }) {\n const Content =\n inputs.content?.get(NavContentBlueprint.dataRefs.component) ??\n DefaultNavContent;\n\n yield coreExtensionData.reactElement(\n <NavContentRenderer\n legacyNavItems={inputs.items.map(item =>\n item.get(NavItemBlueprint.dataRefs.target),\n )}\n Content={Content}\n />,\n );\n },\n});\n"],"names":[],"mappings":"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"}
@@ -115,8 +115,8 @@ function DefaultRouter(props) {
115
115
  {
116
116
  basename: basePath,
117
117
  future: {
118
- v7_relativeSplatPath: true,
119
- v7_startTransition: true
118
+ v7_relativeSplatPath: false,
119
+ v7_startTransition: false
120
120
  },
121
121
  children: props.children
122
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AppRoot.esm.js","sources":["../../src/extensions/AppRoot.tsx"],"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 ComponentType,\n PropsWithChildren,\n ReactNode,\n useState,\n JSX,\n} from 'react';\nimport {\n coreExtensionData,\n discoveryApiRef,\n fetchApiRef,\n errorApiRef,\n createExtension,\n createExtensionInput,\n routeResolutionApiRef,\n} from '@backstage/frontend-plugin-api';\nimport {\n AppRootWrapperBlueprint,\n RouterBlueprint,\n SignInPageBlueprint,\n} from '@backstage/plugin-app-react';\nimport {\n DiscoveryApi,\n ErrorApi,\n FetchApi,\n IdentityApi,\n ProfileInfo,\n SignInPageProps,\n configApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp';\nimport { BrowserRouter } from 'react-router-dom';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/RouteTracker';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { getBasePath } from '../../../../packages/frontend-app-api/src/routing/getBasePath';\n\nexport const AppRoot = createExtension({\n name: 'root',\n attachTo: { id: 'app', input: 'root' },\n inputs: {\n router: createExtensionInput([RouterBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n children: createExtensionInput([coreExtensionData.reactElement], {\n singleton: true,\n }),\n elements: createExtensionInput([coreExtensionData.reactElement]),\n wrappers: createExtensionInput(\n [AppRootWrapperBlueprint.dataRefs.component],\n {\n internal: true,\n },\n ),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs, apis }) {\n if (isProtectedApp()) {\n const identityApi = apis.get(identityApiRef);\n if (!identityApi) {\n throw new Error('App requires an Identity API implementation');\n }\n const appIdentityProxy = toAppIdentityProxy(identityApi);\n const discoveryApi = apis.get(discoveryApiRef);\n const errorApi = apis.get(errorApiRef);\n const fetchApi = apis.get(fetchApiRef);\n if (!discoveryApi || !errorApi || !fetchApi) {\n throw new Error(\n 'App is running in protected mode but missing required APIs',\n );\n }\n appIdentityProxy.enableCookieAuth({\n discoveryApi,\n errorApi,\n fetchApi,\n });\n }\n\n let content: ReactNode = inputs.children.get(\n coreExtensionData.reactElement,\n );\n\n for (const wrapper of inputs.wrappers) {\n const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component);\n if (Component) {\n content = <Component>{content}</Component>;\n }\n }\n\n return [\n coreExtensionData.reactElement(\n <AppRouter\n SignInPageComponent={inputs.signInPage?.get(\n SignInPageBlueprint.dataRefs.component,\n )}\n RouterComponent={inputs.router?.get(\n RouterBlueprint.dataRefs.component,\n )}\n extraElements={inputs.elements?.map(el =>\n el.get(coreExtensionData.reactElement),\n )}\n >\n {content}\n </AppRouter>,\n ),\n ];\n },\n});\n\n// This wraps the sign-in page and waits for sign-in to be completed before rendering the app\nfunction SignInPageWrapper({\n component: Component,\n appIdentityProxy,\n children,\n}: {\n component: ComponentType<SignInPageProps>;\n appIdentityProxy: AppIdentityProxy;\n children: ReactNode;\n}) {\n const [identityApi, setIdentityApi] = useState<IdentityApi>();\n const configApi = useApi(configApiRef);\n const basePath = getBasePath(configApi);\n\n if (!identityApi) {\n return <Component onSignInSuccess={setIdentityApi} />;\n }\n\n appIdentityProxy.setTarget(identityApi, {\n signOutTargetUrl: basePath || '/',\n });\n return <>{children}</>;\n}\n\ntype AppIdentityProxy = IdentityApi & {\n enableCookieAuth(ctx: {\n errorApi: ErrorApi;\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n }): void;\n setTarget(\n impl: IdentityApi & /* backwards compat stuff */ {\n getUserId?(): string;\n getIdToken?(): Promise<string | undefined>;\n getProfile?(): ProfileInfo;\n },\n options: { signOutTargetUrl: string },\n ): void;\n};\n\nfunction toAppIdentityProxy(identityApi: IdentityApi): AppIdentityProxy {\n if (!('enableCookieAuth' in identityApi)) {\n throw new Error('Unexpected Identity API implementation');\n }\n return identityApi as AppIdentityProxy;\n}\n\ntype RouteResolverProxy = {\n getRouteObjects(): any[];\n};\n\n/**\n * Props for the {@link AppRouter} component.\n * @public\n */\nexport interface AppRouterProps {\n children?: ReactNode;\n SignInPageComponent?: ComponentType<SignInPageProps>;\n RouterComponent?: (props: { children: ReactNode }) => JSX.Element | null;\n extraElements?: Array<JSX.Element>;\n}\n\nfunction DefaultRouter(props: PropsWithChildren<{}>) {\n const configApi = useApi(configApiRef);\n const basePath = getBasePath(configApi);\n return (\n <BrowserRouter\n basename={basePath}\n future={{\n v7_relativeSplatPath: true,\n v7_startTransition: true,\n }}\n >\n {props.children}\n </BrowserRouter>\n );\n}\n\n/**\n * App router and sign-in page wrapper.\n *\n * @remarks\n *\n * The AppRouter provides the routing context and renders the sign-in page.\n * Until the user has successfully signed in, this component will render\n * the sign-in page. Once the user has signed-in, it will instead render\n * the app, while providing routing and route tracking for the app.\n */\nexport function AppRouter(props: AppRouterProps) {\n const {\n children,\n SignInPageComponent,\n RouterComponent = DefaultRouter,\n extraElements = [],\n } = props;\n\n const configApi = useApi(configApiRef);\n const appIdentityProxy = toAppIdentityProxy(useApi(identityApiRef));\n const routeResolutionsApi = useApi(routeResolutionApiRef);\n const basePath = getBasePath(configApi);\n\n // TODO: Private access for now, probably replace with path -> node lookup method on the API\n if (!('getRouteObjects' in routeResolutionsApi)) {\n throw new Error('Unexpected route resolution API implementation');\n }\n const routeObjects = (\n routeResolutionsApi as RouteResolverProxy\n ).getRouteObjects();\n\n // If the app hasn't configured a sign-in page, we just continue as guest.\n if (!SignInPageComponent) {\n appIdentityProxy.setTarget(\n {\n getUserId: () => 'guest',\n getIdToken: async () => undefined,\n getProfile: () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getProfileInfo: async () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getBackstageIdentity: async () => ({\n type: 'user',\n userEntityRef: 'user:default/guest',\n ownershipEntityRefs: ['user:default/guest'],\n }),\n getCredentials: async () => ({}),\n signOut: async () => {},\n },\n { signOutTargetUrl: basePath || '/' },\n );\n\n return (\n <RouterComponent>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n {children}\n </RouterComponent>\n );\n }\n\n return (\n <RouterComponent>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n <SignInPageWrapper\n component={SignInPageComponent}\n appIdentityProxy={appIdentityProxy}\n >\n {children}\n </SignInPageWrapper>\n </RouterComponent>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AppRoot.esm.js","sources":["../../src/extensions/AppRoot.tsx"],"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 ComponentType,\n PropsWithChildren,\n ReactNode,\n useState,\n JSX,\n} from 'react';\nimport {\n coreExtensionData,\n discoveryApiRef,\n fetchApiRef,\n errorApiRef,\n createExtension,\n createExtensionInput,\n routeResolutionApiRef,\n} from '@backstage/frontend-plugin-api';\nimport {\n AppRootWrapperBlueprint,\n RouterBlueprint,\n SignInPageBlueprint,\n} from '@backstage/plugin-app-react';\nimport {\n DiscoveryApi,\n ErrorApi,\n FetchApi,\n IdentityApi,\n ProfileInfo,\n SignInPageProps,\n configApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp';\nimport { BrowserRouter } from 'react-router-dom';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/RouteTracker';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { getBasePath } from '../../../../packages/frontend-app-api/src/routing/getBasePath';\n\nexport const AppRoot = createExtension({\n name: 'root',\n attachTo: { id: 'app', input: 'root' },\n inputs: {\n router: createExtensionInput([RouterBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n children: createExtensionInput([coreExtensionData.reactElement], {\n singleton: true,\n }),\n elements: createExtensionInput([coreExtensionData.reactElement]),\n wrappers: createExtensionInput(\n [AppRootWrapperBlueprint.dataRefs.component],\n {\n internal: true,\n },\n ),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs, apis }) {\n if (isProtectedApp()) {\n const identityApi = apis.get(identityApiRef);\n if (!identityApi) {\n throw new Error('App requires an Identity API implementation');\n }\n const appIdentityProxy = toAppIdentityProxy(identityApi);\n const discoveryApi = apis.get(discoveryApiRef);\n const errorApi = apis.get(errorApiRef);\n const fetchApi = apis.get(fetchApiRef);\n if (!discoveryApi || !errorApi || !fetchApi) {\n throw new Error(\n 'App is running in protected mode but missing required APIs',\n );\n }\n appIdentityProxy.enableCookieAuth({\n discoveryApi,\n errorApi,\n fetchApi,\n });\n }\n\n let content: ReactNode = inputs.children.get(\n coreExtensionData.reactElement,\n );\n\n for (const wrapper of inputs.wrappers) {\n const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component);\n if (Component) {\n content = <Component>{content}</Component>;\n }\n }\n\n return [\n coreExtensionData.reactElement(\n <AppRouter\n SignInPageComponent={inputs.signInPage?.get(\n SignInPageBlueprint.dataRefs.component,\n )}\n RouterComponent={inputs.router?.get(\n RouterBlueprint.dataRefs.component,\n )}\n extraElements={inputs.elements?.map(el =>\n el.get(coreExtensionData.reactElement),\n )}\n >\n {content}\n </AppRouter>,\n ),\n ];\n },\n});\n\n// This wraps the sign-in page and waits for sign-in to be completed before rendering the app\nfunction SignInPageWrapper({\n component: Component,\n appIdentityProxy,\n children,\n}: {\n component: ComponentType<SignInPageProps>;\n appIdentityProxy: AppIdentityProxy;\n children: ReactNode;\n}) {\n const [identityApi, setIdentityApi] = useState<IdentityApi>();\n const configApi = useApi(configApiRef);\n const basePath = getBasePath(configApi);\n\n if (!identityApi) {\n return <Component onSignInSuccess={setIdentityApi} />;\n }\n\n appIdentityProxy.setTarget(identityApi, {\n signOutTargetUrl: basePath || '/',\n });\n return <>{children}</>;\n}\n\ntype AppIdentityProxy = IdentityApi & {\n enableCookieAuth(ctx: {\n errorApi: ErrorApi;\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n }): void;\n setTarget(\n impl: IdentityApi & /* backwards compat stuff */ {\n getUserId?(): string;\n getIdToken?(): Promise<string | undefined>;\n getProfile?(): ProfileInfo;\n },\n options: { signOutTargetUrl: string },\n ): void;\n};\n\nfunction toAppIdentityProxy(identityApi: IdentityApi): AppIdentityProxy {\n if (!('enableCookieAuth' in identityApi)) {\n throw new Error('Unexpected Identity API implementation');\n }\n return identityApi as AppIdentityProxy;\n}\n\ntype RouteResolverProxy = {\n getRouteObjects(): any[];\n};\n\n/**\n * Props for the {@link AppRouter} component.\n * @public\n */\nexport interface AppRouterProps {\n children?: ReactNode;\n SignInPageComponent?: ComponentType<SignInPageProps>;\n RouterComponent?: (props: { children: ReactNode }) => JSX.Element | null;\n extraElements?: Array<JSX.Element>;\n}\n\nfunction DefaultRouter(props: PropsWithChildren<{}>) {\n const configApi = useApi(configApiRef);\n const basePath = getBasePath(configApi);\n return (\n <BrowserRouter\n basename={basePath}\n future={{\n v7_relativeSplatPath: false,\n v7_startTransition: false,\n }}\n >\n {props.children}\n </BrowserRouter>\n );\n}\n\n/**\n * App router and sign-in page wrapper.\n *\n * @remarks\n *\n * The AppRouter provides the routing context and renders the sign-in page.\n * Until the user has successfully signed in, this component will render\n * the sign-in page. Once the user has signed-in, it will instead render\n * the app, while providing routing and route tracking for the app.\n */\nexport function AppRouter(props: AppRouterProps) {\n const {\n children,\n SignInPageComponent,\n RouterComponent = DefaultRouter,\n extraElements = [],\n } = props;\n\n const configApi = useApi(configApiRef);\n const appIdentityProxy = toAppIdentityProxy(useApi(identityApiRef));\n const routeResolutionsApi = useApi(routeResolutionApiRef);\n const basePath = getBasePath(configApi);\n\n // TODO: Private access for now, probably replace with path -> node lookup method on the API\n if (!('getRouteObjects' in routeResolutionsApi)) {\n throw new Error('Unexpected route resolution API implementation');\n }\n const routeObjects = (\n routeResolutionsApi as RouteResolverProxy\n ).getRouteObjects();\n\n // If the app hasn't configured a sign-in page, we just continue as guest.\n if (!SignInPageComponent) {\n appIdentityProxy.setTarget(\n {\n getUserId: () => 'guest',\n getIdToken: async () => undefined,\n getProfile: () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getProfileInfo: async () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getBackstageIdentity: async () => ({\n type: 'user',\n userEntityRef: 'user:default/guest',\n ownershipEntityRefs: ['user:default/guest'],\n }),\n getCredentials: async () => ({}),\n signOut: async () => {},\n },\n { signOutTargetUrl: basePath || '/' },\n );\n\n return (\n <RouterComponent>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n {children}\n </RouterComponent>\n );\n }\n\n return (\n <RouterComponent>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n <SignInPageWrapper\n component={SignInPageComponent}\n appIdentityProxy={appIdentityProxy}\n >\n {children}\n </SignInPageWrapper>\n </RouterComponent>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { createExtension, coreExtensionData, createExtensionInput, NotFoundErrorPage } from '@backstage/frontend-plugin-api';
3
- import { useRoutes, Outlet } from 'react-router-dom';
3
+ import { useRoutes } from 'react-router-dom';
4
4
 
5
5
  const AppRoutes = createExtension({
6
6
  name: "routes",
@@ -18,37 +18,9 @@ const AppRoutes = createExtension({
18
18
  const element = useRoutes([
19
19
  ...inputs.routes.map((route) => {
20
20
  const routePath = route.get(coreExtensionData.routePath);
21
- const routeElement = route.get(coreExtensionData.reactElement);
22
- if (routePath === "/") {
23
- return {
24
- path: "/",
25
- element: /* @__PURE__ */ jsx(Outlet, {}),
26
- children: [
27
- {
28
- index: true,
29
- element: routeElement
30
- },
31
- {
32
- path: "*",
33
- element: routeElement
34
- }
35
- ]
36
- };
37
- }
38
- const normalizedPath = routePath.replace(/\/$/, "");
39
21
  return {
40
- path: normalizedPath,
41
- element: /* @__PURE__ */ jsx(Outlet, {}),
42
- children: [
43
- {
44
- index: true,
45
- element: routeElement
46
- },
47
- {
48
- path: "*",
49
- element: routeElement
50
- }
51
- ]
22
+ path: routePath === "/" ? routePath : `${routePath.replace(/\/$/, "")}/*`,
23
+ element: route.get(coreExtensionData.reactElement)
52
24
  };
53
25
  }),
54
26
  {
@@ -1 +1 @@
1
- {"version":3,"file":"AppRoutes.esm.js","sources":["../../src/extensions/AppRoutes.tsx"],"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 createExtension,\n coreExtensionData,\n createExtensionInput,\n NotFoundErrorPage,\n} from '@backstage/frontend-plugin-api';\nimport { useRoutes, Outlet } from 'react-router-dom';\n\nexport const AppRoutes = createExtension({\n name: 'routes',\n attachTo: { id: 'app/layout', input: 'content' },\n inputs: {\n routes: createExtensionInput([\n coreExtensionData.routePath,\n coreExtensionData.routeRef.optional(),\n coreExtensionData.reactElement,\n ]),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs }) {\n const Routes = () => {\n const element = useRoutes([\n ...inputs.routes.map(route => {\n const routePath = route.get(coreExtensionData.routePath);\n const routeElement = route.get(coreExtensionData.reactElement);\n\n // For v7_relativeSplatPath: convert splat paths to parent/child structure\n if (routePath === '/') {\n // Root route: parent with index and splat children\n return {\n path: '/',\n element: <Outlet />,\n children: [\n {\n index: true,\n element: routeElement,\n },\n {\n path: '*',\n element: routeElement,\n },\n ],\n };\n }\n\n // Non-root routes: parent route with splat child\n const normalizedPath = routePath.replace(/\\/$/, '');\n return {\n path: normalizedPath,\n element: <Outlet />,\n children: [\n {\n index: true,\n element: routeElement,\n },\n {\n path: '*',\n element: routeElement,\n },\n ],\n };\n }),\n {\n path: '*',\n element: <NotFoundErrorPage />,\n },\n ]);\n\n return element;\n };\n\n return [coreExtensionData.reactElement(<Routes />)];\n },\n});\n"],"names":[],"mappings":";;;;AAwBO,MAAM,YAAY,eAAA,CAAgB;AAAA,EACvC,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,SAAA,EAAU;AAAA,EAC/C,MAAA,EAAQ;AAAA,IACN,QAAQ,oBAAA,CAAqB;AAAA,MAC3B,iBAAA,CAAkB,SAAA;AAAA,MAClB,iBAAA,CAAkB,SAAS,QAAA,EAAS;AAAA,MACpC,iBAAA,CAAkB;AAAA,KACnB;AAAA,GACH;AAAA,EACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,EACvC,OAAA,CAAQ,EAAE,MAAA,EAAO,EAAG;AAClB,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,MAAM,UAAU,SAAA,CAAU;AAAA,QACxB,GAAG,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS;AAC5B,UAAA,MAAM,SAAA,GAAY,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,SAAS,CAAA;AACvD,UAAA,MAAM,YAAA,GAAe,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,YAAY,CAAA;AAG7D,UAAA,IAAI,cAAc,GAAA,EAAK;AAErB,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,GAAA;AAAA,cACN,OAAA,sBAAU,MAAA,EAAA,EAAO,CAAA;AAAA,cACjB,QAAA,EAAU;AAAA,gBACR;AAAA,kBACE,KAAA,EAAO,IAAA;AAAA,kBACP,OAAA,EAAS;AAAA,iBACX;AAAA,gBACA;AAAA,kBACE,IAAA,EAAM,GAAA;AAAA,kBACN,OAAA,EAAS;AAAA;AACX;AACF,aACF;AAAA,UACF;AAGA,UAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAClD,UAAA,OAAO;AAAA,YACL,IAAA,EAAM,cAAA;AAAA,YACN,OAAA,sBAAU,MAAA,EAAA,EAAO,CAAA;AAAA,YACjB,QAAA,EAAU;AAAA,cACR;AAAA,gBACE,KAAA,EAAO,IAAA;AAAA,gBACP,OAAA,EAAS;AAAA,eACX;AAAA,cACA;AAAA,gBACE,IAAA,EAAM,GAAA;AAAA,gBACN,OAAA,EAAS;AAAA;AACX;AACF,WACF;AAAA,QACF,CAAC,CAAA;AAAA,QACD;AAAA,UACE,IAAA,EAAM,GAAA;AAAA,UACN,OAAA,sBAAU,iBAAA,EAAA,EAAkB;AAAA;AAC9B,OACD,CAAA;AAED,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO,CAAC,iBAAA,CAAkB,YAAA,iBAAa,GAAA,CAAC,MAAA,EAAA,EAAO,CAAE,CAAC,CAAA;AAAA,EACpD;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"AppRoutes.esm.js","sources":["../../src/extensions/AppRoutes.tsx"],"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 createExtension,\n coreExtensionData,\n createExtensionInput,\n NotFoundErrorPage,\n} from '@backstage/frontend-plugin-api';\nimport { useRoutes } from 'react-router-dom';\n\nexport const AppRoutes = createExtension({\n name: 'routes',\n attachTo: { id: 'app/layout', input: 'content' },\n inputs: {\n routes: createExtensionInput([\n coreExtensionData.routePath,\n coreExtensionData.routeRef.optional(),\n coreExtensionData.reactElement,\n ]),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs }) {\n const Routes = () => {\n const element = useRoutes([\n ...inputs.routes.map(route => {\n const routePath = route.get(coreExtensionData.routePath);\n\n return {\n path:\n routePath === '/'\n ? routePath\n : `${routePath.replace(/\\/$/, '')}/*`,\n\n element: route.get(coreExtensionData.reactElement),\n };\n }),\n {\n path: '*',\n element: <NotFoundErrorPage />,\n },\n ]);\n\n return element;\n };\n\n return [coreExtensionData.reactElement(<Routes />)];\n },\n});\n"],"names":[],"mappings":";;;;AAwBO,MAAM,YAAY,eAAA,CAAgB;AAAA,EACvC,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,SAAA,EAAU;AAAA,EAC/C,MAAA,EAAQ;AAAA,IACN,QAAQ,oBAAA,CAAqB;AAAA,MAC3B,iBAAA,CAAkB,SAAA;AAAA,MAClB,iBAAA,CAAkB,SAAS,QAAA,EAAS;AAAA,MACpC,iBAAA,CAAkB;AAAA,KACnB;AAAA,GACH;AAAA,EACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,EACvC,OAAA,CAAQ,EAAE,MAAA,EAAO,EAAG;AAClB,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,MAAM,UAAU,SAAA,CAAU;AAAA,QACxB,GAAG,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS;AAC5B,UAAA,MAAM,SAAA,GAAY,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,SAAS,CAAA;AAEvD,UAAA,OAAO;AAAA,YACL,IAAA,EACE,cAAc,GAAA,GACV,SAAA,GACA,GAAG,SAAA,CAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,YAErC,OAAA,EAAS,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,YAAY;AAAA,WACnD;AAAA,QACF,CAAC,CAAA;AAAA,QACD;AAAA,UACE,IAAA,EAAM,GAAA;AAAA,UACN,OAAA,sBAAU,iBAAA,EAAA,EAAkB;AAAA;AAC9B,OACD,CAAA;AAED,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO,CAAC,iBAAA,CAAkB,YAAA,iBAAa,GAAA,CAAC,MAAA,EAAA,EAAO,CAAE,CAAC,CAAA;AAAA,EACpD;AACF,CAAC;;;;"}
@@ -0,0 +1,28 @@
1
+ import { ApiBlueprint, createExtensionInput, pluginHeaderActionsApiRef, coreExtensionData } from '@backstage/frontend-plugin-api';
2
+ import { DefaultPluginHeaderActionsApi } from '../apis/PluginHeaderActionsApi/DefaultPluginHeaderActionsApi.esm.js';
3
+
4
+ const PluginHeaderActionsApi = ApiBlueprint.makeWithOverrides({
5
+ name: "plugin-header-actions",
6
+ inputs: {
7
+ actions: createExtensionInput([coreExtensionData.reactElement])
8
+ },
9
+ factory: (originalFactory, { inputs }) => {
10
+ return originalFactory(
11
+ (defineParams) => defineParams({
12
+ api: pluginHeaderActionsApiRef,
13
+ deps: {},
14
+ factory: () => {
15
+ return DefaultPluginHeaderActionsApi.fromActions(
16
+ inputs.actions.map((actionInput) => ({
17
+ element: actionInput.get(coreExtensionData.reactElement),
18
+ pluginId: actionInput.node.spec.plugin.pluginId
19
+ }))
20
+ );
21
+ }
22
+ })
23
+ );
24
+ }
25
+ });
26
+
27
+ export { PluginHeaderActionsApi };
28
+ //# sourceMappingURL=PluginHeaderActionsApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginHeaderActionsApi.esm.js","sources":["../../src/extensions/PluginHeaderActionsApi.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreExtensionData,\n pluginHeaderActionsApiRef,\n createExtensionInput,\n ApiBlueprint,\n} from '@backstage/frontend-plugin-api';\nimport { DefaultPluginHeaderActionsApi } from '../apis/PluginHeaderActionsApi';\n\n/**\n * Contains the plugin-scoped header actions installed into the app.\n */\nexport const PluginHeaderActionsApi = ApiBlueprint.makeWithOverrides({\n name: 'plugin-header-actions',\n inputs: {\n actions: createExtensionInput([coreExtensionData.reactElement]),\n },\n factory: (originalFactory, { inputs }) => {\n return originalFactory(defineParams =>\n defineParams({\n api: pluginHeaderActionsApiRef,\n deps: {},\n factory: () => {\n return DefaultPluginHeaderActionsApi.fromActions(\n inputs.actions.map(actionInput => ({\n element: actionInput.get(coreExtensionData.reactElement),\n pluginId: actionInput.node.spec.plugin.pluginId,\n })),\n );\n },\n }),\n );\n },\n});\n"],"names":[],"mappings":";;;AA2BO,MAAM,sBAAA,GAAyB,aAAa,iBAAA,CAAkB;AAAA,EACnE,IAAA,EAAM,uBAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,OAAA,EAAS,oBAAA,CAAqB,CAAC,iBAAA,CAAkB,YAAY,CAAC;AAAA,GAChE;AAAA,EACA,OAAA,EAAS,CAAC,eAAA,EAAiB,EAAE,QAAO,KAAM;AACxC,IAAA,OAAO,eAAA;AAAA,MAAgB,kBACrB,YAAA,CAAa;AAAA,QACX,GAAA,EAAK,yBAAA;AAAA,QACL,MAAM,EAAC;AAAA,QACP,SAAS,MAAM;AACb,UAAA,OAAO,6BAAA,CAA8B,WAAA;AAAA,YACnC,MAAA,CAAO,OAAA,CAAQ,GAAA,CAAI,CAAA,WAAA,MAAgB;AAAA,cACjC,OAAA,EAAS,WAAA,CAAY,GAAA,CAAI,iBAAA,CAAkB,YAAY,CAAA;AAAA,cACvD,QAAA,EAAU,WAAA,CAAY,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO;AAAA,aACzC,CAAE;AAAA,WACJ;AAAA,QACF;AAAA,OACD;AAAA,KACH;AAAA,EACF;AACF,CAAC;;;;"}
@@ -1,8 +1,10 @@
1
- import { jsx } from 'react/jsx-runtime';
2
- import { Progress as Progress$2, NotFoundErrorPage as NotFoundErrorPage$1, ErrorDisplay as ErrorDisplay$1 } from '@backstage/frontend-plugin-api';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { Progress as Progress$2, NotFoundErrorPage as NotFoundErrorPage$1, ErrorDisplay as ErrorDisplay$1, PageLayout as PageLayout$1 } from '@backstage/frontend-plugin-api';
3
3
  import { SwappableComponentBlueprint } from '@backstage/plugin-app-react';
4
4
  import { Progress as Progress$1, ErrorPage, ErrorPanel } from '@backstage/core-components';
5
+ import { PluginHeader } from '@backstage/ui';
5
6
  import Button from '@material-ui/core/Button';
7
+ import { useMemo } from 'react';
6
8
 
7
9
  const Progress = SwappableComponentBlueprint.make({
8
10
  name: "core-progress",
@@ -29,6 +31,37 @@ const ErrorDisplay = SwappableComponentBlueprint.make({
29
31
  }
30
32
  })
31
33
  });
34
+ const PageLayout = SwappableComponentBlueprint.make({
35
+ name: "core-page-layout",
36
+ params: (define) => define({
37
+ component: PageLayout$1,
38
+ loader: () => (props) => {
39
+ const { title, icon, noHeader, headerActions, tabs, children } = props;
40
+ const tabsWithMatchStrategy = useMemo(
41
+ () => tabs?.map((tab) => ({
42
+ ...tab,
43
+ matchStrategy: "prefix"
44
+ })),
45
+ [tabs]
46
+ );
47
+ if (tabsWithMatchStrategy) {
48
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
49
+ !noHeader && /* @__PURE__ */ jsx(
50
+ PluginHeader,
51
+ {
52
+ title,
53
+ icon,
54
+ tabs: tabsWithMatchStrategy,
55
+ customActions: headerActions
56
+ }
57
+ ),
58
+ children
59
+ ] });
60
+ }
61
+ return /* @__PURE__ */ jsx(Fragment, { children });
62
+ }
63
+ })
64
+ });
32
65
 
33
- export { ErrorDisplay, NotFoundErrorPage, Progress };
66
+ export { ErrorDisplay, NotFoundErrorPage, PageLayout, Progress };
34
67
  //# sourceMappingURL=components.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"components.esm.js","sources":["../../src/extensions/components.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n NotFoundErrorPage as SwappableNotFoundErrorPage,\n Progress as SwappableProgress,\n ErrorDisplay as SwappableErrorDisplay,\n} from '@backstage/frontend-plugin-api';\nimport { SwappableComponentBlueprint } from '@backstage/plugin-app-react';\nimport {\n ErrorPage,\n ErrorPanel,\n Progress as ProgressComponent,\n} from '@backstage/core-components';\nimport Button from '@material-ui/core/Button';\n\nexport const Progress = SwappableComponentBlueprint.make({\n name: 'core-progress',\n params: define =>\n define({\n component: SwappableProgress,\n loader: () => ProgressComponent,\n }),\n});\n\nexport const NotFoundErrorPage = SwappableComponentBlueprint.make({\n name: 'core-not-found-error-page',\n params: define =>\n define({\n component: SwappableNotFoundErrorPage,\n loader: () => () =>\n <ErrorPage status=\"404\" statusMessage=\"PAGE NOT FOUND\" />,\n }),\n});\n\nexport const ErrorDisplay = SwappableComponentBlueprint.make({\n name: 'core-error-display',\n params: define =>\n define({\n component: SwappableErrorDisplay,\n loader: () => props => {\n const { plugin, error, resetError } = props;\n const title = `Error in ${plugin?.id}`;\n return (\n <ErrorPanel title={title} error={error} defaultExpanded>\n <Button variant=\"outlined\" onClick={resetError}>\n Retry\n </Button>\n </ErrorPanel>\n );\n },\n }),\n});\n"],"names":["SwappableProgress","ProgressComponent","SwappableNotFoundErrorPage","SwappableErrorDisplay"],"mappings":";;;;;;AA4BO,MAAM,QAAA,GAAW,4BAA4B,IAAA,CAAK;AAAA,EACvD,IAAA,EAAM,eAAA;AAAA,EACN,MAAA,EAAQ,YACN,MAAA,CAAO;AAAA,IACL,SAAA,EAAWA,UAAA;AAAA,IACX,QAAQ,MAAMC;AAAA,GACf;AACL,CAAC;AAEM,MAAM,iBAAA,GAAoB,4BAA4B,IAAA,CAAK;AAAA,EAChE,IAAA,EAAM,2BAAA;AAAA,EACN,MAAA,EAAQ,YACN,MAAA,CAAO;AAAA,IACL,SAAA,EAAWC,mBAAA;AAAA,IACX,MAAA,EAAQ,MAAM,sBACZ,GAAA,CAAC,aAAU,MAAA,EAAO,KAAA,EAAM,eAAc,gBAAA,EAAiB;AAAA,GAC1D;AACL,CAAC;AAEM,MAAM,YAAA,GAAe,4BAA4B,IAAA,CAAK;AAAA,EAC3D,IAAA,EAAM,oBAAA;AAAA,EACN,MAAA,EAAQ,YACN,MAAA,CAAO;AAAA,IACL,SAAA,EAAWC,cAAA;AAAA,IACX,MAAA,EAAQ,MAAM,CAAA,KAAA,KAAS;AACrB,MAAA,MAAM,EAAE,MAAA,EAAQ,KAAA,EAAO,UAAA,EAAW,GAAI,KAAA;AACtC,MAAA,MAAM,KAAA,GAAQ,CAAA,SAAA,EAAY,MAAA,EAAQ,EAAE,CAAA,CAAA;AACpC,MAAA,uBACE,GAAA,CAAC,UAAA,EAAA,EAAW,KAAA,EAAc,KAAA,EAAc,eAAA,EAAe,IAAA,EACrD,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAO,OAAA,EAAQ,UAAA,EAAW,OAAA,EAAS,UAAA,EAAY,mBAEhD,CAAA,EACF,CAAA;AAAA,IAEJ;AAAA,GACD;AACL,CAAC;;;;"}
1
+ {"version":3,"file":"components.esm.js","sources":["../../src/extensions/components.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n NotFoundErrorPage as SwappableNotFoundErrorPage,\n Progress as SwappableProgress,\n ErrorDisplay as SwappableErrorDisplay,\n PageLayout as SwappablePageLayout,\n type PageLayoutProps,\n} from '@backstage/frontend-plugin-api';\nimport { SwappableComponentBlueprint } from '@backstage/plugin-app-react';\nimport {\n ErrorPage,\n ErrorPanel,\n Progress as ProgressComponent,\n} from '@backstage/core-components';\nimport { PluginHeader } from '@backstage/ui';\nimport Button from '@material-ui/core/Button';\nimport { useMemo } from 'react';\n\nexport const Progress = SwappableComponentBlueprint.make({\n name: 'core-progress',\n params: define =>\n define({\n component: SwappableProgress,\n loader: () => ProgressComponent,\n }),\n});\n\nexport const NotFoundErrorPage = SwappableComponentBlueprint.make({\n name: 'core-not-found-error-page',\n params: define =>\n define({\n component: SwappableNotFoundErrorPage,\n loader: () => () =>\n <ErrorPage status=\"404\" statusMessage=\"PAGE NOT FOUND\" />,\n }),\n});\n\nexport const ErrorDisplay = SwappableComponentBlueprint.make({\n name: 'core-error-display',\n params: define =>\n define({\n component: SwappableErrorDisplay,\n loader: () => props => {\n const { plugin, error, resetError } = props;\n const title = `Error in ${plugin?.id}`;\n return (\n <ErrorPanel title={title} error={error} defaultExpanded>\n <Button variant=\"outlined\" onClick={resetError}>\n Retry\n </Button>\n </ErrorPanel>\n );\n },\n }),\n});\n\nexport const PageLayout = SwappableComponentBlueprint.make({\n name: 'core-page-layout',\n params: define =>\n define({\n component: SwappablePageLayout,\n loader: () => (props: PageLayoutProps) => {\n const { title, icon, noHeader, headerActions, tabs, children } = props;\n const tabsWithMatchStrategy = useMemo(\n () =>\n tabs?.map(tab => ({\n ...tab,\n matchStrategy: 'prefix' as const,\n })),\n [tabs],\n );\n\n if (tabsWithMatchStrategy) {\n return (\n <>\n {!noHeader && (\n <PluginHeader\n title={title}\n icon={icon}\n tabs={tabsWithMatchStrategy}\n customActions={headerActions}\n />\n )}\n {children}\n </>\n );\n }\n return <>{children}</>;\n },\n }),\n});\n"],"names":["SwappableProgress","ProgressComponent","SwappableNotFoundErrorPage","SwappableErrorDisplay","SwappablePageLayout"],"mappings":";;;;;;;;AAgCO,MAAM,QAAA,GAAW,4BAA4B,IAAA,CAAK;AAAA,EACvD,IAAA,EAAM,eAAA;AAAA,EACN,MAAA,EAAQ,YACN,MAAA,CAAO;AAAA,IACL,SAAA,EAAWA,UAAA;AAAA,IACX,QAAQ,MAAMC;AAAA,GACf;AACL,CAAC;AAEM,MAAM,iBAAA,GAAoB,4BAA4B,IAAA,CAAK;AAAA,EAChE,IAAA,EAAM,2BAAA;AAAA,EACN,MAAA,EAAQ,YACN,MAAA,CAAO;AAAA,IACL,SAAA,EAAWC,mBAAA;AAAA,IACX,MAAA,EAAQ,MAAM,sBACZ,GAAA,CAAC,aAAU,MAAA,EAAO,KAAA,EAAM,eAAc,gBAAA,EAAiB;AAAA,GAC1D;AACL,CAAC;AAEM,MAAM,YAAA,GAAe,4BAA4B,IAAA,CAAK;AAAA,EAC3D,IAAA,EAAM,oBAAA;AAAA,EACN,MAAA,EAAQ,YACN,MAAA,CAAO;AAAA,IACL,SAAA,EAAWC,cAAA;AAAA,IACX,MAAA,EAAQ,MAAM,CAAA,KAAA,KAAS;AACrB,MAAA,MAAM,EAAE,MAAA,EAAQ,KAAA,EAAO,UAAA,EAAW,GAAI,KAAA;AACtC,MAAA,MAAM,KAAA,GAAQ,CAAA,SAAA,EAAY,MAAA,EAAQ,EAAE,CAAA,CAAA;AACpC,MAAA,uBACE,GAAA,CAAC,UAAA,EAAA,EAAW,KAAA,EAAc,KAAA,EAAc,eAAA,EAAe,IAAA,EACrD,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAO,OAAA,EAAQ,UAAA,EAAW,OAAA,EAAS,UAAA,EAAY,mBAEhD,CAAA,EACF,CAAA;AAAA,IAEJ;AAAA,GACD;AACL,CAAC;AAEM,MAAM,UAAA,GAAa,4BAA4B,IAAA,CAAK;AAAA,EACzD,IAAA,EAAM,kBAAA;AAAA,EACN,MAAA,EAAQ,YACN,MAAA,CAAO;AAAA,IACL,SAAA,EAAWC,YAAA;AAAA,IACX,MAAA,EAAQ,MAAM,CAAC,KAAA,KAA2B;AACxC,MAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAM,UAAU,aAAA,EAAe,IAAA,EAAM,UAAS,GAAI,KAAA;AACjE,MAAA,MAAM,qBAAA,GAAwB,OAAA;AAAA,QAC5B,MACE,IAAA,EAAM,GAAA,CAAI,CAAA,GAAA,MAAQ;AAAA,UAChB,GAAG,GAAA;AAAA,UACH,aAAA,EAAe;AAAA,SACjB,CAAE,CAAA;AAAA,QACJ,CAAC,IAAI;AAAA,OACP;AAEA,MAAA,IAAI,qBAAA,EAAuB;AACzB,QAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACG,QAAA,EAAA;AAAA,UAAA,CAAC,QAAA,oBACA,GAAA;AAAA,YAAC,YAAA;AAAA,YAAA;AAAA,cACC,KAAA;AAAA,cACA,IAAA;AAAA,cACA,IAAA,EAAM,qBAAA;AAAA,cACN,aAAA,EAAe;AAAA;AAAA,WACjB;AAAA,UAED;AAAA,SAAA,EACH,CAAA;AAAA,MAEJ;AACA,MAAA,uCAAU,QAAA,EAAS,CAAA;AAAA,IACrB;AAAA,GACD;AACL,CAAC;;;;"}
package/dist/index.d.ts CHANGED
@@ -287,7 +287,7 @@ declare const appPlugin: _backstage_frontend_plugin_api.OverridableFrontendPlugi
287
287
  output: _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.AnyApiFactory, "core.api.factory", {}>;
288
288
  inputs: {
289
289
  icons: _backstage_frontend_plugin_api.ExtensionInput<_backstage_frontend_plugin_api.ConfigurableExtensionDataRef<{
290
- [x: string]: _backstage_frontend_plugin_api.IconComponent;
290
+ [x: string]: _backstage_frontend_plugin_api.IconComponent | _backstage_frontend_plugin_api.IconElement;
291
291
  }, "core.icons", {}>, {
292
292
  singleton: false;
293
293
  optional: false;
@@ -352,6 +352,21 @@ declare const appPlugin: _backstage_frontend_plugin_api.OverridableFrontendPlugi
352
352
  inputs: {};
353
353
  params: <TApi, TImpl extends TApi, TDeps extends { [name in string]: unknown; }>(params: _backstage_frontend_plugin_api.ApiFactory<TApi, TImpl, TDeps>) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<_backstage_frontend_plugin_api.AnyApiFactory>;
354
354
  }>;
355
+ "api:app/plugin-header-actions": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
356
+ config: {};
357
+ configInput: {};
358
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.AnyApiFactory, "core.api.factory", {}>;
359
+ inputs: {
360
+ actions: _backstage_frontend_plugin_api.ExtensionInput<_backstage_frontend_plugin_api.ConfigurableExtensionDataRef<react.JSX.Element, "core.reactElement", {}>, {
361
+ singleton: false;
362
+ optional: false;
363
+ internal: false;
364
+ }>;
365
+ };
366
+ kind: "api";
367
+ name: "plugin-header-actions";
368
+ params: <TApi, TImpl extends TApi, TDeps extends { [name in string]: unknown; }>(params: _backstage_frontend_plugin_api.ApiFactory<TApi, TImpl, TDeps>) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<_backstage_frontend_plugin_api.AnyApiFactory>;
369
+ }>;
355
370
  "api:app/plugin-wrapper": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
356
371
  config: {};
357
372
  configInput: {};
@@ -531,6 +546,28 @@ declare const appPlugin: _backstage_frontend_plugin_api.OverridableFrontendPlugi
531
546
  loader: Ref extends _backstage_frontend_plugin_api.SwappableComponentRef<infer IInnerComponentProps, any> ? (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) : never;
532
547
  }>;
533
548
  }>;
549
+ "component:app/core-page-layout": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
550
+ kind: "component";
551
+ name: "core-page-layout";
552
+ config: {};
553
+ configInput: {};
554
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<{
555
+ ref: _backstage_frontend_plugin_api.SwappableComponentRef;
556
+ loader: (() => (props: {}) => JSX.Element | null) | (() => Promise<(props: {}) => JSX.Element | null>);
557
+ }, "core.swappableComponent", {}>;
558
+ inputs: {};
559
+ params: <Ref extends _backstage_frontend_plugin_api.SwappableComponentRef<any>>(params: {
560
+ component: Ref extends _backstage_frontend_plugin_api.SwappableComponentRef<any, infer IExternalComponentProps> ? {
561
+ ref: Ref;
562
+ } & ((props: IExternalComponentProps) => JSX.Element | null) : never;
563
+ loader: Ref extends _backstage_frontend_plugin_api.SwappableComponentRef<infer IInnerComponentProps, any> ? (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) : never;
564
+ }) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<{
565
+ component: Ref extends _backstage_frontend_plugin_api.SwappableComponentRef<any, infer IExternalComponentProps> ? {
566
+ ref: Ref;
567
+ } & ((props: IExternalComponentProps) => JSX.Element | null) : never;
568
+ loader: Ref extends _backstage_frontend_plugin_api.SwappableComponentRef<infer IInnerComponentProps, any> ? (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) : never;
569
+ }>;
570
+ }>;
534
571
  "component:app/core-progress": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
535
572
  kind: "component";
536
573
  name: "core-progress";
@@ -1,11 +1,42 @@
1
+ import { isValidElement, createElement } from 'react';
2
+
1
3
  class DefaultIconsApi {
2
4
  #icons;
5
+ #components = /* @__PURE__ */ new Map();
3
6
  constructor(icons) {
4
- this.#icons = new Map(Object.entries(icons));
7
+ const deprecatedKeys = [];
8
+ this.#icons = new Map(
9
+ Object.entries(icons).map(([key, value]) => {
10
+ if (value === null || isValidElement(value)) {
11
+ return [key, value];
12
+ }
13
+ deprecatedKeys.push(key);
14
+ return [key, createElement(value)];
15
+ })
16
+ );
17
+ if (deprecatedKeys.length > 0) {
18
+ const keys = deprecatedKeys.join(", ");
19
+ console.warn(
20
+ `The following icons were registered as IconComponent, which is deprecated. Use IconElement instead by passing <MyIcon /> rather than MyIcon: ${keys}`
21
+ );
22
+ }
5
23
  }
6
- getIcon(key) {
24
+ icon(key) {
7
25
  return this.#icons.get(key);
8
26
  }
27
+ getIcon(key) {
28
+ let component = this.#components.get(key);
29
+ if (component) {
30
+ return component;
31
+ }
32
+ const el = this.#icons.get(key);
33
+ if (el === void 0) {
34
+ return void 0;
35
+ }
36
+ component = () => el;
37
+ this.#components.set(key, component);
38
+ return component;
39
+ }
9
40
  listIconKeys() {
10
41
  return Array.from(this.#icons.keys());
11
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultIconsApi.esm.js","sources":["../../../../../../../../../packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.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 { IconComponent, IconsApi } from '@backstage/frontend-plugin-api';\n\n/**\n * Implementation for the {@link IconsApi}\n *\n * @internal\n */\nexport class DefaultIconsApi implements IconsApi {\n #icons: Map<string, IconComponent>;\n\n constructor(icons: { [key in string]: IconComponent }) {\n this.#icons = new Map(Object.entries(icons));\n }\n\n getIcon(key: string): IconComponent | undefined {\n return this.#icons.get(key);\n }\n\n listIconKeys(): string[] {\n return Array.from(this.#icons.keys());\n }\n}\n"],"names":[],"mappings":"AAuBO,MAAM,eAAA,CAAoC;AAAA,EAC/C,MAAA;AAAA,EAEA,YAAY,KAAA,EAA2C;AACrD,IAAA,IAAA,CAAK,SAAS,IAAI,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAC7C;AAAA,EAEA,QAAQ,GAAA,EAAwC;AAC9C,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,CAAA;AAAA,EAC5B;AAAA,EAEA,YAAA,GAAyB;AACvB,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAAA,EACtC;AACF;;;;"}
1
+ {"version":3,"file":"DefaultIconsApi.esm.js","sources":["../../../../../../../../../packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.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 IconComponent,\n IconElement,\n IconsApi,\n} from '@backstage/frontend-plugin-api';\nimport { createElement, isValidElement } from 'react';\n\n/**\n * Implementation for the {@link IconsApi}\n *\n * @internal\n */\nexport class DefaultIconsApi implements IconsApi {\n #icons: Map<string, IconElement>;\n #components = new Map<string, IconComponent>();\n\n constructor(icons: { [key in string]: IconComponent | IconElement }) {\n const deprecatedKeys: string[] = [];\n\n this.#icons = new Map(\n Object.entries(icons).map(([key, value]) => {\n if (value === null || isValidElement(value)) {\n return [key, value];\n }\n deprecatedKeys.push(key);\n return [key, createElement(value as IconComponent)];\n }),\n );\n\n if (deprecatedKeys.length > 0) {\n const keys = deprecatedKeys.join(', ');\n // eslint-disable-next-line no-console\n console.warn(\n `The following icons were registered as IconComponent, which is deprecated. Use IconElement instead by passing <MyIcon /> rather than MyIcon: ${keys}`,\n );\n }\n }\n\n icon(key: string): IconElement | undefined {\n return this.#icons.get(key);\n }\n\n getIcon(key: string): IconComponent | undefined {\n let component = this.#components.get(key);\n if (component) {\n return component;\n }\n const el = this.#icons.get(key);\n if (el === undefined) {\n return undefined;\n }\n component = () => el;\n this.#components.set(key, component);\n return component;\n }\n\n listIconKeys(): string[] {\n return Array.from(this.#icons.keys());\n }\n}\n"],"names":[],"mappings":";;AA4BO,MAAM,eAAA,CAAoC;AAAA,EAC/C,MAAA;AAAA,EACA,WAAA,uBAAkB,GAAA,EAA2B;AAAA,EAE7C,YAAY,KAAA,EAAyD;AACnE,IAAA,MAAM,iBAA2B,EAAC;AAElC,IAAA,IAAA,CAAK,SAAS,IAAI,GAAA;AAAA,MAChB,MAAA,CAAO,QAAQ,KAAK,CAAA,CAAE,IAAI,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AAC1C,QAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,cAAA,CAAe,KAAK,CAAA,EAAG;AAC3C,UAAA,OAAO,CAAC,KAAK,KAAK,CAAA;AAAA,QACpB;AACA,QAAA,cAAA,CAAe,KAAK,GAAG,CAAA;AACvB,QAAA,OAAO,CAAC,GAAA,EAAK,aAAA,CAAc,KAAsB,CAAC,CAAA;AAAA,MACpD,CAAC;AAAA,KACH;AAEA,IAAA,IAAI,cAAA,CAAe,SAAS,CAAA,EAAG;AAC7B,MAAA,MAAM,IAAA,GAAO,cAAA,CAAe,IAAA,CAAK,IAAI,CAAA;AAErC,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,gJAAgJ,IAAI,CAAA;AAAA,OACtJ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,KAAK,GAAA,EAAsC;AACzC,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,CAAA;AAAA,EAC5B;AAAA,EAEA,QAAQ,GAAA,EAAwC;AAC9C,IAAA,IAAI,SAAA,GAAY,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA;AACxC,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,OAAO,SAAA;AAAA,IACT;AACA,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,CAAA;AAC9B,IAAA,IAAI,OAAO,MAAA,EAAW;AACpB,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,SAAA,GAAY,MAAM,EAAA;AAClB,IAAA,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,GAAA,EAAK,SAAS,CAAA;AACnC,IAAA,OAAO,SAAA;AAAA,EACT;AAAA,EAEA,YAAA,GAAyB;AACvB,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAAA,EACtC;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"InternalFrontendPlugin.esm.js","sources":["../../../../../../../packages/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":";;AAwBoC,WAAW,MAAA,CAW5C;AAAA,EACD,IAAA,EAAM,2BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC"}
1
+ {"version":3,"file":"InternalFrontendPlugin.esm.js","sources":["../../../../../../../packages/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":";;AAyBoC,WAAW,MAAA,CAa5C;AAAA,EACD,IAAA,EAAM,2BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC"}
@@ -14,8 +14,9 @@ import { TranslationsApi } from './extensions/TranslationsApi.esm.js';
14
14
  import { DefaultSignInPage } from './extensions/DefaultSignInPage.esm.js';
15
15
  import { dialogDisplayAppRootElement } from './extensions/DialogDisplay.esm.js';
16
16
  import { oauthRequestDialogAppRootElement, alertDisplayAppRootElement } from './extensions/elements.esm.js';
17
- import { Progress, NotFoundErrorPage, ErrorDisplay } from './extensions/components.esm.js';
17
+ import { Progress, NotFoundErrorPage, ErrorDisplay, PageLayout } from './extensions/components.esm.js';
18
18
  import { PluginWrapperApi } from './extensions/PluginWrapperApi.esm.js';
19
+ import { PluginHeaderActionsApi } from './extensions/PluginHeaderActionsApi.esm.js';
19
20
  import { apis } from './defaultApis.esm.js';
20
21
 
21
22
  const appPlugin = createFrontendPlugin({
@@ -36,6 +37,7 @@ const appPlugin = createFrontendPlugin({
36
37
  IconsApi,
37
38
  FeatureFlagsApi,
38
39
  PluginWrapperApi,
40
+ PluginHeaderActionsApi,
39
41
  TranslationsApi,
40
42
  DefaultSignInPage,
41
43
  oauthRequestDialogAppRootElement,
@@ -44,6 +46,7 @@ const appPlugin = createFrontendPlugin({
44
46
  Progress,
45
47
  NotFoundErrorPage,
46
48
  ErrorDisplay,
49
+ PageLayout,
47
50
  LegacyComponentsApi
48
51
  ]
49
52
  });
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.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 { createFrontendPlugin } from '@backstage/frontend-plugin-api';\nimport {\n App,\n AppLanguageApi,\n AppLayout,\n AppNav,\n AppRoot,\n AppRoutes,\n AppThemeApi,\n DarkTheme,\n LightTheme,\n SwappableComponentsApi,\n IconsApi,\n FeatureFlagsApi,\n PluginWrapperApi,\n TranslationsApi,\n oauthRequestDialogAppRootElement,\n alertDisplayAppRootElement,\n DefaultSignInPage,\n dialogDisplayAppRootElement,\n Progress,\n NotFoundErrorPage,\n ErrorDisplay,\n LegacyComponentsApi,\n} from './extensions';\nimport { apis } from './defaultApis';\n\n/** @public */\nexport const appPlugin = createFrontendPlugin({\n pluginId: 'app',\n info: { packageJson: () => import('../package.json') },\n extensions: [\n ...apis,\n App,\n AppLanguageApi,\n AppLayout,\n AppNav,\n AppRoot,\n AppRoutes,\n AppThemeApi,\n DarkTheme,\n LightTheme,\n SwappableComponentsApi,\n IconsApi,\n FeatureFlagsApi,\n PluginWrapperApi,\n TranslationsApi,\n DefaultSignInPage,\n oauthRequestDialogAppRootElement,\n alertDisplayAppRootElement,\n dialogDisplayAppRootElement,\n Progress,\n NotFoundErrorPage,\n ErrorDisplay,\n LegacyComponentsApi,\n ],\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA4CO,MAAM,YAAY,oBAAA,CAAqB;AAAA,EAC5C,QAAA,EAAU,KAAA;AAAA,EACV,MAAM,EAAE,WAAA,EAAa,MAAM,OAAO,mCAAiB,CAAA,EAAE;AAAA,EACrD,UAAA,EAAY;AAAA,IACV,GAAG,IAAA;AAAA,IACH,GAAA;AAAA,IACA,cAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA,IACA,sBAAA;AAAA,IACA,QAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,eAAA;AAAA,IACA,iBAAA;AAAA,IACA,gCAAA;AAAA,IACA,0BAAA;AAAA,IACA,2BAAA;AAAA,IACA,QAAA;AAAA,IACA,iBAAA;AAAA,IACA,YAAA;AAAA,IACA;AAAA;AAEJ,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.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 { createFrontendPlugin } from '@backstage/frontend-plugin-api';\nimport {\n App,\n AppLanguageApi,\n AppLayout,\n AppNav,\n AppRoot,\n AppRoutes,\n AppThemeApi,\n DarkTheme,\n LightTheme,\n SwappableComponentsApi,\n IconsApi,\n FeatureFlagsApi,\n PluginWrapperApi,\n PluginHeaderActionsApi,\n TranslationsApi,\n oauthRequestDialogAppRootElement,\n alertDisplayAppRootElement,\n DefaultSignInPage,\n dialogDisplayAppRootElement,\n Progress,\n NotFoundErrorPage,\n ErrorDisplay,\n PageLayout,\n LegacyComponentsApi,\n} from './extensions';\nimport { apis } from './defaultApis';\n\n/** @public */\nexport const appPlugin = createFrontendPlugin({\n pluginId: 'app',\n info: { packageJson: () => import('../package.json') },\n extensions: [\n ...apis,\n App,\n AppLanguageApi,\n AppLayout,\n AppNav,\n AppRoot,\n AppRoutes,\n AppThemeApi,\n DarkTheme,\n LightTheme,\n SwappableComponentsApi,\n IconsApi,\n FeatureFlagsApi,\n PluginWrapperApi,\n PluginHeaderActionsApi,\n TranslationsApi,\n DefaultSignInPage,\n oauthRequestDialogAppRootElement,\n alertDisplayAppRootElement,\n dialogDisplayAppRootElement,\n Progress,\n NotFoundErrorPage,\n ErrorDisplay,\n PageLayout,\n LegacyComponentsApi,\n ],\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AA8CO,MAAM,YAAY,oBAAA,CAAqB;AAAA,EAC5C,QAAA,EAAU,KAAA;AAAA,EACV,MAAM,EAAE,WAAA,EAAa,MAAM,OAAO,mCAAiB,CAAA,EAAE;AAAA,EACrD,UAAA,EAAY;AAAA,IACV,GAAG,IAAA;AAAA,IACH,GAAA;AAAA,IACA,cAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA,IACA,sBAAA;AAAA,IACA,QAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,sBAAA;AAAA,IACA,eAAA;AAAA,IACA,iBAAA;AAAA,IACA,gCAAA;AAAA,IACA,0BAAA;AAAA,IACA,2BAAA;AAAA,IACA,QAAA;AAAA,IACA,iBAAA;AAAA,IACA,YAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA;AAEJ,CAAC;;;;"}
@@ -1,5 +1,5 @@
1
1
  var name = "@backstage/plugin-app";
2
- var version = "0.4.0-next.2";
2
+ var version = "0.4.0";
3
3
  var backstage = {
4
4
  role: "frontend-plugin",
5
5
  pluginId: "app",
@@ -58,6 +58,7 @@ var dependencies = {
58
58
  "@backstage/plugin-permission-react": "workspace:^",
59
59
  "@backstage/theme": "workspace:^",
60
60
  "@backstage/types": "workspace:^",
61
+ "@backstage/ui": "workspace:^",
61
62
  "@backstage/version-bridge": "workspace:^",
62
63
  "@material-ui/core": "^4.9.13",
63
64
  "@material-ui/icons": "^4.9.1",
@@ -1 +1 @@
1
- {"version":3,"file":"package.json.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"package.json.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-app",
3
- "version": "0.4.0-next.2",
3
+ "version": "0.4.0",
4
4
  "backstage": {
5
5
  "role": "frontend-plugin",
6
6
  "pluginId": "app",
@@ -63,15 +63,16 @@
63
63
  "test": "backstage-cli package test"
64
64
  },
65
65
  "dependencies": {
66
- "@backstage/core-components": "0.18.7-next.2",
67
- "@backstage/core-plugin-api": "1.12.3-next.1",
68
- "@backstage/frontend-plugin-api": "0.14.0-next.2",
69
- "@backstage/integration-react": "1.2.15-next.2",
70
- "@backstage/plugin-app-react": "0.1.1-next.0",
71
- "@backstage/plugin-permission-react": "0.4.40-next.1",
72
- "@backstage/theme": "0.7.2-next.1",
73
- "@backstage/types": "1.2.2",
74
- "@backstage/version-bridge": "1.0.12-next.0",
66
+ "@backstage/core-components": "^0.18.7",
67
+ "@backstage/core-plugin-api": "^1.12.3",
68
+ "@backstage/frontend-plugin-api": "^0.14.0",
69
+ "@backstage/integration-react": "^1.2.15",
70
+ "@backstage/plugin-app-react": "^0.2.0",
71
+ "@backstage/plugin-permission-react": "^0.4.40",
72
+ "@backstage/theme": "^0.7.2",
73
+ "@backstage/types": "^1.2.2",
74
+ "@backstage/ui": "^0.12.0",
75
+ "@backstage/version-bridge": "^1.0.12",
75
76
  "@material-ui/core": "^4.9.13",
76
77
  "@material-ui/icons": "^4.9.1",
77
78
  "@material-ui/lab": "^4.0.0-alpha.61",
@@ -80,11 +81,11 @@
80
81
  "zod": "^3.25.76"
81
82
  },
82
83
  "devDependencies": {
83
- "@backstage/cli": "0.35.4-next.2",
84
- "@backstage/dev-utils": "1.1.20-next.2",
85
- "@backstage/frontend-defaults": "0.4.0-next.2",
86
- "@backstage/frontend-test-utils": "0.5.0-next.2",
87
- "@backstage/test-utils": "1.7.15-next.2",
84
+ "@backstage/cli": "^0.35.4",
85
+ "@backstage/dev-utils": "^1.1.20",
86
+ "@backstage/frontend-defaults": "^0.4.0",
87
+ "@backstage/frontend-test-utils": "^0.5.0",
88
+ "@backstage/test-utils": "^1.7.15",
88
89
  "@testing-library/jest-dom": "^6.0.0",
89
90
  "@testing-library/react": "^16.0.0",
90
91
  "@testing-library/user-event": "^14.0.0",