@grafana/create-plugin 7.12.3 → 7.14.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 +31 -0
- package/dist/codemods/additions/additions.js +20 -7
- package/dist/codemods/additions/prompts/docs-content.md +119 -0
- package/dist/codemods/additions/prompts/example-prompt.md +56 -0
- package/dist/codemods/additions/scripts/docs-content.js +30 -0
- package/dist/codemods/additions/scripts/docs.js +250 -0
- package/dist/codemods/additions/scripts/experimental-app-sdk.js +39 -33
- package/dist/codemods/agentic/definitions.js +31 -0
- package/dist/codemods/agentic/detect.js +57 -0
- package/dist/codemods/agentic/handoff.js +35 -0
- package/dist/codemods/agentic/index.js +143 -0
- package/dist/codemods/agentic/prompts.js +115 -0
- package/dist/codemods/agentic/resolve.js +120 -0
- package/dist/codemods/agentic/runner.js +79 -0
- package/dist/codemods/agentic/verify.js +55 -0
- package/dist/codemods/migrations/migrations.js +12 -0
- package/dist/codemods/migrations/scripts/017-jest-30.js +32 -0
- package/dist/codemods/migrations/scripts/018-replace-eslint-webpack-plugin.js +115 -0
- package/dist/codemods/utils.agents.js +64 -0
- package/dist/codemods/utils.js +51 -8
- package/dist/codemods/utils.workflows.js +94 -0
- package/dist/commands/add.command.js +74 -12
- package/dist/utils/utils.cli.js +7 -2
- package/dist/utils/utils.console.js +10 -1
- package/dist/utils/utils.git.js +10 -1
- package/dist/utils/utils.handlebars.js +9 -2
- package/dist/utils/utils.packageManager.js +2 -2
- package/dist/utils/utils.templates.js +38 -24
- package/package.json +1 -1
- package/src/codemods/AGENTS.md +25 -0
- package/src/codemods/additions/additions.test.ts +69 -4
- package/src/codemods/additions/additions.ts +50 -8
- package/src/codemods/additions/prompts/docs-content.md +119 -0
- package/src/codemods/additions/prompts/example-prompt.md +56 -0
- package/src/codemods/additions/scripts/docs-content.test.ts +94 -0
- package/src/codemods/additions/scripts/docs-content.ts +32 -0
- package/src/codemods/additions/scripts/docs.test.ts +1037 -0
- package/src/codemods/additions/scripts/docs.ts +353 -0
- package/src/codemods/additions/scripts/experimental-app-sdk.test.ts +40 -80
- package/src/codemods/additions/scripts/experimental-app-sdk.ts +42 -34
- package/src/codemods/agentic/definitions.test.ts +67 -0
- package/src/codemods/agentic/definitions.ts +33 -0
- package/src/codemods/agentic/detect.test.ts +136 -0
- package/src/codemods/agentic/detect.ts +75 -0
- package/src/codemods/agentic/handoff.test.ts +102 -0
- package/src/codemods/agentic/handoff.ts +45 -0
- package/src/codemods/agentic/index.test.ts +342 -0
- package/src/codemods/agentic/index.ts +221 -0
- package/src/codemods/agentic/prompts.test.ts +233 -0
- package/src/codemods/agentic/prompts.ts +166 -0
- package/src/codemods/agentic/resolve.test.ts +220 -0
- package/src/codemods/agentic/resolve.ts +165 -0
- package/src/codemods/agentic/runner.test.ts +146 -0
- package/src/codemods/agentic/runner.ts +103 -0
- package/src/codemods/agentic/types.ts +50 -0
- package/src/codemods/agentic/verify.test.ts +106 -0
- package/src/codemods/agentic/verify.ts +93 -0
- package/src/codemods/migrations/migrations.ts +14 -0
- package/src/codemods/migrations/scripts/004-eslint9-flat-config.ts +1 -3
- package/src/codemods/migrations/scripts/017-jest-30.test.ts +72 -0
- package/src/codemods/migrations/scripts/017-jest-30.ts +39 -0
- package/src/codemods/migrations/scripts/018-replace-eslint-webpack-plugin.test.ts +174 -0
- package/src/codemods/migrations/scripts/018-replace-eslint-webpack-plugin.ts +141 -0
- package/src/codemods/utils.agents.test.ts +140 -0
- package/src/codemods/utils.agents.ts +81 -0
- package/src/codemods/utils.test.ts +131 -1
- package/src/codemods/utils.ts +79 -8
- package/src/codemods/utils.workflows.test.ts +153 -0
- package/src/codemods/utils.workflows.ts +140 -0
- package/src/commands/add.command.ts +92 -9
- package/src/utils/utils.cli.ts +5 -0
- package/src/utils/utils.git.test.ts +57 -0
- package/src/utils/utils.git.ts +25 -0
- package/src/utils/utils.handlebars.ts +9 -0
- package/src/utils/utils.packageManager.test.ts +23 -0
- package/src/utils/utils.packageManager.ts +3 -2
- package/src/utils/utils.templates.ts +50 -25
- package/templates/app-sdk/.config/app-sdk/generate-kinds.mjs +1 -1
- package/templates/common/.config/webpack/webpack.config.ts +4 -5
- package/templates/common/_package.json +9 -8
- package/templates/docs/common/workflows/validate-docs.yml +52 -0
- package/templates/docs/panel/docs/AGENTS.md +107 -0
- package/templates/docs/panel/docs/CLAUDE.md +1 -0
- package/templates/docs/panel/docs/README.md +20 -0
- package/templates/docs/panel/docs/data-formats.md +27 -0
- package/templates/docs/panel/docs/examples.md +35 -0
- package/templates/docs/panel/docs/index.md +26 -0
- package/templates/docs/panel/docs/options.md +53 -0
- package/templates/docs/panel/docs/troubleshooting.md +35 -0
- package/templates/github/workflows/bundle-stats.yml +1 -1
- package/templates/github/workflows/ci.yml +7 -7
- package/templates/github/workflows/is-compatible.yml +3 -3
- package/templates/github/workflows/release.yml +1 -1
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import defaultAdditions from '../codemods/additions/additions.js';
|
|
1
|
+
import defaultAdditions, { hasPromptStep, isScriptAddition } from '../codemods/additions/additions.js';
|
|
2
|
+
import { buildAdditionTodosWarning, prepareAgenticAddition, runAgenticStep } from '../codemods/agentic/index.js';
|
|
3
|
+
import { buildDirectiveBlock, buildNextStepsLine, getPromptPath } from '../codemods/agentic/prompts.js';
|
|
4
|
+
import { buildVerifyWarning, runVerifyScripts } from '../codemods/agentic/verify.js';
|
|
5
|
+
import { Context } from '../codemods/context.js';
|
|
2
6
|
import { runCodemod } from '../codemods/runner.js';
|
|
3
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
getPackageManagerExecCmd,
|
|
9
|
+
getPackageManagerFromUserAgent,
|
|
10
|
+
getPackageManagerWithFallback,
|
|
11
|
+
} from '../utils/utils.packageManager.js';
|
|
4
12
|
import { performPreCodemodChecks } from '../utils/utils.checks.js';
|
|
13
|
+
import { findAdditionTodos, isGitDirectoryClean } from '../utils/utils.git.js';
|
|
5
14
|
import minimist from 'minimist';
|
|
6
15
|
import { output } from '../utils/utils.console.js';
|
|
7
16
|
|
|
17
|
+
const ADD_FEATURES_DOCS_URL = 'https://grafana.com/developers/plugin-tools/how-to-guides/add-features-to-a-plugin';
|
|
18
|
+
|
|
8
19
|
export const add = async (argv: minimist.ParsedArgs) => {
|
|
9
20
|
const subCommand = argv._[1];
|
|
10
21
|
|
|
@@ -22,18 +33,88 @@ export const add = async (argv: minimist.ParsedArgs) => {
|
|
|
22
33
|
throw new Error(`Unknown addition: ${subCommand}\n\nAvailable additions: ${additionsList.join(', ')}`);
|
|
23
34
|
}
|
|
24
35
|
|
|
25
|
-
// filter out minimist internal properties (_ and $0) before passing to codemod
|
|
26
|
-
const { _, $0, ...codemodOptions } = argv;
|
|
27
|
-
|
|
36
|
+
// filter out minimist internal properties (_ and $0) and the agent flag before passing to codemod
|
|
37
|
+
const { _, $0, agent: agentFlag, ...codemodOptions } = argv;
|
|
38
|
+
|
|
39
|
+
// resolved before any codemod work so a missing agent cannot leave a half-applied addition.
|
|
40
|
+
// undefined when this addition carries no agent instructions
|
|
41
|
+
const resolution = await prepareAgenticAddition(addition, agentFlag);
|
|
42
|
+
|
|
43
|
+
// captured before the codemod flushes, so we know whether --force let unrelated changes through
|
|
44
|
+
const treeWasDirty = resolution?.mode === 'enabled' ? !(await isGitDirectoryClean()) : false;
|
|
45
|
+
|
|
46
|
+
let context: Context | undefined;
|
|
47
|
+
if (isScriptAddition(addition)) {
|
|
48
|
+
context = await runCodemod(addition, codemodOptions);
|
|
49
|
+
}
|
|
28
50
|
|
|
29
|
-
const message = context
|
|
51
|
+
const message = context?.getMessage();
|
|
30
52
|
if (message) {
|
|
31
53
|
output[message.level]({ title: message.title, body: message.body });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!resolution || !hasPromptStep(addition)) {
|
|
57
|
+
if (!message) {
|
|
58
|
+
output.success({
|
|
59
|
+
title: `Successfully added ${addition.name} to your plugin.`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (resolution.mode === 'enabled') {
|
|
66
|
+
const result = await runAgenticStep({
|
|
67
|
+
addition,
|
|
68
|
+
agent: resolution.agent,
|
|
69
|
+
context,
|
|
70
|
+
basePath: process.cwd(),
|
|
71
|
+
treeWasDirty,
|
|
35
72
|
});
|
|
73
|
+
|
|
74
|
+
const summaryBody = result.kind === 'applied' ? [result.summary] : [];
|
|
75
|
+
const { packageManagerName } = getPackageManagerWithFallback();
|
|
76
|
+
const verifyWarning = buildVerifyWarning(
|
|
77
|
+
addition.name,
|
|
78
|
+
packageManagerName,
|
|
79
|
+
runVerifyScripts({ scripts: addition.verify ?? [], cwd: process.cwd(), packageManagerName })
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (verifyWarning) {
|
|
83
|
+
output.warning({ title: verifyWarning.title, body: [...summaryBody, ...verifyWarning.body] });
|
|
84
|
+
} else {
|
|
85
|
+
output.success({
|
|
86
|
+
title: `Successfully added ${addition.name} to your plugin.`,
|
|
87
|
+
body: summaryBody.length > 0 ? summaryBody : undefined,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const todosWarning = buildAdditionTodosWarning(
|
|
92
|
+
addition.name,
|
|
93
|
+
await findAdditionTodos(addition.name, process.cwd())
|
|
94
|
+
);
|
|
95
|
+
if (todosWarning) {
|
|
96
|
+
output.warning(todosWarning);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// opted out, or already running inside an agent: the agent step is handed onwards
|
|
102
|
+
const deferredAddition = {
|
|
103
|
+
name: addition.name,
|
|
104
|
+
description: addition.description,
|
|
105
|
+
instructionsPath: getPromptPath(addition.prompt),
|
|
106
|
+
verifyScripts: addition.verify ?? [],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (resolution.mode === 'inside-agent') {
|
|
110
|
+
output.logSingleLine(buildDirectiveBlock(deferredAddition));
|
|
111
|
+
return;
|
|
36
112
|
}
|
|
113
|
+
|
|
114
|
+
output.warning({
|
|
115
|
+
title: `${addition.name} includes AI-agent instructions that were not applied.`,
|
|
116
|
+
body: [buildNextStepsLine(deferredAddition)],
|
|
117
|
+
});
|
|
37
118
|
} catch (error) {
|
|
38
119
|
if (error instanceof Error) {
|
|
39
120
|
output.error({
|
|
@@ -56,6 +137,8 @@ async function showAdditionsHelp() {
|
|
|
56
137
|
'',
|
|
57
138
|
'Available additions:',
|
|
58
139
|
...output.bulletList(additionsList),
|
|
140
|
+
'',
|
|
141
|
+
`Learn more: ${output.formatUrl(ADD_FEATURES_DOCS_URL)}`,
|
|
59
142
|
],
|
|
60
143
|
});
|
|
61
144
|
}
|
package/src/utils/utils.cli.ts
CHANGED
|
@@ -13,10 +13,15 @@ export const argv = minimist(args, {
|
|
|
13
13
|
hasBackend: 'backend',
|
|
14
14
|
pluginName: 'plugin-name',
|
|
15
15
|
orgName: 'org-name',
|
|
16
|
+
docsPath: 'docs-path',
|
|
16
17
|
},
|
|
17
18
|
// tell minimist to parse boolean flags as true/false to prevent minimist from interpreting
|
|
18
19
|
// them as the command name if passed first. e.g. `create-plugin --force update`
|
|
19
20
|
boolean: ['force', 'experimental-updates'],
|
|
21
|
+
// --agent is tri-state: absent, bare (pick an agent), or a value (an agent id or a path to a binary).
|
|
22
|
+
// parsing it as a string keeps bare --agent as '' rather than true, so all three are distinguishable.
|
|
23
|
+
// --no-agent still arrives as false.
|
|
24
|
+
string: ['agent', 'docsPath'],
|
|
20
25
|
});
|
|
21
26
|
|
|
22
27
|
export const commandName = argv._[0] || 'generate';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { findAdditionTodos } from './utils.git.js';
|
|
3
|
+
|
|
4
|
+
vi.mock('node:child_process', () => ({ exec: vi.fn() }));
|
|
5
|
+
|
|
6
|
+
type ExecCallback = (error: Error | null, result?: { stdout: string; stderr: string }) => void;
|
|
7
|
+
|
|
8
|
+
function mockExec(outcome: { stdout: string } | { error: Error }) {
|
|
9
|
+
vi.mocked(exec).mockImplementation(((_command: string, _options: unknown, callback: ExecCallback) => {
|
|
10
|
+
if ('error' in outcome) {
|
|
11
|
+
callback(outcome.error);
|
|
12
|
+
} else {
|
|
13
|
+
callback(null, { stdout: outcome.stdout, stderr: '' });
|
|
14
|
+
}
|
|
15
|
+
}) as unknown as typeof exec);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('findAdditionTodos', () => {
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
vi.mocked(exec).mockReset();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should search tracked and untracked files for the addition marker', async () => {
|
|
24
|
+
mockExec({ stdout: '' });
|
|
25
|
+
|
|
26
|
+
await findAdditionTodos('rspack', '/plugin');
|
|
27
|
+
|
|
28
|
+
expect(exec).toHaveBeenCalledWith(
|
|
29
|
+
'git grep -n --untracked -F "TODO(rspack)"',
|
|
30
|
+
{ cwd: '/plugin' },
|
|
31
|
+
expect.any(Function)
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return the file, line and text of each marker', async () => {
|
|
36
|
+
mockExec({
|
|
37
|
+
stdout: [
|
|
38
|
+
'rspack.config.ts:12: // TODO(rspack): CorsWorkerPlugin needs a RuntimeModule port',
|
|
39
|
+
'build/utils.ts:3:// TODO(rspack): swc plugin ABI: mismatch',
|
|
40
|
+
'',
|
|
41
|
+
].join('\n'),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const todos = await findAdditionTodos('rspack', '/plugin');
|
|
45
|
+
|
|
46
|
+
expect(todos).toEqual([
|
|
47
|
+
{ file: 'rspack.config.ts', line: 12, text: '// TODO(rspack): CorsWorkerPlugin needs a RuntimeModule port' },
|
|
48
|
+
{ file: 'build/utils.ts', line: 3, text: '// TODO(rspack): swc plugin ABI: mismatch' },
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return no markers when git grep finds nothing or fails', async () => {
|
|
53
|
+
mockExec({ error: Object.assign(new Error('Command failed'), { code: 1 }) });
|
|
54
|
+
|
|
55
|
+
await expect(findAdditionTodos('rspack', '/plugin')).resolves.toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
});
|
package/src/utils/utils.git.ts
CHANGED
|
@@ -41,3 +41,28 @@ export async function gitCommitNoVerify(commitMsg: string) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
export interface AdditionTodo {
|
|
46
|
+
file: string;
|
|
47
|
+
line: number;
|
|
48
|
+
text: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const GIT_GREP_LINE_PATTERN = /^(.+?):(\d+):(.*)$/;
|
|
52
|
+
|
|
53
|
+
// Finds the `TODO(<additionName>)` markers an addition's instructions ask the agent to leave for
|
|
54
|
+
// anything it could not apply. --untracked covers files the addition created
|
|
55
|
+
export async function findAdditionTodos(additionName: string, cwd: string): Promise<AdditionTodo[]> {
|
|
56
|
+
try {
|
|
57
|
+
const { stdout } = await exec(`git grep -n --untracked -F "TODO(${additionName})"`, { cwd });
|
|
58
|
+
|
|
59
|
+
return stdout
|
|
60
|
+
.split('\n')
|
|
61
|
+
.map((line) => GIT_GREP_LINE_PATTERN.exec(line))
|
|
62
|
+
.filter((match): match is RegExpExecArray => match !== null)
|
|
63
|
+
.map(([, file, line, text]) => ({ file, line: Number(line), text: text.trim() }));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// git grep exits 1 when nothing matches. that, and git being unavailable, both mean nothing to report
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -41,6 +41,14 @@ export const kebabToPascalKebab = (str: string) => {
|
|
|
41
41
|
.join('-');
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
// for text inside a double-quoted YAML scalar, e.g. `title: "{{{yamlEscape pluginName}}}"`
|
|
45
|
+
export const yamlEscape = (str: string) => {
|
|
46
|
+
if (typeof str !== 'string') {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
return str.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
|
50
|
+
};
|
|
51
|
+
|
|
44
52
|
// Register our helpers and partials with handlebars.
|
|
45
53
|
registerHandlebarsHelpers();
|
|
46
54
|
registerHandlebarsPartials();
|
|
@@ -63,6 +71,7 @@ function registerHandlebarsHelpers() {
|
|
|
63
71
|
properCase: pascalCase,
|
|
64
72
|
pascalCase: pascalCase,
|
|
65
73
|
if_eq: ifEq,
|
|
74
|
+
yamlEscape: yamlEscape,
|
|
66
75
|
};
|
|
67
76
|
|
|
68
77
|
Object.keys(helpers).forEach((helperName) =>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { getPackageManagerWithFallback } from './utils.packageManager.js';
|
|
3
|
+
import { getPackageJson } from './utils.packagejson.js';
|
|
4
|
+
|
|
5
|
+
vi.mock(import('./utils.packagejson.js'), () => ({
|
|
6
|
+
getPackageJson: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock(import('@libs/find-up'), () => ({
|
|
10
|
+
findUpSync: () => undefined,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('getPackageManagerWithFallback', () => {
|
|
14
|
+
it('reads the packageManager field', () => {
|
|
15
|
+
vi.mocked(getPackageJson).mockReturnValue({ packageManager: 'pnpm@10.4.1' } as never);
|
|
16
|
+
expect(getPackageManagerWithFallback()).toEqual({ packageManagerName: 'pnpm', packageManagerVersion: '10.4.1' });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('ignores a packageManager field without a version', () => {
|
|
20
|
+
vi.mocked(getPackageJson).mockReturnValue({ packageManager: 'yarn' } as never);
|
|
21
|
+
expect(getPackageManagerWithFallback()).toEqual({ packageManagerName: 'npm', packageManagerVersion: '10.9.0' });
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -170,8 +170,9 @@ function getPackageManagerFromLockFile(): PackageManager | undefined {
|
|
|
170
170
|
|
|
171
171
|
function getPackageManagerFromPackageJson(): PackageManager | undefined {
|
|
172
172
|
const packageJson = getPackageJson();
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
// without a version there's nothing to pick the install command by, so the lockfile decides
|
|
174
|
+
const [packageManagerName, packageManagerVersion] = packageJson?.packageManager?.split('@') ?? [];
|
|
175
|
+
if (packageManagerName && packageManagerVersion) {
|
|
175
176
|
return { packageManagerName, packageManagerVersion };
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
getPackageManagerFromUserAgent,
|
|
12
12
|
getPackageManagerInstallCmd,
|
|
13
13
|
getPackageManagerWithFallback,
|
|
14
|
+
type PackageManager,
|
|
14
15
|
} from './utils.packageManager.js';
|
|
15
16
|
import { normalizeId, renderHandlebarsTemplate } from './utils.handlebars.js';
|
|
16
17
|
|
|
17
18
|
import { CURRENT_APP_VERSION } from './utils.version.js';
|
|
18
19
|
import { debug } from './utils.cli.js';
|
|
19
20
|
import fs from 'node:fs';
|
|
20
|
-
import { getConfig } from './utils.config.js';
|
|
21
|
+
import { type FeatureFlags, getConfig } from './utils.config.js';
|
|
21
22
|
import { getExportFileName } from '../utils/utils.files.js';
|
|
22
23
|
import { getPluginJson } from './utils.plugin.js';
|
|
23
24
|
import { glob } from 'glob';
|
|
@@ -92,12 +93,53 @@ export function renderTemplateFromFile(templateFile: string, data?: any) {
|
|
|
92
93
|
return renderHandlebarsTemplate(fs.readFileSync(templateFile).toString(), data);
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Builds the template data for an existing plugin from its plugin.json, package manager and feature flags.
|
|
98
|
+
* Pure, so codemods can pass in values read from their Context instead of from disk.
|
|
99
|
+
*/
|
|
100
|
+
export function buildTemplateData({
|
|
101
|
+
pluginJson,
|
|
102
|
+
packageManager,
|
|
103
|
+
features,
|
|
104
|
+
}: {
|
|
105
|
+
pluginJson: Record<string, any>;
|
|
106
|
+
packageManager: PackageManager;
|
|
107
|
+
features: FeatureFlags;
|
|
108
|
+
}): TemplateData {
|
|
109
|
+
const { packageManagerName, packageManagerVersion } = packageManager;
|
|
110
|
+
return {
|
|
111
|
+
...EXTRA_TEMPLATE_VARIABLES,
|
|
112
|
+
pluginId: pluginJson.id,
|
|
113
|
+
pluginName: pluginJson.name,
|
|
114
|
+
hasBackend: pluginJson.backend,
|
|
115
|
+
orgName: pluginJson.info?.author?.name,
|
|
116
|
+
pluginType: pluginJson.type,
|
|
117
|
+
packageManagerName,
|
|
118
|
+
packageManagerVersion,
|
|
119
|
+
packageManagerInstallCmd: getPackageManagerInstallCmd(packageManagerName, packageManagerVersion),
|
|
120
|
+
isAppType: isAppType(pluginJson.type),
|
|
121
|
+
isNPM: isNPM(packageManagerName),
|
|
122
|
+
version: CURRENT_APP_VERSION,
|
|
123
|
+
bundleGrafanaUI: features.bundleGrafanaUI ?? DEFAULT_FEATURE_FLAGS.bundleGrafanaUI,
|
|
124
|
+
scenesVersion: '^8.17.0',
|
|
125
|
+
pluginExecutable: pluginJson.executable,
|
|
126
|
+
useExperimentalRspack: Boolean(features.useExperimentalRspack),
|
|
127
|
+
frontendBundler: features.useExperimentalRspack ? 'rspack' : 'webpack',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isAppType(pluginType: string) {
|
|
132
|
+
return pluginType === PLUGIN_TYPES.app || pluginType === PLUGIN_TYPES.scenes;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isNPM(packageManagerName: string) {
|
|
136
|
+
return packageManagerName === 'npm';
|
|
137
|
+
}
|
|
138
|
+
|
|
95
139
|
export function getTemplateData(cliArgs?: GenerateCliArgs): TemplateData {
|
|
96
140
|
const { features } = getConfig();
|
|
97
141
|
const currentVersion = CURRENT_APP_VERSION;
|
|
98
142
|
const bundleGrafanaUI = features.bundleGrafanaUI ?? DEFAULT_FEATURE_FLAGS.bundleGrafanaUI;
|
|
99
|
-
const isAppType = (pluginType: string) => pluginType === PLUGIN_TYPES.app || pluginType === PLUGIN_TYPES.scenes;
|
|
100
|
-
const isNPM = (packageManagerName: string) => packageManagerName === 'npm';
|
|
101
143
|
const frontendBundler = features.useExperimentalRspack ? 'rspack' : 'webpack';
|
|
102
144
|
|
|
103
145
|
let templateData: TemplateData;
|
|
@@ -128,28 +170,11 @@ export function getTemplateData(cliArgs?: GenerateCliArgs): TemplateData {
|
|
|
128
170
|
// Updating or migrating a plugin
|
|
129
171
|
// (plugin.json and package.json files are only present if it's an existing plugin)
|
|
130
172
|
} else {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
pluginId: pluginJson.id,
|
|
137
|
-
pluginName: pluginJson.name,
|
|
138
|
-
hasBackend: pluginJson.backend,
|
|
139
|
-
orgName: pluginJson.info.author.name,
|
|
140
|
-
pluginType: pluginJson.type,
|
|
141
|
-
packageManagerName: packageManagerName,
|
|
142
|
-
packageManagerVersion: packageManagerVersion,
|
|
143
|
-
packageManagerInstallCmd: getPackageManagerInstallCmd(packageManagerName, packageManagerVersion),
|
|
144
|
-
isAppType: isAppType(pluginJson.type),
|
|
145
|
-
isNPM: isNPM(packageManagerName),
|
|
146
|
-
version: currentVersion,
|
|
147
|
-
bundleGrafanaUI,
|
|
148
|
-
scenesVersion: '^8.17.0',
|
|
149
|
-
pluginExecutable: pluginJson.executable,
|
|
150
|
-
useExperimentalRspack: Boolean(features.useExperimentalRspack),
|
|
151
|
-
frontendBundler,
|
|
152
|
-
};
|
|
173
|
+
templateData = buildTemplateData({
|
|
174
|
+
pluginJson: getPluginJson(),
|
|
175
|
+
packageManager: getPackageManagerWithFallback(),
|
|
176
|
+
features,
|
|
177
|
+
});
|
|
153
178
|
}
|
|
154
179
|
|
|
155
180
|
templatesDebugger('\nTemplate data:\n' + JSON.stringify(templateData, null, 2));
|
|
@@ -21,7 +21,7 @@ import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs
|
|
|
21
21
|
import { join, resolve } from 'node:path';
|
|
22
22
|
import { arch, platform, tmpdir } from 'node:os';
|
|
23
23
|
|
|
24
|
-
const VERSION = 'v0.60.
|
|
24
|
+
const VERSION = 'v0.60.16';
|
|
25
25
|
const REPO = 'grafana/grafana-app-sdk';
|
|
26
26
|
const BIN = 'grafana-app-sdk';
|
|
27
27
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
|
9
|
-
import
|
|
9
|
+
import DiagnosticsPlugin from 'diagnostics-webpack-plugin';
|
|
10
10
|
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin';
|
|
@@ -208,10 +208,9 @@ const config = async (env: Env): Promise<Configuration> => {
|
|
|
208
208
|
},
|
|
209
209
|
typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') },
|
|
210
210
|
}),
|
|
211
|
-
new
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
failOnError: Boolean(env.production),
|
|
211
|
+
new DiagnosticsPlugin({
|
|
212
|
+
lintOnStart: false, // don't lint on start, only lint changed files
|
|
213
|
+
checks: [{ use: 'eslint', extensions: ['ts', 'tsx'] }],
|
|
215
214
|
}),
|
|
216
215
|
] : []),
|
|
217
216
|
],
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@grafana/eslint-config": "^10.0.0",
|
|
20
|
-
"@grafana/plugin-e2e": "^3.
|
|
20
|
+
"@grafana/plugin-e2e": "^3.15.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}}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@swc/jest": "^0.2.0",
|
|
30
30
|
"@testing-library/jest-dom": "^6.6.0",
|
|
31
31
|
"@testing-library/react": "^16.3.0",
|
|
32
|
-
"@types/jest": "^
|
|
32
|
+
"@types/jest": "^30.0.0",
|
|
33
33
|
"@types/node": "^24.0.0",
|
|
34
34
|
"@types/react": "^19.0.0",
|
|
35
35
|
"@types/react-dom": "^19.0.0",{{#if useExperimentalRspack}}
|
|
@@ -37,21 +37,22 @@
|
|
|
37
37
|
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
38
38
|
"@typescript-eslint/parser": "^8.38.0",{{#unless useExperimentalRspack}}
|
|
39
39
|
"copy-webpack-plugin": "^14.0.0",{{/unless}}
|
|
40
|
-
"css-loader": "^7.1.0",
|
|
40
|
+
"css-loader": "^7.1.0",{{#unless useExperimentalRspack}}
|
|
41
|
+
"diagnostics-webpack-plugin": "^1.0.1",{{/unless}}
|
|
41
42
|
"eslint": "^9.32.0",
|
|
42
43
|
"eslint-config-prettier": "^10.1.0",
|
|
43
44
|
"eslint-plugin-jsdoc": "^52.0.0",
|
|
44
45
|
"eslint-plugin-react": "^7.37.0",
|
|
45
|
-
"eslint-plugin-react-hooks": "^7.1.0",
|
|
46
|
-
"eslint-webpack-plugin": "^5.0.0",{{#unless useExperimentalRspack}}
|
|
46
|
+
"eslint-plugin-react-hooks": "^7.1.0",{{#if useExperimentalRspack}}
|
|
47
|
+
"eslint-webpack-plugin": "^5.0.0",{{/if}}{{#unless useExperimentalRspack}}
|
|
47
48
|
"fork-ts-checker-webpack-plugin": "^9.1.0",{{/unless}}
|
|
48
49
|
"glob": "^11.1.0",
|
|
49
50
|
"identity-obj-proxy": "^3.0.0",
|
|
50
51
|
"imports-loader": "^5.0.0",
|
|
51
52
|
"intersection-observer": "^0.12.2",
|
|
52
|
-
"jest": "^
|
|
53
|
+
"jest": "^30.5.0",
|
|
53
54
|
"jest-canvas-mock": "^2.5.2",
|
|
54
|
-
"jest-environment-jsdom": "^
|
|
55
|
+
"jest-environment-jsdom": "^30.5.0",
|
|
55
56
|
"prettier": "^3.6.0",
|
|
56
57
|
"replace-in-file-webpack-plugin": "^1.0.0",{{#if useExperimentalRspack}}
|
|
57
58
|
"rspack-plugin-virtual-module": "^1.0.0",{{/if}}
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"ts-node": "^10.9.0",{{#if useExperimentalRspack}}
|
|
65
66
|
"ts-checker-rspack-plugin": "^1.2.0",{{/if}}
|
|
66
67
|
"typescript": "5.9.2",
|
|
67
|
-
"webpack": "^5.
|
|
68
|
+
"webpack": "^5.106.0"{{#unless useExperimentalRspack}},
|
|
68
69
|
"webpack-cli": "^6.0.0",
|
|
69
70
|
"webpack-livereload-plugin": "^3.0.2",
|
|
70
71
|
"webpack-subresource-integrity": "^5.1.0",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Validate documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
paths:
|
|
9
|
+
- '{{docsPath}}/**'
|
|
10
|
+
- 'src/plugin.json'
|
|
11
|
+
- 'package.json'
|
|
12
|
+
{{#if lockFile}}
|
|
13
|
+
- '{{lockFile}}'
|
|
14
|
+
{{/if}}
|
|
15
|
+
pull_request:
|
|
16
|
+
branches:
|
|
17
|
+
- main
|
|
18
|
+
- master
|
|
19
|
+
paths:
|
|
20
|
+
- '{{docsPath}}/**'
|
|
21
|
+
- 'src/plugin.json'
|
|
22
|
+
- 'package.json'
|
|
23
|
+
{{#if lockFile}}
|
|
24
|
+
- '{{lockFile}}'
|
|
25
|
+
{{/if}}
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
validate:
|
|
29
|
+
name: Validate plugin docs
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
permissions:
|
|
32
|
+
contents: read
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v7
|
|
35
|
+
with:
|
|
36
|
+
persist-credentials: false
|
|
37
|
+
{{#if_eq packageManagerName "pnpm"}}
|
|
38
|
+
# pnpm action uses the packageManager field in package.json to
|
|
39
|
+
# understand which version to install.
|
|
40
|
+
- uses: pnpm/action-setup@v6
|
|
41
|
+
{{/if_eq}}
|
|
42
|
+
- name: Setup Node.js environment
|
|
43
|
+
uses: actions/setup-node@v7
|
|
44
|
+
with:
|
|
45
|
+
node-version: '22'
|
|
46
|
+
cache: '{{packageManagerName}}'
|
|
47
|
+
|
|
48
|
+
- name: Install dependencies
|
|
49
|
+
run: {{packageManagerInstallCmd}}
|
|
50
|
+
|
|
51
|
+
- name: Validate plugin documentation
|
|
52
|
+
run: {{packageManagerName}} run docs:validate
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Plugin docs
|
|
2
|
+
|
|
3
|
+
This folder is the documentation for **{{{pluginName}}}**, published at
|
|
4
|
+
`grafana.com/grafana/plugins/{{pluginId}}/docs/`.
|
|
5
|
+
|
|
6
|
+
## Guides
|
|
7
|
+
|
|
8
|
+
Read these before you write or change any page. This docs format is new and your training
|
|
9
|
+
data does not cover it, so do not rely on what you know about other docs tools.
|
|
10
|
+
|
|
11
|
+
Run `{{packageManagerName}} run docs:validate` and open the guide on its last line, `Docs guides:`. It's
|
|
12
|
+
the guides' `index.md`, which says which guide to read for what, and the other guides sit next to it.
|
|
13
|
+
The guides match the version of the validator this plugin uses. The line is a local path, usually
|
|
14
|
+
`node_modules/@grafana/plugin-docs-cli/docs/index.md`, or a URL when the package isn't on disk, such as
|
|
15
|
+
with Yarn Plug'n'Play.
|
|
16
|
+
|
|
17
|
+
The guides name scripts such as `docs:validate`. This plugin uses **{{packageManagerName}}**, so run them
|
|
18
|
+
with `{{packageManagerName}} run`, as in the commands below.
|
|
19
|
+
|
|
20
|
+
## Pages and their source
|
|
21
|
+
|
|
22
|
+
When you change code, update the page that documents it:
|
|
23
|
+
|
|
24
|
+
| Page | Source of truth |
|
|
25
|
+
| -------------------- | -------------------------------------------------------------------------------------- |
|
|
26
|
+
| `index.md` | Nothing - it is a curated router. Update it when pages are added or removed. |
|
|
27
|
+
| `options.md` | `setPanelOptions` and `useFieldConfig` in `src/module.ts` |
|
|
28
|
+
| `data-formats.md` | The panel component's data handling, plus any field pickers in `src/module.ts` |
|
|
29
|
+
| `examples.md` | `provisioning/dashboards/*.json` if present, otherwise the panel's own option defaults |
|
|
30
|
+
| `troubleshooting.md` | Error and empty-state messages in source, known and reported issues. Never guess. |
|
|
31
|
+
|
|
32
|
+
Never guess failures in `troubleshooting.md`. Each symptom must come from an error, warning or empty-state
|
|
33
|
+
message in source, a known issue in the README or other docs or a reported issue.
|
|
34
|
+
|
|
35
|
+
## Optional pages
|
|
36
|
+
|
|
37
|
+
Add one of these pages when the plugin has the matching feature. Use the filename given here, give the page
|
|
38
|
+
a `sidebar_position` that puts it where it belongs and link it from `index.md`.
|
|
39
|
+
|
|
40
|
+
| Trigger | Page | Scope |
|
|
41
|
+
| ---------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------ |
|
|
42
|
+
| `setPresetsSupplier(...)` in source | `presets.md` | The presets the panel ships, when each applies, screenshots. |
|
|
43
|
+
| RBAC role declarations in `src/plugin.json` | `permissions.md` | What each role grants, default assignments. |
|
|
44
|
+
| Substantial accessibility considerations (keyboard shortcuts, screen reader notes) | `accessibility.md` | Keyboard interaction, ARIA, contrast, motion preferences. |
|
|
45
|
+
|
|
46
|
+
## Section briefs
|
|
47
|
+
|
|
48
|
+
Each section of a scaffolded page has a brief telling you what belongs there. A brief is an HTML comment
|
|
49
|
+
that starts with `section-brief`. Search the docs folder for `section-brief` to find what is left to write:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
## Options
|
|
53
|
+
|
|
54
|
+
<!-- section-brief
|
|
55
|
+
Fill this in: Describe every option the plugin registers, one row per option.
|
|
56
|
+
|
|
57
|
+
If the plugin has no options, remove this section entirely.
|
|
58
|
+
-->
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Some sections also have fixed text, with a brief that builds on it:
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
## Basic example
|
|
65
|
+
|
|
66
|
+
A panel excerpt in dashboard JSON has this shape:
|
|
67
|
+
|
|
68
|
+
<!-- section-brief
|
|
69
|
+
Fill this in: Complete the excerpt above with the options the example needs.
|
|
70
|
+
-->
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Text outside a brief is written for every panel and is part of the scaffold. Keep it, and change it
|
|
74
|
+
only where it's wrong for this plugin, such as a menu path or a step that differs.
|
|
75
|
+
|
|
76
|
+
Everything inside a brief is the instruction. A brief covers the section it sits under, unless it says
|
|
77
|
+
otherwise: it may tell you to extend the fixed text above it, to replace it with several sections or to
|
|
78
|
+
remove the page. When a brief says to remove a section, remove its heading and fixed text too.
|
|
79
|
+
|
|
80
|
+
To fill a section:
|
|
81
|
+
|
|
82
|
+
1. Read the brief.
|
|
83
|
+
2. Read the source the page documents, from the table above, then write the section.
|
|
84
|
+
3. Delete the whole comment, from `<!-- section-brief` to `-->`.
|
|
85
|
+
|
|
86
|
+
Briefs are not shown in the preview or the catalog, so a section you haven't written looks like a bare
|
|
87
|
+
heading. `docs:validate` lists every section that still has only its brief.
|
|
88
|
+
|
|
89
|
+
## Validate
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
{{packageManagerName}} run docs:validate
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Run `docs:validate` after every page you write or change and fix what it reports. A docs task is done when
|
|
96
|
+
it reports no warnings and no suggestions about section briefs. In particular, an `empty-section` or
|
|
97
|
+
`unfilled-section-brief` suggestion means a section is still unwritten or a brief is still in place. Neither
|
|
98
|
+
fails validation, so a passing exit code alone does not mean the docs are done.
|
|
99
|
+
|
|
100
|
+
## Preview
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
{{packageManagerName}} run docs:serve # local preview at http://localhost:3001, reloads on save
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`docs:serve` keeps running until you stop it. Start it in the background if you need to look at the
|
|
107
|
+
rendered pages, and stop it when you are done.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|