@grafana/create-plugin 1.11.0-canary.365.32f6dfa.0 → 1.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 +24 -0
- package/README.md +1 -1
- package/package.json +2 -2
- package/templates/_partials/distributing-your-plugin.md +1 -1
- package/templates/app/README.md +1 -1
- package/templates/common/.config/webpack/utils.ts +19 -0
- package/templates/common/.config/webpack/webpack.config.ts +180 -168
- package/templates/datasource/README.md +1 -1
- package/templates/panel/README.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
# v1.12.0 (Fri Aug 18 2023)
|
|
2
|
+
|
|
3
|
+
#### 🚀 Enhancement
|
|
4
|
+
|
|
5
|
+
- create-plugin: Add link to documentation about troubleshooting create-plugin on windows [#365](https://github.com/grafana/plugin-tools/pull/365) ([@Ukochka](https://github.com/Ukochka))
|
|
6
|
+
|
|
7
|
+
#### Authors: 1
|
|
8
|
+
|
|
9
|
+
- Yulia Shanyrova ([@Ukochka](https://github.com/Ukochka))
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# v1.11.0 (Wed Aug 16 2023)
|
|
14
|
+
|
|
15
|
+
#### 🚀 Enhancement
|
|
16
|
+
|
|
17
|
+
- create-plugin: Enable webpack watchOption -> poll if WSL is detected [#356](https://github.com/grafana/plugin-tools/pull/356) ([@Ukochka](https://github.com/Ukochka))
|
|
18
|
+
|
|
19
|
+
#### Authors: 1
|
|
20
|
+
|
|
21
|
+
- Yulia Shanyrova ([@Ukochka](https://github.com/Ukochka))
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
1
25
|
# v1.10.1 (Mon Aug 14 2023)
|
|
2
26
|
|
|
3
27
|
#### 🐛 Bug Fix
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Create Grafana plugins with ease.
|
|
|
20
20
|
|
|
21
21
|
- [Plugin Tools docs](https://grafana.github.io/plugin-tools/)
|
|
22
22
|
- [Plugin developer docs](https://grafana.com/docs/grafana/latest/developers/plugins/)
|
|
23
|
-
- [Plugin migration guide](https://grafana.com/docs/grafana/latest/developers/plugins/migration-guide
|
|
23
|
+
- [Plugin migration guide](https://grafana.com/docs/grafana/latest/developers/plugins/migration-guide)
|
|
24
24
|
|
|
25
25
|
**`@grafana/create-plugin`** works on macOS, Linux and Windows Subsystem for Linux (WSL).<br />
|
|
26
26
|
If something doesn't work, please [file an issue](https://github.com/grafana/plugin-tools/issues/new).<br />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/create-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"directory": "packages/create-plugin",
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">=16"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "05bf90d24a46c8a5458aeeebed9327b5add8f35b"
|
|
70
70
|
}
|
|
@@ -14,7 +14,7 @@ Before signing a plugin for the first time please consult the Grafana [plugin si
|
|
|
14
14
|
|
|
15
15
|
1. Create a [Grafana Cloud account](https://grafana.com/signup).
|
|
16
16
|
2. Make sure that the first part of the plugin ID matches the slug of your Grafana Cloud account.
|
|
17
|
-
- _You can find the plugin ID in the plugin.json file inside your plugin directory. For example, if your account slug is `acmecorp`, you need to prefix the plugin ID with `acmecorp-`._
|
|
17
|
+
- _You can find the plugin ID in the `plugin.json` file inside your plugin directory. For example, if your account slug is `acmecorp`, you need to prefix the plugin ID with `acmecorp-`._
|
|
18
18
|
3. Create a Grafana Cloud API key with the `PluginPublisher` role.
|
|
19
19
|
4. Keep a record of this API key as it will be required for signing a plugin
|
|
20
20
|
|
package/templates/app/README.md
CHANGED
|
@@ -18,5 +18,5 @@ App plugins can let you create a custom out-of-the-box monitoring experience by
|
|
|
18
18
|
Below you can find source code for existing app plugins and other related documentation.
|
|
19
19
|
|
|
20
20
|
- [Basic app plugin example](https://github.com/grafana/grafana-plugin-examples/tree/master/examples/app-basic#readme)
|
|
21
|
-
- [
|
|
21
|
+
- [`plugin.json` documentation](https://grafana.com/developers/plugin-tools/reference-plugin-json)
|
|
22
22
|
- [How to sign a plugin?](https://grafana.com/docs/grafana/latest/developers/plugins/sign-a-plugin/)
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import process from 'process';
|
|
3
|
+
import os from 'os';
|
|
2
4
|
import path from 'path';
|
|
3
5
|
import util from 'util';
|
|
4
6
|
import { glob } from 'glob';
|
|
5
7
|
import { SOURCE_DIR } from './constants';
|
|
6
8
|
|
|
9
|
+
|
|
10
|
+
export function isWSL() {
|
|
11
|
+
if (process.platform !== 'linux') {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (os.release().toLowerCase().includes('microsoft')) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
export function getPackageJson() {
|
|
8
27
|
return require(path.resolve(process.cwd(), 'package.json'));
|
|
9
28
|
}
|
|
@@ -13,189 +13,201 @@ import path from 'path';
|
|
|
13
13
|
import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin';
|
|
14
14
|
import { Configuration } from 'webpack';
|
|
15
15
|
|
|
16
|
-
import { getPackageJson, getPluginJson, hasReadme, getEntries } from './utils';
|
|
16
|
+
import { getPackageJson, getPluginJson, hasReadme, getEntries, isWSL } from './utils';
|
|
17
17
|
import { SOURCE_DIR, DIST_DIR } from './constants';
|
|
18
18
|
|
|
19
19
|
const pluginJson = getPluginJson();
|
|
20
20
|
|
|
21
|
-
const config = async (env): Promise<Configuration> =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
context: path.join(process.cwd(), SOURCE_DIR),
|
|
30
|
-
|
|
31
|
-
devtool: env.production ? 'source-map' : 'eval-source-map',
|
|
32
|
-
|
|
33
|
-
entry: await getEntries(),
|
|
34
|
-
|
|
35
|
-
externals: [
|
|
36
|
-
'lodash',
|
|
37
|
-
'jquery',
|
|
38
|
-
'moment',
|
|
39
|
-
'slate',
|
|
40
|
-
'emotion',
|
|
41
|
-
'@emotion/react',
|
|
42
|
-
'@emotion/css',
|
|
43
|
-
'prismjs',
|
|
44
|
-
'slate-plain-serializer',
|
|
45
|
-
'@grafana/slate-react',
|
|
46
|
-
'react',
|
|
47
|
-
'react-dom',
|
|
48
|
-
'react-redux',
|
|
49
|
-
'redux',
|
|
50
|
-
'rxjs',
|
|
51
|
-
'react-router',
|
|
52
|
-
'react-router-dom',
|
|
53
|
-
'd3',
|
|
54
|
-
'angular',
|
|
55
|
-
'@grafana/ui',
|
|
56
|
-
'@grafana/runtime',
|
|
57
|
-
'@grafana/data',
|
|
58
|
-
|
|
59
|
-
// Mark legacy SDK imports as external if their name starts with the "grafana/" prefix
|
|
60
|
-
({ request }, callback) => {
|
|
61
|
-
const prefix = 'grafana/';
|
|
62
|
-
const hasPrefix = (request) => request.indexOf(prefix) === 0;
|
|
63
|
-
const stripPrefix = (request) => request.substr(prefix.length);
|
|
64
|
-
|
|
65
|
-
if (hasPrefix(request)) {
|
|
66
|
-
return callback(undefined, stripPrefix(request));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
callback();
|
|
21
|
+
const config = async (env): Promise<Configuration> => {
|
|
22
|
+
const baseConfig: Configuration = {
|
|
23
|
+
cache: {
|
|
24
|
+
type: 'filesystem',
|
|
25
|
+
buildDependencies: {
|
|
26
|
+
config: [__filename],
|
|
27
|
+
},
|
|
70
28
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
29
|
+
|
|
30
|
+
context: path.join(process.cwd(), SOURCE_DIR),
|
|
31
|
+
|
|
32
|
+
devtool: env.production ? 'source-map' : 'eval-source-map',
|
|
33
|
+
|
|
34
|
+
entry: await getEntries(),
|
|
35
|
+
|
|
36
|
+
externals: [
|
|
37
|
+
'lodash',
|
|
38
|
+
'jquery',
|
|
39
|
+
'moment',
|
|
40
|
+
'slate',
|
|
41
|
+
'emotion',
|
|
42
|
+
'@emotion/react',
|
|
43
|
+
'@emotion/css',
|
|
44
|
+
'prismjs',
|
|
45
|
+
'slate-plain-serializer',
|
|
46
|
+
'@grafana/slate-react',
|
|
47
|
+
'react',
|
|
48
|
+
'react-dom',
|
|
49
|
+
'react-redux',
|
|
50
|
+
'redux',
|
|
51
|
+
'rxjs',
|
|
52
|
+
'react-router',
|
|
53
|
+
'react-router-dom',
|
|
54
|
+
'd3',
|
|
55
|
+
'angular',
|
|
56
|
+
'@grafana/ui',
|
|
57
|
+
'@grafana/runtime',
|
|
58
|
+
'@grafana/data',
|
|
59
|
+
|
|
60
|
+
// Mark legacy SDK imports as external if their name starts with the "grafana/" prefix
|
|
61
|
+
({ request }, callback) => {
|
|
62
|
+
const prefix = 'grafana/';
|
|
63
|
+
const hasPrefix = (request) => request.indexOf(prefix) === 0;
|
|
64
|
+
const stripPrefix = (request) => request.substr(prefix.length);
|
|
65
|
+
|
|
66
|
+
if (hasPrefix(request)) {
|
|
67
|
+
return callback(undefined, stripPrefix(request));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
callback();
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
|
|
74
|
+
mode: env.production ? 'production' : 'development',
|
|
75
|
+
|
|
76
|
+
module: {
|
|
77
|
+
rules: [
|
|
78
|
+
{
|
|
79
|
+
exclude: /(node_modules)/,
|
|
80
|
+
test: /\.[tj]sx?$/,
|
|
81
|
+
use: {
|
|
82
|
+
loader: 'swc-loader',
|
|
83
|
+
options: {
|
|
84
|
+
jsc: {
|
|
85
|
+
baseUrl: './src',
|
|
86
|
+
target: 'es2015',
|
|
87
|
+
loose: false,
|
|
88
|
+
parser: {
|
|
89
|
+
syntax: 'typescript',
|
|
90
|
+
tsx: true,
|
|
91
|
+
decorators: false,
|
|
92
|
+
dynamicImport: true,
|
|
93
|
+
},
|
|
92
94
|
},
|
|
93
95
|
},
|
|
94
96
|
},
|
|
95
97
|
},
|
|
98
|
+
{
|
|
99
|
+
test: /\.css$/,
|
|
100
|
+
use: ["style-loader", "css-loader"]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
test: /\.s[ac]ss$/,
|
|
104
|
+
use: ['style-loader', 'css-loader', 'sass-loader'],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
test: /\.(png|jpe?g|gif|svg)$/,
|
|
108
|
+
type: 'asset/resource',
|
|
109
|
+
generator: {
|
|
110
|
+
// Keep publicPath relative for host.com/grafana/ deployments
|
|
111
|
+
publicPath: `public/plugins/${pluginJson.id}/img/`,
|
|
112
|
+
outputPath: 'img/',
|
|
113
|
+
filename: Boolean(env.production) ? '[hash][ext]' : '[name][ext]',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
test: /\.(woff|woff2|eot|ttf|otf)(\?v=\d+\.\d+\.\d+)?$/,
|
|
118
|
+
type: 'asset/resource',
|
|
119
|
+
generator: {
|
|
120
|
+
// Keep publicPath relative for host.com/grafana/ deployments
|
|
121
|
+
publicPath: `public/plugins/${pluginJson.id}/fonts/`,
|
|
122
|
+
outputPath: 'fonts/',
|
|
123
|
+
filename: Boolean(env.production) ? '[hash][ext]' : '[name][ext]',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
output: {
|
|
130
|
+
clean: {
|
|
131
|
+
keep: new RegExp(`(.*?_(amd64|arm(64)?)(.exe)?|go_plugin_build_manifest)`),
|
|
96
132
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
test: /\.s[ac]ss$/,
|
|
103
|
-
use: ['style-loader', 'css-loader', 'sass-loader'],
|
|
133
|
+
filename: '[name].js',
|
|
134
|
+
library: {
|
|
135
|
+
type: 'amd',
|
|
104
136
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
137
|
+
path: path.resolve(process.cwd(), DIST_DIR),
|
|
138
|
+
publicPath: '/',
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
plugins: [
|
|
142
|
+
new CopyWebpackPlugin({
|
|
143
|
+
patterns: [
|
|
144
|
+
// If src/README.md exists use it; otherwise the root README
|
|
145
|
+
// To `compiler.options.output`
|
|
146
|
+
{ from: hasReadme() ? 'README.md' : '../README.md', to: '.', force: true },
|
|
147
|
+
{ from: 'plugin.json', to: '.' },
|
|
148
|
+
{ from: '../LICENSE', to: '.' },
|
|
149
|
+
{ from: '../CHANGELOG.md', to: '.', force: true },
|
|
150
|
+
{ from: '**/*.json', to: '.' }, // TODO<Add an error for checking the basic structure of the repo>
|
|
151
|
+
{ from: '**/*.svg', to: '.', noErrorOnMissing: true }, // Optional
|
|
152
|
+
{ from: '**/*.png', to: '.', noErrorOnMissing: true }, // Optional
|
|
153
|
+
{ from: '**/*.html', to: '.', noErrorOnMissing: true }, // Optional
|
|
154
|
+
{ from: 'img/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
155
|
+
{ from: 'libs/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
156
|
+
{ from: 'static/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
157
|
+
],
|
|
158
|
+
}),
|
|
159
|
+
// Replace certain template-variables in the README and plugin.json
|
|
160
|
+
new ReplaceInFileWebpackPlugin([
|
|
161
|
+
{
|
|
162
|
+
dir: DIST_DIR,
|
|
163
|
+
files: ['plugin.json', 'README.md'],
|
|
164
|
+
rules: [
|
|
165
|
+
{
|
|
166
|
+
search: /\%VERSION\%/g,
|
|
167
|
+
replace: getPackageJson().version,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
search: /\%TODAY\%/g,
|
|
171
|
+
replace: new Date().toISOString().substring(0, 10),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
search: /\%PLUGIN_ID\%/g,
|
|
175
|
+
replace: pluginJson.id,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
113
178
|
},
|
|
114
|
-
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Keep publicPath relative for host.com/grafana/ deployments
|
|
120
|
-
publicPath: `public/plugins/${pluginJson.id}/fonts/`,
|
|
121
|
-
outputPath: 'fonts/',
|
|
122
|
-
filename: Boolean(env.production) ? '[hash][ext]' : '[name][ext]',
|
|
179
|
+
]),
|
|
180
|
+
new ForkTsCheckerWebpackPlugin({
|
|
181
|
+
async: Boolean(env.development),
|
|
182
|
+
issue: {
|
|
183
|
+
include: [{ file: '**/*.{ts,tsx}' }],
|
|
123
184
|
},
|
|
124
|
-
|
|
185
|
+
typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') },
|
|
186
|
+
}),
|
|
187
|
+
new ESLintPlugin({
|
|
188
|
+
extensions: ['.ts', '.tsx'],
|
|
189
|
+
lintDirtyModulesOnly: Boolean(env.development), // don't lint on start, only lint changed files
|
|
190
|
+
}),
|
|
191
|
+
...(env.development ? [new LiveReloadPlugin()] : []),
|
|
125
192
|
],
|
|
126
|
-
},
|
|
127
193
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
library: {
|
|
134
|
-
type: 'amd',
|
|
194
|
+
resolve: {
|
|
195
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
196
|
+
// handle resolving "rootDir" paths
|
|
197
|
+
modules: [path.resolve(process.cwd(), 'src'), 'node_modules'],
|
|
198
|
+
unsafeCache: true,
|
|
135
199
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
{ from: '../CHANGELOG.md', to: '.', force: true },
|
|
149
|
-
{ from: '**/*.json', to: '.' }, // TODO<Add an error for checking the basic structure of the repo>
|
|
150
|
-
{ from: '**/*.svg', to: '.', noErrorOnMissing: true }, // Optional
|
|
151
|
-
{ from: '**/*.png', to: '.', noErrorOnMissing: true }, // Optional
|
|
152
|
-
{ from: '**/*.html', to: '.', noErrorOnMissing: true }, // Optional
|
|
153
|
-
{ from: 'img/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
154
|
-
{ from: 'libs/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
155
|
-
{ from: 'static/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
156
|
-
],
|
|
157
|
-
}),
|
|
158
|
-
// Replace certain template-variables in the README and plugin.json
|
|
159
|
-
new ReplaceInFileWebpackPlugin([
|
|
160
|
-
{
|
|
161
|
-
dir: DIST_DIR,
|
|
162
|
-
files: ['plugin.json', 'README.md'],
|
|
163
|
-
rules: [
|
|
164
|
-
{
|
|
165
|
-
search: /\%VERSION\%/g,
|
|
166
|
-
replace: getPackageJson().version,
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
search: /\%TODAY\%/g,
|
|
170
|
-
replace: new Date().toISOString().substring(0, 10),
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
search: /\%PLUGIN_ID\%/g,
|
|
174
|
-
replace: pluginJson.id,
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
},
|
|
178
|
-
]),
|
|
179
|
-
new ForkTsCheckerWebpackPlugin({
|
|
180
|
-
async: Boolean(env.development),
|
|
181
|
-
issue: {
|
|
182
|
-
include: [{ file: '**/*.{ts,tsx}' }],
|
|
183
|
-
},
|
|
184
|
-
typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') },
|
|
185
|
-
}),
|
|
186
|
-
new ESLintPlugin({
|
|
187
|
-
extensions: ['.ts', '.tsx'],
|
|
188
|
-
lintDirtyModulesOnly: Boolean(env.development), // don't lint on start, only lint changed files
|
|
189
|
-
}),
|
|
190
|
-
...(env.development ? [new LiveReloadPlugin()] : []),
|
|
191
|
-
],
|
|
192
|
-
|
|
193
|
-
resolve: {
|
|
194
|
-
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
195
|
-
// handle resolving "rootDir" paths
|
|
196
|
-
modules: [path.resolve(process.cwd(), 'src'), 'node_modules'],
|
|
197
|
-
unsafeCache: true,
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if(isWSL()) {
|
|
203
|
+
baseConfig.watchOptions = {
|
|
204
|
+
poll: 3000,
|
|
205
|
+
ignored: /node_modules/,
|
|
206
|
+
}}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
return baseConfig;
|
|
210
|
+
|
|
211
|
+
};
|
|
200
212
|
|
|
201
213
|
export default config;
|
|
@@ -18,5 +18,5 @@ Grafana supports a wide range of data sources, including Prometheus, MySQL, and
|
|
|
18
18
|
Below you can find source code for existing app plugins and other related documentation.
|
|
19
19
|
|
|
20
20
|
- [Basic data source plugin example](https://github.com/grafana/grafana-plugin-examples/tree/master/examples/datasource-basic#readme)
|
|
21
|
-
- [
|
|
21
|
+
- [`plugin.json` documentation](https://grafana.com/developers/plugin-tools/reference-plugin-json)
|
|
22
22
|
- [How to sign a plugin?](https://grafana.com/docs/grafana/latest/developers/plugins/sign-a-plugin/)
|
|
@@ -19,5 +19,5 @@ Use panel plugins when you want to do things like visualize data returned by dat
|
|
|
19
19
|
Below you can find source code for existing app plugins and other related documentation.
|
|
20
20
|
|
|
21
21
|
- [Basic panel plugin example](https://github.com/grafana/grafana-plugin-examples/tree/master/examples/panel-basic#readme)
|
|
22
|
-
- [
|
|
22
|
+
- [`plugin.json` documentation](https://grafana.com/developers/plugin-tools/reference-plugin-json)
|
|
23
23
|
- [How to sign a plugin?](https://grafana.com/docs/grafana/latest/developers/plugins/sign-a-plugin/)
|