@backstage/frontend-app-api 0.16.4-next.1 → 0.16.5-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 +22 -0
- package/config.schema.json +168 -0
- package/dist/wiring/predicates.esm.js +30 -3
- package/dist/wiring/predicates.esm.js.map +1 -1
- package/package.json +12 -12
- package/config.d.ts +0 -129
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @backstage/frontend-app-api
|
|
2
2
|
|
|
3
|
+
## 0.16.5-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 74fbcd7: Extension `if` predicates now support specifying an `action` attribute in the permission reference. When a permission name contains a `#` separator (e.g. `catalog.entity.read#read`), the part after `#` is used as `attributes.action` when evaluating the permission. This removes the need to configure action attributes separately for basic permissions.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/filter-predicates@0.1.4-next.0
|
|
10
|
+
- @backstage/core-app-api@1.20.3-next.0
|
|
11
|
+
- @backstage/frontend-defaults@0.5.4-next.0
|
|
12
|
+
- @backstage/frontend-plugin-api@0.17.3-next.0
|
|
13
|
+
- @backstage/core-plugin-api@1.12.8-next.0
|
|
14
|
+
|
|
15
|
+
## 0.16.4
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- @backstage/frontend-plugin-api@0.17.2
|
|
21
|
+
- @backstage/frontend-defaults@0.5.3
|
|
22
|
+
- @backstage/core-app-api@1.20.2
|
|
23
|
+
- @backstage/core-plugin-api@1.12.7
|
|
24
|
+
|
|
3
25
|
## 0.16.4-next.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"app": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"packages": {
|
|
9
|
+
"anyOf": [
|
|
10
|
+
{
|
|
11
|
+
"type": "string",
|
|
12
|
+
"const": "all"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"include": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"items": {
|
|
20
|
+
"type": "string"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"exclude": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"description": "Controls what packages are loaded by the new frontend system.",
|
|
33
|
+
"visibility": "frontend",
|
|
34
|
+
"deepVisibility": "frontend"
|
|
35
|
+
},
|
|
36
|
+
"routes": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"bindings": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": {
|
|
42
|
+
"anyOf": [
|
|
43
|
+
{
|
|
44
|
+
"type": "string"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"const": false
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"description": "Maps external route references to regular route references. Both the key and the value is expected to be on the form `<pluginId>.<routeId>`. If the value is `false`, the route will be disabled even if it has a default mapping.",
|
|
53
|
+
"deepVisibility": "frontend"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"extensions": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"anyOf": [
|
|
61
|
+
{
|
|
62
|
+
"type": "string"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": {
|
|
67
|
+
"anyOf": [
|
|
68
|
+
{
|
|
69
|
+
"type": "boolean"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"attachTo": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {
|
|
77
|
+
"id": {
|
|
78
|
+
"type": "string"
|
|
79
|
+
},
|
|
80
|
+
"input": {
|
|
81
|
+
"type": "string"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"required": [
|
|
85
|
+
"id",
|
|
86
|
+
"input"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
"disabled": {
|
|
90
|
+
"type": "boolean"
|
|
91
|
+
},
|
|
92
|
+
"config": {}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
"deepVisibility": "frontend"
|
|
101
|
+
},
|
|
102
|
+
"pluginOverrides": {
|
|
103
|
+
"type": "array",
|
|
104
|
+
"items": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"match": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": {
|
|
110
|
+
"pluginId": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"description": "A pattern that is matched against the plugin ID."
|
|
113
|
+
},
|
|
114
|
+
"packageName": {
|
|
115
|
+
"type": "string",
|
|
116
|
+
"description": "A pattern that is matched against the package name."
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"description": "The criteria for matching plugins to override."
|
|
120
|
+
},
|
|
121
|
+
"info": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"description": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Override the description of the plugin."
|
|
127
|
+
},
|
|
128
|
+
"ownerEntityRefs": {
|
|
129
|
+
"type": "array",
|
|
130
|
+
"items": {
|
|
131
|
+
"type": "string"
|
|
132
|
+
},
|
|
133
|
+
"description": "Override the owner entity references of the plugin."
|
|
134
|
+
},
|
|
135
|
+
"links": {
|
|
136
|
+
"type": "array",
|
|
137
|
+
"items": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"properties": {
|
|
140
|
+
"title": {
|
|
141
|
+
"type": "string"
|
|
142
|
+
},
|
|
143
|
+
"url": {
|
|
144
|
+
"type": "string"
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"required": [
|
|
148
|
+
"title",
|
|
149
|
+
"url"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
"description": "Override the links of the plugin."
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"description": "Overrides individual top-level fields of the plugin info."
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"required": [
|
|
159
|
+
"info"
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
"description": "This section enables you to override certain properties of specific or groups of plugins.",
|
|
163
|
+
"deepVisibility": "frontend"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -5,6 +5,24 @@ const EMPTY_PREDICATE_CONTEXT = {
|
|
|
5
5
|
featureFlags: [],
|
|
6
6
|
permissions: []
|
|
7
7
|
};
|
|
8
|
+
function parsePermissionName(permissionName) {
|
|
9
|
+
const parts = permissionName.split("#");
|
|
10
|
+
const [name, action, ...rest] = parts;
|
|
11
|
+
if (rest.length > 0 || !name || parts.length === 2 && !action) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`Invalid permission name: ${permissionName}. Permission names must be in the format "permissionName" or "permissionName#action" (both parts must be non-empty).`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
if (action === void 0) {
|
|
17
|
+
return { name };
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
attributes: {
|
|
22
|
+
action
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
8
26
|
const localPermissionApiRef = createApiRef({
|
|
9
27
|
id: "plugin.permission.api"
|
|
10
28
|
});
|
|
@@ -40,10 +58,19 @@ function createPredicateContextLoader(options) {
|
|
|
40
58
|
if (permissionApi) {
|
|
41
59
|
try {
|
|
42
60
|
const permissionNames = options.predicateReferences.permissions;
|
|
61
|
+
const hydratedPermissions = permissionNames.map((name) => {
|
|
62
|
+
const { name: permissionName, attributes } = parsePermissionName(name);
|
|
63
|
+
const permission = {
|
|
64
|
+
name: permissionName,
|
|
65
|
+
type: "basic",
|
|
66
|
+
attributes: attributes || {}
|
|
67
|
+
};
|
|
68
|
+
return permission;
|
|
69
|
+
});
|
|
43
70
|
const responses = await Promise.all(
|
|
44
|
-
|
|
45
|
-
(
|
|
46
|
-
permission
|
|
71
|
+
hydratedPermissions.map(
|
|
72
|
+
(permission) => permissionApi.authorize({
|
|
73
|
+
permission
|
|
47
74
|
})
|
|
48
75
|
)
|
|
49
76
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"predicates.esm.js","sources":["../../src/wiring/predicates.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 ApiHolder,\n createApiRef,\n featureFlagsApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { FilterPredicate } from '@backstage/filter-predicates';\nimport type {\n EvaluatePermissionRequest,\n EvaluatePermissionResponse,\n} from '@backstage/plugin-permission-common';\nimport { ForwardedError } from '@backstage/errors';\n\nexport type ExtensionPredicateContext = {\n featureFlags: string[];\n permissions: string[];\n};\n\nexport const EMPTY_PREDICATE_CONTEXT: ExtensionPredicateContext = {\n featureFlags: [],\n permissions: [],\n};\n\n// Minimal local permission API interface to avoid a dependency on @backstage/plugin-permission-react\ntype MinimalPermissionApi = {\n authorize(\n request: EvaluatePermissionRequest,\n ): Promise<EvaluatePermissionResponse>;\n};\n\nexport const localPermissionApiRef = createApiRef<MinimalPermissionApi>({\n id: 'plugin.permission.api',\n});\n\nexport function createPredicateContextLoader(options: {\n apis: ApiHolder;\n predicateReferences: ExtensionPredicateContext;\n}) {\n function getActiveFeatureFlags() {\n const featureFlagsApi = options.apis.get(featureFlagsApiRef);\n if (!featureFlagsApi) {\n return [];\n }\n\n return options.predicateReferences.featureFlags.filter(name =>\n featureFlagsApi.isActive(name),\n );\n }\n\n function getImmediate(): ExtensionPredicateContext | undefined {\n if (options.predicateReferences.permissions.length > 0) {\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n return undefined;\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: [],\n };\n }\n\n async function load() {\n const immediatePredicateContext = getImmediate();\n if (immediatePredicateContext) {\n return immediatePredicateContext;\n }\n\n let allowedPermissions: string[] = [];\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n try {\n const permissionNames = options.predicateReferences.permissions;\n const responses = await Promise.all(\n permissionNames.map(name =>\n permissionApi.authorize({\n permission: { name, type: 'basic', attributes: {} },\n }),\n ),\n );\n allowedPermissions = permissionNames.filter(\n (_, i) => responses[i].result === 'ALLOW',\n );\n } catch (error) {\n throw new ForwardedError(\n 'Failed to authorize extension permissions',\n error,\n );\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: allowedPermissions,\n };\n }\n\n return {\n getImmediate,\n load,\n };\n}\n\nexport function collectPredicateReferences(\n nodes: Iterable<{ spec: { if?: FilterPredicate } }>,\n): ExtensionPredicateContext {\n const featureFlags = new Set<string>();\n const permissions = new Set<string>();\n\n for (const node of nodes) {\n if (node.spec.if === undefined) {\n continue;\n }\n\n for (const name of extractFeatureFlagNames(node.spec.if)) {\n featureFlags.add(name);\n }\n for (const name of extractPermissionNames(node.spec.if)) {\n permissions.add(name);\n }\n }\n\n return {\n featureFlags: Array.from(featureFlags),\n permissions: Array.from(permissions),\n };\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `featureFlags: { $contains: '...' }` expressions. This lets us call\n * `isActive()` only for the flags that are actually used in predicates rather\n * than fetching the full registered-flag list.\n */\nfunction extractFeatureFlagNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'featureFlags');\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `permissions: { $contains: '...' }` expressions. This lets us issue a\n * single batched authorize call for only the permissions actually referenced.\n */\nfunction extractPermissionNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'permissions');\n}\n\nfunction extractPredicateKeyNames(\n predicate: FilterPredicate,\n key: string,\n): string[] {\n if (typeof predicate !== 'object' || predicate === null) {\n return [];\n }\n const obj = predicate as Record<string, unknown>;\n if (Array.isArray(obj.$all)) {\n return (obj.$all as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (Array.isArray(obj.$any)) {\n return (obj.$any as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (obj.$not !== undefined) {\n return extractPredicateKeyNames(obj.$not as FilterPredicate, key);\n }\n const value = obj[key];\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const contains = (value as Record<string, unknown>).$contains;\n if (typeof contains === 'string') {\n return [contains];\n }\n }\n return [];\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"predicates.esm.js","sources":["../../src/wiring/predicates.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 ApiHolder,\n createApiRef,\n featureFlagsApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { FilterPredicate } from '@backstage/filter-predicates';\nimport type {\n EvaluatePermissionRequest,\n EvaluatePermissionResponse,\n} from '@backstage/plugin-permission-common';\nimport { ForwardedError } from '@backstage/errors';\n\nexport type ExtensionPredicateContext = {\n featureFlags: string[];\n permissions: string[];\n};\n\nexport const EMPTY_PREDICATE_CONTEXT: ExtensionPredicateContext = {\n featureFlags: [],\n permissions: [],\n};\n\n// Minimal local permission API interface to avoid a dependency on @backstage/plugin-permission-react\ntype MinimalPermissionApi = {\n authorize(\n request: EvaluatePermissionRequest,\n ): Promise<EvaluatePermissionResponse>;\n};\n\nfunction parsePermissionName(permissionName: string) {\n const parts = permissionName.split('#');\n const [name, action, ...rest] = parts;\n\n if (rest.length > 0 || !name || (parts.length === 2 && !action)) {\n throw new Error(\n `Invalid permission name: ${permissionName}. Permission names must be in the format \"permissionName\" or \"permissionName#action\" (both parts must be non-empty).`,\n );\n }\n\n if (action === undefined) {\n return { name };\n }\n\n return {\n name,\n attributes: {\n action,\n },\n };\n}\n\nexport const localPermissionApiRef = createApiRef<MinimalPermissionApi>({\n id: 'plugin.permission.api',\n});\n\nexport function createPredicateContextLoader(options: {\n apis: ApiHolder;\n predicateReferences: ExtensionPredicateContext;\n}) {\n function getActiveFeatureFlags() {\n const featureFlagsApi = options.apis.get(featureFlagsApiRef);\n if (!featureFlagsApi) {\n return [];\n }\n\n return options.predicateReferences.featureFlags.filter(name =>\n featureFlagsApi.isActive(name),\n );\n }\n\n function getImmediate(): ExtensionPredicateContext | undefined {\n if (options.predicateReferences.permissions.length > 0) {\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n return undefined;\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: [],\n };\n }\n\n async function load() {\n const immediatePredicateContext = getImmediate();\n if (immediatePredicateContext) {\n return immediatePredicateContext;\n }\n\n let allowedPermissions: string[] = [];\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n try {\n const permissionNames = options.predicateReferences.permissions;\n const hydratedPermissions = permissionNames.map(name => {\n const { name: permissionName, attributes } =\n parsePermissionName(name);\n const permission = {\n name: permissionName,\n type: 'basic',\n attributes: attributes || {},\n } as const;\n return permission;\n });\n const responses = await Promise.all(\n hydratedPermissions.map(permission =>\n permissionApi.authorize({\n permission,\n }),\n ),\n );\n allowedPermissions = permissionNames.filter(\n (_, i) => responses[i].result === 'ALLOW',\n );\n } catch (error) {\n throw new ForwardedError(\n 'Failed to authorize extension permissions',\n error,\n );\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: allowedPermissions,\n };\n }\n\n return {\n getImmediate,\n load,\n };\n}\n\nexport function collectPredicateReferences(\n nodes: Iterable<{ spec: { if?: FilterPredicate } }>,\n): ExtensionPredicateContext {\n const featureFlags = new Set<string>();\n const permissions = new Set<string>();\n\n for (const node of nodes) {\n if (node.spec.if === undefined) {\n continue;\n }\n\n for (const name of extractFeatureFlagNames(node.spec.if)) {\n featureFlags.add(name);\n }\n for (const name of extractPermissionNames(node.spec.if)) {\n permissions.add(name);\n }\n }\n\n return {\n featureFlags: Array.from(featureFlags),\n permissions: Array.from(permissions),\n };\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `featureFlags: { $contains: '...' }` expressions. This lets us call\n * `isActive()` only for the flags that are actually used in predicates rather\n * than fetching the full registered-flag list.\n */\nfunction extractFeatureFlagNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'featureFlags');\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `permissions: { $contains: '...' }` expressions. This lets us issue a\n * single batched authorize call for only the permissions actually referenced.\n */\nfunction extractPermissionNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'permissions');\n}\n\nfunction extractPredicateKeyNames(\n predicate: FilterPredicate,\n key: string,\n): string[] {\n if (typeof predicate !== 'object' || predicate === null) {\n return [];\n }\n const obj = predicate as Record<string, unknown>;\n if (Array.isArray(obj.$all)) {\n return (obj.$all as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (Array.isArray(obj.$any)) {\n return (obj.$any as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (obj.$not !== undefined) {\n return extractPredicateKeyNames(obj.$not as FilterPredicate, key);\n }\n const value = obj[key];\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const contains = (value as Record<string, unknown>).$contains;\n if (typeof contains === 'string') {\n return [contains];\n }\n }\n return [];\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/frontend-app-api",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.5-next.0",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "web-library"
|
|
6
6
|
},
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"main": "dist/index.esm.js",
|
|
20
20
|
"types": "dist/index.d.ts",
|
|
21
21
|
"files": [
|
|
22
|
-
"config.
|
|
22
|
+
"config.schema.json",
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
@@ -33,23 +33,23 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@backstage/config": "1.3.8",
|
|
36
|
-
"@backstage/core-app-api": "1.20.
|
|
37
|
-
"@backstage/core-plugin-api": "1.12.
|
|
36
|
+
"@backstage/core-app-api": "1.20.3-next.0",
|
|
37
|
+
"@backstage/core-plugin-api": "1.12.8-next.0",
|
|
38
38
|
"@backstage/errors": "1.3.1",
|
|
39
|
-
"@backstage/filter-predicates": "0.1.
|
|
40
|
-
"@backstage/frontend-defaults": "0.5.
|
|
41
|
-
"@backstage/frontend-plugin-api": "0.17.
|
|
39
|
+
"@backstage/filter-predicates": "0.1.4-next.0",
|
|
40
|
+
"@backstage/frontend-defaults": "0.5.4-next.0",
|
|
41
|
+
"@backstage/frontend-plugin-api": "0.17.3-next.0",
|
|
42
42
|
"@backstage/types": "1.2.2",
|
|
43
43
|
"@backstage/version-bridge": "1.0.12",
|
|
44
44
|
"lodash": "^4.17.21",
|
|
45
45
|
"zod": "^3.25.76 || ^4.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@backstage/cli": "0.36.
|
|
49
|
-
"@backstage/frontend-test-utils": "0.6.
|
|
50
|
-
"@backstage/plugin-app": "0.
|
|
48
|
+
"@backstage/cli": "0.36.4-next.0",
|
|
49
|
+
"@backstage/frontend-test-utils": "0.6.2-next.0",
|
|
50
|
+
"@backstage/plugin-app": "0.5.1-next.0",
|
|
51
51
|
"@backstage/plugin-permission-common": "0.9.9",
|
|
52
|
-
"@backstage/test-utils": "1.7.
|
|
52
|
+
"@backstage/test-utils": "1.7.20-next.0",
|
|
53
53
|
"@testing-library/jest-dom": "^6.0.0",
|
|
54
54
|
"@testing-library/react": "^16.0.0",
|
|
55
55
|
"@types/react": "^18.0.0",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"optional": true
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
|
-
"configSchema": "config.
|
|
71
|
+
"configSchema": "config.schema.json",
|
|
72
72
|
"typesVersions": {
|
|
73
73
|
"*": {
|
|
74
74
|
"package.json": [
|
package/config.d.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2023 The Backstage Authors
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export interface Config {
|
|
18
|
-
app?: {
|
|
19
|
-
/**
|
|
20
|
-
* Controls what packages are loaded by the new frontend system.
|
|
21
|
-
*
|
|
22
|
-
* @remarks
|
|
23
|
-
*
|
|
24
|
-
* When using the 'all' option, all feature packages that were added as
|
|
25
|
-
* dependencies to the app will be loaded automatically.
|
|
26
|
-
*
|
|
27
|
-
* The `include` and `exclude` options can be used to more finely control
|
|
28
|
-
* which individual package names to include or exclude.
|
|
29
|
-
*
|
|
30
|
-
* @visibility frontend
|
|
31
|
-
* @deepVisibility frontend
|
|
32
|
-
*/
|
|
33
|
-
packages?: 'all' | { include?: string[]; exclude?: string[] };
|
|
34
|
-
|
|
35
|
-
routes?: {
|
|
36
|
-
/**
|
|
37
|
-
* Maps external route references to regular route references. Both the
|
|
38
|
-
* key and the value is expected to be on the form `<pluginId>.<routeId>`.
|
|
39
|
-
* If the value is `false`, the route will be disabled even if it has a
|
|
40
|
-
* default mapping.
|
|
41
|
-
*
|
|
42
|
-
* @deepVisibility frontend
|
|
43
|
-
*/
|
|
44
|
-
bindings?: { [externalRouteRefId: string]: string | false };
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @deepVisibility frontend
|
|
49
|
-
*/
|
|
50
|
-
extensions?: Array<
|
|
51
|
-
| string
|
|
52
|
-
| {
|
|
53
|
-
[extensionId: string]:
|
|
54
|
-
| boolean
|
|
55
|
-
| {
|
|
56
|
-
attachTo?: { id: string; input: string };
|
|
57
|
-
disabled?: boolean;
|
|
58
|
-
config?: unknown;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
>;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* This section enables you to override certain properties of specific or
|
|
65
|
-
* groups of plugins.
|
|
66
|
-
*
|
|
67
|
-
* @remarks
|
|
68
|
-
* All matching entries will be applied to each plugin, with the later
|
|
69
|
-
* entries taking precedence.
|
|
70
|
-
*
|
|
71
|
-
* This configuration is intended to be used primarily to apply overrides
|
|
72
|
-
* for third-party plugins.
|
|
73
|
-
*
|
|
74
|
-
* @deepVisibility frontend
|
|
75
|
-
*/
|
|
76
|
-
pluginOverrides?: Array<{
|
|
77
|
-
/**
|
|
78
|
-
* The criteria for matching plugins to override.
|
|
79
|
-
*
|
|
80
|
-
* @remarks
|
|
81
|
-
* If no match criteria are provided, the override will be applied to
|
|
82
|
-
* all plugins.
|
|
83
|
-
*/
|
|
84
|
-
match?: {
|
|
85
|
-
/**
|
|
86
|
-
* A pattern that is matched against the plugin ID.
|
|
87
|
-
*
|
|
88
|
-
* @remarks
|
|
89
|
-
* By default the string is interpreted as a glob pattern, but if the
|
|
90
|
-
* string is surrounded by '/' it is interpreted as a regex.
|
|
91
|
-
*/
|
|
92
|
-
pluginId?: string;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* A pattern that is matched against the package name.
|
|
96
|
-
*
|
|
97
|
-
* @remarks
|
|
98
|
-
* By default the string is interpreted as a glob pattern, but if the
|
|
99
|
-
* string is surrounded by '/' it is interpreted as a regex.
|
|
100
|
-
*
|
|
101
|
-
* Note that this will only work for plugins that provide a
|
|
102
|
-
* `package.json` info loader.
|
|
103
|
-
*/
|
|
104
|
-
packageName?: string;
|
|
105
|
-
};
|
|
106
|
-
/**
|
|
107
|
-
* Overrides individual top-level fields of the plugin info.
|
|
108
|
-
*/
|
|
109
|
-
info: {
|
|
110
|
-
/**
|
|
111
|
-
* Override the description of the plugin.
|
|
112
|
-
*/
|
|
113
|
-
description?: string;
|
|
114
|
-
/**
|
|
115
|
-
* Override the owner entity references of the plugin.
|
|
116
|
-
*
|
|
117
|
-
* @remarks
|
|
118
|
-
* The provided values are interpreted as entity references defaulting
|
|
119
|
-
* to Group entities in the default namespace.
|
|
120
|
-
*/
|
|
121
|
-
ownerEntityRefs?: string[];
|
|
122
|
-
/**
|
|
123
|
-
* Override the links of the plugin.
|
|
124
|
-
*/
|
|
125
|
-
links?: Array<{ title: string; url: string }>;
|
|
126
|
-
};
|
|
127
|
-
}>;
|
|
128
|
-
};
|
|
129
|
-
}
|