@grafana/create-plugin 7.10.1 → 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 +33 -0
- package/dist/codemods/additions/additions.js +5 -0
- package/dist/codemods/additions/scripts/experimental-app-sdk.js +275 -0
- package/dist/codemods/context.js +13 -1
- package/dist/codemods/migrations/migrations.js +6 -0
- package/dist/codemods/migrations/scripts/014-eslint-config-v10.js +35 -0
- package/dist/codemods/runner.js +2 -1
- package/dist/codemods/utils.goMod.js +45 -0
- package/dist/codemods/utils.js +25 -1
- package/dist/commands/add.command.js +9 -4
- package/dist/commands/generate/print-success-message.js +1 -1
- package/dist/utils/utils.templates.js +2 -2
- package/package.json +3 -3
- package/src/codemods/additions/additions.ts +5 -0
- package/src/codemods/additions/scripts/experimental-app-sdk.test.ts +655 -0
- package/src/codemods/additions/scripts/experimental-app-sdk.ts +431 -0
- package/src/codemods/context.test.ts +7 -1
- package/src/codemods/context.ts +23 -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/codemods/runner.ts +3 -1
- package/src/codemods/utils.goMod.test.ts +86 -0
- package/src/codemods/utils.goMod.ts +70 -0
- package/src/codemods/utils.test.ts +49 -0
- package/src/codemods/utils.ts +39 -0
- package/src/commands/add.command.ts +10 -5
- package/src/commands/generate/print-success-message.ts +1 -1
- 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 +99 -0
- package/templates/app-sdk/.config/app-sdk/README.md +126 -0
- package/templates/app-sdk/.config/app-sdk/generate-kinds.mjs +138 -0
- package/templates/app-sdk/.github/workflows/generate-kinds-drift.yml +84 -0
- package/templates/app-sdk/kinds/README.md +3 -0
- package/templates/app-sdk/kinds/config.cue +39 -0
- package/templates/app-sdk/kinds/cue.mod/module.cue +4 -0
- package/templates/app-sdk/kinds/example.cue +32 -0
- package/templates/app-sdk/kinds/manifest.cue +53 -0
- package/templates/app-sdk/pkg/generated/example/v1alpha1/doc.go +11 -0
- package/templates/app-sdk/pkg/generated/manifestdata/doc.go +12 -0
- package/templates/app-sdk/pkg/provider/provider.go +30 -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 +13 -13
- 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/vitest.setup.ts +2 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Runs grafana-app-sdk kind code generation from the CUE kinds in ./kinds.
|
|
2
|
+
//
|
|
3
|
+
// Always runs the CLI at the version pinned in VERSION below, resolved as:
|
|
4
|
+
// 1. a previously obtained copy in node_modules/.cache/grafana-app-sdk/<version>/
|
|
5
|
+
// 2. a fresh copy, downloaded from the official release and verified against its checksum
|
|
6
|
+
//
|
|
7
|
+
// A `grafana-app-sdk` on your PATH is deliberately ignored: generated code has to match the library
|
|
8
|
+
// version it is compiled against, and silently generating with whatever happens to be installed makes
|
|
9
|
+
// that mismatch invisible. To use your own build, set GRAFANA_APP_SDK_BIN to the binary — an explicit,
|
|
10
|
+
// per-invocation override for local development.
|
|
11
|
+
//
|
|
12
|
+
// Downloading the CLI needs no Go toolchain. Generation itself only needs Go when codegen.goEnabled
|
|
13
|
+
// is set in kinds/config.cue: the generator formats Go output with golang.org/x/tools, which shells
|
|
14
|
+
// out to `go`.
|
|
15
|
+
//
|
|
16
|
+
// Output paths are configured in kinds/config.cue. Generated code is intended to be committed.
|
|
17
|
+
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { join, resolve } from 'node:path';
|
|
22
|
+
import { arch, platform, tmpdir } from 'node:os';
|
|
23
|
+
|
|
24
|
+
const VERSION = 'v0.60.0';
|
|
25
|
+
const REPO = 'grafana/grafana-app-sdk';
|
|
26
|
+
const BIN = 'grafana-app-sdk';
|
|
27
|
+
|
|
28
|
+
// Escape hatch for developing against a local CLI build, e.g. one you are changing yourself. Unlike a
|
|
29
|
+
// binary that merely happens to be on PATH, setting this is a deliberate act, so it is honoured.
|
|
30
|
+
const BIN_OVERRIDE = 'GRAFANA_APP_SDK_BIN';
|
|
31
|
+
|
|
32
|
+
// node_modules is already gitignored, so nothing extra needs ignoring for this cache.
|
|
33
|
+
const CACHE_DIR = resolve('node_modules', '.cache', 'grafana-app-sdk', VERSION);
|
|
34
|
+
|
|
35
|
+
function run(command, args, options = {}) {
|
|
36
|
+
return spawnSync(command, args, { stdio: 'inherit', ...options });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const EXE = platform() === 'win32' ? '.exe' : '';
|
|
40
|
+
|
|
41
|
+
/** Returns the release asset's platform pair, or exits if this platform has no published build. */
|
|
42
|
+
function target() {
|
|
43
|
+
const goos = { linux: 'linux', darwin: 'darwin', win32: 'windows' }[platform()];
|
|
44
|
+
const goarch = { x64: 'amd64', arm64: 'arm64' }[arch()];
|
|
45
|
+
|
|
46
|
+
if (!goos || !goarch) {
|
|
47
|
+
console.error(
|
|
48
|
+
`No grafana-app-sdk release build for ${platform()}/${arch()}.\n` +
|
|
49
|
+
`Install the CLI yourself and re-run: https://github.com/${REPO}/releases`
|
|
50
|
+
);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { goos, goarch };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function fetchOrDie(url, what) {
|
|
58
|
+
const response = await fetch(url);
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
console.error(`Failed to download ${what} (${response.status} ${response.statusText})\n ${url}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
return Buffer.from(await response.arrayBuffer());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Downloads the release archive, verifies it against checksums.txt, and unpacks it into CACHE_DIR. */
|
|
67
|
+
async function download({ goos, goarch }) {
|
|
68
|
+
// Release assets drop the leading "v" from the tag.
|
|
69
|
+
const archive = `${BIN}_${VERSION.replace(/^v/, '')}_${goos}_${goarch}.tar.gz`;
|
|
70
|
+
const base = `https://github.com/${REPO}/releases/download/${VERSION}`;
|
|
71
|
+
|
|
72
|
+
console.log(`Downloading ${BIN} ${VERSION} for ${goos}/${goarch}...`);
|
|
73
|
+
const [tarball, checksums] = await Promise.all([
|
|
74
|
+
fetchOrDie(`${base}/${archive}`, archive),
|
|
75
|
+
fetchOrDie(`${base}/checksums.txt`, 'checksums.txt'),
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const expected = checksums
|
|
79
|
+
.toString('utf8')
|
|
80
|
+
.split('\n')
|
|
81
|
+
.map((line) => line.trim().split(/\s+/))
|
|
82
|
+
.find(([, name]) => name === archive)?.[0];
|
|
83
|
+
|
|
84
|
+
if (!expected) {
|
|
85
|
+
console.error(`${archive} is not listed in checksums.txt; refusing to run an unverified binary.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const actual = createHash('sha256').update(tarball).digest('hex');
|
|
90
|
+
if (actual !== expected) {
|
|
91
|
+
console.error(`Checksum mismatch for ${archive}.\n expected ${expected}\n actual ${actual}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Unpack via tar(1): available on macOS/Linux and shipped with Windows 10+. The archive lays the
|
|
96
|
+
// binary flat at its root (alongside LICENSE and README.md), so extract straight into CACHE_DIR.
|
|
97
|
+
const tarPath = join(tmpdir(), `${archive}-${process.pid}`);
|
|
98
|
+
writeFileSync(tarPath, tarball);
|
|
99
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
100
|
+
|
|
101
|
+
const untar = run('tar', ['-xzf', tarPath, '-C', CACHE_DIR, `${BIN}${EXE}`]);
|
|
102
|
+
rmSync(tarPath, { force: true });
|
|
103
|
+
if (untar.status !== 0) {
|
|
104
|
+
console.error(`Could not unpack ${archive}. Is tar available on your PATH?`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const binary = join(CACHE_DIR, `${BIN}${EXE}`);
|
|
109
|
+
chmodSync(binary, 0o755);
|
|
110
|
+
|
|
111
|
+
return binary;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function resolveBinary() {
|
|
115
|
+
const override = process.env[BIN_OVERRIDE];
|
|
116
|
+
if (override) {
|
|
117
|
+
// Exit rather than falling back to the pinned version: you asked for a specific binary, so
|
|
118
|
+
// silently generating with a different one is the surprise worth avoiding.
|
|
119
|
+
if (!existsSync(override)) {
|
|
120
|
+
console.error(`${BIN_OVERRIDE} is set to ${override}, but no such file exists.`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(`Using ${BIN} from ${BIN_OVERRIDE}: ${override}`);
|
|
125
|
+
return resolve(override);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const cached = join(CACHE_DIR, `${BIN}${EXE}`);
|
|
129
|
+
if (existsSync(cached)) {
|
|
130
|
+
return cached;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return download(target());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const binary = await resolveBinary();
|
|
137
|
+
const result = run(binary, ['generate', '--source', 'kinds']);
|
|
138
|
+
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Kinds drift check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
- main
|
|
8
|
+
paths:
|
|
9
|
+
- 'kinds/**'
|
|
10
|
+
- 'pkg/generated/**'
|
|
11
|
+
- 'src/generated/**'
|
|
12
|
+
- 'src/app-sdk-manifest.json'
|
|
13
|
+
- '.github/workflows/generate-kinds-drift.yml'
|
|
14
|
+
pull_request:
|
|
15
|
+
branches:
|
|
16
|
+
- master
|
|
17
|
+
- main
|
|
18
|
+
paths:
|
|
19
|
+
- 'kinds/**'
|
|
20
|
+
- 'pkg/generated/**'
|
|
21
|
+
- 'src/generated/**'
|
|
22
|
+
- 'src/app-sdk-manifest.json'
|
|
23
|
+
- '.github/workflows/generate-kinds-drift.yml'
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
check-drift:
|
|
30
|
+
name: Check generated kinds are up to date
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v6
|
|
34
|
+
with:
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
{{#if_eq packageManagerName "pnpm"}}
|
|
37
|
+
# pnpm action uses the packageManager field in package.json to
|
|
38
|
+
# understand which version to install.
|
|
39
|
+
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
|
40
|
+
{{/if_eq}}
|
|
41
|
+
- name: Setup Node.js environment
|
|
42
|
+
uses: actions/setup-node@v6
|
|
43
|
+
with:
|
|
44
|
+
node-version: '22'
|
|
45
|
+
cache: '{{ packageManagerName }}'
|
|
46
|
+
|
|
47
|
+
- name: Check for backend
|
|
48
|
+
id: check-for-backend
|
|
49
|
+
run: |
|
|
50
|
+
if [ -f "Magefile.go" ]
|
|
51
|
+
then
|
|
52
|
+
echo "has-backend=true" >> $GITHUB_OUTPUT
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
- name: Setup Go environment
|
|
56
|
+
if: steps.check-for-backend.outputs.has-backend == 'true'
|
|
57
|
+
uses: actions/setup-go@v6
|
|
58
|
+
with:
|
|
59
|
+
go-version-file: go.mod
|
|
60
|
+
|
|
61
|
+
- name: Install dependencies
|
|
62
|
+
run: {{ packageManagerInstallCmd }}
|
|
63
|
+
|
|
64
|
+
- name: Cache grafana-app-sdk CLI
|
|
65
|
+
uses: actions/cache@v4
|
|
66
|
+
with:
|
|
67
|
+
path: node_modules/.cache/grafana-app-sdk
|
|
68
|
+
key: grafana-app-sdk-cli-${{ '{{' }} hashFiles('.config/app-sdk/generate-kinds.mjs') {{ '}}' }}
|
|
69
|
+
|
|
70
|
+
- name: Generate kinds
|
|
71
|
+
run: {{ packageManagerName }} run generate:kinds
|
|
72
|
+
|
|
73
|
+
- name: Fail if generated code is out of date
|
|
74
|
+
run: |
|
|
75
|
+
# Scoped to kinds/ and the generated output: installing dependencies above can itself touch
|
|
76
|
+
# files (e.g. package-lock.json, if CI's Node version differs from the one used to generate
|
|
77
|
+
# the committed lockfile), which is not kind drift and shouldn't fail this check.
|
|
78
|
+
paths="kinds src/generated pkg/generated src/app-sdk-manifest.json"
|
|
79
|
+
if [ -n "$(git status --porcelain -- $paths)" ]; then
|
|
80
|
+
echo "::error::Generated code is out of date. Run '{{ packageManagerName }} run generate:kinds' and commit the result."
|
|
81
|
+
git status --porcelain -- $paths
|
|
82
|
+
git diff --stat -- $paths
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package kinds
|
|
2
|
+
|
|
3
|
+
// config holds the grafana-app-sdk code generation settings, and is read by
|
|
4
|
+
// `grafana-app-sdk generate` (via the default `-c config` selector) when you run
|
|
5
|
+
// `{{ packageManagerName }} run generate:kinds`.
|
|
6
|
+
//
|
|
7
|
+
// Paths are relative to the plugin root and match the layout scaffolded by @grafana/create-plugin:
|
|
8
|
+
// the frontend lives in src/.
|
|
9
|
+
config: {
|
|
10
|
+
definitions: {
|
|
11
|
+
manifestVersion: "v1alpha2"
|
|
12
|
+
|
|
13
|
+
// Do not edit this section; the generated manifest must be named exactly as below.
|
|
14
|
+
// The manifest tells Grafana which kinds and capabilities your app serves. Write it
|
|
15
|
+
// straight into src/ so the frontend build picks it up as-is.
|
|
16
|
+
manifestSchemas: true
|
|
17
|
+
path: "src"
|
|
18
|
+
manifestFileName: "app-sdk-manifest.json"
|
|
19
|
+
encoding: "json"
|
|
20
|
+
// Do not generate separate files per-CRD, they are not used.
|
|
21
|
+
genCRDs: false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
codegen: {
|
|
25
|
+
{{#if hasBackend}}
|
|
26
|
+
// Generated Go types land alongside the plugin backend.
|
|
27
|
+
goEnabled: true
|
|
28
|
+
goGenPath: "pkg/generated/"
|
|
29
|
+
{{else}}
|
|
30
|
+
// This plugin has no Go backend, so skip Go code generation entirely: only TypeScript and
|
|
31
|
+
// the definitions below are emitted, and no Go toolchain is needed to generate them.
|
|
32
|
+
goEnabled: false
|
|
33
|
+
{{/if}}
|
|
34
|
+
// Generated TypeScript types land in the frontend source dir.
|
|
35
|
+
tsGenPath: "src/generated/"
|
|
36
|
+
enableK8sPostProcessing: false
|
|
37
|
+
enableOperatorStatusGeneration: false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package kinds
|
|
2
|
+
|
|
3
|
+
// exampleKind holds the information about the Example kind that does not change between versions.
|
|
4
|
+
// Rename this kind (and its file) to model your own resource.
|
|
5
|
+
exampleKind: {
|
|
6
|
+
kind: "Example"
|
|
7
|
+
pluralName: "Examples"
|
|
8
|
+
// Namespaced resources are created per-tenant. Use "Cluster" for global resources.
|
|
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
|
|
13
|
+
codegen: {
|
|
14
|
+
ts: {
|
|
15
|
+
enabled: true
|
|
16
|
+
}
|
|
17
|
+
go: {
|
|
18
|
+
enabled: true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// examplev1alpha1 is the v1alpha1 version of the Example kind: the common kind information plus
|
|
24
|
+
// this version's schema. Edit the spec to model your resource, then re-run code generation.
|
|
25
|
+
examplev1alpha1: exampleKind & {
|
|
26
|
+
schema: {
|
|
27
|
+
spec: {
|
|
28
|
+
title: string
|
|
29
|
+
description: string
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package kinds
|
|
2
|
+
|
|
3
|
+
manifest: {
|
|
4
|
+
// appName is the unique name of your app. It is used to reference the app from other config
|
|
5
|
+
// objects, and to derive the API group your app serves (by default,
|
|
6
|
+
// LOWER(strip dashes)+".ext.grafana.app"). Set `groupOverride` if you need to pin a shorter or
|
|
7
|
+
// different group.
|
|
8
|
+
appName: "{{ pluginId }}"
|
|
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
|
+
|
|
14
|
+
// versions maps each version your app serves to the kinds it exposes. Version names follow the
|
|
15
|
+
// format "v<integer>" or "v<integer>(alpha|beta)<integer>".
|
|
16
|
+
versions: {
|
|
17
|
+
"v1alpha1": v1alpha1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// extraPermissions declares any additional permissions your app needs, e.g. access to kinds
|
|
21
|
+
// owned by other apps.
|
|
22
|
+
extraPermissions: {
|
|
23
|
+
accessKinds: []
|
|
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
|
+
// ]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// v1alpha1 is the v1alpha1 version of the app's API.
|
|
48
|
+
v1alpha1: {
|
|
49
|
+
// kinds lists the version-specific kind values served by this version.
|
|
50
|
+
kinds: [examplev1alpha1]
|
|
51
|
+
// served indicates whether this version is served by the API server. Defaults to true.
|
|
52
|
+
served: true
|
|
53
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Package v1alpha1 is a stub that exists purely so `go mod tidy` resolves its dependencies before
|
|
2
|
+
// code generation has run. `generate:kinds` fills this package with its own generated files, without
|
|
3
|
+
// removing this one — it's left behind, harmless, since it exports nothing.
|
|
4
|
+
package v1alpha1
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
_ "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
8
|
+
_ "k8s.io/apimachinery/pkg/runtime"
|
|
9
|
+
_ "k8s.io/apimachinery/pkg/runtime/schema"
|
|
10
|
+
_ "k8s.io/apimachinery/pkg/types"
|
|
11
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Package manifestdata is a stub that exists purely so `go mod tidy` resolves its dependencies before
|
|
2
|
+
// code generation has run. `generate:kinds` fills this package with its own generated files, without
|
|
3
|
+
// removing this one — it's left behind, harmless, since it exports nothing.
|
|
4
|
+
package manifestdata
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
_ "github.com/grafana/grafana-app-sdk/app"
|
|
8
|
+
_ "github.com/grafana/grafana-app-sdk/resource"
|
|
9
|
+
_ "k8s.io/apimachinery/pkg/runtime"
|
|
10
|
+
_ "k8s.io/kube-openapi/pkg/spec3"
|
|
11
|
+
_ "k8s.io/kube-openapi/pkg/validation/spec"
|
|
12
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package provider
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"github.com/grafana/grafana-app-sdk/app"
|
|
5
|
+
"github.com/grafana/grafana-app-sdk/simple"
|
|
6
|
+
|
|
7
|
+
examplev1alpha1 "github.com/{{ kebabCase orgName }}/{{ kebabCase pluginName }}/pkg/generated/example/v1alpha1"
|
|
8
|
+
"github.com/{{ kebabCase orgName }}/{{ kebabCase pluginName }}/pkg/generated/manifestdata"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// New returns the app.Provider, the single entry point to this package. main.go uses it to read the
|
|
12
|
+
// manifest and instantiate the app.App.
|
|
13
|
+
func New() app.Provider {
|
|
14
|
+
return simple.NewAppProvider(manifestdata.LocalManifest(), nil, newApp)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// newApp is the factory passed to simple.NewAppProvider; it builds the app.App from the resolved
|
|
18
|
+
// config. Attach a Validator, Mutator, or Reconciler per kind as you need one — see
|
|
19
|
+
// simple.AppManagedKind and ./.config/AGENTS/app-sdk.md.
|
|
20
|
+
func newApp(cfg app.Config) (app.App, error) {
|
|
21
|
+
return simple.NewApp(simple.AppConfig{
|
|
22
|
+
Name: "{{ pluginId }}",
|
|
23
|
+
KubeConfig: cfg.KubeConfig,
|
|
24
|
+
ManagedKinds: []simple.AppManagedKind{
|
|
25
|
+
{
|
|
26
|
+
Kind: examplev1alpha1.Kind(),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
}
|
|
@@ -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": "^
|
|
20
|
-
"@grafana/plugin-e2e": "^3.
|
|
19
|
+
"@grafana/eslint-config": "^10.0.0",
|
|
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
|
},
|
package/vitest.setup.ts
CHANGED
|
@@ -28,10 +28,10 @@ const parseContent = (content?: string) => (content ? JSON.parse(content) : '');
|
|
|
28
28
|
expect.extend({
|
|
29
29
|
async toBeIdempotent(migrate: (context: Context) => Promise<Context>, context: Context) {
|
|
30
30
|
const firstRun = await migrate(context);
|
|
31
|
-
const firstRunDeepCopy = structuredClone(firstRun) as unknown as ClonedContext;
|
|
31
|
+
const firstRunDeepCopy = { files: structuredClone(firstRun.listChanges()) } as unknown as ClonedContext;
|
|
32
32
|
|
|
33
33
|
const secondRun = await migrate(firstRun);
|
|
34
|
-
const secondRunDeepCopy = structuredClone(secondRun) as unknown as ClonedContext;
|
|
34
|
+
const secondRunDeepCopy = { files: structuredClone(secondRun.listChanges()) } as unknown as ClonedContext;
|
|
35
35
|
|
|
36
36
|
const result = await compareContexts(firstRunDeepCopy, secondRunDeepCopy);
|
|
37
37
|
|