@grafana/create-plugin 7.7.0-canary.2621.26422794927.0 → 7.7.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 +36 -0
- package/dist/codemods/migrations/migrations.js +6 -0
- package/dist/codemods/migrations/scripts/011-secure-sign-script.js +22 -0
- package/package.json +27 -26
- package/src/codemods/migrations/migrations.ts +7 -0
- package/src/codemods/migrations/scripts/011-secure-sign-script.test.ts +124 -0
- package/src/codemods/migrations/scripts/011-secure-sign-script.ts +26 -0
- package/templates/common/_package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
# v7.7.0 (Fri May 29 2026)
|
|
2
|
+
|
|
3
|
+
#### 🚀 Enhancement
|
|
4
|
+
|
|
5
|
+
- feat: replace insecure sign script [#2656](https://github.com/grafana/plugin-tools/pull/2656) ([@jackw](https://github.com/jackw))
|
|
6
|
+
|
|
7
|
+
#### Authors: 1
|
|
8
|
+
|
|
9
|
+
- Jack Westbrook ([@jackw](https://github.com/jackw))
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# v7.6.2 (Wed May 27 2026)
|
|
14
|
+
|
|
15
|
+
#### 🐛 Bug Fix
|
|
16
|
+
|
|
17
|
+
- fix: pin dependencies for better security [#2658](https://github.com/grafana/plugin-tools/pull/2658) ([@jackw](https://github.com/jackw))
|
|
18
|
+
|
|
19
|
+
#### Authors: 1
|
|
20
|
+
|
|
21
|
+
- Jack Westbrook ([@jackw](https://github.com/jackw))
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# v7.6.1 (Tue May 26 2026)
|
|
26
|
+
|
|
27
|
+
#### 🐛 Bug Fix
|
|
28
|
+
|
|
29
|
+
- fix(create-plugin): make playwright report publishing opt-in [#2640](https://github.com/grafana/plugin-tools/pull/2640) ([@sunker](https://github.com/sunker))
|
|
30
|
+
|
|
31
|
+
#### Authors: 1
|
|
32
|
+
|
|
33
|
+
- Erik Sundell ([@sunker](https://github.com/sunker))
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
1
37
|
# v7.6.0 (Thu May 14 2026)
|
|
2
38
|
|
|
3
39
|
#### 🚀 Enhancement
|
|
@@ -60,6 +60,12 @@ var defaultMigrations = [
|
|
|
60
60
|
version: "7.4.1",
|
|
61
61
|
description: "Fix ts-node compatibility with the latest @grafana/tsconfig: outdated module/moduleResolution/target overrides break TypeScript 5/6 builds, replaced with nodenext/nodenext/es2022.",
|
|
62
62
|
scriptPath: import.meta.resolve("./scripts/010-ts-node-nodenext.js")
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "011-secure-sign-script",
|
|
66
|
+
version: "7.6.3",
|
|
67
|
+
description: "Security: replace insecure inline `npx --yes @grafana/sign-plugin@latest` sign script with a locked @grafana/sign-plugin devDependency to prevent arbitrary code execution from a compromised @latest publish.",
|
|
68
|
+
scriptPath: import.meta.resolve("./scripts/011-secure-sign-script.js")
|
|
63
69
|
}
|
|
64
70
|
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
|
|
65
71
|
// for those written before the switch to updates as migrations.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { addDependenciesToPackageJson } from '../../utils.js';
|
|
2
|
+
|
|
3
|
+
const SIGN_PLUGIN_VERSION = "^3.2.2";
|
|
4
|
+
const NEW_SIGN_SCRIPT = "sign-plugin";
|
|
5
|
+
const INSECURE_SIGN_PATTERN = /^npx\s+(?:--yes|-y)\s+@grafana\/sign-plugin(?:@\S+)?(\s.*)?$/;
|
|
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 currentSign = packageJson.scripts?.sign;
|
|
12
|
+
const match = currentSign?.match(INSECURE_SIGN_PATTERN);
|
|
13
|
+
if (match) {
|
|
14
|
+
const trailingArgs = match[1] ?? "";
|
|
15
|
+
packageJson.scripts.sign = `${NEW_SIGN_SCRIPT}${trailingArgs}`;
|
|
16
|
+
context.updateFile("package.json", JSON.stringify(packageJson, null, 2));
|
|
17
|
+
}
|
|
18
|
+
addDependenciesToPackageJson(context, {}, { "@grafana/sign-plugin": SIGN_PLUGIN_VERSION });
|
|
19
|
+
return context;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { migrate as default };
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/create-plugin",
|
|
3
|
-
"version": "7.7.0
|
|
3
|
+
"version": "7.7.0",
|
|
4
4
|
"repository": {
|
|
5
|
+
"type": "git",
|
|
5
6
|
"directory": "packages/create-plugin",
|
|
6
7
|
"url": "https://github.com/grafana/plugin-tools"
|
|
7
8
|
},
|
|
@@ -25,35 +26,35 @@
|
|
|
25
26
|
"typecheck": "tsc --noEmit"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"@babel/parser": "
|
|
29
|
-
"@ivanmaxlogiudice/gitignore": "
|
|
30
|
-
"change-case": "
|
|
31
|
-
"debug": "
|
|
32
|
-
"enquirer": "
|
|
33
|
-
"find-up": "
|
|
34
|
-
"glob": "
|
|
35
|
-
"handlebars": "
|
|
36
|
-
"jsonc-parser": "
|
|
37
|
-
"minimist": "
|
|
38
|
-
"recast": "
|
|
39
|
-
"semver": "
|
|
40
|
-
"title-case": "
|
|
41
|
-
"valibot": "
|
|
42
|
-
"which": "
|
|
43
|
-
"yaml": "
|
|
29
|
+
"@babel/parser": "7.29.0",
|
|
30
|
+
"@ivanmaxlogiudice/gitignore": "0.0.2",
|
|
31
|
+
"change-case": "5.4.4",
|
|
32
|
+
"debug": "4.4.3",
|
|
33
|
+
"enquirer": "2.4.1",
|
|
34
|
+
"find-up": "8.0.0",
|
|
35
|
+
"glob": "13.0.6",
|
|
36
|
+
"handlebars": "4.7.9",
|
|
37
|
+
"jsonc-parser": "3.3.1",
|
|
38
|
+
"minimist": "1.2.8",
|
|
39
|
+
"recast": "0.23.11",
|
|
40
|
+
"semver": "7.7.2",
|
|
41
|
+
"title-case": "4.3.2",
|
|
42
|
+
"valibot": "1.2.0",
|
|
43
|
+
"which": "6.0.1",
|
|
44
|
+
"yaml": "2.9.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
|
-
"@libs/output": "
|
|
47
|
-
"@libs/version": "
|
|
48
|
-
"@types/glob": "
|
|
49
|
-
"@types/minimist": "
|
|
50
|
-
"@types/semver": "
|
|
51
|
-
"@types/tmp": "
|
|
52
|
-
"@types/which": "
|
|
53
|
-
"tmp": "
|
|
47
|
+
"@libs/output": "1.0.3",
|
|
48
|
+
"@libs/version": "1.0.2",
|
|
49
|
+
"@types/glob": "9.0.0",
|
|
50
|
+
"@types/minimist": "1.2.5",
|
|
51
|
+
"@types/semver": "7.7.1",
|
|
52
|
+
"@types/tmp": "0.2.6",
|
|
53
|
+
"@types/which": "3.0.4",
|
|
54
|
+
"tmp": "0.2.5"
|
|
54
55
|
},
|
|
55
56
|
"engines": {
|
|
56
57
|
"node": ">=20"
|
|
57
58
|
},
|
|
58
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "57e7611bcbbd068694bda70df57fa86008dd3353"
|
|
59
60
|
}
|
|
@@ -71,6 +71,13 @@ export default [
|
|
|
71
71
|
'Fix ts-node compatibility with the latest @grafana/tsconfig: outdated module/moduleResolution/target overrides break TypeScript 5/6 builds, replaced with nodenext/nodenext/es2022.',
|
|
72
72
|
scriptPath: import.meta.resolve('./scripts/010-ts-node-nodenext.js'),
|
|
73
73
|
},
|
|
74
|
+
{
|
|
75
|
+
name: '011-secure-sign-script',
|
|
76
|
+
version: '7.6.3',
|
|
77
|
+
description:
|
|
78
|
+
'Security: replace insecure inline `npx --yes @grafana/sign-plugin@latest` sign script with a locked @grafana/sign-plugin devDependency to prevent arbitrary code execution from a compromised @latest publish.',
|
|
79
|
+
scriptPath: import.meta.resolve('./scripts/011-secure-sign-script.js'),
|
|
80
|
+
},
|
|
74
81
|
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
|
|
75
82
|
// for those written before the switch to updates as migrations.
|
|
76
83
|
] satisfies Migration[];
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import migrate from './011-secure-sign-script.js';
|
|
3
|
+
import { Context } from '../../context.js';
|
|
4
|
+
|
|
5
|
+
describe('011-secure-sign-script', () => {
|
|
6
|
+
it('should replace insecure sign script and add @grafana/sign-plugin devDependency', () => {
|
|
7
|
+
const context = new Context('/virtual');
|
|
8
|
+
context.addFile(
|
|
9
|
+
'package.json',
|
|
10
|
+
JSON.stringify({
|
|
11
|
+
scripts: {
|
|
12
|
+
sign: 'npx --yes @grafana/sign-plugin@latest',
|
|
13
|
+
},
|
|
14
|
+
devDependencies: {
|
|
15
|
+
'@grafana/tsconfig': '^2.0.1',
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const result = migrate(context);
|
|
21
|
+
const packageJson = JSON.parse(result.getFile('package.json') || '{}');
|
|
22
|
+
|
|
23
|
+
expect(packageJson.scripts.sign).toBe('sign-plugin');
|
|
24
|
+
expect(packageJson.devDependencies['@grafana/sign-plugin']).toBe('^3.2.2');
|
|
25
|
+
expect(packageJson.devDependencies['@grafana/tsconfig']).toBe('^2.0.1');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should preserve trailing CLI flags when rewriting the sign script', () => {
|
|
29
|
+
const context = new Context('/virtual');
|
|
30
|
+
context.addFile(
|
|
31
|
+
'package.json',
|
|
32
|
+
JSON.stringify({
|
|
33
|
+
scripts: {
|
|
34
|
+
sign: 'npx --yes @grafana/sign-plugin@latest --rootUrls https://example.com/grafana',
|
|
35
|
+
},
|
|
36
|
+
devDependencies: {},
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const result = migrate(context);
|
|
41
|
+
const packageJson = JSON.parse(result.getFile('package.json') || '{}');
|
|
42
|
+
|
|
43
|
+
expect(packageJson.scripts.sign).toBe('sign-plugin --rootUrls https://example.com/grafana');
|
|
44
|
+
expect(packageJson.devDependencies['@grafana/sign-plugin']).toBe('^3.2.2');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should match the -y short flag as well as --yes', () => {
|
|
48
|
+
const context = new Context('/virtual');
|
|
49
|
+
context.addFile(
|
|
50
|
+
'package.json',
|
|
51
|
+
JSON.stringify({
|
|
52
|
+
scripts: {
|
|
53
|
+
sign: 'npx -y @grafana/sign-plugin@latest',
|
|
54
|
+
},
|
|
55
|
+
devDependencies: {},
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const result = migrate(context);
|
|
60
|
+
const packageJson = JSON.parse(result.getFile('package.json') || '{}');
|
|
61
|
+
|
|
62
|
+
expect(packageJson.scripts.sign).toBe('sign-plugin');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should leave a customised sign script alone but still add the devDependency', () => {
|
|
66
|
+
const context = new Context('/virtual');
|
|
67
|
+
const customSign = './scripts/my-custom-sign.sh';
|
|
68
|
+
context.addFile(
|
|
69
|
+
'package.json',
|
|
70
|
+
JSON.stringify({
|
|
71
|
+
scripts: {
|
|
72
|
+
sign: customSign,
|
|
73
|
+
},
|
|
74
|
+
devDependencies: {},
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const result = migrate(context);
|
|
79
|
+
const packageJson = JSON.parse(result.getFile('package.json') || '{}');
|
|
80
|
+
|
|
81
|
+
expect(packageJson.scripts.sign).toBe(customSign);
|
|
82
|
+
expect(packageJson.devDependencies['@grafana/sign-plugin']).toBe('^3.2.2');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should be a no-op when already migrated', () => {
|
|
86
|
+
const context = new Context('/virtual');
|
|
87
|
+
const original = JSON.stringify({
|
|
88
|
+
scripts: {
|
|
89
|
+
sign: 'sign-plugin',
|
|
90
|
+
},
|
|
91
|
+
devDependencies: {
|
|
92
|
+
'@grafana/sign-plugin': '^3.2.2',
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
context.addFile('package.json', original);
|
|
96
|
+
|
|
97
|
+
const result = migrate(context);
|
|
98
|
+
|
|
99
|
+
expect(result.getFile('package.json')).toBe(original);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should be a no-op when package.json does not exist', () => {
|
|
103
|
+
const context = new Context('/virtual');
|
|
104
|
+
|
|
105
|
+
const result = migrate(context);
|
|
106
|
+
|
|
107
|
+
expect(result.hasChanges()).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should be idempotent', async () => {
|
|
111
|
+
const context = new Context('/virtual');
|
|
112
|
+
context.addFile(
|
|
113
|
+
'package.json',
|
|
114
|
+
JSON.stringify({
|
|
115
|
+
scripts: {
|
|
116
|
+
sign: 'npx --yes @grafana/sign-plugin@latest',
|
|
117
|
+
},
|
|
118
|
+
devDependencies: {},
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
await expect(migrate).toBeIdempotent(context);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Context } from '../../context.js';
|
|
2
|
+
import { addDependenciesToPackageJson } from '../../utils.js';
|
|
3
|
+
|
|
4
|
+
const SIGN_PLUGIN_VERSION = '^3.2.2';
|
|
5
|
+
const NEW_SIGN_SCRIPT = 'sign-plugin';
|
|
6
|
+
const INSECURE_SIGN_PATTERN = /^npx\s+(?:--yes|-y)\s+@grafana\/sign-plugin(?:@\S+)?(\s.*)?$/;
|
|
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 currentSign: string | undefined = packageJson.scripts?.sign;
|
|
15
|
+
const match = currentSign?.match(INSECURE_SIGN_PATTERN);
|
|
16
|
+
|
|
17
|
+
if (match) {
|
|
18
|
+
const trailingArgs = match[1] ?? '';
|
|
19
|
+
packageJson.scripts.sign = `${NEW_SIGN_SCRIPT}${trailingArgs}`;
|
|
20
|
+
context.updateFile('package.json', JSON.stringify(packageJson, null, 2));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
addDependenciesToPackageJson(context, {}, { '@grafana/sign-plugin': SIGN_PLUGIN_VERSION });
|
|
24
|
+
|
|
25
|
+
return context;
|
|
26
|
+
}
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
"lint:fix": "{{ packageManagerName }} run lint{{#if isNPM}} --{{/if}} --fix && prettier --write --list-different .",
|
|
12
12
|
"e2e": "playwright test",
|
|
13
13
|
"server": "docker compose up --build",
|
|
14
|
-
"sign": "
|
|
14
|
+
"sign": "sign-plugin"
|
|
15
15
|
},
|
|
16
16
|
"author": "{{ sentenceCase orgName }}",
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@grafana/eslint-config": "^9.0.0",
|
|
20
20
|
"@grafana/plugin-e2e": "^3.6.1",
|
|
21
|
+
"@grafana/sign-plugin": "^3.2.2",
|
|
21
22
|
"@grafana/tsconfig": "^2.0.1",
|
|
22
23
|
"@playwright/test": "^1.57.0",{{#if useExperimentalRspack}}
|
|
23
24
|
"@rspack/core": "^1.6.0",
|