@grafana/create-plugin 7.10.0 → 7.10.1

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,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.10.1](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.10.0...@grafana/create-plugin@7.10.1) (2026-08-27)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **create-plugin:** add @react-hookz/web + @ver0/deep-equal to Jest ESM ignore ([#2847](https://github.com/grafana/plugin-tools/issues/2847)) ([66068ab](https://github.com/grafana/plugin-tools/commit/66068abc145224767470902386949fd8b65f6eaa))
9
+
3
10
  ## [7.10.0](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.9.2...@grafana/create-plugin@7.10.0) (2026-08-21)
4
11
 
5
12
 
@@ -72,6 +72,12 @@ var defaultMigrations = [
72
72
  version: "7.8.2",
73
73
  description: "Emit a single LICENSE.txt file for all licenses in bundled code.",
74
74
  scriptPath: import.meta.resolve("./scripts/012-terser-license-file.js")
75
+ },
76
+ {
77
+ name: "013-jest-esmodules",
78
+ version: "7.10.1",
79
+ description: "Support @grafana/ui@13.2.0 in Jest by transforming @react-hookz/web and @ver0/deep-equal ESM dependencies.",
80
+ scriptPath: import.meta.resolve("./scripts/013-jest-esmodules.js")
75
81
  }
76
82
  // Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
77
83
  // for those written before the switch to updates as migrations.
@@ -0,0 +1,65 @@
1
+ import { join } from 'node:path';
2
+ import * as recast from 'recast';
3
+ import { migrationsDebug } from '../../utils.js';
4
+ import { parseAsTypescript, findVariableDeclaration, printAST } from '../../utils.ast.js';
5
+
6
+ const { builders } = recast.types;
7
+ const JEST_UTILS_PATH = join(".config", "jest", "utils.js");
8
+ const ESM_MODULES_TO_ADD = ["@react-hookz/web", "@ver0/deep-equal"];
9
+ function migrate(context) {
10
+ if (!context.doesFileExist(JEST_UTILS_PATH)) {
11
+ migrationsDebug(`${JEST_UTILS_PATH} not found. Skipping Jest ESM modules migration.`);
12
+ return context;
13
+ }
14
+ const source = context.getFile(JEST_UTILS_PATH);
15
+ if (!source) {
16
+ migrationsDebug(`${JEST_UTILS_PATH} is empty. Skipping Jest ESM modules migration.`);
17
+ return context;
18
+ }
19
+ const parsed = parseAsTypescript(source);
20
+ if (!parsed.success) {
21
+ migrationsDebug(`Failed to parse ${JEST_UTILS_PATH}. Error: ${parsed.error.message}`);
22
+ return context;
23
+ }
24
+ const grafanaESModules = findVariableDeclaration(parsed.ast, "grafanaESModules");
25
+ if (!grafanaESModules) {
26
+ migrationsDebug(`Could not find grafanaESModules variable declaration in ${JEST_UTILS_PATH}`);
27
+ return context;
28
+ }
29
+ if (grafanaESModules.init?.type !== "ArrayExpression") {
30
+ migrationsDebug(`grafanaESModules variable in ${JEST_UTILS_PATH} is not an array.`);
31
+ return context;
32
+ }
33
+ const existingModules = new Set(
34
+ grafanaESModules.init.elements.map(getStringValue).filter((value) => value !== void 0)
35
+ );
36
+ const missingModules = ESM_MODULES_TO_ADD.filter((moduleName) => !existingModules.has(moduleName));
37
+ if (missingModules.length === 0) {
38
+ return context;
39
+ }
40
+ const schemaIndex = grafanaESModules.init.elements.findIndex(
41
+ (element) => getStringValue(element) === "@grafana/schema"
42
+ );
43
+ const insertIndex = schemaIndex === -1 ? grafanaESModules.init.elements.length : schemaIndex + 1;
44
+ grafanaESModules.init.elements.splice(
45
+ insertIndex,
46
+ 0,
47
+ ...missingModules.map((moduleName) => builders.literal(moduleName))
48
+ );
49
+ context.updateFile(JEST_UTILS_PATH, printAST(parsed.ast));
50
+ return context;
51
+ }
52
+ function getStringValue(element) {
53
+ if (!element) {
54
+ return void 0;
55
+ }
56
+ if (element.type === "Literal" && typeof element.value === "string") {
57
+ return element.value;
58
+ }
59
+ if (element.type === "StringLiteral") {
60
+ return element.value;
61
+ }
62
+ return void 0;
63
+ }
64
+
65
+ export { migrate as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/create-plugin",
3
- "version": "7.10.0",
3
+ "version": "7.10.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "directory": "packages/create-plugin",
@@ -84,6 +84,13 @@ export default [
84
84
  description: 'Emit a single LICENSE.txt file for all licenses in bundled code.',
85
85
  scriptPath: import.meta.resolve('./scripts/012-terser-license-file.js'),
86
86
  },
87
+ {
88
+ name: '013-jest-esmodules',
89
+ version: '7.10.1',
90
+ description:
91
+ 'Support @grafana/ui@13.2.0 in Jest by transforming @react-hookz/web and @ver0/deep-equal ESM dependencies.',
92
+ scriptPath: import.meta.resolve('./scripts/013-jest-esmodules.js'),
93
+ },
87
94
  // Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
88
95
  // for those written before the switch to updates as migrations.
89
96
  ] satisfies Migration[];
@@ -0,0 +1,99 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import migrate from './013-jest-esmodules.js';
3
+ import { Context } from '../../context.js';
4
+
5
+ const JEST_UTILS_PATH = '.config/jest/utils.js';
6
+
7
+ const JEST_UTILS = `/*
8
+ * ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY \`@grafana/create-plugin\`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
9
+ *
10
+ * In order to extend the configuration follow the steps in .config/README.md
11
+ */
12
+
13
+ /*
14
+ * This utility function is useful in combination with jest \`transformIgnorePatterns\` config
15
+ * to transform specific packages (e.g.ES modules) in a projects node_modules folder.
16
+ */
17
+ const nodeModulesToTransform = (moduleNames) => \`node_modules\\/(?!.*(\${moduleNames.join('|')})\\/.*)\`;
18
+
19
+ // Array of known nested grafana package dependencies that only bundle an ESM version
20
+ const grafanaESModules = [
21
+ '.pnpm', // Support using pnpm symlinked packages
22
+ '@grafana/schema',
23
+ '@wojtekmaj/date-utils',
24
+ 'd3',
25
+ 'uuid',
26
+ ];
27
+
28
+ module.exports = {
29
+ nodeModulesToTransform,
30
+ grafanaESModules,
31
+ };
32
+ `;
33
+
34
+ describe('013-jest-esmodules', () => {
35
+ it('should add @react-hookz/web and @ver0/deep-equal to grafanaESModules', () => {
36
+ const context = new Context('/virtual');
37
+ context.addFile(JEST_UTILS_PATH, JEST_UTILS);
38
+
39
+ const result = migrate(context);
40
+ const updated = result.getFile(JEST_UTILS_PATH) ?? '';
41
+
42
+ expect(updated).toContain("'@react-hookz/web'");
43
+ expect(updated).toContain("'@ver0/deep-equal'");
44
+ expect(updated.indexOf("'@grafana/schema'")).toBeLessThan(updated.indexOf("'@react-hookz/web'"));
45
+ expect(updated.indexOf("'@ver0/deep-equal'")).toBeLessThan(updated.indexOf("'@wojtekmaj/date-utils'"));
46
+ });
47
+
48
+ it('should only add missing modules', () => {
49
+ const context = new Context('/virtual');
50
+ context.addFile(
51
+ JEST_UTILS_PATH,
52
+ JEST_UTILS.replace("'@wojtekmaj/date-utils',", "'@react-hookz/web',\n '@wojtekmaj/date-utils',")
53
+ );
54
+
55
+ const result = migrate(context);
56
+ const updated = result.getFile(JEST_UTILS_PATH) ?? '';
57
+
58
+ expect(updated.match(/'@react-hookz\/web'/g)).toHaveLength(1);
59
+ expect(updated.match(/'@ver0\/deep-equal'/g)).toHaveLength(1);
60
+ });
61
+
62
+ it('should be idempotent', async () => {
63
+ const context = new Context('/virtual');
64
+ context.addFile(JEST_UTILS_PATH, JEST_UTILS);
65
+
66
+ await expect(migrate).toBeIdempotent(context);
67
+ });
68
+
69
+ it('should do nothing when the Jest utils file does not exist', () => {
70
+ const context = new Context('/virtual');
71
+
72
+ const result = migrate(context);
73
+
74
+ expect(result.hasChanges()).toBe(false);
75
+ });
76
+
77
+ it('should do nothing when grafanaESModules is missing', () => {
78
+ const context = new Context('/virtual');
79
+ const customizedUtils = JEST_UTILS.replace('const grafanaESModules = [', 'const customESModules = [');
80
+ context.addFile(JEST_UTILS_PATH, customizedUtils);
81
+
82
+ const result = migrate(context);
83
+
84
+ expect(result.getFile(JEST_UTILS_PATH)).toBe(customizedUtils);
85
+ });
86
+
87
+ it('should do nothing when grafanaESModules is not an array', () => {
88
+ const context = new Context('/virtual');
89
+ const customizedUtils = JEST_UTILS.replace(
90
+ /const grafanaESModules = \[[\s\S]*?\];/,
91
+ "const grafanaESModules = require('./grafana-es-modules');"
92
+ );
93
+ context.addFile(JEST_UTILS_PATH, customizedUtils);
94
+
95
+ const result = migrate(context);
96
+
97
+ expect(result.getFile(JEST_UTILS_PATH)).toBe(customizedUtils);
98
+ });
99
+ });
@@ -0,0 +1,79 @@
1
+ import { join } from 'node:path';
2
+ import * as recast from 'recast';
3
+ import type { Context } from '../../context.js';
4
+ import { migrationsDebug } from '../../utils.js';
5
+ import { findVariableDeclaration, parseAsTypescript, printAST } from '../../utils.ast.js';
6
+
7
+ const { builders } = recast.types;
8
+
9
+ const JEST_UTILS_PATH = join('.config', 'jest', 'utils.js');
10
+ const ESM_MODULES_TO_ADD = ['@react-hookz/web', '@ver0/deep-equal'];
11
+
12
+ export default function migrate(context: Context): Context {
13
+ if (!context.doesFileExist(JEST_UTILS_PATH)) {
14
+ migrationsDebug(`${JEST_UTILS_PATH} not found. Skipping Jest ESM modules migration.`);
15
+ return context;
16
+ }
17
+
18
+ const source = context.getFile(JEST_UTILS_PATH);
19
+ if (!source) {
20
+ migrationsDebug(`${JEST_UTILS_PATH} is empty. Skipping Jest ESM modules migration.`);
21
+ return context;
22
+ }
23
+
24
+ const parsed = parseAsTypescript(source);
25
+ if (!parsed.success) {
26
+ migrationsDebug(`Failed to parse ${JEST_UTILS_PATH}. Error: ${parsed.error.message}`);
27
+ return context;
28
+ }
29
+
30
+ const grafanaESModules = findVariableDeclaration(parsed.ast, 'grafanaESModules');
31
+ if (!grafanaESModules) {
32
+ migrationsDebug(`Could not find grafanaESModules variable declaration in ${JEST_UTILS_PATH}`);
33
+ return context;
34
+ }
35
+
36
+ if (grafanaESModules.init?.type !== 'ArrayExpression') {
37
+ migrationsDebug(`grafanaESModules variable in ${JEST_UTILS_PATH} is not an array.`);
38
+ return context;
39
+ }
40
+
41
+ const existingModules = new Set(
42
+ grafanaESModules.init.elements.map(getStringValue).filter((value) => value !== undefined)
43
+ );
44
+ const missingModules = ESM_MODULES_TO_ADD.filter((moduleName) => !existingModules.has(moduleName));
45
+
46
+ if (missingModules.length === 0) {
47
+ return context;
48
+ }
49
+
50
+ const schemaIndex = grafanaESModules.init.elements.findIndex(
51
+ (element) => getStringValue(element) === '@grafana/schema'
52
+ );
53
+ const insertIndex = schemaIndex === -1 ? grafanaESModules.init.elements.length : schemaIndex + 1;
54
+
55
+ grafanaESModules.init.elements.splice(
56
+ insertIndex,
57
+ 0,
58
+ ...missingModules.map((moduleName) => builders.literal(moduleName))
59
+ );
60
+ context.updateFile(JEST_UTILS_PATH, printAST(parsed.ast));
61
+
62
+ return context;
63
+ }
64
+
65
+ function getStringValue(element: recast.types.namedTypes.ArrayExpression['elements'][number]): string | undefined {
66
+ if (!element) {
67
+ return undefined;
68
+ }
69
+
70
+ if (element.type === 'Literal' && typeof element.value === 'string') {
71
+ return element.value;
72
+ }
73
+
74
+ if (element.type === 'StringLiteral') {
75
+ return element.value;
76
+ }
77
+
78
+ return undefined;
79
+ }
@@ -14,6 +14,8 @@ const nodeModulesToTransform = (moduleNames) => `node_modules\/(?!.*(${moduleNam
14
14
  const grafanaESModules = [
15
15
  '.pnpm', // Support using pnpm symlinked packages
16
16
  '@grafana/schema',
17
+ '@react-hookz/web',
18
+ '@ver0/deep-equal',
17
19
  '@wojtekmaj/date-utils',
18
20
  'd3',
19
21
  'd3-color',