@backstage/frontend-app-api 0.16.4 → 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 CHANGED
@@ -1,5 +1,17 @@
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
+
3
15
  ## 0.16.4
4
16
 
5
17
  ### 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
- permissionNames.map(
45
- (name) => permissionApi.authorize({
46
- permission: { name, type: "basic", attributes: {} }
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.4",
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.d.ts",
22
+ "config.schema.json",
23
23
  "dist"
24
24
  ],
25
25
  "scripts": {
@@ -32,24 +32,24 @@
32
32
  "test": "backstage-cli package test"
33
33
  },
34
34
  "dependencies": {
35
- "@backstage/config": "^1.3.8",
36
- "@backstage/core-app-api": "^1.20.2",
37
- "@backstage/core-plugin-api": "^1.12.7",
38
- "@backstage/errors": "^1.3.1",
39
- "@backstage/filter-predicates": "^0.1.3",
40
- "@backstage/frontend-defaults": "^0.5.3",
41
- "@backstage/frontend-plugin-api": "^0.17.2",
42
- "@backstage/types": "^1.2.2",
43
- "@backstage/version-bridge": "^1.0.12",
35
+ "@backstage/config": "1.3.8",
36
+ "@backstage/core-app-api": "1.20.3-next.0",
37
+ "@backstage/core-plugin-api": "1.12.8-next.0",
38
+ "@backstage/errors": "1.3.1",
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
+ "@backstage/types": "1.2.2",
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.3",
49
- "@backstage/frontend-test-utils": "^0.6.1",
50
- "@backstage/plugin-app": "^0.5.0",
51
- "@backstage/plugin-permission-common": "^0.9.9",
52
- "@backstage/test-utils": "^1.7.19",
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
+ "@backstage/plugin-permission-common": "0.9.9",
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.d.ts",
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
- }