@grafana/create-plugin 7.5.1-canary.2560.25849471867.0 → 7.6.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 CHANGED
@@ -1,3 +1,15 @@
1
+ # v7.6.0 (Thu May 14 2026)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - feat: support Typescript 6 [#2614](https://github.com/grafana/plugin-tools/pull/2614) ([@jackw](https://github.com/jackw))
6
+
7
+ #### Authors: 1
8
+
9
+ - Jack Westbrook ([@jackw](https://github.com/jackw))
10
+
11
+ ---
12
+
1
13
  # v7.5.0 (Wed May 13 2026)
2
14
 
3
15
  #### 🚀 Enhancement
package/CONTRIBUTING.md CHANGED
@@ -109,8 +109,6 @@ Note that certain files are intentionally named differently (e.g. npmrc, package
109
109
 
110
110
  ### Migrations
111
111
 
112
- > **Note:** Migrations are currently behind the `--experimental-updates` flag and are not enabled by default.
113
-
114
112
  Migrations are scripts that update a particular aspect of a project created with create-plugin. When users run `@grafana/create-plugin@latest update`, the command compares their project's version against the running package version and executes any necessary migrations to bring their project up to date.
115
113
 
116
114
  ```js
@@ -48,6 +48,18 @@ var defaultMigrations = [
48
48
  version: "7.3.2",
49
49
  description: "Harden bundle-stats/bundle-size workflow permissions: contents permission was set to write but only read access is required; restricted to read for least-privilege.",
50
50
  scriptPath: import.meta.resolve("./scripts/008-bundle-stats-permissions.js")
51
+ },
52
+ {
53
+ name: "009-remove-tsconfig-baseurl",
54
+ version: "7.4.1",
55
+ description: "Fix TypeScript 6 compatibility: baseUrl is deprecated in TS6 (error TS5101) and must be replaced with an equivalent paths entry to preserve non-relative import resolution.",
56
+ scriptPath: import.meta.resolve("./scripts/009-remove-tsconfig-baseurl.js")
57
+ },
58
+ {
59
+ name: "010-ts-node-nodenext",
60
+ version: "7.4.1",
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
+ scriptPath: import.meta.resolve("./scripts/010-ts-node-nodenext.js")
51
63
  }
52
64
  // Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
53
65
  // for those written before the switch to updates as migrations.
@@ -0,0 +1,24 @@
1
+ import { applyEdits, modify } from 'jsonc-parser';
2
+
3
+ function migrate(context) {
4
+ if (!context.doesFileExist(".config/tsconfig.json")) {
5
+ return context;
6
+ }
7
+ const content = context.getFile(".config/tsconfig.json") || "";
8
+ if (!content.includes('"baseUrl"')) {
9
+ return context;
10
+ }
11
+ const formattingOptions = { formattingOptions: { insertSpaces: true, tabSize: 2 } };
12
+ const contentWithoutBaseUrl = applyEdits(
13
+ content,
14
+ modify(content, ["compilerOptions", "baseUrl"], void 0, formattingOptions)
15
+ );
16
+ const contentWithPaths = applyEdits(
17
+ contentWithoutBaseUrl,
18
+ modify(contentWithoutBaseUrl, ["compilerOptions", "paths", "*"], ["../src/*"], formattingOptions)
19
+ );
20
+ context.updateFile(".config/tsconfig.json", contentWithPaths);
21
+ return context;
22
+ }
23
+
24
+ export { migrate as default };
@@ -0,0 +1,33 @@
1
+ import { applyEdits, modify } from 'jsonc-parser';
2
+
3
+ const TSCONFIG_PATH = ".config/tsconfig.json";
4
+ const FORMATTING_OPTIONS = {
5
+ formattingOptions: { insertSpaces: true, tabSize: 2 }
6
+ };
7
+ const UPDATES = [
8
+ { key: "module", oldValue: "commonjs", newValue: "nodenext" },
9
+ { key: "moduleResolution", newValue: "nodenext" },
10
+ { key: "target", oldValue: "es5", newValue: "es2022" }
11
+ ];
12
+ function migrate(context) {
13
+ if (!context.doesFileExist(TSCONFIG_PATH)) {
14
+ return context;
15
+ }
16
+ const original = context.getFile(TSCONFIG_PATH) || "";
17
+ let next = original;
18
+ for (const { key, oldValue, newValue } of UPDATES) {
19
+ if (oldValue !== void 0) {
20
+ const oldPattern = `"${key}": "${oldValue}"`;
21
+ if (!next.includes(oldPattern)) {
22
+ continue;
23
+ }
24
+ }
25
+ next = applyEdits(next, modify(next, ["ts-node", "compilerOptions", key], newValue, FORMATTING_OPTIONS));
26
+ }
27
+ if (next !== original) {
28
+ context.updateFile(TSCONFIG_PATH, next);
29
+ }
30
+ return context;
31
+ }
32
+
33
+ export { migrate as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/create-plugin",
3
- "version": "7.5.1-canary.2560.25849471867.0",
3
+ "version": "7.6.0",
4
4
  "repository": {
5
5
  "directory": "packages/create-plugin",
6
6
  "url": "https://github.com/grafana/plugin-tools"
@@ -55,5 +55,5 @@
55
55
  "engines": {
56
56
  "node": ">=20"
57
57
  },
58
- "gitHead": "44214a8ca8a55aab4b33e1e56cee3216af32c0df"
58
+ "gitHead": "9397fd0f84f80c9d8a6dcfb69f3fbba271200c6b"
59
59
  }
@@ -57,6 +57,20 @@ export default [
57
57
  'Harden bundle-stats/bundle-size workflow permissions: contents permission was set to write but only read access is required; restricted to read for least-privilege.',
58
58
  scriptPath: import.meta.resolve('./scripts/008-bundle-stats-permissions.js'),
59
59
  },
60
+ {
61
+ name: '009-remove-tsconfig-baseurl',
62
+ version: '7.4.1',
63
+ description:
64
+ 'Fix TypeScript 6 compatibility: baseUrl is deprecated in TS6 (error TS5101) and must be replaced with an equivalent paths entry to preserve non-relative import resolution.',
65
+ scriptPath: import.meta.resolve('./scripts/009-remove-tsconfig-baseurl.js'),
66
+ },
67
+ {
68
+ name: '010-ts-node-nodenext',
69
+ version: '7.4.1',
70
+ description:
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
+ scriptPath: import.meta.resolve('./scripts/010-ts-node-nodenext.js'),
73
+ },
60
74
  // Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
61
75
  // for those written before the switch to updates as migrations.
62
76
  ] satisfies Migration[];
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parse } from 'jsonc-parser';
3
+ import migrate from './009-remove-tsconfig-baseurl.js';
4
+ import { Context } from '../../context.js';
5
+
6
+ const SCAFFOLD_TSCONFIG = `/*
7
+ * ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY \`@grafana/create-plugin\`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
8
+ */
9
+ {
10
+ "compilerOptions": {
11
+ "alwaysStrict": true,
12
+ "rootDir": "../src",
13
+ "baseUrl": "../src",
14
+ "typeRoots": ["../node_modules/@types"],
15
+ "resolveJsonModule": true
16
+ },
17
+ "extends": "@grafana/tsconfig"
18
+ }
19
+ `;
20
+
21
+ describe('008-remove-tsconfig-baseurl', () => {
22
+ it('should remove baseUrl and add paths catch-all, preserving comment header and rootDir', () => {
23
+ const context = new Context('/virtual');
24
+ context.addFile('.config/tsconfig.json', SCAFFOLD_TSCONFIG);
25
+
26
+ const result = migrate(context);
27
+ const content = result.getFile('.config/tsconfig.json') || '';
28
+ const config = parse(content) as { compilerOptions: Record<string, unknown> };
29
+
30
+ expect(config.compilerOptions['baseUrl']).toBeUndefined();
31
+ expect(config.compilerOptions['paths']).toEqual({ '*': ['../src/*'] });
32
+ expect(config.compilerOptions['rootDir']).toBe('../src');
33
+ expect(content).toContain('DO NOT EDIT THIS FILE DIRECTLY');
34
+ });
35
+
36
+ it('should be idempotent', async () => {
37
+ const context = new Context('/virtual');
38
+ context.addFile('.config/tsconfig.json', SCAFFOLD_TSCONFIG);
39
+
40
+ await expect(migrate).toBeIdempotent(context);
41
+ });
42
+
43
+ it('should not modify file when baseUrl is already absent', () => {
44
+ const context = new Context('/virtual');
45
+ const content = `{
46
+ "compilerOptions": {
47
+ "rootDir": "../src",
48
+ "paths": { "*": ["../src/*"] }
49
+ }
50
+ }
51
+ `;
52
+ context.addFile('.config/tsconfig.json', content);
53
+
54
+ const result = migrate(context);
55
+
56
+ expect(result.getFile('.config/tsconfig.json')).toBe(content);
57
+ });
58
+
59
+ it('should not modify anything when .config/tsconfig.json does not exist', () => {
60
+ const context = new Context('/virtual');
61
+
62
+ const result = migrate(context);
63
+
64
+ expect(result.hasChanges()).toBe(false);
65
+ });
66
+
67
+ it('should merge paths["*"] into an existing paths object without overwriting other entries', () => {
68
+ const context = new Context('/virtual');
69
+ context.addFile(
70
+ '.config/tsconfig.json',
71
+ `{
72
+ "compilerOptions": {
73
+ "rootDir": "../src",
74
+ "baseUrl": "../src",
75
+ "paths": {
76
+ "@components/*": ["../src/components/*"]
77
+ }
78
+ }
79
+ }
80
+ `
81
+ );
82
+
83
+ const result = migrate(context);
84
+ const content = result.getFile('.config/tsconfig.json') || '';
85
+ const config = parse(content) as { compilerOptions: { paths: Record<string, string[]> } };
86
+
87
+ expect(config.compilerOptions.paths['@components/*']).toEqual(['../src/components/*']);
88
+ expect(config.compilerOptions.paths['*']).toEqual(['../src/*']);
89
+ expect(config.compilerOptions).not.toHaveProperty('baseUrl');
90
+ });
91
+ });
@@ -0,0 +1,32 @@
1
+ import { modify, applyEdits } from 'jsonc-parser';
2
+ import type { Context } from '../../context.js';
3
+
4
+ export default function migrate(context: Context) {
5
+ if (!context.doesFileExist('.config/tsconfig.json')) {
6
+ return context;
7
+ }
8
+
9
+ const content = context.getFile('.config/tsconfig.json') || '';
10
+
11
+ if (!content.includes('"baseUrl"')) {
12
+ return context;
13
+ }
14
+
15
+ const formattingOptions = { formattingOptions: { insertSpaces: true, tabSize: 2 } };
16
+
17
+ // applyEdits accepts Edit[] and modify returns Edit[], but both modify calls must operate on
18
+ // the same base string to produce correct offsets. Chaining them sequentially is safer since
19
+ // removing baseUrl shifts positions in compilerOptions before we add paths.
20
+ const contentWithoutBaseUrl = applyEdits(
21
+ content,
22
+ modify(content, ['compilerOptions', 'baseUrl'], undefined, formattingOptions)
23
+ );
24
+ const contentWithPaths = applyEdits(
25
+ contentWithoutBaseUrl,
26
+ modify(contentWithoutBaseUrl, ['compilerOptions', 'paths', '*'], ['../src/*'], formattingOptions)
27
+ );
28
+
29
+ context.updateFile('.config/tsconfig.json', contentWithPaths);
30
+
31
+ return context;
32
+ }
@@ -0,0 +1,109 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import migrate from './010-ts-node-nodenext.js';
3
+ import { Context } from '../../context.js';
4
+
5
+ const SCAFFOLDED_TSCONFIG = `/*
6
+ * ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY \`@grafana/create-plugin\`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
7
+ *
8
+ * In order to extend the configuration follow the steps in
9
+ * https://grafana.com/developers/plugin-tools/how-to-guides/extend-configurations#extend-the-typescript-config
10
+ */
11
+ {
12
+ "compilerOptions": {
13
+ "alwaysStrict": true,
14
+ "declaration": false,
15
+ "rootDir": "../src",
16
+ "paths": {
17
+ "*": ["../src/*"]
18
+ },
19
+ "typeRoots": ["../node_modules/@types"],
20
+ "resolveJsonModule": true
21
+ },
22
+ "ts-node": {
23
+ "compilerOptions": {
24
+ "module": "commonjs",
25
+ "moduleResolution": "node",
26
+ "target": "es5",
27
+ "esModuleInterop": true
28
+ },
29
+ "transpileOnly": true
30
+ },
31
+ "include": ["../src", "./types"],
32
+ "extends": "@grafana/tsconfig"
33
+ }
34
+ `;
35
+
36
+ describe('009-ts-node-nodenext', () => {
37
+ it('should update module, moduleResolution, and target inside ts-node compilerOptions', () => {
38
+ const context = new Context('/virtual');
39
+ context.addFile('.config/tsconfig.json', SCAFFOLDED_TSCONFIG);
40
+
41
+ const result = migrate(context);
42
+ const updated = result.getFile('.config/tsconfig.json') || '';
43
+
44
+ expect(updated).toContain('"module": "nodenext"');
45
+ expect(updated).toContain('"moduleResolution": "nodenext"');
46
+ expect(updated).toContain('"target": "es2022"');
47
+ expect(updated).not.toContain('"module": "commonjs"');
48
+ expect(updated).not.toContain('"moduleResolution": "node"');
49
+ expect(updated).not.toContain('"target": "es5"');
50
+ });
51
+
52
+ it('should preserve the JSONC comment header', () => {
53
+ const context = new Context('/virtual');
54
+ context.addFile('.config/tsconfig.json', SCAFFOLDED_TSCONFIG);
55
+
56
+ const result = migrate(context);
57
+ const updated = result.getFile('.config/tsconfig.json') || '';
58
+
59
+ expect(updated).toContain('THIS FILE WAS SCAFFOLDED BY');
60
+ expect(updated.startsWith('/*')).toBe(true);
61
+ });
62
+
63
+ it('should be idempotent', async () => {
64
+ const context = new Context('/virtual');
65
+ context.addFile('.config/tsconfig.json', SCAFFOLDED_TSCONFIG);
66
+
67
+ await expect(migrate).toBeIdempotent(context);
68
+ });
69
+
70
+ it('should be a no-op when the file is already migrated', () => {
71
+ const alreadyMigrated = SCAFFOLDED_TSCONFIG.replace('"module": "commonjs"', '"module": "nodenext"')
72
+ .replace('"moduleResolution": "node"', '"moduleResolution": "nodenext"')
73
+ .replace('"target": "es5"', '"target": "es2022"');
74
+
75
+ const context = new Context('/virtual');
76
+ context.addFile('.config/tsconfig.json', alreadyMigrated);
77
+
78
+ const result = migrate(context);
79
+
80
+ expect(result.getFile('.config/tsconfig.json')).toBe(alreadyMigrated);
81
+ });
82
+
83
+ it('should be a no-op when .config/tsconfig.json does not exist', () => {
84
+ const context = new Context('/virtual');
85
+
86
+ const result = migrate(context);
87
+
88
+ expect(result.hasChanges()).toBe(false);
89
+ });
90
+
91
+ it('should leave user-customised module/target untouched but always force moduleResolution to nodenext', () => {
92
+ const partiallyCustomised = SCAFFOLDED_TSCONFIG.replace('"target": "es5"', '"target": "es2020"').replace(
93
+ '"moduleResolution": "node"',
94
+ '"moduleResolution": "bundler"'
95
+ );
96
+
97
+ const context = new Context('/virtual');
98
+ context.addFile('.config/tsconfig.json', partiallyCustomised);
99
+
100
+ const result = migrate(context);
101
+ const updated = result.getFile('.config/tsconfig.json') || '';
102
+
103
+ expect(updated).toContain('"module": "nodenext"');
104
+ expect(updated).toContain('"moduleResolution": "nodenext"');
105
+ expect(updated).toContain('"target": "es2020"');
106
+ expect(updated).not.toContain('"moduleResolution": "bundler"');
107
+ expect(updated).not.toContain('"target": "es2022"');
108
+ });
109
+ });
@@ -0,0 +1,39 @@
1
+ import { applyEdits, modify } from 'jsonc-parser';
2
+ import type { Context } from '../../context.js';
3
+
4
+ const TSCONFIG_PATH = '.config/tsconfig.json';
5
+
6
+ const FORMATTING_OPTIONS = {
7
+ formattingOptions: { insertSpaces: true, tabSize: 2 },
8
+ };
9
+
10
+ const UPDATES: Array<{ key: string; oldValue?: string; newValue: string }> = [
11
+ { key: 'module', oldValue: 'commonjs', newValue: 'nodenext' },
12
+ { key: 'moduleResolution', newValue: 'nodenext' },
13
+ { key: 'target', oldValue: 'es5', newValue: 'es2022' },
14
+ ];
15
+
16
+ export default function migrate(context: Context) {
17
+ if (!context.doesFileExist(TSCONFIG_PATH)) {
18
+ return context;
19
+ }
20
+
21
+ const original = context.getFile(TSCONFIG_PATH) || '';
22
+ let next = original;
23
+
24
+ for (const { key, oldValue, newValue } of UPDATES) {
25
+ if (oldValue !== undefined) {
26
+ const oldPattern = `"${key}": "${oldValue}"`;
27
+ if (!next.includes(oldPattern)) {
28
+ continue;
29
+ }
30
+ }
31
+ next = applyEdits(next, modify(next, ['ts-node', 'compilerOptions', key], newValue, FORMATTING_OPTIONS));
32
+ }
33
+
34
+ if (next !== original) {
35
+ context.updateFile(TSCONFIG_PATH, next);
36
+ }
37
+
38
+ return context;
39
+ }
@@ -9,15 +9,17 @@
9
9
  "alwaysStrict": true,
10
10
  "declaration": false,
11
11
  "rootDir": "../src",
12
- "baseUrl": "../src",
12
+ "paths": {
13
+ "*": ["../src/*"]
14
+ },
13
15
  "typeRoots": ["../node_modules/@types"],
14
16
  "resolveJsonModule": true
15
17
  },
16
18
  "ts-node": {
17
19
  "compilerOptions": {
18
- "module": "commonjs",
19
- "moduleResolution": "node",
20
- "target": "es5",
20
+ "module": "nodenext",
21
+ "moduleResolution": "nodenext",
22
+ "target": "es2022",
21
23
  "esModuleInterop": true
22
24
  },
23
25
  "transpileOnly": true
@@ -31,7 +31,7 @@ jobs:
31
31
  {{#if_eq packageManagerName "pnpm"}}
32
32
  # pnpm action uses the packageManager field in package.json to
33
33
  # understand which version to install.
34
- - uses: pnpm/action-setup@v6
34
+ - uses: pnpm/action-setup@v4
35
35
  {{/if_eq}}
36
36
  - name: Setup Node.js environment
37
37
  uses: actions/setup-node@v6
@@ -74,14 +74,14 @@ jobs:
74
74
 
75
75
  - name: Build backend
76
76
  if: steps.check-for-backend.outputs.has-backend == 'true'
77
- uses: magefile/mage-action@a662bd8c29d8106879588cfff83b2faf6e6f59db # v4
77
+ uses: magefile/mage-action@6f50bbb8ea47d56e62dee92392788acbc8192d0b # v3.1.0
78
78
  with:
79
79
  version: latest
80
80
  args: buildAll
81
81
 
82
82
  - name: Test backend
83
83
  if: steps.check-for-backend.outputs.has-backend == 'true'
84
- uses: magefile/mage-action@a662bd8c29d8106879588cfff83b2faf6e6f59db # v4
84
+ uses: magefile/mage-action@6f50bbb8ea47d56e62dee92392788acbc8192d0b # v3.1.0
85
85
  with:
86
86
  version: latest
87
87
  args: test
@@ -187,7 +187,7 @@ jobs:
187
187
  {{#if_eq packageManagerName "pnpm"}}
188
188
  # pnpm action uses the packageManager field in package.json to
189
189
  # understand which version to install.
190
- - uses: pnpm/action-setup@v6
190
+ - uses: pnpm/action-setup@v4
191
191
  {{/if_eq}}
192
192
  - name: Setup Node.js environment
193
193
  uses: actions/setup-node@v6
@@ -14,11 +14,11 @@ jobs:
14
14
  {{#if_eq packageManagerName "pnpm"}}
15
15
  # pnpm action uses the packageManager field in package.json to
16
16
  # understand which version to install.
17
- - uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v6.0.6
17
+ - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
18
18
  {{/if_eq}}
19
19
 
20
20
  - name: Setup Node.js environment
21
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
21
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
22
22
  with:
23
23
  node-version: '22'
24
24
  cache: '{{ packageManagerName }}'