@grafana/create-plugin 7.11.0 → 7.12.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 +13 -0
- package/dist/codemods/migrations/migrations.js +6 -0
- package/dist/codemods/migrations/scripts/014-eslint-config-v10.js +35 -0
- package/dist/utils/utils.templates.js +2 -2
- package/package.json +1 -1
- package/src/codemods/migrations/migrations.ts +7 -0
- package/src/codemods/migrations/scripts/014-eslint-config-v10.test.ts +94 -0
- package/src/codemods/migrations/scripts/014-eslint-config-v10.ts +40 -0
- package/src/utils/utils.templates.ts +2 -2
- package/templates/app/src/plugin.json +1 -1
- package/templates/app-sdk/.config/AGENTS/app-sdk.md +16 -4
- package/templates/app-sdk/.config/app-sdk/README.md +66 -0
- package/templates/app-sdk/kinds/example.cue +3 -0
- package/templates/app-sdk/kinds/manifest.cue +24 -0
- package/templates/common/.config/docker-compose-base.yaml +1 -1
- package/templates/common/.config/eslint.config.mjs +1 -1
- package/templates/common/.config/jest-setup.js +3 -1
- package/templates/common/_package.json +12 -12
- package/templates/datasource/src/plugin.json +1 -1
- package/templates/panel/src/plugin.json +1 -1
- package/templates/scenes-app/src/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [7.12.0](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.11.0...@grafana/create-plugin@7.12.0) (2026-09-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **deps:** bump grafana libs to latest in templates in create plugin ([#2893](https://github.com/grafana/plugin-tools/issues/2893)) ([d3c3f4b](https://github.com/grafana/plugin-tools/commit/d3c3f4b2a775d9bfcb0a9209a0c62a2ef16bbd58))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **create-plugin:** Improve experimental-app-sdk templates with RBAC guidance. ([#2892](https://github.com/grafana/plugin-tools/issues/2892)) ([ec59abe](https://github.com/grafana/plugin-tools/commit/ec59abe7f7508e6dc206a24a0658d121d64b3f1f))
|
|
14
|
+
* **create-plugin:** support @grafana/eslint-config v10 in scaffold ([#2873](https://github.com/grafana/plugin-tools/issues/2873)) ([67f6fec](https://github.com/grafana/plugin-tools/commit/67f6fec1321943ae8062e130745b37f3e45c1d72))
|
|
15
|
+
|
|
3
16
|
## [7.11.0](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.10.1...@grafana/create-plugin@7.11.0) (2026-09-16)
|
|
4
17
|
|
|
5
18
|
|
|
@@ -78,6 +78,12 @@ var defaultMigrations = [
|
|
|
78
78
|
version: "7.10.1",
|
|
79
79
|
description: "Support @grafana/ui@13.2.0 in Jest by transforming @react-hookz/web and @ver0/deep-equal ESM dependencies.",
|
|
80
80
|
scriptPath: import.meta.resolve("./scripts/013-jest-esmodules.js")
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "014-eslint-config-v10",
|
|
84
|
+
version: "7.11.1",
|
|
85
|
+
description: "Update @grafana/eslint-config to v10: drop the removed ./flat.js subpath import and swap @stylistic/eslint-plugin-ts for @stylistic/eslint-plugin.",
|
|
86
|
+
scriptPath: import.meta.resolve("./scripts/014-eslint-config-v10.js")
|
|
81
87
|
}
|
|
82
88
|
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
|
|
83
89
|
// for those written before the switch to updates as migrations.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isVersionGreater, addDependenciesToPackageJson, removeDependenciesFromPackageJson } from '../../utils.js';
|
|
2
|
+
|
|
3
|
+
const OLD_IMPORT = "import grafanaConfig from '@grafana/eslint-config/flat.js';";
|
|
4
|
+
const NEW_IMPORT = "import grafanaConfig from '@grafana/eslint-config';";
|
|
5
|
+
const ESLINT_CONFIG_PATH = ".config/eslint.config.mjs";
|
|
6
|
+
function migrate(context) {
|
|
7
|
+
if (!context.doesFileExist("package.json")) {
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
10
|
+
const packageJson = JSON.parse(context.getFile("package.json") || "{}");
|
|
11
|
+
const currentVersion = packageJson.devDependencies?.["@grafana/eslint-config"];
|
|
12
|
+
if (!currentVersion || !isVersionGreater("^10.0.0", currentVersion)) {
|
|
13
|
+
return context;
|
|
14
|
+
}
|
|
15
|
+
if (context.doesFileExist(ESLINT_CONFIG_PATH)) {
|
|
16
|
+
const content = context.getFile(ESLINT_CONFIG_PATH) || "";
|
|
17
|
+
if (content.includes(OLD_IMPORT)) {
|
|
18
|
+
context.updateFile(ESLINT_CONFIG_PATH, content.replace(OLD_IMPORT, NEW_IMPORT));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
addDependenciesToPackageJson(
|
|
22
|
+
context,
|
|
23
|
+
{},
|
|
24
|
+
{
|
|
25
|
+
"@grafana/eslint-config": "^10.0.0",
|
|
26
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
27
|
+
"eslint-config-prettier": "^10.1.0",
|
|
28
|
+
"eslint-plugin-react-hooks": "^7.1.0"
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
removeDependenciesFromPackageJson(context, [], ["@stylistic/eslint-plugin-ts"]);
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { migrate as default };
|
|
@@ -78,7 +78,7 @@ function getTemplateData(cliArgs) {
|
|
|
78
78
|
isNPM: isNPM(packageManagerName),
|
|
79
79
|
version: currentVersion,
|
|
80
80
|
bundleGrafanaUI,
|
|
81
|
-
scenesVersion: "^
|
|
81
|
+
scenesVersion: "^8.17.0",
|
|
82
82
|
useExperimentalRspack: Boolean(features.useExperimentalRspack),
|
|
83
83
|
frontendBundler
|
|
84
84
|
};
|
|
@@ -99,7 +99,7 @@ function getTemplateData(cliArgs) {
|
|
|
99
99
|
isNPM: isNPM(packageManagerName),
|
|
100
100
|
version: currentVersion,
|
|
101
101
|
bundleGrafanaUI,
|
|
102
|
-
scenesVersion: "^
|
|
102
|
+
scenesVersion: "^8.17.0",
|
|
103
103
|
pluginExecutable: pluginJson.executable,
|
|
104
104
|
useExperimentalRspack: Boolean(features.useExperimentalRspack),
|
|
105
105
|
frontendBundler
|
package/package.json
CHANGED
|
@@ -91,6 +91,13 @@ export default [
|
|
|
91
91
|
'Support @grafana/ui@13.2.0 in Jest by transforming @react-hookz/web and @ver0/deep-equal ESM dependencies.',
|
|
92
92
|
scriptPath: import.meta.resolve('./scripts/013-jest-esmodules.js'),
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
name: '014-eslint-config-v10',
|
|
96
|
+
version: '7.11.1',
|
|
97
|
+
description:
|
|
98
|
+
'Update @grafana/eslint-config to v10: drop the removed ./flat.js subpath import and swap @stylistic/eslint-plugin-ts for @stylistic/eslint-plugin.',
|
|
99
|
+
scriptPath: import.meta.resolve('./scripts/014-eslint-config-v10.js'),
|
|
100
|
+
},
|
|
94
101
|
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
|
|
95
102
|
// for those written before the switch to updates as migrations.
|
|
96
103
|
] satisfies Migration[];
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import migrate from './014-eslint-config-v10.js';
|
|
3
|
+
import { Context } from '../../context.js';
|
|
4
|
+
|
|
5
|
+
const SCAFFOLD_ESLINT_CONFIG = `/*
|
|
6
|
+
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY \`@grafana/create-plugin\`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { defineConfig } from 'eslint/config';
|
|
10
|
+
import grafanaConfig from '@grafana/eslint-config/flat.js';
|
|
11
|
+
|
|
12
|
+
export default defineConfig([...grafanaConfig]);
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
function addPackageJson(context: Context, eslintConfigVersion = '^9.0.0') {
|
|
16
|
+
context.addFile(
|
|
17
|
+
'package.json',
|
|
18
|
+
JSON.stringify({
|
|
19
|
+
devDependencies: {
|
|
20
|
+
'@grafana/eslint-config': eslintConfigVersion,
|
|
21
|
+
'@stylistic/eslint-plugin-ts': '^4.4.0',
|
|
22
|
+
'eslint-config-prettier': '^8.8.0',
|
|
23
|
+
'eslint-plugin-react-hooks': '^7.0.0',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('014-eslint-config-v10', () => {
|
|
30
|
+
it('should replace the removed flat.js subpath import', () => {
|
|
31
|
+
const context = new Context('/virtual');
|
|
32
|
+
context.addFile('.config/eslint.config.mjs', SCAFFOLD_ESLINT_CONFIG);
|
|
33
|
+
addPackageJson(context);
|
|
34
|
+
|
|
35
|
+
const result = migrate(context);
|
|
36
|
+
const content = result.getFile('.config/eslint.config.mjs') || '';
|
|
37
|
+
|
|
38
|
+
expect(content).toContain("import grafanaConfig from '@grafana/eslint-config';");
|
|
39
|
+
expect(content).not.toContain('@grafana/eslint-config/flat.js');
|
|
40
|
+
expect(content).toContain('DO NOT EDIT THIS FILE DIRECTLY');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should update package.json dependencies for eslint-config v10', () => {
|
|
44
|
+
const context = new Context('/virtual');
|
|
45
|
+
context.addFile('.config/eslint.config.mjs', SCAFFOLD_ESLINT_CONFIG);
|
|
46
|
+
addPackageJson(context);
|
|
47
|
+
|
|
48
|
+
const result = migrate(context);
|
|
49
|
+
const packageJson = JSON.parse(result.getFile('package.json') || '{}');
|
|
50
|
+
|
|
51
|
+
expect(packageJson.devDependencies['@grafana/eslint-config']).toBe('^10.0.0');
|
|
52
|
+
expect(packageJson.devDependencies['@stylistic/eslint-plugin']).toBe('^5.10.0');
|
|
53
|
+
expect(packageJson.devDependencies['@stylistic/eslint-plugin-ts']).toBeUndefined();
|
|
54
|
+
expect(packageJson.devDependencies['eslint-config-prettier']).toBe('^10.1.0');
|
|
55
|
+
expect(packageJson.devDependencies['eslint-plugin-react-hooks']).toBe('^7.1.0');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should not modify anything when package.json does not exist', () => {
|
|
59
|
+
const context = new Context('/virtual');
|
|
60
|
+
|
|
61
|
+
const result = migrate(context);
|
|
62
|
+
|
|
63
|
+
expect(result.hasChanges()).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should not modify anything when @grafana/eslint-config is already on v10', () => {
|
|
67
|
+
const context = new Context('/virtual');
|
|
68
|
+
context.addFile('.config/eslint.config.mjs', SCAFFOLD_ESLINT_CONFIG.replace('/flat.js', ''));
|
|
69
|
+
addPackageJson(context, '^10.0.0');
|
|
70
|
+
|
|
71
|
+
const initialChanges = context.listChanges();
|
|
72
|
+
migrate(context);
|
|
73
|
+
|
|
74
|
+
expect(context.listChanges()).toEqual(initialChanges);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should not modify anything when there are no devDependencies', () => {
|
|
78
|
+
const context = new Context('/virtual');
|
|
79
|
+
context.addFile('package.json', JSON.stringify({}));
|
|
80
|
+
|
|
81
|
+
const initialChanges = context.listChanges();
|
|
82
|
+
migrate(context);
|
|
83
|
+
|
|
84
|
+
expect(context.listChanges()).toEqual(initialChanges);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should be idempotent', async () => {
|
|
88
|
+
const context = new Context('/virtual');
|
|
89
|
+
context.addFile('.config/eslint.config.mjs', SCAFFOLD_ESLINT_CONFIG);
|
|
90
|
+
addPackageJson(context);
|
|
91
|
+
|
|
92
|
+
await expect(migrate).toBeIdempotent(context);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Context } from '../../context.js';
|
|
2
|
+
import { addDependenciesToPackageJson, isVersionGreater, removeDependenciesFromPackageJson } from '../../utils.js';
|
|
3
|
+
|
|
4
|
+
const OLD_IMPORT = "import grafanaConfig from '@grafana/eslint-config/flat.js';";
|
|
5
|
+
const NEW_IMPORT = "import grafanaConfig from '@grafana/eslint-config';";
|
|
6
|
+
const ESLINT_CONFIG_PATH = '.config/eslint.config.mjs';
|
|
7
|
+
|
|
8
|
+
export default function migrate(context: Context) {
|
|
9
|
+
if (!context.doesFileExist('package.json')) {
|
|
10
|
+
return context;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const packageJson = JSON.parse(context.getFile('package.json') || '{}');
|
|
14
|
+
const currentVersion = packageJson.devDependencies?.['@grafana/eslint-config'];
|
|
15
|
+
|
|
16
|
+
if (!currentVersion || !isVersionGreater('^10.0.0', currentVersion)) {
|
|
17
|
+
return context;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (context.doesFileExist(ESLINT_CONFIG_PATH)) {
|
|
21
|
+
const content = context.getFile(ESLINT_CONFIG_PATH) || '';
|
|
22
|
+
if (content.includes(OLD_IMPORT)) {
|
|
23
|
+
context.updateFile(ESLINT_CONFIG_PATH, content.replace(OLD_IMPORT, NEW_IMPORT));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
addDependenciesToPackageJson(
|
|
28
|
+
context,
|
|
29
|
+
{},
|
|
30
|
+
{
|
|
31
|
+
'@grafana/eslint-config': '^10.0.0',
|
|
32
|
+
'@stylistic/eslint-plugin': '^5.10.0',
|
|
33
|
+
'eslint-config-prettier': '^10.1.0',
|
|
34
|
+
'eslint-plugin-react-hooks': '^7.1.0',
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
removeDependenciesFromPackageJson(context, [], ['@stylistic/eslint-plugin-ts']);
|
|
38
|
+
|
|
39
|
+
return context;
|
|
40
|
+
}
|
|
@@ -121,7 +121,7 @@ export function getTemplateData(cliArgs?: GenerateCliArgs): TemplateData {
|
|
|
121
121
|
isNPM: isNPM(packageManagerName),
|
|
122
122
|
version: currentVersion,
|
|
123
123
|
bundleGrafanaUI,
|
|
124
|
-
scenesVersion: '^
|
|
124
|
+
scenesVersion: '^8.17.0',
|
|
125
125
|
useExperimentalRspack: Boolean(features.useExperimentalRspack),
|
|
126
126
|
frontendBundler,
|
|
127
127
|
};
|
|
@@ -145,7 +145,7 @@ export function getTemplateData(cliArgs?: GenerateCliArgs): TemplateData {
|
|
|
145
145
|
isNPM: isNPM(packageManagerName),
|
|
146
146
|
version: currentVersion,
|
|
147
147
|
bundleGrafanaUI,
|
|
148
|
-
scenesVersion: '^
|
|
148
|
+
scenesVersion: '^8.17.0',
|
|
149
149
|
pluginExecutable: pluginJson.executable,
|
|
150
150
|
useExperimentalRspack: Boolean(features.useExperimentalRspack),
|
|
151
151
|
frontendBundler,
|
|
@@ -22,8 +22,8 @@ TypeScript types and an app manifest that Grafana reads from the plugin bundle.
|
|
|
22
22
|
```
|
|
23
23
|
- **Generated code is committed.** Commit the regenerated files alongside the CUE change so schema
|
|
24
24
|
changes are reviewable and a fresh clone builds without running code generation.
|
|
25
|
-
- **
|
|
26
|
-
generation emits only TypeScript and the JSON definitions
|
|
25
|
+
- **Go code generation follows `kinds/config.cue`'s `codegen.goEnabled` setting.** If it's `false`,
|
|
26
|
+
generation emits only TypeScript and the JSON definitions — don't add Go output paths or a Go
|
|
27
27
|
backend to work around a generation problem.
|
|
28
28
|
- **Do not set `GRAFANA_APP_SDK_BIN`.** It overrides the pinned CLI with a local build, and is meant
|
|
29
29
|
for people working on the app-sdk itself. Code generated with it can differ from what `VERSION` in
|
|
@@ -31,6 +31,18 @@ TypeScript types and an app manifest that Grafana reads from the plugin bundle.
|
|
|
31
31
|
`generate:kinds` without it.
|
|
32
32
|
- **Do not add code generation to the build.** It is a schema-change-time step, not a build step. The
|
|
33
33
|
frontend build must keep working without a Go toolchain.
|
|
34
|
+
- **Never write authorization/permission checks in app code.** Access is declared in the manifest and
|
|
35
|
+
enforced by Grafana's API server, not by this plugin.
|
|
36
|
+
- **A `Namespaced` kind is folder-scoped by default:** callers need the Stack Role *and* folder access,
|
|
37
|
+
and every write must set the `grafana.app/folder` annotation to a real folder the caller can access —
|
|
38
|
+
this applies to anything your backend writes on a user's behalf too. Set `folderScoped: false` on the
|
|
39
|
+
kind if it shouldn't be gated by folders (writes then skip the annotation).
|
|
40
|
+
- **Every role needs a `roleBindings` entry to be granted to anyone.** Defining a role in
|
|
41
|
+
`kinds/manifest.cue` without binding it to a basic role (`viewer`/`editor`/`admin`) leaves it unused.
|
|
42
|
+
- **Set `appDisplayName` in `kinds/manifest.cue`** so the app's roles show a readable name in Grafana's
|
|
43
|
+
role picker, instead of the raw `appName`.
|
|
44
|
+
- **`admin` currently grants the same actions as `editor`.** Don't design a feature assuming admin has
|
|
45
|
+
extra permissions today.
|
|
34
46
|
|
|
35
47
|
## Layout
|
|
36
48
|
|
|
@@ -83,5 +95,5 @@ Resources are served under a Kubernetes-style path:
|
|
|
83
95
|
The namespace is deployment-dependent — `default` on single-tenant Grafana, `stacks-<id>` on Grafana
|
|
84
96
|
Cloud. Read it from `config.namespace` in `@grafana/runtime`; never hardcode it.
|
|
85
97
|
|
|
86
|
-
|
|
87
|
-
mutation), conversion between versions, and custom routes
|
|
98
|
+
No backend is needed for storage or CRUD — that comes from the manifest alone. Admission (validation
|
|
99
|
+
and mutation), conversion between versions, and custom routes require a backend.
|
|
@@ -58,3 +58,69 @@ be required for:
|
|
|
58
58
|
- Controller logic (informers, watchers, or any other background work)
|
|
59
59
|
|
|
60
60
|
If this plugin has a backend, `pkg/provider/provider.go` is where you wire those up as your app grows.
|
|
61
|
+
|
|
62
|
+
## Authorization & RBAC
|
|
63
|
+
|
|
64
|
+
You don't write permission checks in code. You declare roles and scoping in the manifest, and Grafana's
|
|
65
|
+
aggregated API server enforces them for every caller (kubectl, Terraform, generated clients, your
|
|
66
|
+
frontend). This only applies to requests from users (UI, Service Account tokens) — requests from a
|
|
67
|
+
Service Identity (another internal operator) are less strict.
|
|
68
|
+
|
|
69
|
+
### Folder scoping
|
|
70
|
+
|
|
71
|
+
Namespaced kinds (set in `kinds/*.cue`, see `example.cue`) are **folder-scoped by default**. Access
|
|
72
|
+
requires *both* the right Stack Role (see below) *and* folder access on the resource's
|
|
73
|
+
`grafana.app/folder`. If your kind's data doesn't belong in folders, opt out with
|
|
74
|
+
`folderScoped: false` — access then comes down to the Stack Role alone, and adding a folder annotation
|
|
75
|
+
to a request is rejected.
|
|
76
|
+
|
|
77
|
+
```cue
|
|
78
|
+
examplev1alpha1: exampleKind & {
|
|
79
|
+
// Opt this kind out of folder-scoped access.
|
|
80
|
+
folderScoped: false
|
|
81
|
+
schema: {
|
|
82
|
+
spec: {
|
|
83
|
+
title: string
|
|
84
|
+
description: string
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Roles and role bindings
|
|
91
|
+
|
|
92
|
+
Declare custom roles in `kinds/manifest.cue`. A role name follows `<app>:<role>` and grants one of three
|
|
93
|
+
tiers — `viewer` (read), `editor` (read + write), or `admin` (currently identical to `editor` — don't
|
|
94
|
+
design around admin having extra powers yet). Role bindings attach your app's roles to Grafana's basic
|
|
95
|
+
roles (`viewer`, `editor`, `admin`), so every Grafana user inherits the matching app role automatically.
|
|
96
|
+
Set `appDisplayName` so your app shows up with a readable name in the Grafana UI's role picker, instead
|
|
97
|
+
of the raw `appName`.
|
|
98
|
+
|
|
99
|
+
```cue
|
|
100
|
+
manifest: {
|
|
101
|
+
appName: "{{ pluginId }}"
|
|
102
|
+
appDisplayName: "{{ pluginId }}"
|
|
103
|
+
versions: {
|
|
104
|
+
"v1alpha1": v1alpha1
|
|
105
|
+
}
|
|
106
|
+
extraPermissions: {
|
|
107
|
+
accessKinds: []
|
|
108
|
+
}
|
|
109
|
+
// roles your app grants, and which Grafana basic role gets each one by default.
|
|
110
|
+
roles: [
|
|
111
|
+
{
|
|
112
|
+
name: "{{ pluginId }}:editor"
|
|
113
|
+
permissionSet: "editor"
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
roleBindings: [
|
|
117
|
+
{
|
|
118
|
+
roleName: "{{ pluginId }}:editor"
|
|
119
|
+
basicRole: "editor"
|
|
120
|
+
},
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Without a `roleBindings` entry, a role you define is never granted to anyone — add one for every role you
|
|
126
|
+
want users to actually have.
|
|
@@ -7,6 +7,9 @@ exampleKind: {
|
|
|
7
7
|
pluralName: "Examples"
|
|
8
8
|
// Namespaced resources are created per-tenant. Use "Cluster" for global resources.
|
|
9
9
|
scope: "Namespaced"
|
|
10
|
+
// Namespaced kinds are folder-scoped by default: callers need both the Stack Role (see roles in
|
|
11
|
+
// manifest.cue) and folder access. Uncomment to opt this kind out of folder scoping.
|
|
12
|
+
// folderScoped: false
|
|
10
13
|
codegen: {
|
|
11
14
|
ts: {
|
|
12
15
|
enabled: true
|
|
@@ -7,6 +7,10 @@ manifest: {
|
|
|
7
7
|
// different group.
|
|
8
8
|
appName: "{{ pluginId }}"
|
|
9
9
|
|
|
10
|
+
// appDisplayName is the human-readable name shown for your app's roles in Grafana's role picker.
|
|
11
|
+
// Set it to something more readable than appName.
|
|
12
|
+
appDisplayName: "{{ pluginId }}"
|
|
13
|
+
|
|
10
14
|
// versions maps each version your app serves to the kinds it exposes. Version names follow the
|
|
11
15
|
// format "v<integer>" or "v<integer>(alpha|beta)<integer>".
|
|
12
16
|
versions: {
|
|
@@ -18,6 +22,26 @@ manifest: {
|
|
|
18
22
|
extraPermissions: {
|
|
19
23
|
accessKinds: []
|
|
20
24
|
}
|
|
25
|
+
|
|
26
|
+
// roles declares custom RBAC roles for your app. Each role name follows "<app>:<role>" and grants
|
|
27
|
+
// one of three tiers: "viewer" (read), "editor" (read + write), or "admin" (currently identical
|
|
28
|
+
// to "editor"). A role you define here is never granted to anyone without a matching entry in
|
|
29
|
+
// roleBindings below.
|
|
30
|
+
// roles: [
|
|
31
|
+
// {
|
|
32
|
+
// name: "{{ pluginId }}:editor"
|
|
33
|
+
// permissionSet: "editor"
|
|
34
|
+
// },
|
|
35
|
+
// ]
|
|
36
|
+
|
|
37
|
+
// roleBindings attaches your app's roles to Grafana's basic roles (viewer/editor/admin), so every
|
|
38
|
+
// Grafana user with that basic role automatically inherits the matching app role.
|
|
39
|
+
// roleBindings: [
|
|
40
|
+
// {
|
|
41
|
+
// roleName: "{{ pluginId }}:editor"
|
|
42
|
+
// basicRole: "editor"
|
|
43
|
+
// },
|
|
44
|
+
// ]
|
|
21
45
|
}
|
|
22
46
|
|
|
23
47
|
// v1alpha1 is the v1alpha1 version of the app's API.
|
|
@@ -7,7 +7,7 @@ services:
|
|
|
7
7
|
context: .
|
|
8
8
|
args:
|
|
9
9
|
grafana_image: ${GRAFANA_IMAGE:-{{~grafanaImage~}} }
|
|
10
|
-
grafana_version: ${GRAFANA_VERSION:-13.
|
|
10
|
+
grafana_version: ${GRAFANA_VERSION:-13.2.2}
|
|
11
11
|
development: ${DEVELOPMENT:-false}
|
|
12
12
|
anonymous_auth_enabled: ${ANONYMOUS_AUTH_ENABLED:-true}
|
|
13
13
|
ports:
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import '@testing-library/jest-dom';
|
|
9
9
|
import { TextEncoder, TextDecoder } from 'util';
|
|
10
|
+
import { MessageChannel, MessagePort } from 'worker_threads';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
// React 19's scheduler relies on MessageChannel, which jsdom does not implement.
|
|
13
|
+
Object.assign(global, { TextDecoder, TextEncoder, MessageChannel, MessagePort });
|
|
12
14
|
|
|
13
15
|
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
|
14
16
|
Object.defineProperty(global, 'matchMedia', {
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
"author": "{{ sentenceCase orgName }}",
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@grafana/eslint-config": "^
|
|
19
|
+
"@grafana/eslint-config": "^10.0.0",
|
|
20
20
|
"@grafana/plugin-e2e": "^3.12.0",
|
|
21
21
|
"@grafana/sign-plugin": "^3.3.3",
|
|
22
22
|
"@grafana/tsconfig": "^2.2.0",
|
|
23
23
|
"@playwright/test": "^1.62.1",{{#if useExperimentalRspack}}
|
|
24
24
|
"@rspack/core": "^1.6.0",
|
|
25
25
|
"@rspack/cli": "^1.6.0",{{/if}}
|
|
26
|
-
"@stylistic/eslint-plugin
|
|
26
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
27
27
|
"@swc/core": "^1.15.0",
|
|
28
28
|
"@swc/helpers": "^0.5.0",
|
|
29
29
|
"@swc/jest": "^0.2.0",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@testing-library/react": "^16.3.0",
|
|
32
32
|
"@types/jest": "^29.5.0",
|
|
33
33
|
"@types/node": "^24.0.0",
|
|
34
|
-
"@types/react": "^
|
|
35
|
-
"@types/react-dom": "^
|
|
34
|
+
"@types/react": "^19.0.0",
|
|
35
|
+
"@types/react-dom": "^19.0.0",{{#if useExperimentalRspack}}
|
|
36
36
|
"@types/ws": "^8.18.1",{{/if}}
|
|
37
37
|
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
38
38
|
"@typescript-eslint/parser": "^8.38.0",{{#unless useExperimentalRspack}}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"eslint-config-prettier": "^10.1.0",
|
|
43
43
|
"eslint-plugin-jsdoc": "^52.0.0",
|
|
44
44
|
"eslint-plugin-react": "^7.37.0",
|
|
45
|
-
"eslint-plugin-react-hooks": "^7.
|
|
45
|
+
"eslint-plugin-react-hooks": "^7.1.0",
|
|
46
46
|
"eslint-webpack-plugin": "^5.0.0",{{#unless useExperimentalRspack}}
|
|
47
47
|
"fork-ts-checker-webpack-plugin": "^9.1.0",{{/unless}}
|
|
48
48
|
"glob": "^11.1.0",
|
|
@@ -74,14 +74,14 @@
|
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@emotion/css": "11.10.6",
|
|
77
|
-
"@grafana/data": "13.
|
|
78
|
-
"@grafana/i18n": "13.
|
|
79
|
-
"@grafana/runtime": "13.
|
|
80
|
-
"@grafana/ui": "13.
|
|
81
|
-
"@grafana/schema": "13.
|
|
77
|
+
"@grafana/data": "13.2.2",
|
|
78
|
+
"@grafana/i18n": "13.2.2",
|
|
79
|
+
"@grafana/runtime": "13.2.2",
|
|
80
|
+
"@grafana/ui": "13.2.2",
|
|
81
|
+
"@grafana/schema": "13.2.2",{{#if_eq pluginType "scenesapp" }}
|
|
82
82
|
"@grafana/scenes": "{{ scenesVersion }}",{{/if_eq}}
|
|
83
|
-
"react": "^
|
|
84
|
-
"react-dom": "^
|
|
83
|
+
"react": "^19.0.0",
|
|
84
|
+
"react-dom": "^19.0.0"{{#if isAppType}},
|
|
85
85
|
"react-router-dom": "^6.22.0",
|
|
86
86
|
"rxjs": "7.8.2"{{/if}}
|
|
87
87
|
},
|