@backstage/frontend-app-api 0.11.2 → 0.11.3-next.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @backstage/frontend-app-api
2
2
 
3
+ ## 0.11.3-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @backstage/frontend-plugin-api@0.10.3-next.1
9
+ - @backstage/config@1.3.2
10
+ - @backstage/core-app-api@1.17.0
11
+ - @backstage/core-plugin-api@1.10.7
12
+ - @backstage/errors@1.2.7
13
+ - @backstage/frontend-defaults@0.2.3-next.1
14
+ - @backstage/types@1.2.1
15
+ - @backstage/version-bridge@1.0.11
16
+
17
+ ## 0.11.3-next.0
18
+
19
+ ### Patch Changes
20
+
21
+ - c38c9e8: Implemented support for the `plugin.info()` method in specialized apps with a default resolved for `package.json` and `catalog-info.yaml`. The default resolution logic can be overridden via the `pluginInfoResolver` option to `createSpecializedApp`, and plugin-specific overrides can be applied via the new `app.pluginOverrides` key in static configuration.
22
+ - Updated dependencies
23
+ - @backstage/frontend-plugin-api@0.10.3-next.0
24
+ - @backstage/frontend-defaults@0.2.3-next.0
25
+
3
26
  ## 0.11.2
4
27
 
5
28
  ### Patch Changes
package/config.d.ts CHANGED
@@ -51,5 +51,71 @@ export interface Config {
51
51
  };
52
52
  }
53
53
  >;
54
+
55
+ /**
56
+ * This section enables you to override certain properties of specific or
57
+ * groups of plugins.
58
+ *
59
+ * @remarks
60
+ * All matching entries will be applied to each plugin, with the later
61
+ * entries taking precedence.
62
+ *
63
+ * This configuration is intended to be used primarily to apply overrides
64
+ * for third-party plugins.
65
+ *
66
+ * @deepVisibility frontend
67
+ */
68
+ pluginOverrides?: Array<{
69
+ /**
70
+ * The criteria for matching plugins to override.
71
+ *
72
+ * @remarks
73
+ * If no match criteria are provided, the override will be applied to
74
+ * all plugins.
75
+ */
76
+ match?: {
77
+ /**
78
+ * A pattern that is matched against the plugin ID.
79
+ *
80
+ * @remarks
81
+ * By default the string is interpreted as a glob pattern, but if the
82
+ * string is surrounded by '/' it is interpreted as a regex.
83
+ */
84
+ pluginId?: string;
85
+
86
+ /**
87
+ * A pattern that is matched against the package name.
88
+ *
89
+ * @remarks
90
+ * By default the string is interpreted as a glob pattern, but if the
91
+ * string is surrounded by '/' it is interpreted as a regex.
92
+ *
93
+ * Note that this will only work for plugins that provide a
94
+ * `package.json` info loader.
95
+ */
96
+ packageName?: string;
97
+ };
98
+ /**
99
+ * Overrides individual top-level fields of the plugin info.
100
+ */
101
+ info: {
102
+ /**
103
+ * Override the description of the plugin.
104
+ */
105
+ description?: string;
106
+ /**
107
+ * Override the owner entity references of the plugin.
108
+ *
109
+ * @remarks
110
+ * The provided values are interpreted as entity references defaulting
111
+ * to Group entities in the default namespace.
112
+ */
113
+ ownerEntityRefs?: string[];
114
+ /**
115
+ * Override the links of the plugin.
116
+ */
117
+ links?: Array<{ title: string; url: string }>;
118
+ };
119
+ }>;
54
120
  };
55
121
  }
@@ -0,0 +1,4 @@
1
+ const DEFAULT_NAMESPACE = "default";
2
+
3
+ export { DEFAULT_NAMESPACE };
4
+ //# sourceMappingURL=constants.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.esm.js","sources":["../../../../../catalog-model/src/entity/constants.ts"],"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\n/**\n * The namespace that entities without an explicit namespace fall into.\n *\n * @public\n */\nexport const DEFAULT_NAMESPACE = 'default';\n\n/**\n * Annotation for linking to entity page from catalog pages.\n *\n * @public\n */\nexport const ANNOTATION_VIEW_URL = 'backstage.io/view-url';\n\n/**\n * Annotation for linking to entity edit page from catalog pages.\n *\n * @public\n */\nexport const ANNOTATION_EDIT_URL = 'backstage.io/edit-url';\n\n/**\n * Annotation for specifying the API server of a Kubernetes cluster\n *\n * @deprecated Import this constant from `@backstage/plugin-kubernetes-common` instead\n * @public\n */\nexport const ANNOTATION_KUBERNETES_API_SERVER = 'kubernetes.io/api-server';\n\n/**\n * Annotation for specifying the Certificate Authority of an API server for a Kubernetes cluster\n *\n * @deprecated Import this constant from `@backstage/plugin-kubernetes-common` instead\n * @public\n */\nexport const ANNOTATION_KUBERNETES_API_SERVER_CA =\n 'kubernetes.io/api-server-certificate-authority';\n\n/**\n * Annotation for specifying the auth provider for a Kubernetes cluster\n *\n * @deprecated Import this constant from `@backstage/plugin-kubernetes-common` instead\n * @public\n */\nexport const ANNOTATION_KUBERNETES_AUTH_PROVIDER =\n 'kubernetes.io/auth-provider';\n"],"names":[],"mappings":"AAqBO,MAAM,iBAAoB,GAAA;;;;"}
@@ -0,0 +1,73 @@
1
+ import { DEFAULT_NAMESPACE } from './constants.esm.js';
2
+
3
+ function parseRefString(ref) {
4
+ let colonI = ref.indexOf(":");
5
+ const slashI = ref.indexOf("/");
6
+ if (slashI !== -1 && slashI < colonI) {
7
+ colonI = -1;
8
+ }
9
+ const kind = colonI === -1 ? void 0 : ref.slice(0, colonI);
10
+ const namespace = slashI === -1 ? void 0 : ref.slice(colonI + 1, slashI);
11
+ const name = ref.slice(Math.max(colonI + 1, slashI + 1));
12
+ if (kind === "" || namespace === "" || name === "") {
13
+ throw new TypeError(
14
+ `Entity reference "${ref}" was not on the form [<kind>:][<namespace>/]<name>`
15
+ );
16
+ }
17
+ return { kind, namespace, name };
18
+ }
19
+ function parseEntityRef(ref, context) {
20
+ if (!ref) {
21
+ throw new Error(`Entity reference must not be empty`);
22
+ }
23
+ const defaultKind = context?.defaultKind;
24
+ const defaultNamespace = context?.defaultNamespace || DEFAULT_NAMESPACE;
25
+ let kind;
26
+ let namespace;
27
+ let name;
28
+ if (typeof ref === "string") {
29
+ const parsed = parseRefString(ref);
30
+ kind = parsed.kind ?? defaultKind;
31
+ namespace = parsed.namespace ?? defaultNamespace;
32
+ name = parsed.name;
33
+ } else {
34
+ kind = ref.kind ?? defaultKind;
35
+ namespace = ref.namespace ?? defaultNamespace;
36
+ name = ref.name;
37
+ }
38
+ if (!kind) {
39
+ const textual = JSON.stringify(ref);
40
+ throw new Error(
41
+ `Entity reference ${textual} had missing or empty kind (e.g. did not start with "component:" or similar)`
42
+ );
43
+ } else if (!namespace) {
44
+ const textual = JSON.stringify(ref);
45
+ throw new Error(
46
+ `Entity reference ${textual} had missing or empty namespace`
47
+ );
48
+ } else if (!name) {
49
+ const textual = JSON.stringify(ref);
50
+ throw new Error(`Entity reference ${textual} had missing or empty name`);
51
+ }
52
+ return { kind, namespace, name };
53
+ }
54
+ function stringifyEntityRef(ref) {
55
+ let kind;
56
+ let namespace;
57
+ let name;
58
+ if ("metadata" in ref) {
59
+ kind = ref.kind;
60
+ namespace = ref.metadata.namespace ?? DEFAULT_NAMESPACE;
61
+ name = ref.metadata.name;
62
+ } else {
63
+ kind = ref.kind;
64
+ namespace = ref.namespace ?? DEFAULT_NAMESPACE;
65
+ name = ref.name;
66
+ }
67
+ return `${kind.toLocaleLowerCase("en-US")}:${namespace.toLocaleLowerCase(
68
+ "en-US"
69
+ )}/${name.toLocaleLowerCase("en-US")}`;
70
+ }
71
+
72
+ export { parseEntityRef, stringifyEntityRef };
73
+ //# sourceMappingURL=ref.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.esm.js","sources":["../../../../../catalog-model/src/entity/ref.ts"],"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 { DEFAULT_NAMESPACE } from './constants';\nimport { CompoundEntityRef } from '../types';\nimport { Entity } from './Entity';\n\nfunction parseRefString(ref: string): {\n kind?: string;\n namespace?: string;\n name: string;\n} {\n let colonI = ref.indexOf(':');\n const slashI = ref.indexOf('/');\n\n // If the / is ahead of the :, treat the rest as the name\n if (slashI !== -1 && slashI < colonI) {\n colonI = -1;\n }\n\n const kind = colonI === -1 ? undefined : ref.slice(0, colonI);\n const namespace = slashI === -1 ? undefined : ref.slice(colonI + 1, slashI);\n const name = ref.slice(Math.max(colonI + 1, slashI + 1));\n\n if (kind === '' || namespace === '' || name === '') {\n throw new TypeError(\n `Entity reference \"${ref}\" was not on the form [<kind>:][<namespace>/]<name>`,\n );\n }\n\n return { kind, namespace, name };\n}\n\n/**\n * Extracts the kind, namespace and name that form the compound entity ref\n * triplet of the given entity.\n *\n * @public\n * @param entity - An entity\n * @returns The compound entity ref\n */\nexport function getCompoundEntityRef(entity: Entity): CompoundEntityRef {\n return {\n kind: entity.kind,\n namespace: entity.metadata.namespace || DEFAULT_NAMESPACE,\n name: entity.metadata.name,\n };\n}\n\n/**\n * Parses an entity reference, either on string or compound form, and returns\n * a structure with a name, and optional kind and namespace.\n *\n * @remarks\n *\n * The context object can contain default values for the kind and namespace,\n * that will be used if the input reference did not specify any.\n *\n * @public\n * @param ref - The reference to parse\n * @param context - The context of defaults that the parsing happens within\n * @returns The compound form of the reference\n */\nexport function parseEntityRef(\n ref: string | { kind?: string; namespace?: string; name: string },\n context?: {\n /** The default kind, if none is given in the reference */\n defaultKind?: string;\n /** The default namespace, if none is given in the reference */\n defaultNamespace?: string;\n },\n): CompoundEntityRef {\n if (!ref) {\n throw new Error(`Entity reference must not be empty`);\n }\n\n const defaultKind = context?.defaultKind;\n const defaultNamespace = context?.defaultNamespace || DEFAULT_NAMESPACE;\n\n let kind: string | undefined;\n let namespace: string | undefined;\n let name: string | undefined;\n\n if (typeof ref === 'string') {\n const parsed = parseRefString(ref);\n kind = parsed.kind ?? defaultKind;\n namespace = parsed.namespace ?? defaultNamespace;\n name = parsed.name;\n } else {\n kind = ref.kind ?? defaultKind;\n namespace = ref.namespace ?? defaultNamespace;\n name = ref.name;\n }\n\n if (!kind) {\n const textual = JSON.stringify(ref);\n throw new Error(\n `Entity reference ${textual} had missing or empty kind (e.g. did not start with \"component:\" or similar)`,\n );\n } else if (!namespace) {\n const textual = JSON.stringify(ref);\n throw new Error(\n `Entity reference ${textual} had missing or empty namespace`,\n );\n } else if (!name) {\n const textual = JSON.stringify(ref);\n throw new Error(`Entity reference ${textual} had missing or empty name`);\n }\n\n return { kind, namespace, name };\n}\n\n/**\n * Takes an entity or entity name/reference, and returns the string form of an\n * entity ref.\n *\n * @remarks\n *\n * This function creates a canonical and unique reference to the entity, converting\n * all parts of the name to lowercase and inserts the default namespace if needed.\n * It is typically not the best way to represent the entity reference to the user.\n *\n * @public\n * @param ref - The reference to serialize\n * @returns The same reference on either string or compound form\n */\nexport function stringifyEntityRef(\n ref: Entity | { kind: string; namespace?: string; name: string },\n): string {\n let kind;\n let namespace;\n let name;\n\n if ('metadata' in ref) {\n kind = ref.kind;\n namespace = ref.metadata.namespace ?? DEFAULT_NAMESPACE;\n name = ref.metadata.name;\n } else {\n kind = ref.kind;\n namespace = ref.namespace ?? DEFAULT_NAMESPACE;\n name = ref.name;\n }\n\n return `${kind.toLocaleLowerCase('en-US')}:${namespace.toLocaleLowerCase(\n 'en-US',\n )}/${name.toLocaleLowerCase('en-US')}`;\n}\n"],"names":[],"mappings":";;AAoBA,SAAS,eAAe,GAItB,EAAA;AACA,EAAI,IAAA,MAAA,GAAS,GAAI,CAAA,OAAA,CAAQ,GAAG,CAAA;AAC5B,EAAM,MAAA,MAAA,GAAS,GAAI,CAAA,OAAA,CAAQ,GAAG,CAAA;AAG9B,EAAI,IAAA,MAAA,KAAW,CAAM,CAAA,IAAA,MAAA,GAAS,MAAQ,EAAA;AACpC,IAAS,MAAA,GAAA,CAAA,CAAA;AAAA;AAGX,EAAA,MAAM,OAAO,MAAW,KAAA,CAAA,CAAA,GAAK,SAAY,GAAI,CAAA,KAAA,CAAM,GAAG,MAAM,CAAA;AAC5D,EAAM,MAAA,SAAA,GAAY,WAAW,CAAK,CAAA,GAAA,KAAA,CAAA,GAAY,IAAI,KAAM,CAAA,MAAA,GAAS,GAAG,MAAM,CAAA;AAC1E,EAAM,MAAA,IAAA,GAAO,IAAI,KAAM,CAAA,IAAA,CAAK,IAAI,MAAS,GAAA,CAAA,EAAG,MAAS,GAAA,CAAC,CAAC,CAAA;AAEvD,EAAA,IAAI,IAAS,KAAA,EAAA,IAAM,SAAc,KAAA,EAAA,IAAM,SAAS,EAAI,EAAA;AAClD,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,qBAAqB,GAAG,CAAA,mDAAA;AAAA,KAC1B;AAAA;AAGF,EAAO,OAAA,EAAE,IAAM,EAAA,SAAA,EAAW,IAAK,EAAA;AACjC;AAgCgB,SAAA,cAAA,CACd,KACA,OAMmB,EAAA;AACnB,EAAA,IAAI,CAAC,GAAK,EAAA;AACR,IAAM,MAAA,IAAI,MAAM,CAAoC,kCAAA,CAAA,CAAA;AAAA;AAGtD,EAAA,MAAM,cAAc,OAAS,EAAA,WAAA;AAC7B,EAAM,MAAA,gBAAA,GAAmB,SAAS,gBAAoB,IAAA,iBAAA;AAEtD,EAAI,IAAA,IAAA;AACJ,EAAI,IAAA,SAAA;AACJ,EAAI,IAAA,IAAA;AAEJ,EAAI,IAAA,OAAO,QAAQ,QAAU,EAAA;AAC3B,IAAM,MAAA,MAAA,GAAS,eAAe,GAAG,CAAA;AACjC,IAAA,IAAA,GAAO,OAAO,IAAQ,IAAA,WAAA;AACtB,IAAA,SAAA,GAAY,OAAO,SAAa,IAAA,gBAAA;AAChC,IAAA,IAAA,GAAO,MAAO,CAAA,IAAA;AAAA,GACT,MAAA;AACL,IAAA,IAAA,GAAO,IAAI,IAAQ,IAAA,WAAA;AACnB,IAAA,SAAA,GAAY,IAAI,SAAa,IAAA,gBAAA;AAC7B,IAAA,IAAA,GAAO,GAAI,CAAA,IAAA;AAAA;AAGb,EAAA,IAAI,CAAC,IAAM,EAAA;AACT,IAAM,MAAA,OAAA,GAAU,IAAK,CAAA,SAAA,CAAU,GAAG,CAAA;AAClC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,oBAAoB,OAAO,CAAA,4EAAA;AAAA,KAC7B;AAAA,GACF,MAAA,IAAW,CAAC,SAAW,EAAA;AACrB,IAAM,MAAA,OAAA,GAAU,IAAK,CAAA,SAAA,CAAU,GAAG,CAAA;AAClC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,oBAAoB,OAAO,CAAA,+BAAA;AAAA,KAC7B;AAAA,GACF,MAAA,IAAW,CAAC,IAAM,EAAA;AAChB,IAAM,MAAA,OAAA,GAAU,IAAK,CAAA,SAAA,CAAU,GAAG,CAAA;AAClC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAoB,iBAAA,EAAA,OAAO,CAA4B,0BAAA,CAAA,CAAA;AAAA;AAGzE,EAAO,OAAA,EAAE,IAAM,EAAA,SAAA,EAAW,IAAK,EAAA;AACjC;AAgBO,SAAS,mBACd,GACQ,EAAA;AACR,EAAI,IAAA,IAAA;AACJ,EAAI,IAAA,SAAA;AACJ,EAAI,IAAA,IAAA;AAEJ,EAAA,IAAI,cAAc,GAAK,EAAA;AACrB,IAAA,IAAA,GAAO,GAAI,CAAA,IAAA;AACX,IAAY,SAAA,GAAA,GAAA,CAAI,SAAS,SAAa,IAAA,iBAAA;AACtC,IAAA,IAAA,GAAO,IAAI,QAAS,CAAA,IAAA;AAAA,GACf,MAAA;AACL,IAAA,IAAA,GAAO,GAAI,CAAA,IAAA;AACX,IAAA,SAAA,GAAY,IAAI,SAAa,IAAA,iBAAA;AAC7B,IAAA,IAAA,GAAO,GAAI,CAAA,IAAA;AAAA;AAGb,EAAA,OAAO,GAAG,IAAK,CAAA,iBAAA,CAAkB,OAAO,CAAC,IAAI,SAAU,CAAA,iBAAA;AAAA,IACrD;AAAA,GACD,CAAA,CAAA,EAAI,IAAK,CAAA,iBAAA,CAAkB,OAAO,CAAC,CAAA,CAAA;AACtC;;;;"}
@@ -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 FrontendPlugin,\n} from '@backstage/frontend-plugin-api';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueFrontendPlugin = OpaqueType.create<{\n public: FrontendPlugin;\n versions: {\n readonly version: 'v1';\n readonly extensions: Extension<unknown>[];\n readonly featureFlags: FeatureFlagConfig[];\n };\n}>({\n type: '@backstage/FrontendPlugin',\n versions: ['v1'],\n});\n"],"names":[],"mappings":";;AAuBa,MAAA,oBAAA,GAAuB,WAAW,MAO5C,CAAA;AAAA,EACD,IAAM,EAAA,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 FrontendPlugin,\n} from '@backstage/frontend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueFrontendPlugin = OpaqueType.create<{\n public: FrontendPlugin;\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":";;AAwBa,MAAA,oBAAA,GAAuB,WAAW,MAW5C,CAAA;AAAA,EACD,IAAM,EAAA,2BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC;;;;"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { ExternalRouteRef, RouteRef, SubRouteRef, FrontendFeature as FrontendFeature$1, ExtensionFactoryMiddleware, AppTree } from '@backstage/frontend-plugin-api';
1
+ import { ExternalRouteRef, RouteRef, SubRouteRef, FrontendPluginInfo, FrontendFeature as FrontendFeature$1, ExtensionFactoryMiddleware, AppTree } from '@backstage/frontend-plugin-api';
2
2
  import { ConfigApi, ApiHolder } from '@backstage/core-plugin-api';
3
+ import { JsonObject } from '@backstage/types';
3
4
 
4
5
  /**
5
6
  * Extracts a union of the keys in a map whose value extends the given type
@@ -39,10 +40,23 @@ type CreateAppRouteBinder = <TExternalRoutes extends {
39
40
  [name: string]: ExternalRouteRef;
40
41
  }>(externalRoutes: TExternalRoutes, targetRoutes: PartialKeys<TargetRouteMap<TExternalRoutes>, KeysWithType<TExternalRoutes, ExternalRouteRef<any>>>) => void;
41
42
 
42
- /** @public
43
- * @deprecated Use {@link @backstage/frontend-plugin-api#FrontendFeature} instead.
43
+ /**
44
+ * A function that resolves plugin info from a plugin manifest and package.json.
45
+ *
46
+ * @public
44
47
  */
45
- type FrontendFeature = FrontendFeature$1;
48
+ type FrontendPluginInfoResolver = (ctx: {
49
+ packageJson(): Promise<JsonObject | undefined>;
50
+ manifest(): Promise<JsonObject | undefined>;
51
+ defaultResolver(sources: {
52
+ packageJson: JsonObject | undefined;
53
+ manifest: JsonObject | undefined;
54
+ }): Promise<{
55
+ info: FrontendPluginInfo;
56
+ }>;
57
+ }) => Promise<{
58
+ info: FrontendPluginInfo;
59
+ }>;
46
60
 
47
61
  /**
48
62
  * Creates an empty app without any default features. This is a low-level API is
@@ -52,7 +66,7 @@ type FrontendFeature = FrontendFeature$1;
52
66
  * @public
53
67
  */
54
68
  declare function createSpecializedApp(options?: {
55
- features?: FrontendFeature[];
69
+ features?: FrontendFeature$1[];
56
70
  config?: ConfigApi;
57
71
  bindRoutes?(context: {
58
72
  bind: CreateAppRouteBinder;
@@ -62,9 +76,15 @@ declare function createSpecializedApp(options?: {
62
76
  flags?: {
63
77
  allowUnknownExtensionConfig?: boolean;
64
78
  };
79
+ pluginInfoResolver?: FrontendPluginInfoResolver;
65
80
  }): {
66
81
  apis: ApiHolder;
67
82
  tree: AppTree;
68
83
  };
69
84
 
70
- export { type CreateAppRouteBinder, type FrontendFeature, createSpecializedApp };
85
+ /** @public
86
+ * @deprecated Use {@link @backstage/frontend-plugin-api#FrontendFeature} instead.
87
+ */
88
+ type FrontendFeature = FrontendFeature$1;
89
+
90
+ export { type CreateAppRouteBinder, type FrontendFeature, type FrontendPluginInfoResolver, createSpecializedApp };
@@ -0,0 +1,169 @@
1
+ import once from 'lodash/once';
2
+ import { stringifyEntityRef, parseEntityRef } from '../catalog-model/src/entity/ref.esm.js';
3
+ import { OpaqueFrontendPlugin } from '../frontend-internal/src/wiring/InternalFrontendPlugin.esm.js';
4
+
5
+ function createPluginInfoAttacher(config, infoResolver = async (ctx) => ctx.defaultResolver({
6
+ packageJson: await ctx.packageJson(),
7
+ manifest: await ctx.manifest()
8
+ })) {
9
+ const applyInfoOverrides = createPluginInfoOverrider(config);
10
+ return (feature) => {
11
+ if (!OpaqueFrontendPlugin.isType(feature)) {
12
+ return feature;
13
+ }
14
+ const plugin = OpaqueFrontendPlugin.toInternal(feature);
15
+ return {
16
+ ...plugin,
17
+ info: once(async () => {
18
+ const manifestLoader = plugin.infoOptions?.manifest;
19
+ const packageJsonLoader = plugin.infoOptions?.packageJson;
20
+ const { info: resolvedInfo } = await infoResolver({
21
+ manifest: async () => manifestLoader?.(),
22
+ packageJson: async () => packageJsonLoader?.(),
23
+ defaultResolver: async (sources) => ({
24
+ info: {
25
+ ...resolvePackageInfo(sources.packageJson),
26
+ ...resolveManifestInfo(sources.manifest)
27
+ }
28
+ })
29
+ });
30
+ const infoWithOverrides = applyInfoOverrides(plugin.id, resolvedInfo);
31
+ return normalizePluginInfo(infoWithOverrides);
32
+ })
33
+ };
34
+ };
35
+ }
36
+ function normalizePluginInfo(info) {
37
+ return {
38
+ ...info,
39
+ ownerEntityRefs: info.ownerEntityRefs?.map(
40
+ (ref) => stringifyEntityRef(
41
+ parseEntityRef(ref, {
42
+ defaultKind: "Group"
43
+ })
44
+ )
45
+ )
46
+ };
47
+ }
48
+ function createPluginInfoOverrider(config) {
49
+ const overrideConfigs = config.getOptionalConfigArray("app.pluginOverrides") ?? [];
50
+ const overrideMatchers = overrideConfigs.map((overrideConfig) => {
51
+ const pluginIdMatcher = makeStringMatcher(
52
+ overrideConfig.getOptionalString("match.pluginId")
53
+ );
54
+ const packageNameMatcher = makeStringMatcher(
55
+ overrideConfig.getOptionalString("match.packageName")
56
+ );
57
+ const description = overrideConfig.getOptionalString("info.description");
58
+ const ownerEntityRefs = overrideConfig.getOptionalStringArray(
59
+ "info.ownerEntityRefs"
60
+ );
61
+ const links = overrideConfig.getOptionalConfigArray("info.links")?.map((linkConfig) => ({
62
+ title: linkConfig.getString("title"),
63
+ url: linkConfig.getString("url")
64
+ }));
65
+ return {
66
+ test(pluginId, packageName) {
67
+ return packageNameMatcher(packageName) && pluginIdMatcher(pluginId);
68
+ },
69
+ info: {
70
+ description,
71
+ ownerEntityRefs,
72
+ links
73
+ }
74
+ };
75
+ });
76
+ return (pluginId, info) => {
77
+ const { packageName } = info;
78
+ for (const matcher of overrideMatchers) {
79
+ if (matcher.test(pluginId, packageName)) {
80
+ if (matcher.info.description) {
81
+ info.description = matcher.info.description;
82
+ }
83
+ if (matcher.info.ownerEntityRefs) {
84
+ info.ownerEntityRefs = matcher.info.ownerEntityRefs;
85
+ }
86
+ if (matcher.info.links) {
87
+ info.links = matcher.info.links;
88
+ }
89
+ }
90
+ }
91
+ return info;
92
+ };
93
+ }
94
+ function resolveManifestInfo(manifest) {
95
+ if (!isJsonObject(manifest) || !isJsonObject(manifest.metadata)) {
96
+ return void 0;
97
+ }
98
+ const info = {};
99
+ if (isJsonObject(manifest.spec) && typeof manifest.spec.owner === "string") {
100
+ info.ownerEntityRefs = [
101
+ stringifyEntityRef(
102
+ parseEntityRef(manifest.spec.owner, {
103
+ defaultKind: "Group",
104
+ defaultNamespace: manifest.metadata.namespace?.toString()
105
+ })
106
+ )
107
+ ];
108
+ }
109
+ if (Array.isArray(manifest.metadata.links)) {
110
+ info.links = manifest.metadata.links.filter(isJsonObject).map((link) => ({
111
+ title: String(link.title),
112
+ url: String(link.url)
113
+ }));
114
+ }
115
+ return info;
116
+ }
117
+ function resolvePackageInfo(packageJson) {
118
+ if (!packageJson) {
119
+ return void 0;
120
+ }
121
+ const info = {
122
+ packageName: packageJson?.name?.toString(),
123
+ version: packageJson?.version?.toString(),
124
+ description: packageJson?.description?.toString()
125
+ };
126
+ const links = [];
127
+ if (typeof packageJson.homepage === "string") {
128
+ links.push({
129
+ title: "Homepage",
130
+ url: packageJson.homepage
131
+ });
132
+ }
133
+ if (isJsonObject(packageJson.repository) && typeof packageJson.repository?.url === "string") {
134
+ try {
135
+ const url = new URL(packageJson.repository?.url);
136
+ if (url.protocol === "http:" || url.protocol === "https:") {
137
+ if (url.hostname === "github.com" && typeof packageJson.repository.directory === "string") {
138
+ const path = `${url.pathname}/tree/-/${packageJson.repository.directory}`;
139
+ url.pathname = path.replaceAll("//", "/");
140
+ }
141
+ links.push({
142
+ title: "Repository",
143
+ url: url.toString()
144
+ });
145
+ }
146
+ } catch {
147
+ }
148
+ }
149
+ if (links.length > 0) {
150
+ info.links = links;
151
+ }
152
+ return info;
153
+ }
154
+ function makeStringMatcher(pattern) {
155
+ if (!pattern) {
156
+ return () => true;
157
+ }
158
+ if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length > 2) {
159
+ const regex = new RegExp(pattern.slice(1, -1));
160
+ return (str) => str ? regex.test(str) : false;
161
+ }
162
+ return (str) => str === pattern;
163
+ }
164
+ function isJsonObject(value) {
165
+ return typeof value === "object" && value !== null && !Array.isArray(value);
166
+ }
167
+
168
+ export { createPluginInfoAttacher };
169
+ //# sourceMappingURL=createPluginInfoAttacher.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createPluginInfoAttacher.esm.js","sources":["../../src/wiring/createPluginInfoAttacher.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConfigApi } from '@backstage/core-plugin-api';\nimport {\n FrontendFeature,\n FrontendPluginInfo,\n} from '@backstage/frontend-plugin-api';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport once from 'lodash/once';\n// Avoid full dependency on catalog-model\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n parseEntityRef,\n stringifyEntityRef,\n} from '../../../catalog-model/src/entity/ref';\n\n/**\n * A function that resolves plugin info from a plugin manifest and package.json.\n *\n * @public\n */\nexport type FrontendPluginInfoResolver = (ctx: {\n packageJson(): Promise<JsonObject | undefined>;\n manifest(): Promise<JsonObject | undefined>;\n defaultResolver(sources: {\n packageJson: JsonObject | undefined;\n manifest: JsonObject | undefined;\n }): Promise<{ info: FrontendPluginInfo }>;\n}) => Promise<{ info: FrontendPluginInfo }>;\n\nexport function createPluginInfoAttacher(\n config: ConfigApi,\n infoResolver: FrontendPluginInfoResolver = async ctx =>\n ctx.defaultResolver({\n packageJson: await ctx.packageJson(),\n manifest: await ctx.manifest(),\n }),\n): (feature: FrontendFeature) => FrontendFeature {\n const applyInfoOverrides = createPluginInfoOverrider(config);\n\n return (feature: FrontendFeature) => {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n return feature;\n }\n\n const plugin = OpaqueFrontendPlugin.toInternal(feature);\n\n return {\n ...plugin,\n info: once(async () => {\n const manifestLoader = plugin.infoOptions?.manifest;\n const packageJsonLoader = plugin.infoOptions?.packageJson;\n\n const { info: resolvedInfo } = await infoResolver({\n manifest: async () => manifestLoader?.(),\n packageJson: async () => packageJsonLoader?.(),\n defaultResolver: async sources => ({\n info: {\n ...resolvePackageInfo(sources.packageJson),\n ...resolveManifestInfo(sources.manifest),\n },\n }),\n });\n\n const infoWithOverrides = applyInfoOverrides(plugin.id, resolvedInfo);\n return normalizePluginInfo(infoWithOverrides);\n }),\n };\n };\n}\n\nfunction normalizePluginInfo(info: FrontendPluginInfo) {\n return {\n ...info,\n ownerEntityRefs: info.ownerEntityRefs?.map(ref =>\n stringifyEntityRef(\n parseEntityRef(ref, {\n defaultKind: 'Group',\n }),\n ),\n ),\n };\n}\n\nfunction createPluginInfoOverrider(config: ConfigApi) {\n const overrideConfigs =\n config.getOptionalConfigArray('app.pluginOverrides') ?? [];\n\n const overrideMatchers = overrideConfigs.map(overrideConfig => {\n const pluginIdMatcher = makeStringMatcher(\n overrideConfig.getOptionalString('match.pluginId'),\n );\n const packageNameMatcher = makeStringMatcher(\n overrideConfig.getOptionalString('match.packageName'),\n );\n const description = overrideConfig.getOptionalString('info.description');\n const ownerEntityRefs = overrideConfig.getOptionalStringArray(\n 'info.ownerEntityRefs',\n );\n const links = overrideConfig\n .getOptionalConfigArray('info.links')\n ?.map(linkConfig => ({\n title: linkConfig.getString('title'),\n url: linkConfig.getString('url'),\n }));\n\n return {\n test(pluginId: string, packageName?: string) {\n return packageNameMatcher(packageName) && pluginIdMatcher(pluginId);\n },\n info: {\n description,\n ownerEntityRefs,\n links,\n },\n };\n });\n\n return (pluginId: string, info: FrontendPluginInfo) => {\n const { packageName } = info;\n for (const matcher of overrideMatchers) {\n if (matcher.test(pluginId, packageName)) {\n if (matcher.info.description) {\n info.description = matcher.info.description;\n }\n if (matcher.info.ownerEntityRefs) {\n info.ownerEntityRefs = matcher.info.ownerEntityRefs;\n }\n if (matcher.info.links) {\n info.links = matcher.info.links;\n }\n }\n }\n return info;\n };\n}\n\nfunction resolveManifestInfo(manifest?: JsonValue) {\n if (!isJsonObject(manifest) || !isJsonObject(manifest.metadata)) {\n return undefined;\n }\n\n const info: FrontendPluginInfo = {};\n\n if (isJsonObject(manifest.spec) && typeof manifest.spec.owner === 'string') {\n info.ownerEntityRefs = [\n stringifyEntityRef(\n parseEntityRef(manifest.spec.owner, {\n defaultKind: 'Group',\n defaultNamespace: manifest.metadata.namespace?.toString(),\n }),\n ),\n ];\n }\n\n if (Array.isArray(manifest.metadata.links)) {\n info.links = manifest.metadata.links.filter(isJsonObject).map(link => ({\n title: String(link.title),\n url: String(link.url),\n }));\n }\n\n return info;\n}\n\nfunction resolvePackageInfo(packageJson?: JsonObject) {\n if (!packageJson) {\n return undefined;\n }\n\n const info: FrontendPluginInfo = {\n packageName: packageJson?.name?.toString(),\n version: packageJson?.version?.toString(),\n description: packageJson?.description?.toString(),\n };\n\n const links: { title: string; url: string }[] = [];\n\n if (typeof packageJson.homepage === 'string') {\n links.push({\n title: 'Homepage',\n url: packageJson.homepage,\n });\n }\n\n if (\n isJsonObject(packageJson.repository) &&\n typeof packageJson.repository?.url === 'string'\n ) {\n try {\n const url = new URL(packageJson.repository?.url);\n if (url.protocol === 'http:' || url.protocol === 'https:') {\n // TODO(Rugvip): Support more variants\n if (\n url.hostname === 'github.com' &&\n typeof packageJson.repository.directory === 'string'\n ) {\n const path = `${url.pathname}/tree/-/${packageJson.repository.directory}`;\n url.pathname = path.replaceAll('//', '/');\n }\n\n links.push({\n title: 'Repository',\n url: url.toString(),\n });\n }\n } catch {\n /* ignored */\n }\n }\n\n if (links.length > 0) {\n info.links = links;\n }\n return info;\n}\n\nfunction makeStringMatcher(pattern: string | undefined) {\n if (!pattern) {\n return () => true;\n }\n if (pattern.startsWith('/') && pattern.endsWith('/') && pattern.length > 2) {\n const regex = new RegExp(pattern.slice(1, -1));\n return (str?: string) => (str ? regex.test(str) : false);\n }\n\n return (str?: string) => str === pattern;\n}\n\nfunction isJsonObject(value?: JsonValue): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"],"names":[],"mappings":"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"}
@@ -17,6 +17,7 @@ import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree.esm.js';
17
17
  import { ApiRegistry } from '../core-app-api/src/apis/system/ApiRegistry.esm.js';
18
18
  import { AppIdentityProxy } from '../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.esm.js';
19
19
  import { matchRoutes } from 'react-router-dom';
20
+ import { createPluginInfoAttacher } from './createPluginInfoAttacher.esm.js';
20
21
  import { OpaqueFrontendPlugin } from '../frontend-internal/src/wiring/InternalFrontendPlugin.esm.js';
21
22
  import { createExtensionDataContainer } from '../frontend-internal/src/wiring/createExtensionDataContainer.esm.js';
22
23
 
@@ -97,7 +98,9 @@ class RouteResolutionApiProxy {
97
98
  }
98
99
  function createSpecializedApp(options) {
99
100
  const config = options?.config ?? new ConfigReader({}, "empty-config");
100
- const features = deduplicateFeatures(options?.features ?? []);
101
+ const features = deduplicateFeatures(options?.features ?? []).map(
102
+ createPluginInfoAttacher(config, options?.pluginInfoResolver)
103
+ );
101
104
  const tree = resolveAppTree(
102
105
  "root",
103
106
  resolveAppNodeSpecs({
@@ -1 +1 @@
1
- {"version":3,"file":"createSpecializedApp.esm.js","sources":["../../src/wiring/createSpecializedApp.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 { ConfigReader } from '@backstage/config';\nimport {\n ApiBlueprint,\n AppTree,\n AppTreeApi,\n appTreeApiRef,\n RouteRef,\n ExternalRouteRef,\n SubRouteRef,\n AnyRouteRefParams,\n RouteFunc,\n RouteResolutionApiResolveOptions,\n RouteResolutionApi,\n createApiFactory,\n routeResolutionApiRef,\n AppNode,\n ExtensionFactoryMiddleware,\n} from '@backstage/frontend-plugin-api';\nimport {\n AnyApiFactory,\n ApiHolder,\n ConfigApi,\n configApiRef,\n featureFlagsApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api';\nimport {\n createExtensionDataContainer,\n OpaqueFrontendPlugin,\n} from '@internal/frontend';\n\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n resolveExtensionDefinition,\n toInternalExtension,\n} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\n\nimport { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode';\n\nimport { CreateAppRouteBinder } from '../routing';\nimport { RouteResolver } from '../routing/RouteResolver';\nimport { resolveRouteBindings } from '../routing/resolveRouteBindings';\nimport { collectRouteIds } from '../routing/collectRouteIds';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n toInternalFrontendModule,\n isInternalFrontendModule,\n} from '../../../frontend-plugin-api/src/wiring/createFrontendModule';\nimport { getBasePath } from '../routing/getBasePath';\nimport { Root } from '../extensions/Root';\nimport { resolveAppTree } from '../tree/resolveAppTree';\nimport { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs';\nimport { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig';\nimport { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';\nimport { BackstageRouteObject } from '../routing/types';\nimport { FrontendFeature, RouteInfo } from './types';\nimport { matchRoutes } from 'react-router-dom';\n\nfunction deduplicateFeatures(\n allFeatures: FrontendFeature[],\n): FrontendFeature[] {\n // Start by removing duplicates by reference\n const features = Array.from(new Set(allFeatures));\n\n // Plugins are deduplicated by ID, last one wins\n const seenIds = new Set<string>();\n return features\n .reverse()\n .filter(feature => {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n return true;\n }\n if (seenIds.has(feature.id)) {\n return false;\n }\n seenIds.add(feature.id);\n return true;\n })\n .reverse();\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nclass AppTreeApiProxy implements AppTreeApi {\n #routeInfo?: RouteInfo;\n\n constructor(\n private readonly tree: AppTree,\n private readonly appBasePath: string,\n ) {}\n\n private checkIfInitialized() {\n if (!this.#routeInfo) {\n throw new Error(\n `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n }\n\n getTree() {\n this.checkIfInitialized();\n\n return { tree: this.tree };\n }\n\n getNodesByRoutePath(sourcePath: string): { nodes: AppNode[] } {\n this.checkIfInitialized();\n\n let path = sourcePath;\n if (path.startsWith(this.appBasePath)) {\n path = path.slice(this.appBasePath.length);\n }\n\n const matchedRoutes = matchRoutes(this.#routeInfo!.routeObjects, path);\n\n const matchedAppNodes =\n matchedRoutes\n ?.filter(routeObj => !!routeObj.route.appNode)\n .map(routeObj => routeObj.route.appNode!) || [];\n\n return { nodes: matchedAppNodes };\n }\n\n initialize(routeInfo: RouteInfo) {\n this.#routeInfo = routeInfo;\n }\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nclass RouteResolutionApiProxy implements RouteResolutionApi {\n #delegate: RouteResolutionApi | undefined;\n #routeObjects: BackstageRouteObject[] | undefined;\n\n constructor(\n private readonly routeBindings: Map<\n ExternalRouteRef,\n RouteRef | SubRouteRef\n >,\n private readonly appBasePath: string,\n ) {}\n\n resolve<TParams extends AnyRouteRefParams>(\n anyRouteRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n options?: RouteResolutionApiResolveOptions,\n ): RouteFunc<TParams> | undefined {\n if (!this.#delegate) {\n throw new Error(\n `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n\n return this.#delegate.resolve(anyRouteRef, options);\n }\n\n initialize(routeInfo: RouteInfo) {\n this.#delegate = new RouteResolver(\n routeInfo.routePaths,\n routeInfo.routeParents,\n routeInfo.routeObjects,\n this.routeBindings,\n this.appBasePath,\n );\n this.#routeObjects = routeInfo.routeObjects;\n\n return routeInfo;\n }\n\n getRouteObjects() {\n return this.#routeObjects;\n }\n}\n\n/**\n * Creates an empty app without any default features. This is a low-level API is\n * intended for use in tests or specialized setups. Typically you want to use\n * `createApp` from `@backstage/frontend-defaults` instead.\n *\n * @public\n */\nexport function createSpecializedApp(options?: {\n features?: FrontendFeature[];\n config?: ConfigApi;\n bindRoutes?(context: { bind: CreateAppRouteBinder }): void;\n apis?: ApiHolder;\n extensionFactoryMiddleware?:\n | ExtensionFactoryMiddleware\n | ExtensionFactoryMiddleware[];\n flags?: { allowUnknownExtensionConfig?: boolean };\n}): { apis: ApiHolder; tree: AppTree } {\n const config = options?.config ?? new ConfigReader({}, 'empty-config');\n const features = deduplicateFeatures(options?.features ?? []);\n\n const tree = resolveAppTree(\n 'root',\n resolveAppNodeSpecs({\n features,\n builtinExtensions: [\n resolveExtensionDefinition(Root, { namespace: 'root' }),\n ],\n parameters: readAppExtensionsConfig(config),\n forbidden: new Set(['root']),\n allowUnknownExtensionConfig: options?.flags?.allowUnknownExtensionConfig,\n }),\n );\n\n const factories = createApiFactories({ tree });\n const appBasePath = getBasePath(config);\n const appTreeApi = new AppTreeApiProxy(tree, appBasePath);\n const routeResolutionApi = new RouteResolutionApiProxy(\n resolveRouteBindings(\n options?.bindRoutes,\n config,\n collectRouteIds(features),\n ),\n appBasePath,\n );\n\n const appIdentityProxy = new AppIdentityProxy();\n const apis =\n options?.apis ??\n createApiHolder({\n factories,\n staticFactories: [\n createApiFactory(appTreeApiRef, appTreeApi),\n createApiFactory(configApiRef, config),\n createApiFactory(routeResolutionApiRef, routeResolutionApi),\n createApiFactory(identityApiRef, appIdentityProxy),\n ],\n });\n\n const featureFlagApi = apis.get(featureFlagsApiRef);\n if (featureFlagApi) {\n for (const feature of features) {\n if (OpaqueFrontendPlugin.isType(feature)) {\n OpaqueFrontendPlugin.toInternal(feature).featureFlags.forEach(flag =>\n featureFlagApi.registerFlag({\n name: flag.name,\n pluginId: feature.id,\n }),\n );\n }\n if (isInternalFrontendModule(feature)) {\n toInternalFrontendModule(feature).featureFlags.forEach(flag =>\n featureFlagApi.registerFlag({\n name: flag.name,\n pluginId: feature.pluginId,\n }),\n );\n }\n }\n }\n\n // Now instantiate the entire tree, which will skip anything that's already been instantiated\n instantiateAppNodeTree(\n tree.root,\n apis,\n mergeExtensionFactoryMiddleware(options?.extensionFactoryMiddleware),\n );\n\n const routeInfo = extractRouteInfoFromAppNode(tree.root);\n\n routeResolutionApi.initialize(routeInfo);\n appTreeApi.initialize(routeInfo);\n\n return { apis, tree };\n}\n\nfunction createApiFactories(options: { tree: AppTree }): AnyApiFactory[] {\n const emptyApiHolder = ApiRegistry.from([]);\n const factories = new Array<AnyApiFactory>();\n\n for (const apiNode of options.tree.root.edges.attachments.get('apis') ?? []) {\n instantiateAppNodeTree(apiNode, emptyApiHolder);\n const apiFactory = apiNode.instance?.getData(ApiBlueprint.dataRefs.factory);\n if (!apiFactory) {\n throw new Error(\n `No API factory found in for extension ${apiNode.spec.id}`,\n );\n }\n factories.push(apiFactory);\n }\n\n return factories;\n}\n\nfunction createApiHolder(options: {\n factories: AnyApiFactory[];\n staticFactories: AnyApiFactory[];\n}): ApiHolder {\n const factoryRegistry = new ApiFactoryRegistry();\n\n for (const factory of options.factories.slice().reverse()) {\n factoryRegistry.register('default', factory);\n }\n\n for (const factory of options.staticFactories) {\n factoryRegistry.register('static', factory);\n }\n\n ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis());\n\n return new ApiResolver(factoryRegistry);\n}\n\nfunction mergeExtensionFactoryMiddleware(\n middlewares?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[],\n): ExtensionFactoryMiddleware | undefined {\n if (!middlewares) {\n return undefined;\n }\n if (!Array.isArray(middlewares)) {\n return middlewares;\n }\n if (middlewares.length <= 1) {\n return middlewares[0];\n }\n return middlewares.reduce((prev, next) => {\n if (!prev || !next) {\n return prev ?? next;\n }\n return (orig, ctx) => {\n const internalExt = toInternalExtension(ctx.node.spec.extension);\n if (internalExt.version !== 'v2') {\n return orig();\n }\n return next(ctxOverrides => {\n return createExtensionDataContainer(\n prev(orig, {\n node: ctx.node,\n apis: ctx.apis,\n config: ctxOverrides?.config ?? ctx.config,\n }),\n );\n }, ctx);\n };\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"createSpecializedApp.esm.js","sources":["../../src/wiring/createSpecializedApp.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 { ConfigReader } from '@backstage/config';\nimport {\n ApiBlueprint,\n AppTree,\n AppTreeApi,\n appTreeApiRef,\n RouteRef,\n ExternalRouteRef,\n SubRouteRef,\n AnyRouteRefParams,\n RouteFunc,\n RouteResolutionApiResolveOptions,\n RouteResolutionApi,\n createApiFactory,\n routeResolutionApiRef,\n AppNode,\n ExtensionFactoryMiddleware,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\nimport {\n AnyApiFactory,\n ApiHolder,\n ConfigApi,\n configApiRef,\n featureFlagsApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api';\nimport {\n createExtensionDataContainer,\n OpaqueFrontendPlugin,\n} from '@internal/frontend';\n\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n resolveExtensionDefinition,\n toInternalExtension,\n} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\n\nimport { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode';\n\nimport { CreateAppRouteBinder } from '../routing';\nimport { RouteResolver } from '../routing/RouteResolver';\nimport { resolveRouteBindings } from '../routing/resolveRouteBindings';\nimport { collectRouteIds } from '../routing/collectRouteIds';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n toInternalFrontendModule,\n isInternalFrontendModule,\n} from '../../../frontend-plugin-api/src/wiring/createFrontendModule';\nimport { getBasePath } from '../routing/getBasePath';\nimport { Root } from '../extensions/Root';\nimport { resolveAppTree } from '../tree/resolveAppTree';\nimport { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs';\nimport { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig';\nimport { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';\nimport { BackstageRouteObject } from '../routing/types';\nimport { RouteInfo } from './types';\nimport { matchRoutes } from 'react-router-dom';\nimport {\n createPluginInfoAttacher,\n FrontendPluginInfoResolver,\n} from './createPluginInfoAttacher';\n\nfunction deduplicateFeatures(\n allFeatures: FrontendFeature[],\n): FrontendFeature[] {\n // Start by removing duplicates by reference\n const features = Array.from(new Set(allFeatures));\n\n // Plugins are deduplicated by ID, last one wins\n const seenIds = new Set<string>();\n return features\n .reverse()\n .filter(feature => {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n return true;\n }\n if (seenIds.has(feature.id)) {\n return false;\n }\n seenIds.add(feature.id);\n return true;\n })\n .reverse();\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nclass AppTreeApiProxy implements AppTreeApi {\n #routeInfo?: RouteInfo;\n\n constructor(\n private readonly tree: AppTree,\n private readonly appBasePath: string,\n ) {}\n\n private checkIfInitialized() {\n if (!this.#routeInfo) {\n throw new Error(\n `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n }\n\n getTree() {\n this.checkIfInitialized();\n\n return { tree: this.tree };\n }\n\n getNodesByRoutePath(sourcePath: string): { nodes: AppNode[] } {\n this.checkIfInitialized();\n\n let path = sourcePath;\n if (path.startsWith(this.appBasePath)) {\n path = path.slice(this.appBasePath.length);\n }\n\n const matchedRoutes = matchRoutes(this.#routeInfo!.routeObjects, path);\n\n const matchedAppNodes =\n matchedRoutes\n ?.filter(routeObj => !!routeObj.route.appNode)\n .map(routeObj => routeObj.route.appNode!) || [];\n\n return { nodes: matchedAppNodes };\n }\n\n initialize(routeInfo: RouteInfo) {\n this.#routeInfo = routeInfo;\n }\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nclass RouteResolutionApiProxy implements RouteResolutionApi {\n #delegate: RouteResolutionApi | undefined;\n #routeObjects: BackstageRouteObject[] | undefined;\n\n constructor(\n private readonly routeBindings: Map<\n ExternalRouteRef,\n RouteRef | SubRouteRef\n >,\n private readonly appBasePath: string,\n ) {}\n\n resolve<TParams extends AnyRouteRefParams>(\n anyRouteRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n options?: RouteResolutionApiResolveOptions,\n ): RouteFunc<TParams> | undefined {\n if (!this.#delegate) {\n throw new Error(\n `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n\n return this.#delegate.resolve(anyRouteRef, options);\n }\n\n initialize(routeInfo: RouteInfo) {\n this.#delegate = new RouteResolver(\n routeInfo.routePaths,\n routeInfo.routeParents,\n routeInfo.routeObjects,\n this.routeBindings,\n this.appBasePath,\n );\n this.#routeObjects = routeInfo.routeObjects;\n\n return routeInfo;\n }\n\n getRouteObjects() {\n return this.#routeObjects;\n }\n}\n\n/**\n * Creates an empty app without any default features. This is a low-level API is\n * intended for use in tests or specialized setups. Typically you want to use\n * `createApp` from `@backstage/frontend-defaults` instead.\n *\n * @public\n */\nexport function createSpecializedApp(options?: {\n features?: FrontendFeature[];\n config?: ConfigApi;\n bindRoutes?(context: { bind: CreateAppRouteBinder }): void;\n apis?: ApiHolder;\n extensionFactoryMiddleware?:\n | ExtensionFactoryMiddleware\n | ExtensionFactoryMiddleware[];\n flags?: { allowUnknownExtensionConfig?: boolean };\n pluginInfoResolver?: FrontendPluginInfoResolver;\n}): { apis: ApiHolder; tree: AppTree } {\n const config = options?.config ?? new ConfigReader({}, 'empty-config');\n const features = deduplicateFeatures(options?.features ?? []).map(\n createPluginInfoAttacher(config, options?.pluginInfoResolver),\n );\n\n const tree = resolveAppTree(\n 'root',\n resolveAppNodeSpecs({\n features,\n builtinExtensions: [\n resolveExtensionDefinition(Root, { namespace: 'root' }),\n ],\n parameters: readAppExtensionsConfig(config),\n forbidden: new Set(['root']),\n allowUnknownExtensionConfig: options?.flags?.allowUnknownExtensionConfig,\n }),\n );\n\n const factories = createApiFactories({ tree });\n const appBasePath = getBasePath(config);\n const appTreeApi = new AppTreeApiProxy(tree, appBasePath);\n const routeResolutionApi = new RouteResolutionApiProxy(\n resolveRouteBindings(\n options?.bindRoutes,\n config,\n collectRouteIds(features),\n ),\n appBasePath,\n );\n\n const appIdentityProxy = new AppIdentityProxy();\n const apis =\n options?.apis ??\n createApiHolder({\n factories,\n staticFactories: [\n createApiFactory(appTreeApiRef, appTreeApi),\n createApiFactory(configApiRef, config),\n createApiFactory(routeResolutionApiRef, routeResolutionApi),\n createApiFactory(identityApiRef, appIdentityProxy),\n ],\n });\n\n const featureFlagApi = apis.get(featureFlagsApiRef);\n if (featureFlagApi) {\n for (const feature of features) {\n if (OpaqueFrontendPlugin.isType(feature)) {\n OpaqueFrontendPlugin.toInternal(feature).featureFlags.forEach(flag =>\n featureFlagApi.registerFlag({\n name: flag.name,\n pluginId: feature.id,\n }),\n );\n }\n if (isInternalFrontendModule(feature)) {\n toInternalFrontendModule(feature).featureFlags.forEach(flag =>\n featureFlagApi.registerFlag({\n name: flag.name,\n pluginId: feature.pluginId,\n }),\n );\n }\n }\n }\n\n // Now instantiate the entire tree, which will skip anything that's already been instantiated\n instantiateAppNodeTree(\n tree.root,\n apis,\n mergeExtensionFactoryMiddleware(options?.extensionFactoryMiddleware),\n );\n\n const routeInfo = extractRouteInfoFromAppNode(tree.root);\n\n routeResolutionApi.initialize(routeInfo);\n appTreeApi.initialize(routeInfo);\n\n return { apis, tree };\n}\n\nfunction createApiFactories(options: { tree: AppTree }): AnyApiFactory[] {\n const emptyApiHolder = ApiRegistry.from([]);\n const factories = new Array<AnyApiFactory>();\n\n for (const apiNode of options.tree.root.edges.attachments.get('apis') ?? []) {\n instantiateAppNodeTree(apiNode, emptyApiHolder);\n const apiFactory = apiNode.instance?.getData(ApiBlueprint.dataRefs.factory);\n if (!apiFactory) {\n throw new Error(\n `No API factory found in for extension ${apiNode.spec.id}`,\n );\n }\n factories.push(apiFactory);\n }\n\n return factories;\n}\n\nfunction createApiHolder(options: {\n factories: AnyApiFactory[];\n staticFactories: AnyApiFactory[];\n}): ApiHolder {\n const factoryRegistry = new ApiFactoryRegistry();\n\n for (const factory of options.factories.slice().reverse()) {\n factoryRegistry.register('default', factory);\n }\n\n for (const factory of options.staticFactories) {\n factoryRegistry.register('static', factory);\n }\n\n ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis());\n\n return new ApiResolver(factoryRegistry);\n}\n\nfunction mergeExtensionFactoryMiddleware(\n middlewares?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[],\n): ExtensionFactoryMiddleware | undefined {\n if (!middlewares) {\n return undefined;\n }\n if (!Array.isArray(middlewares)) {\n return middlewares;\n }\n if (middlewares.length <= 1) {\n return middlewares[0];\n }\n return middlewares.reduce((prev, next) => {\n if (!prev || !next) {\n return prev ?? next;\n }\n return (orig, ctx) => {\n const internalExt = toInternalExtension(ctx.node.spec.extension);\n if (internalExt.version !== 'v2') {\n return orig();\n }\n return next(ctxOverrides => {\n return createExtensionDataContainer(\n prev(orig, {\n node: ctx.node,\n apis: ctx.apis,\n config: ctxOverrides?.config ?? ctx.config,\n }),\n );\n }, ctx);\n };\n });\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAoFA,SAAS,oBACP,WACmB,EAAA;AAEnB,EAAA,MAAM,WAAW,KAAM,CAAA,IAAA,CAAK,IAAI,GAAA,CAAI,WAAW,CAAC,CAAA;AAGhD,EAAM,MAAA,OAAA,uBAAc,GAAY,EAAA;AAChC,EAAA,OAAO,QACJ,CAAA,OAAA,EACA,CAAA,MAAA,CAAO,CAAW,OAAA,KAAA;AACjB,IAAA,IAAI,CAAC,oBAAA,CAAqB,MAAO,CAAA,OAAO,CAAG,EAAA;AACzC,MAAO,OAAA,IAAA;AAAA;AAET,IAAA,IAAI,OAAQ,CAAA,GAAA,CAAI,OAAQ,CAAA,EAAE,CAAG,EAAA;AAC3B,MAAO,OAAA,KAAA;AAAA;AAET,IAAQ,OAAA,CAAA,GAAA,CAAI,QAAQ,EAAE,CAAA;AACtB,IAAO,OAAA,IAAA;AAAA,GACR,EACA,OAAQ,EAAA;AACb;AAGA,MAAM,eAAsC,CAAA;AAAA,EAG1C,WAAA,CACmB,MACA,WACjB,EAAA;AAFiB,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA;AAChB,EALH,UAAA;AAAA,EAOQ,kBAAqB,GAAA;AAC3B,IAAI,IAAA,CAAC,KAAK,UAAY,EAAA;AACpB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,+IAAA;AAAA,OACF;AAAA;AACF;AACF,EAEA,OAAU,GAAA;AACR,IAAA,IAAA,CAAK,kBAAmB,EAAA;AAExB,IAAO,OAAA,EAAE,IAAM,EAAA,IAAA,CAAK,IAAK,EAAA;AAAA;AAC3B,EAEA,oBAAoB,UAA0C,EAAA;AAC5D,IAAA,IAAA,CAAK,kBAAmB,EAAA;AAExB,IAAA,IAAI,IAAO,GAAA,UAAA;AACX,IAAA,IAAI,IAAK,CAAA,UAAA,CAAW,IAAK,CAAA,WAAW,CAAG,EAAA;AACrC,MAAA,IAAA,GAAO,IAAK,CAAA,KAAA,CAAM,IAAK,CAAA,WAAA,CAAY,MAAM,CAAA;AAAA;AAG3C,IAAA,MAAM,aAAgB,GAAA,WAAA,CAAY,IAAK,CAAA,UAAA,CAAY,cAAc,IAAI,CAAA;AAErE,IAAA,MAAM,kBACJ,aACI,EAAA,MAAA,CAAO,CAAY,QAAA,KAAA,CAAC,CAAC,QAAS,CAAA,KAAA,CAAM,OAAO,CAAA,CAC5C,IAAI,CAAY,QAAA,KAAA,QAAA,CAAS,KAAM,CAAA,OAAQ,KAAK,EAAC;AAElD,IAAO,OAAA,EAAE,OAAO,eAAgB,EAAA;AAAA;AAClC,EAEA,WAAW,SAAsB,EAAA;AAC/B,IAAA,IAAA,CAAK,UAAa,GAAA,SAAA;AAAA;AAEtB;AAGA,MAAM,uBAAsD,CAAA;AAAA,EAI1D,WAAA,CACmB,eAIA,WACjB,EAAA;AALiB,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AAIA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA;AAChB,EATH,SAAA;AAAA,EACA,aAAA;AAAA,EAUA,OAAA,CACE,aAIA,OACgC,EAAA;AAChC,IAAI,IAAA,CAAC,KAAK,SAAW,EAAA;AACnB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,kJAAA;AAAA,OACF;AAAA;AAGF,IAAA,OAAO,IAAK,CAAA,SAAA,CAAU,OAAQ,CAAA,WAAA,EAAa,OAAO,CAAA;AAAA;AACpD,EAEA,WAAW,SAAsB,EAAA;AAC/B,IAAA,IAAA,CAAK,YAAY,IAAI,aAAA;AAAA,MACnB,SAAU,CAAA,UAAA;AAAA,MACV,SAAU,CAAA,YAAA;AAAA,MACV,SAAU,CAAA,YAAA;AAAA,MACV,IAAK,CAAA,aAAA;AAAA,MACL,IAAK,CAAA;AAAA,KACP;AACA,IAAA,IAAA,CAAK,gBAAgB,SAAU,CAAA,YAAA;AAE/B,IAAO,OAAA,SAAA;AAAA;AACT,EAEA,eAAkB,GAAA;AAChB,IAAA,OAAO,IAAK,CAAA,aAAA;AAAA;AAEhB;AASO,SAAS,qBAAqB,OAUE,EAAA;AACrC,EAAA,MAAM,SAAS,OAAS,EAAA,MAAA,IAAU,IAAI,YAAa,CAAA,IAAI,cAAc,CAAA;AACrE,EAAA,MAAM,WAAW,mBAAoB,CAAA,OAAA,EAAS,QAAY,IAAA,EAAE,CAAE,CAAA,GAAA;AAAA,IAC5D,wBAAA,CAAyB,MAAQ,EAAA,OAAA,EAAS,kBAAkB;AAAA,GAC9D;AAEA,EAAA,MAAM,IAAO,GAAA,cAAA;AAAA,IACX,MAAA;AAAA,IACA,mBAAoB,CAAA;AAAA,MAClB,QAAA;AAAA,MACA,iBAAmB,EAAA;AAAA,QACjB,0BAA2B,CAAA,IAAA,EAAM,EAAE,SAAA,EAAW,QAAQ;AAAA,OACxD;AAAA,MACA,UAAA,EAAY,wBAAwB,MAAM,CAAA;AAAA,MAC1C,SAAW,kBAAA,IAAI,GAAI,CAAA,CAAC,MAAM,CAAC,CAAA;AAAA,MAC3B,2BAAA,EAA6B,SAAS,KAAO,EAAA;AAAA,KAC9C;AAAA,GACH;AAEA,EAAA,MAAM,SAAY,GAAA,kBAAA,CAAmB,EAAE,IAAA,EAAM,CAAA;AAC7C,EAAM,MAAA,WAAA,GAAc,YAAY,MAAM,CAAA;AACtC,EAAA,MAAM,UAAa,GAAA,IAAI,eAAgB,CAAA,IAAA,EAAM,WAAW,CAAA;AACxD,EAAA,MAAM,qBAAqB,IAAI,uBAAA;AAAA,IAC7B,oBAAA;AAAA,MACE,OAAS,EAAA,UAAA;AAAA,MACT,MAAA;AAAA,MACA,gBAAgB,QAAQ;AAAA,KAC1B;AAAA,IACA;AAAA,GACF;AAEA,EAAM,MAAA,gBAAA,GAAmB,IAAI,gBAAiB,EAAA;AAC9C,EAAM,MAAA,IAAA,GACJ,OAAS,EAAA,IAAA,IACT,eAAgB,CAAA;AAAA,IACd,SAAA;AAAA,IACA,eAAiB,EAAA;AAAA,MACf,gBAAA,CAAiB,eAAe,UAAU,CAAA;AAAA,MAC1C,gBAAA,CAAiB,cAAc,MAAM,CAAA;AAAA,MACrC,gBAAA,CAAiB,uBAAuB,kBAAkB,CAAA;AAAA,MAC1D,gBAAA,CAAiB,gBAAgB,gBAAgB;AAAA;AACnD,GACD,CAAA;AAEH,EAAM,MAAA,cAAA,GAAiB,IAAK,CAAA,GAAA,CAAI,kBAAkB,CAAA;AAClD,EAAA,IAAI,cAAgB,EAAA;AAClB,IAAA,KAAA,MAAW,WAAW,QAAU,EAAA;AAC9B,MAAI,IAAA,oBAAA,CAAqB,MAAO,CAAA,OAAO,CAAG,EAAA;AACxC,QAAqB,oBAAA,CAAA,UAAA,CAAW,OAAO,CAAA,CAAE,YAAa,CAAA,OAAA;AAAA,UAAQ,CAAA,IAAA,KAC5D,eAAe,YAAa,CAAA;AAAA,YAC1B,MAAM,IAAK,CAAA,IAAA;AAAA,YACX,UAAU,OAAQ,CAAA;AAAA,WACnB;AAAA,SACH;AAAA;AAEF,MAAI,IAAA,wBAAA,CAAyB,OAAO,CAAG,EAAA;AACrC,QAAyB,wBAAA,CAAA,OAAO,EAAE,YAAa,CAAA,OAAA;AAAA,UAAQ,CAAA,IAAA,KACrD,eAAe,YAAa,CAAA;AAAA,YAC1B,MAAM,IAAK,CAAA,IAAA;AAAA,YACX,UAAU,OAAQ,CAAA;AAAA,WACnB;AAAA,SACH;AAAA;AACF;AACF;AAIF,EAAA,sBAAA;AAAA,IACE,IAAK,CAAA,IAAA;AAAA,IACL,IAAA;AAAA,IACA,+BAAA,CAAgC,SAAS,0BAA0B;AAAA,GACrE;AAEA,EAAM,MAAA,SAAA,GAAY,2BAA4B,CAAA,IAAA,CAAK,IAAI,CAAA;AAEvD,EAAA,kBAAA,CAAmB,WAAW,SAAS,CAAA;AACvC,EAAA,UAAA,CAAW,WAAW,SAAS,CAAA;AAE/B,EAAO,OAAA,EAAE,MAAM,IAAK,EAAA;AACtB;AAEA,SAAS,mBAAmB,OAA6C,EAAA;AACvE,EAAA,MAAM,cAAiB,GAAA,WAAA,CAAY,IAAK,CAAA,EAAE,CAAA;AAC1C,EAAM,MAAA,SAAA,GAAY,IAAI,KAAqB,EAAA;AAE3C,EAAW,KAAA,MAAA,OAAA,IAAW,OAAQ,CAAA,IAAA,CAAK,IAAK,CAAA,KAAA,CAAM,YAAY,GAAI,CAAA,MAAM,CAAK,IAAA,EAAI,EAAA;AAC3E,IAAA,sBAAA,CAAuB,SAAS,cAAc,CAAA;AAC9C,IAAA,MAAM,aAAa,OAAQ,CAAA,QAAA,EAAU,OAAQ,CAAA,YAAA,CAAa,SAAS,OAAO,CAAA;AAC1E,IAAA,IAAI,CAAC,UAAY,EAAA;AACf,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,sCAAA,EAAyC,OAAQ,CAAA,IAAA,CAAK,EAAE,CAAA;AAAA,OAC1D;AAAA;AAEF,IAAA,SAAA,CAAU,KAAK,UAAU,CAAA;AAAA;AAG3B,EAAO,OAAA,SAAA;AACT;AAEA,SAAS,gBAAgB,OAGX,EAAA;AACZ,EAAM,MAAA,eAAA,GAAkB,IAAI,kBAAmB,EAAA;AAE/C,EAAA,KAAA,MAAW,WAAW,OAAQ,CAAA,SAAA,CAAU,KAAM,EAAA,CAAE,SAAW,EAAA;AACzD,IAAgB,eAAA,CAAA,QAAA,CAAS,WAAW,OAAO,CAAA;AAAA;AAG7C,EAAW,KAAA,MAAA,OAAA,IAAW,QAAQ,eAAiB,EAAA;AAC7C,IAAgB,eAAA,CAAA,QAAA,CAAS,UAAU,OAAO,CAAA;AAAA;AAG5C,EAAA,WAAA,CAAY,iBAAkB,CAAA,eAAA,EAAiB,eAAgB,CAAA,UAAA,EAAY,CAAA;AAE3E,EAAO,OAAA,IAAI,YAAY,eAAe,CAAA;AACxC;AAEA,SAAS,gCACP,WACwC,EAAA;AACxC,EAAA,IAAI,CAAC,WAAa,EAAA;AAChB,IAAO,OAAA,KAAA,CAAA;AAAA;AAET,EAAA,IAAI,CAAC,KAAA,CAAM,OAAQ,CAAA,WAAW,CAAG,EAAA;AAC/B,IAAO,OAAA,WAAA;AAAA;AAET,EAAI,IAAA,WAAA,CAAY,UAAU,CAAG,EAAA;AAC3B,IAAA,OAAO,YAAY,CAAC,CAAA;AAAA;AAEtB,EAAA,OAAO,WAAY,CAAA,MAAA,CAAO,CAAC,IAAA,EAAM,IAAS,KAAA;AACxC,IAAI,IAAA,CAAC,IAAQ,IAAA,CAAC,IAAM,EAAA;AAClB,MAAA,OAAO,IAAQ,IAAA,IAAA;AAAA;AAEjB,IAAO,OAAA,CAAC,MAAM,GAAQ,KAAA;AACpB,MAAA,MAAM,WAAc,GAAA,mBAAA,CAAoB,GAAI,CAAA,IAAA,CAAK,KAAK,SAAS,CAAA;AAC/D,MAAI,IAAA,WAAA,CAAY,YAAY,IAAM,EAAA;AAChC,QAAA,OAAO,IAAK,EAAA;AAAA;AAEd,MAAA,OAAO,KAAK,CAAgB,YAAA,KAAA;AAC1B,QAAO,OAAA,4BAAA;AAAA,UACL,KAAK,IAAM,EAAA;AAAA,YACT,MAAM,GAAI,CAAA,IAAA;AAAA,YACV,MAAM,GAAI,CAAA,IAAA;AAAA,YACV,MAAA,EAAQ,YAAc,EAAA,MAAA,IAAU,GAAI,CAAA;AAAA,WACrC;AAAA,SACH;AAAA,SACC,GAAG,CAAA;AAAA,KACR;AAAA,GACD,CAAA;AACH;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/frontend-app-api",
3
- "version": "0.11.2",
3
+ "version": "0.11.3-next.1",
4
4
  "backstage": {
5
5
  "role": "web-library"
6
6
  },
@@ -32,21 +32,21 @@
32
32
  "test": "backstage-cli package test"
33
33
  },
34
34
  "dependencies": {
35
- "@backstage/config": "^1.3.2",
36
- "@backstage/core-app-api": "^1.17.0",
37
- "@backstage/core-plugin-api": "^1.10.7",
38
- "@backstage/errors": "^1.2.7",
39
- "@backstage/frontend-defaults": "^0.2.2",
40
- "@backstage/frontend-plugin-api": "^0.10.2",
41
- "@backstage/types": "^1.2.1",
42
- "@backstage/version-bridge": "^1.0.11",
35
+ "@backstage/config": "1.3.2",
36
+ "@backstage/core-app-api": "1.17.0",
37
+ "@backstage/core-plugin-api": "1.10.7",
38
+ "@backstage/errors": "1.2.7",
39
+ "@backstage/frontend-defaults": "0.2.3-next.1",
40
+ "@backstage/frontend-plugin-api": "0.10.3-next.1",
41
+ "@backstage/types": "1.2.1",
42
+ "@backstage/version-bridge": "1.0.11",
43
43
  "lodash": "^4.17.21",
44
44
  "zod": "^3.22.4"
45
45
  },
46
46
  "devDependencies": {
47
- "@backstage/cli": "^0.32.1",
48
- "@backstage/plugin-app": "^0.1.9",
49
- "@backstage/test-utils": "^1.7.8",
47
+ "@backstage/cli": "0.33.0-next.1",
48
+ "@backstage/plugin-app": "0.1.10-next.1",
49
+ "@backstage/test-utils": "1.7.8",
50
50
  "@testing-library/jest-dom": "^6.0.0",
51
51
  "@testing-library/react": "^16.0.0",
52
52
  "@types/react": "^18.0.0",