@backstage/frontend-app-api 0.12.1-next.0 → 0.13.1-next.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,34 @@
1
1
  # @backstage/frontend-app-api
2
2
 
3
+ ## 0.13.1-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @backstage/frontend-defaults@0.3.2-next.0
9
+ - @backstage/frontend-plugin-api@0.12.1-next.0
10
+ - @backstage/config@1.3.3
11
+ - @backstage/core-app-api@1.19.0
12
+ - @backstage/core-plugin-api@1.11.0
13
+ - @backstage/errors@1.2.7
14
+ - @backstage/types@1.2.2
15
+ - @backstage/version-bridge@1.0.11
16
+
17
+ ## 0.13.0
18
+
19
+ ### Minor Changes
20
+
21
+ - 6516c3d: The `createSpecializedApp` no longer throws when encountering many common errors when starting up the app. It will instead return them through the `errors` property so that they can be handled more gracefully in the app.
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies
26
+ - @backstage/frontend-plugin-api@0.12.0
27
+ - @backstage/core-plugin-api@1.11.0
28
+ - @backstage/types@1.2.2
29
+ - @backstage/frontend-defaults@0.3.1
30
+ - @backstage/core-app-api@1.19.0
31
+
3
32
  ## 0.12.1-next.0
4
33
 
5
34
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ExternalRouteRef, RouteRef, SubRouteRef, FrontendPluginInfo, FrontendFeature, ExtensionFactoryMiddleware, AppTree } from '@backstage/frontend-plugin-api';
1
+ import { ExternalRouteRef, RouteRef, SubRouteRef, FrontendPluginInfo, FrontendPlugin, AppNode, FrontendFeature, ExtensionFactoryMiddleware, AppTree } from '@backstage/frontend-plugin-api';
2
2
  import { ConfigApi, ApiHolder } from '@backstage/core-plugin-api';
3
3
  import { JsonObject } from '@backstage/types';
4
4
 
@@ -58,6 +58,114 @@ type FrontendPluginInfoResolver = (ctx: {
58
58
  info: FrontendPluginInfo;
59
59
  }>;
60
60
 
61
+ /**
62
+ * @public
63
+ */
64
+ type AppErrorTypes = {
65
+ EXTENSION_IGNORED: {
66
+ context: {
67
+ plugin: FrontendPlugin;
68
+ extensionId: string;
69
+ };
70
+ };
71
+ INVALID_EXTENSION_CONFIG_KEY: {
72
+ context: {
73
+ extensionId: string;
74
+ };
75
+ };
76
+ EXTENSION_INPUT_REDIRECT_CONFLICT: {
77
+ context: {
78
+ node: AppNode;
79
+ inputName: string;
80
+ };
81
+ };
82
+ EXTENSION_INPUT_DATA_IGNORED: {
83
+ context: {
84
+ node: AppNode;
85
+ inputName: string;
86
+ };
87
+ };
88
+ EXTENSION_INPUT_DATA_MISSING: {
89
+ context: {
90
+ node: AppNode;
91
+ inputName: string;
92
+ };
93
+ };
94
+ EXTENSION_ATTACHMENT_CONFLICT: {
95
+ context: {
96
+ node: AppNode;
97
+ inputName: string;
98
+ };
99
+ };
100
+ EXTENSION_ATTACHMENT_MISSING: {
101
+ context: {
102
+ node: AppNode;
103
+ inputName: string;
104
+ };
105
+ };
106
+ EXTENSION_CONFIGURATION_INVALID: {
107
+ context: {
108
+ node: AppNode;
109
+ };
110
+ };
111
+ EXTENSION_INVALID: {
112
+ context: {
113
+ node: AppNode;
114
+ };
115
+ };
116
+ EXTENSION_OUTPUT_CONFLICT: {
117
+ context: {
118
+ node: AppNode;
119
+ dataRefId: string;
120
+ };
121
+ };
122
+ EXTENSION_OUTPUT_MISSING: {
123
+ context: {
124
+ node: AppNode;
125
+ dataRefId: string;
126
+ };
127
+ };
128
+ EXTENSION_OUTPUT_IGNORED: {
129
+ context: {
130
+ node: AppNode;
131
+ dataRefId: string;
132
+ };
133
+ };
134
+ EXTENSION_FACTORY_ERROR: {
135
+ context: {
136
+ node: AppNode;
137
+ };
138
+ };
139
+ API_EXTENSION_INVALID: {
140
+ context: {
141
+ node: AppNode;
142
+ };
143
+ };
144
+ ROUTE_DUPLICATE: {
145
+ context: {
146
+ routeId: string;
147
+ };
148
+ };
149
+ ROUTE_BINDING_INVALID_VALUE: {
150
+ context: {
151
+ routeId: string;
152
+ };
153
+ };
154
+ ROUTE_NOT_FOUND: {
155
+ context: {
156
+ routeId: string;
157
+ };
158
+ };
159
+ };
160
+ /**
161
+ * @public
162
+ */
163
+ type AppError = keyof AppErrorTypes extends infer ICode extends keyof AppErrorTypes ? ICode extends any ? {
164
+ code: ICode;
165
+ message: string;
166
+ context: AppErrorTypes[ICode]['context'];
167
+ } : never : never;
168
+
61
169
  /**
62
170
  * Options for {@link createSpecializedApp}.
63
171
  *
@@ -130,6 +238,7 @@ type CreateSpecializedAppOptions = {
130
238
  declare function createSpecializedApp(options?: CreateSpecializedAppOptions): {
131
239
  apis: ApiHolder;
132
240
  tree: AppTree;
241
+ errors?: AppError[];
133
242
  };
134
243
 
135
- export { type CreateAppRouteBinder, type CreateSpecializedAppOptions, type FrontendPluginInfoResolver, createSpecializedApp };
244
+ export { type AppError, type AppErrorTypes, type CreateAppRouteBinder, type CreateSpecializedAppOptions, type FrontendPluginInfoResolver, createSpecializedApp };
@@ -5,7 +5,7 @@ import { OpaqueFrontendPlugin } from '../frontend-internal/src/wiring/InternalFr
5
5
  import '../frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js';
6
6
  import '../frontend-internal/src/wiring/InternalExtensionDefinition.esm.js';
7
7
 
8
- function collectRouteIds(features) {
8
+ function collectRouteIds(features, collector) {
9
9
  const routesById = /* @__PURE__ */ new Map();
10
10
  const externalRoutesById = /* @__PURE__ */ new Map();
11
11
  for (const feature of features) {
@@ -15,7 +15,12 @@ function collectRouteIds(features) {
15
15
  for (const [name, ref] of Object.entries(feature.routes)) {
16
16
  const refId = `${feature.id}.${name}`;
17
17
  if (routesById.has(refId)) {
18
- throw new Error(`Unexpected duplicate route '${refId}'`);
18
+ collector.report({
19
+ code: "ROUTE_DUPLICATE",
20
+ message: `Duplicate route id '${refId}' encountered while collecting routes`,
21
+ context: { routeId: refId }
22
+ });
23
+ continue;
19
24
  }
20
25
  if (isRouteRef(ref)) {
21
26
  const internalRef = toInternalRouteRef(ref);
@@ -29,7 +34,12 @@ function collectRouteIds(features) {
29
34
  for (const [name, ref] of Object.entries(feature.externalRoutes)) {
30
35
  const refId = `${feature.id}.${name}`;
31
36
  if (externalRoutesById.has(refId)) {
32
- throw new Error(`Unexpected duplicate external route '${refId}'`);
37
+ collector.report({
38
+ code: "ROUTE_DUPLICATE",
39
+ message: `Duplicate external route id '${refId}' encountered while collecting routes`,
40
+ context: { routeId: refId }
41
+ });
42
+ continue;
33
43
  }
34
44
  const internalRef = toInternalExternalRouteRef(ref);
35
45
  internalRef.setId(refId);
@@ -1 +1 @@
1
- {"version":3,"file":"collectRouteIds.esm.js","sources":["../../src/routing/collectRouteIds.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 RouteRef,\n SubRouteRef,\n ExternalRouteRef,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isRouteRef,\n toInternalRouteRef,\n} from '../../../frontend-plugin-api/src/routing/RouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\n\n/** @internal */\nexport interface RouteRefsById {\n routes: Map<string, RouteRef | SubRouteRef>;\n externalRoutes: Map<string, ExternalRouteRef>;\n}\n\n/** @internal */\nexport function collectRouteIds(features: FrontendFeature[]): RouteRefsById {\n const routesById = new Map<string, RouteRef | SubRouteRef>();\n const externalRoutesById = new Map<string, ExternalRouteRef>();\n\n for (const feature of features) {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n continue;\n }\n\n for (const [name, ref] of Object.entries(feature.routes)) {\n const refId = `${feature.id}.${name}`;\n if (routesById.has(refId)) {\n throw new Error(`Unexpected duplicate route '${refId}'`);\n }\n\n if (isRouteRef(ref)) {\n const internalRef = toInternalRouteRef(ref);\n internalRef.setId(refId);\n routesById.set(refId, ref);\n } else {\n const internalRef = toInternalSubRouteRef(ref);\n routesById.set(refId, internalRef);\n }\n }\n for (const [name, ref] of Object.entries(feature.externalRoutes)) {\n const refId = `${feature.id}.${name}`;\n if (externalRoutesById.has(refId)) {\n throw new Error(`Unexpected duplicate external route '${refId}'`);\n }\n\n const internalRef = toInternalExternalRouteRef(ref);\n internalRef.setId(refId);\n externalRoutesById.set(refId, ref);\n }\n }\n\n return { routes: routesById, externalRoutes: externalRoutesById };\n}\n"],"names":[],"mappings":";;;;;;;AAwCO,SAAS,gBAAgB,QAAA,EAA4C;AAC1E,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAoC;AAC3D,EAAA,MAAM,kBAAA,uBAAyB,GAAA,EAA8B;AAE7D,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,CAAC,oBAAA,CAAqB,MAAA,CAAO,OAAO,CAAA,EAAG;AACzC,MAAA;AAAA,IACF;AAEA,IAAA,KAAA,MAAW,CAAC,MAAM,GAAG,CAAA,IAAK,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA,EAAG;AACxD,MAAA,MAAM,KAAA,GAAQ,CAAA,EAAG,OAAA,CAAQ,EAAE,IAAI,IAAI,CAAA,CAAA;AACnC,MAAA,IAAI,UAAA,CAAW,GAAA,CAAI,KAAK,CAAA,EAAG;AACzB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,4BAAA,EAA+B,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,MACzD;AAEA,MAAA,IAAI,UAAA,CAAW,GAAG,CAAA,EAAG;AACnB,QAAA,MAAM,WAAA,GAAc,mBAAmB,GAAG,CAAA;AAC1C,QAAA,WAAA,CAAY,MAAM,KAAK,CAAA;AACvB,QAAA,UAAA,CAAW,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,MAC3B,CAAA,MAAO;AACL,QAAA,MAAM,WAAA,GAAc,sBAAsB,GAAG,CAAA;AAC7C,QAAA,UAAA,CAAW,GAAA,CAAI,OAAO,WAAW,CAAA;AAAA,MACnC;AAAA,IACF;AACA,IAAA,KAAA,MAAW,CAAC,MAAM,GAAG,CAAA,IAAK,OAAO,OAAA,CAAQ,OAAA,CAAQ,cAAc,CAAA,EAAG;AAChE,MAAA,MAAM,KAAA,GAAQ,CAAA,EAAG,OAAA,CAAQ,EAAE,IAAI,IAAI,CAAA,CAAA;AACnC,MAAA,IAAI,kBAAA,CAAmB,GAAA,CAAI,KAAK,CAAA,EAAG;AACjC,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qCAAA,EAAwC,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,MAClE;AAEA,MAAA,MAAM,WAAA,GAAc,2BAA2B,GAAG,CAAA;AAClD,MAAA,WAAA,CAAY,MAAM,KAAK,CAAA;AACvB,MAAA,kBAAA,CAAmB,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,IACnC;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,cAAA,EAAgB,kBAAA,EAAmB;AAClE;;;;"}
1
+ {"version":3,"file":"collectRouteIds.esm.js","sources":["../../src/routing/collectRouteIds.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 RouteRef,\n SubRouteRef,\n ExternalRouteRef,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isRouteRef,\n toInternalRouteRef,\n} from '../../../frontend-plugin-api/src/routing/RouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\nimport { ErrorCollector } from '../wiring/createErrorCollector';\n\n/** @internal */\nexport interface RouteRefsById {\n routes: Map<string, RouteRef | SubRouteRef>;\n externalRoutes: Map<string, ExternalRouteRef>;\n}\n\n/** @internal */\nexport function collectRouteIds(\n features: FrontendFeature[],\n collector: ErrorCollector,\n): RouteRefsById {\n const routesById = new Map<string, RouteRef | SubRouteRef>();\n const externalRoutesById = new Map<string, ExternalRouteRef>();\n\n for (const feature of features) {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n continue;\n }\n\n for (const [name, ref] of Object.entries(feature.routes)) {\n const refId = `${feature.id}.${name}`;\n if (routesById.has(refId)) {\n collector.report({\n code: 'ROUTE_DUPLICATE',\n message: `Duplicate route id '${refId}' encountered while collecting routes`,\n context: { routeId: refId },\n });\n continue;\n }\n\n if (isRouteRef(ref)) {\n const internalRef = toInternalRouteRef(ref);\n internalRef.setId(refId);\n routesById.set(refId, ref);\n } else {\n const internalRef = toInternalSubRouteRef(ref);\n routesById.set(refId, internalRef);\n }\n }\n for (const [name, ref] of Object.entries(feature.externalRoutes)) {\n const refId = `${feature.id}.${name}`;\n if (externalRoutesById.has(refId)) {\n collector.report({\n code: 'ROUTE_DUPLICATE',\n message: `Duplicate external route id '${refId}' encountered while collecting routes`,\n context: { routeId: refId },\n });\n continue;\n }\n\n const internalRef = toInternalExternalRouteRef(ref);\n internalRef.setId(refId);\n externalRoutesById.set(refId, ref);\n }\n }\n\n return { routes: routesById, externalRoutes: externalRoutesById };\n}\n"],"names":[],"mappings":";;;;;;;AAyCO,SAAS,eAAA,CACd,UACA,SAAA,EACe;AACf,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAoC;AAC3D,EAAA,MAAM,kBAAA,uBAAyB,GAAA,EAA8B;AAE7D,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,CAAC,oBAAA,CAAqB,MAAA,CAAO,OAAO,CAAA,EAAG;AACzC,MAAA;AAAA,IACF;AAEA,IAAA,KAAA,MAAW,CAAC,MAAM,GAAG,CAAA,IAAK,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA,EAAG;AACxD,MAAA,MAAM,KAAA,GAAQ,CAAA,EAAG,OAAA,CAAQ,EAAE,IAAI,IAAI,CAAA,CAAA;AACnC,MAAA,IAAI,UAAA,CAAW,GAAA,CAAI,KAAK,CAAA,EAAG;AACzB,QAAA,SAAA,CAAU,MAAA,CAAO;AAAA,UACf,IAAA,EAAM,iBAAA;AAAA,UACN,OAAA,EAAS,uBAAuB,KAAK,CAAA,qCAAA,CAAA;AAAA,UACrC,OAAA,EAAS,EAAE,OAAA,EAAS,KAAA;AAAM,SAC3B,CAAA;AACD,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,UAAA,CAAW,GAAG,CAAA,EAAG;AACnB,QAAA,MAAM,WAAA,GAAc,mBAAmB,GAAG,CAAA;AAC1C,QAAA,WAAA,CAAY,MAAM,KAAK,CAAA;AACvB,QAAA,UAAA,CAAW,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,MAC3B,CAAA,MAAO;AACL,QAAA,MAAM,WAAA,GAAc,sBAAsB,GAAG,CAAA;AAC7C,QAAA,UAAA,CAAW,GAAA,CAAI,OAAO,WAAW,CAAA;AAAA,MACnC;AAAA,IACF;AACA,IAAA,KAAA,MAAW,CAAC,MAAM,GAAG,CAAA,IAAK,OAAO,OAAA,CAAQ,OAAA,CAAQ,cAAc,CAAA,EAAG;AAChE,MAAA,MAAM,KAAA,GAAQ,CAAA,EAAG,OAAA,CAAQ,EAAE,IAAI,IAAI,CAAA,CAAA;AACnC,MAAA,IAAI,kBAAA,CAAmB,GAAA,CAAI,KAAK,CAAA,EAAG;AACjC,QAAA,SAAA,CAAU,MAAA,CAAO;AAAA,UACf,IAAA,EAAM,iBAAA;AAAA,UACN,OAAA,EAAS,gCAAgC,KAAK,CAAA,qCAAA,CAAA;AAAA,UAC9C,OAAA,EAAS,EAAE,OAAA,EAAS,KAAA;AAAM,SAC3B,CAAA;AACD,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,WAAA,GAAc,2BAA2B,GAAG,CAAA;AAClD,MAAA,WAAA,CAAY,MAAM,KAAK,CAAA;AACvB,MAAA,kBAAA,CAAmB,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,IACnC;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,cAAA,EAAgB,kBAAA,EAAmB;AAClE;;;;"}
@@ -1,6 +1,6 @@
1
1
  import { toInternalExternalRouteRef } from '../frontend-plugin-api/src/routing/ExternalRouteRef.esm.js';
2
2
 
3
- function resolveRouteBindings(bindRoutes, config, routesById) {
3
+ function resolveRouteBindings(bindRoutes, config, routesById, collector) {
4
4
  const result = /* @__PURE__ */ new Map();
5
5
  const disabledExternalRefs = /* @__PURE__ */ new Set();
6
6
  if (bindRoutes) {
@@ -8,7 +8,12 @@ function resolveRouteBindings(bindRoutes, config, routesById) {
8
8
  for (const [key, value] of Object.entries(targetRoutes)) {
9
9
  const externalRoute = externalRoutes[key];
10
10
  if (!externalRoute) {
11
- throw new Error(`Key ${key} is not an existing external route`);
11
+ collector.report({
12
+ code: "ROUTE_NOT_FOUND",
13
+ message: `Key ${key} is not an existing external route`,
14
+ context: { routeId: String(key) }
15
+ });
16
+ continue;
12
17
  }
13
18
  if (value) {
14
19
  result.set(externalRoute, value);
@@ -23,15 +28,21 @@ function resolveRouteBindings(bindRoutes, config, routesById) {
23
28
  if (bindings) {
24
29
  for (const [externalRefId, targetRefId] of Object.entries(bindings)) {
25
30
  if (!isValidTargetRefId(targetRefId)) {
26
- throw new Error(
27
- `Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string or false`
28
- );
31
+ collector.report({
32
+ code: "ROUTE_BINDING_INVALID_VALUE",
33
+ message: `Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string or false`,
34
+ context: { routeId: externalRefId }
35
+ });
36
+ continue;
29
37
  }
30
38
  const externalRef = routesById.externalRoutes.get(externalRefId);
31
39
  if (!externalRef) {
32
- throw new Error(
33
- `Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`
34
- );
40
+ collector.report({
41
+ code: "ROUTE_NOT_FOUND",
42
+ message: `Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`,
43
+ context: { routeId: externalRefId }
44
+ });
45
+ continue;
35
46
  }
36
47
  if (result.has(externalRef) || disabledExternalRefs.has(externalRef)) {
37
48
  continue;
@@ -41,9 +52,14 @@ function resolveRouteBindings(bindRoutes, config, routesById) {
41
52
  } else {
42
53
  const targetRef = routesById.routes.get(targetRefId);
43
54
  if (!targetRef) {
44
- throw new Error(
45
- `Invalid config at app.routes.bindings['${externalRefId}'], '${targetRefId}' is not a valid route`
46
- );
55
+ collector.report({
56
+ code: "ROUTE_NOT_FOUND",
57
+ message: `Invalid config at app.routes.bindings['${externalRefId}'], '${String(
58
+ targetRefId
59
+ )}' is not a valid route`,
60
+ context: { routeId: String(targetRefId) }
61
+ });
62
+ continue;
47
63
  }
48
64
  result.set(externalRef, targetRef);
49
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resolveRouteBindings.esm.js","sources":["../../src/routing/resolveRouteBindings.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 {\n RouteRef,\n SubRouteRef,\n ExternalRouteRef,\n} from '@backstage/frontend-plugin-api';\nimport { RouteRefsById } from './collectRouteIds';\nimport { Config } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef';\n\n/**\n * Extracts a union of the keys in a map whose value extends the given type\n *\n * @ignore\n */\ntype KeysWithType<Obj extends { [key in string]: any }, Type> = {\n [key in keyof Obj]: Obj[key] extends Type ? key : never;\n}[keyof Obj];\n\n/**\n * Takes a map Map required values and makes all keys matching Keys optional\n *\n * @ignore\n */\ntype PartialKeys<\n Map extends { [name in string]: any },\n Keys extends keyof Map,\n> = Partial<Pick<Map, Keys>> & Required<Omit<Map, Keys>>;\n\n/**\n * Creates a map of target routes with matching parameters based on a map of external routes.\n *\n * @ignore\n */\ntype TargetRouteMap<\n ExternalRoutes extends { [name: string]: ExternalRouteRef },\n> = {\n [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<\n infer Params\n >\n ? RouteRef<Params> | SubRouteRef<Params> | false\n : never;\n};\n\n/**\n * A function that can bind from external routes of a given plugin, to concrete\n * routes of other plugins. See {@link @backstage/frontend-defaults#createApp}.\n *\n * @public\n */\nexport type CreateAppRouteBinder = <\n TExternalRoutes extends { [name: string]: ExternalRouteRef },\n>(\n externalRoutes: TExternalRoutes,\n targetRoutes: PartialKeys<\n TargetRouteMap<TExternalRoutes>,\n KeysWithType<TExternalRoutes, ExternalRouteRef<any>>\n >,\n) => void;\n\n/** @internal */\nexport function resolveRouteBindings(\n bindRoutes: ((context: { bind: CreateAppRouteBinder }) => void) | undefined,\n config: Config,\n routesById: RouteRefsById,\n): Map<ExternalRouteRef, RouteRef | SubRouteRef> {\n const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();\n const disabledExternalRefs = new Set<ExternalRouteRef>();\n\n // Perform callback bindings first with highest priority\n if (bindRoutes) {\n const bind: CreateAppRouteBinder = (\n externalRoutes,\n targetRoutes: { [name: string]: RouteRef | SubRouteRef },\n ) => {\n for (const [key, value] of Object.entries(targetRoutes)) {\n const externalRoute = externalRoutes[key];\n if (!externalRoute) {\n throw new Error(`Key ${key} is not an existing external route`);\n }\n if (value) {\n result.set(externalRoute, value);\n } else if (value === false) {\n disabledExternalRefs.add(externalRoute);\n }\n }\n };\n bindRoutes({ bind });\n }\n\n // Then perform config based bindings with lower priority\n const bindings = config\n .getOptionalConfig('app.routes.bindings')\n ?.get<JsonObject>();\n if (bindings) {\n for (const [externalRefId, targetRefId] of Object.entries(bindings)) {\n if (!isValidTargetRefId(targetRefId)) {\n throw new Error(\n `Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string or false`,\n );\n }\n\n const externalRef = routesById.externalRoutes.get(externalRefId);\n if (!externalRef) {\n throw new Error(\n `Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`,\n );\n }\n\n // Skip if binding was already defined in code\n if (result.has(externalRef) || disabledExternalRefs.has(externalRef)) {\n continue;\n }\n\n if (targetRefId === false) {\n disabledExternalRefs.add(externalRef);\n } else {\n const targetRef = routesById.routes.get(targetRefId);\n if (!targetRef) {\n throw new Error(\n `Invalid config at app.routes.bindings['${externalRefId}'], '${targetRefId}' is not a valid route`,\n );\n }\n\n result.set(externalRef, targetRef);\n }\n }\n }\n\n // Finally fall back to attempting to map defaults, at lowest priority\n for (const externalRef of routesById.externalRoutes.values()) {\n if (!result.has(externalRef) && !disabledExternalRefs.has(externalRef)) {\n const defaultRefId =\n toInternalExternalRouteRef(externalRef).getDefaultTarget();\n if (defaultRefId) {\n const defaultRef = routesById.routes.get(defaultRefId);\n if (defaultRef) {\n result.set(externalRef, defaultRef);\n }\n }\n }\n }\n\n return result;\n}\n\nfunction isValidTargetRefId(value: unknown): value is string | false {\n if (value === false) {\n return true;\n }\n\n if (typeof value === 'string' && value) {\n return true;\n }\n\n return false;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolveRouteBindings.esm.js","sources":["../../src/routing/resolveRouteBindings.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 {\n RouteRef,\n SubRouteRef,\n ExternalRouteRef,\n} from '@backstage/frontend-plugin-api';\nimport { RouteRefsById } from './collectRouteIds';\nimport { ErrorCollector } from '../wiring/createErrorCollector';\nimport { Config } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef';\n\n/**\n * Extracts a union of the keys in a map whose value extends the given type\n *\n * @ignore\n */\ntype KeysWithType<Obj extends { [key in string]: any }, Type> = {\n [key in keyof Obj]: Obj[key] extends Type ? key : never;\n}[keyof Obj];\n\n/**\n * Takes a map Map required values and makes all keys matching Keys optional\n *\n * @ignore\n */\ntype PartialKeys<\n Map extends { [name in string]: any },\n Keys extends keyof Map,\n> = Partial<Pick<Map, Keys>> & Required<Omit<Map, Keys>>;\n\n/**\n * Creates a map of target routes with matching parameters based on a map of external routes.\n *\n * @ignore\n */\ntype TargetRouteMap<\n ExternalRoutes extends { [name: string]: ExternalRouteRef },\n> = {\n [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<\n infer Params\n >\n ? RouteRef<Params> | SubRouteRef<Params> | false\n : never;\n};\n\n/**\n * A function that can bind from external routes of a given plugin, to concrete\n * routes of other plugins. See {@link @backstage/frontend-defaults#createApp}.\n *\n * @public\n */\nexport type CreateAppRouteBinder = <\n TExternalRoutes extends { [name: string]: ExternalRouteRef },\n>(\n externalRoutes: TExternalRoutes,\n targetRoutes: PartialKeys<\n TargetRouteMap<TExternalRoutes>,\n KeysWithType<TExternalRoutes, ExternalRouteRef<any>>\n >,\n) => void;\n\n/** @internal */\nexport function resolveRouteBindings(\n bindRoutes: ((context: { bind: CreateAppRouteBinder }) => void) | undefined,\n config: Config,\n routesById: RouteRefsById,\n collector: ErrorCollector,\n): Map<ExternalRouteRef, RouteRef | SubRouteRef> {\n const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();\n const disabledExternalRefs = new Set<ExternalRouteRef>();\n\n // Perform callback bindings first with highest priority\n if (bindRoutes) {\n const bind: CreateAppRouteBinder = (\n externalRoutes,\n targetRoutes: { [name: string]: RouteRef | SubRouteRef },\n ) => {\n for (const [key, value] of Object.entries(targetRoutes)) {\n const externalRoute = externalRoutes[key];\n if (!externalRoute) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Key ${key} is not an existing external route`,\n context: { routeId: String(key) },\n });\n continue;\n }\n if (value) {\n result.set(externalRoute, value);\n } else if (value === false) {\n disabledExternalRefs.add(externalRoute);\n }\n }\n };\n bindRoutes({ bind });\n }\n\n // Then perform config based bindings with lower priority\n const bindings = config\n .getOptionalConfig('app.routes.bindings')\n ?.get<JsonObject>();\n if (bindings) {\n for (const [externalRefId, targetRefId] of Object.entries(bindings)) {\n if (!isValidTargetRefId(targetRefId)) {\n collector.report({\n code: 'ROUTE_BINDING_INVALID_VALUE',\n message: `Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string or false`,\n context: { routeId: externalRefId },\n });\n continue;\n }\n\n const externalRef = routesById.externalRoutes.get(externalRefId);\n if (!externalRef) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`,\n context: { routeId: externalRefId },\n });\n continue;\n }\n\n // Skip if binding was already defined in code\n if (result.has(externalRef) || disabledExternalRefs.has(externalRef)) {\n continue;\n }\n\n if (targetRefId === false) {\n disabledExternalRefs.add(externalRef);\n } else {\n const targetRef = routesById.routes.get(targetRefId);\n if (!targetRef) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Invalid config at app.routes.bindings['${externalRefId}'], '${String(\n targetRefId,\n )}' is not a valid route`,\n context: { routeId: String(targetRefId) },\n });\n continue;\n }\n\n result.set(externalRef, targetRef);\n }\n }\n }\n\n // Finally fall back to attempting to map defaults, at lowest priority\n for (const externalRef of routesById.externalRoutes.values()) {\n if (!result.has(externalRef) && !disabledExternalRefs.has(externalRef)) {\n const defaultRefId =\n toInternalExternalRouteRef(externalRef).getDefaultTarget();\n if (defaultRefId) {\n const defaultRef = routesById.routes.get(defaultRefId);\n if (defaultRef) {\n result.set(externalRef, defaultRef);\n }\n }\n }\n }\n\n return result;\n}\n\nfunction isValidTargetRefId(value: unknown): value is string | false {\n if (value === false) {\n return true;\n }\n\n if (typeof value === 'string' && value) {\n return true;\n }\n\n return false;\n}\n"],"names":[],"mappings":"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"}