@grafana/create-plugin 6.8.0-canary.2356.20813241719.0 → 6.8.0-canary.2356.20813982803.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
+ # v6.7.1 (Thu Jan 08 2026)
2
+
3
+ #### 🐛 Bug Fix
4
+
5
+ - fix: stop copying all images to bundle [#2369](https://github.com/grafana/plugin-tools/pull/2369) ([@jackw](https://github.com/jackw))
6
+
7
+ #### Authors: 1
8
+
9
+ - Jack Westbrook ([@jackw](https://github.com/jackw))
10
+
11
+ ---
12
+
1
13
  # v6.7.0 (Wed Jan 07 2026)
2
14
 
3
15
  #### 🚀 Enhancement
@@ -1,39 +1,35 @@
1
1
  import * as v from 'valibot';
2
2
  import * as recast from 'recast';
3
- import * as typeScriptParser from 'recast/parsers/typescript.js';
4
3
  import { coerce, gte } from 'semver';
5
4
  import { additionsDebug } from '../../../utils.js';
6
5
  import { updateExternalsArray } from '../../../utils.externals.js';
7
6
 
8
7
  const { builders } = recast.types;
9
- const schema = v.object({});
10
8
  const PLUGIN_JSON_PATH = "src/plugin.json";
11
9
  const MIN_GRAFANA_VERSION = "10.2.0";
12
10
  const WEBPACK_CONFIG_PATH = ".config/webpack/webpack.config.ts";
13
11
  const RSPACK_CONFIG_PATH = ".config/rspack/rspack.config.ts";
14
- function isGrafanaUiRegex(element) {
15
- if (element.type === "RegExpLiteral") {
16
- const regexNode = element;
17
- return regexNode.pattern === "^@grafana\\/ui" && regexNode.flags === "i";
18
- }
19
- if (element.type === "Literal" && "regex" in element && element.regex) {
20
- const regex = element.regex;
21
- return regex.pattern === "^@grafana\\/ui" && regex.flags === "i";
22
- }
23
- return false;
12
+ const schema = v.object({});
13
+ function bundleGrafanaUI(context, _options) {
14
+ additionsDebug("Running bundle-grafana-ui addition...");
15
+ ensureMinGrafanaVersion(context);
16
+ updateExternalsArray(context, createBundleGrafanaUIModifier());
17
+ updateResolveExtensionsSimple(context);
18
+ updateModuleRulesSimple(context);
19
+ return context;
24
20
  }
25
- function isGrafanaDataRegex(element) {
21
+ function isGrafanaRegex(element, pattern) {
26
22
  if (element.type === "RegExpLiteral") {
27
23
  const regexNode = element;
28
- return regexNode.pattern === "^@grafana\\/data" && regexNode.flags === "i";
24
+ return regexNode.pattern === pattern && regexNode.flags === "i";
29
25
  }
30
26
  if (element.type === "Literal" && "regex" in element && element.regex) {
31
27
  const regex = element.regex;
32
- return regex.pattern === "^@grafana\\/data" && regex.flags === "i";
28
+ return regex.pattern === pattern && regex.flags === "i";
33
29
  }
34
30
  return false;
35
31
  }
36
- function modifyExternalsArray(externalsArray) {
32
+ function removeGrafanaUiAndAddReactInlineSvg(externalsArray) {
37
33
  let hasChanges = false;
38
34
  let hasGrafanaUiExternal = false;
39
35
  let hasReactInlineSvg = false;
@@ -41,7 +37,7 @@ function modifyExternalsArray(externalsArray) {
41
37
  if (!element) {
42
38
  continue;
43
39
  }
44
- if (isGrafanaUiRegex(element)) {
40
+ if (isGrafanaRegex(element, "^@grafana\\/ui")) {
45
41
  hasGrafanaUiExternal = true;
46
42
  }
47
43
  if ((element.type === "Literal" || element.type === "StringLiteral") && "value" in element && typeof element.value === "string" && element.value === "react-inlinesvg") {
@@ -53,7 +49,7 @@ function modifyExternalsArray(externalsArray) {
53
49
  if (!element) {
54
50
  return true;
55
51
  }
56
- return !isGrafanaUiRegex(element);
52
+ return !isGrafanaRegex(element, "^@grafana\\/ui");
57
53
  });
58
54
  hasChanges = true;
59
55
  additionsDebug("Removed /^@grafana\\/ui/i from externals array");
@@ -62,7 +58,7 @@ function modifyExternalsArray(externalsArray) {
62
58
  let insertIndex = -1;
63
59
  for (let i = 0; i < externalsArray.elements.length; i++) {
64
60
  const element = externalsArray.elements[i];
65
- if (element && isGrafanaDataRegex(element)) {
61
+ if (element && isGrafanaRegex(element, "^@grafana\\/data")) {
66
62
  insertIndex = i + 1;
67
63
  break;
68
64
  }
@@ -79,196 +75,69 @@ function modifyExternalsArray(externalsArray) {
79
75
  }
80
76
  function createBundleGrafanaUIModifier() {
81
77
  return (externalsArray) => {
82
- return modifyExternalsArray(externalsArray);
78
+ return removeGrafanaUiAndAddReactInlineSvg(externalsArray);
83
79
  };
84
80
  }
85
- function updateBundlerResolveConfig(context) {
86
- if (context.doesFileExist(RSPACK_CONFIG_PATH)) {
87
- additionsDebug(`Found ${RSPACK_CONFIG_PATH}, updating resolve configuration...`);
88
- const rspackContent = context.getFile(RSPACK_CONFIG_PATH);
89
- if (rspackContent) {
90
- try {
91
- const ast = recast.parse(rspackContent, {
92
- parser: typeScriptParser
93
- });
94
- let hasChanges = false;
95
- recast.visit(ast, {
96
- visitObjectExpression(path) {
97
- const { node } = path;
98
- const properties = node.properties;
99
- if (properties) {
100
- for (const prop of properties) {
101
- if (prop && (prop.type === "Property" || prop.type === "ObjectProperty")) {
102
- const key = "key" in prop ? prop.key : null;
103
- const value = "value" in prop ? prop.value : null;
104
- if (key && key.type === "Identifier" && key.name === "resolve" && value && value.type === "ObjectExpression") {
105
- hasChanges = updateResolveObject(value) || hasChanges;
106
- }
107
- if (key && key.type === "Identifier" && key.name === "module" && value && value.type === "ObjectExpression") {
108
- hasChanges = updateModuleRules(value) || hasChanges;
109
- }
110
- }
111
- }
112
- }
113
- return this.traverse(path);
114
- }
115
- });
116
- if (hasChanges) {
117
- const output = recast.print(ast, {
118
- tabWidth: 2,
119
- trailingComma: true,
120
- lineTerminator: "\n"
121
- });
122
- context.updateFile(RSPACK_CONFIG_PATH, output.code);
123
- additionsDebug(`Updated ${RSPACK_CONFIG_PATH}`);
124
- }
125
- } catch (error) {
126
- additionsDebug(`Error updating ${RSPACK_CONFIG_PATH}:`, error);
127
- }
128
- }
81
+ function updateResolveExtensionsSimple(context) {
82
+ const configPath = context.doesFileExist(RSPACK_CONFIG_PATH) ? RSPACK_CONFIG_PATH : context.doesFileExist(WEBPACK_CONFIG_PATH) ? WEBPACK_CONFIG_PATH : null;
83
+ if (!configPath) {
129
84
  return;
130
85
  }
131
- if (context.doesFileExist(WEBPACK_CONFIG_PATH)) {
132
- additionsDebug(`Found ${WEBPACK_CONFIG_PATH}, updating resolve configuration...`);
133
- const webpackContent = context.getFile(WEBPACK_CONFIG_PATH);
134
- if (webpackContent) {
135
- try {
136
- const ast = recast.parse(webpackContent, {
137
- parser: typeScriptParser
138
- });
139
- let hasChanges = false;
140
- recast.visit(ast, {
141
- visitObjectExpression(path) {
142
- const { node } = path;
143
- const properties = node.properties;
144
- if (properties) {
145
- for (const prop of properties) {
146
- if (prop && (prop.type === "Property" || prop.type === "ObjectProperty")) {
147
- const key = "key" in prop ? prop.key : null;
148
- const value = "value" in prop ? prop.value : null;
149
- if (key && key.type === "Identifier" && key.name === "resolve" && value && value.type === "ObjectExpression") {
150
- hasChanges = updateResolveObject(value) || hasChanges;
151
- }
152
- if (key && key.type === "Identifier" && key.name === "module" && value && value.type === "ObjectExpression") {
153
- hasChanges = updateModuleRules(value) || hasChanges;
154
- }
155
- }
156
- }
157
- }
158
- return this.traverse(path);
159
- }
160
- });
161
- if (hasChanges) {
162
- const output = recast.print(ast, {
163
- tabWidth: 2,
164
- trailingComma: true,
165
- lineTerminator: "\n"
166
- });
167
- context.updateFile(WEBPACK_CONFIG_PATH, output.code);
168
- additionsDebug(`Updated ${WEBPACK_CONFIG_PATH}`);
169
- }
170
- } catch (error) {
171
- additionsDebug(`Error updating ${WEBPACK_CONFIG_PATH}:`, error);
172
- }
173
- }
86
+ const content = context.getFile(configPath);
87
+ if (!content) {
88
+ return;
174
89
  }
175
- }
176
- function updateModuleRules(moduleObject) {
177
- if (!moduleObject.properties) {
178
- return false;
90
+ if (content.includes("'.mjs'") || content.includes('".mjs"')) {
91
+ return;
179
92
  }
180
- let hasChanges = false;
181
- let hasMjsRule = false;
182
- let rulesProperty = null;
183
- for (const prop of moduleObject.properties) {
184
- if (!prop || prop.type !== "Property" && prop.type !== "ObjectProperty") {
185
- continue;
186
- }
187
- const key = "key" in prop ? prop.key : null;
188
- const value = "value" in prop ? prop.value : null;
189
- if (key && key.type === "Identifier" && key.name === "rules" && value && value.type === "ArrayExpression") {
190
- rulesProperty = prop;
191
- for (const element of value.elements) {
192
- if (element && element.type === "ObjectExpression" && element.properties) {
193
- for (const ruleProp of element.properties) {
194
- if (ruleProp && (ruleProp.type === "Property" || ruleProp.type === "ObjectProperty") && "key" in ruleProp && ruleProp.key.type === "Identifier" && ruleProp.key.name === "test") {
195
- const testValue = "value" in ruleProp ? ruleProp.value : null;
196
- if (testValue) {
197
- if (testValue.type === "RegExpLiteral" && "pattern" in testValue && testValue.pattern === "\\.mjs$") {
198
- hasMjsRule = true;
199
- break;
200
- }
201
- if (testValue.type === "Literal" && "regex" in testValue && testValue.regex && typeof testValue.regex === "object" && "pattern" in testValue.regex && testValue.regex.pattern === "\\.mjs$") {
202
- hasMjsRule = true;
203
- break;
204
- }
205
- if (testValue.type === "Literal" && "value" in testValue && typeof testValue.value === "string" && testValue.value.includes(".mjs")) {
206
- hasMjsRule = true;
207
- break;
208
- }
209
- }
210
- }
211
- }
212
- }
213
- if (hasMjsRule) {
214
- break;
215
- }
216
- }
217
- break;
93
+ const updated = content.replace(/(extensions:\s*\[)([^\]]+)(\])/, (match, prefix, extensions, suffix) => {
94
+ if (extensions.includes(".mjs")) {
95
+ return match;
218
96
  }
97
+ return `${prefix}${extensions}, '.mjs'${suffix}`;
98
+ });
99
+ if (updated !== content) {
100
+ context.updateFile(configPath, updated);
101
+ additionsDebug("Added '.mjs' to resolve.extensions");
219
102
  }
220
- if (!hasMjsRule && rulesProperty && "value" in rulesProperty) {
221
- const rulesArray = rulesProperty.value;
222
- const mjsRule = builders.objectExpression([
223
- builders.property("init", builders.identifier("test"), builders.literal(/\.mjs$/)),
224
- builders.property("init", builders.identifier("include"), builders.literal(/node_modules/)),
225
- builders.property(
226
- "init",
227
- builders.identifier("resolve"),
228
- builders.objectExpression([
229
- builders.property("init", builders.identifier("fullySpecified"), builders.literal(false))
230
- ])
231
- ),
232
- builders.property("init", builders.identifier("type"), builders.literal("javascript/auto"))
233
- ]);
234
- rulesArray.elements.splice(1, 0, mjsRule);
235
- hasChanges = true;
236
- additionsDebug("Added module rule for .mjs files in node_modules with resolve.fullySpecified: false");
237
- }
238
- return hasChanges;
239
103
  }
240
- function updateResolveObject(resolveObject) {
241
- if (!resolveObject.properties) {
242
- return false;
104
+ function updateModuleRulesSimple(context) {
105
+ const configPath = context.doesFileExist(RSPACK_CONFIG_PATH) ? RSPACK_CONFIG_PATH : context.doesFileExist(WEBPACK_CONFIG_PATH) ? WEBPACK_CONFIG_PATH : null;
106
+ if (!configPath) {
107
+ return;
243
108
  }
244
- let hasChanges = false;
245
- let hasMjsExtension = false;
246
- let extensionsProperty = null;
247
- for (const prop of resolveObject.properties) {
248
- if (!prop || prop.type !== "Property" && prop.type !== "ObjectProperty") {
249
- continue;
250
- }
251
- const key = "key" in prop ? prop.key : null;
252
- const value = "value" in prop ? prop.value : null;
253
- if (key && key.type === "Identifier") {
254
- if (key.name === "extensions" && value && value.type === "ArrayExpression") {
255
- extensionsProperty = prop;
256
- for (const element of value.elements) {
257
- if (element && (element.type === "Literal" || element.type === "StringLiteral") && "value" in element && element.value === ".mjs") {
258
- hasMjsExtension = true;
259
- break;
260
- }
261
- }
109
+ const content = context.getFile(configPath);
110
+ if (!content) {
111
+ return;
112
+ }
113
+ if (content.includes("test: /\\.mjs$") || content.includes("test: /\\\\.mjs$")) {
114
+ return;
115
+ }
116
+ const mjsRule = `{
117
+ test: /\\.mjs$/,
118
+ include: /node_modules/,
119
+ resolve: {
120
+ fullySpecified: false,
121
+ },
122
+ type: 'javascript/auto',
123
+ },`;
124
+ let updated = content;
125
+ if (content.match(/rules:\s*\[\s*\]/)) {
126
+ updated = content.replace(/(rules:\s*\[\s*)(\])/, `$1${mjsRule}
127
+ $2`);
128
+ } else {
129
+ updated = content.replace(/(rules:\s*\[\s*)(\{[\s\S]*?\}),(\s*)/, (match, prefix, firstRule, suffix) => {
130
+ if (match.includes("test: /\\.mjs$")) {
131
+ return match;
262
132
  }
263
- }
133
+ return `${prefix}${firstRule},
134
+ ${mjsRule}${suffix}`;
135
+ });
264
136
  }
265
- if (!hasMjsExtension && extensionsProperty && "value" in extensionsProperty) {
266
- const extensionsArray = extensionsProperty.value;
267
- extensionsArray.elements.push(builders.literal(".mjs"));
268
- hasChanges = true;
269
- additionsDebug("Added '.mjs' to resolve.extensions");
137
+ if (updated !== content) {
138
+ context.updateFile(configPath, updated);
139
+ additionsDebug("Added module rule for .mjs files in node_modules with resolve.fullySpecified: false");
270
140
  }
271
- return hasChanges;
272
141
  }
273
142
  function ensureMinGrafanaVersion(context) {
274
143
  if (!context.doesFileExist(PLUGIN_JSON_PATH)) {
@@ -308,12 +177,5 @@ function ensureMinGrafanaVersion(context) {
308
177
  additionsDebug(`Error updating ${PLUGIN_JSON_PATH}:`, error);
309
178
  }
310
179
  }
311
- function bundleGrafanaUI(context, _options) {
312
- additionsDebug("Running bundle-grafana-ui addition...");
313
- ensureMinGrafanaVersion(context);
314
- updateExternalsArray(context, createBundleGrafanaUIModifier());
315
- updateBundlerResolveConfig(context);
316
- return context;
317
- }
318
180
 
319
181
  export { bundleGrafanaUI as default, schema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/create-plugin",
3
- "version": "6.8.0-canary.2356.20813241719.0",
3
+ "version": "6.8.0-canary.2356.20813982803.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": "a1f3d6909efb7fabc801bc7cc221803fffe0cc2f"
58
+ "gitHead": "fedff1c23ee3079d90f0ed2d6984c78fb294b1b9"
59
59
  }
@@ -1,6 +1,5 @@
1
1
  import * as v from 'valibot';
2
2
  import * as recast from 'recast';
3
- import * as typeScriptParser from 'recast/parsers/typescript.js';
4
3
  import { coerce, gte } from 'semver';
5
4
 
6
5
  import type { Context } from '../../../context.js';
@@ -9,47 +8,47 @@ import { updateExternalsArray, type ExternalsArrayModifier } from '../../../util
9
8
 
10
9
  const { builders } = recast.types;
11
10
 
12
- export const schema = v.object({});
13
-
14
- type BundleGrafanaUIOptions = v.InferOutput<typeof schema>;
15
-
16
11
  const PLUGIN_JSON_PATH = 'src/plugin.json';
17
12
  const MIN_GRAFANA_VERSION = '10.2.0';
18
13
  const WEBPACK_CONFIG_PATH = '.config/webpack/webpack.config.ts';
19
14
  const RSPACK_CONFIG_PATH = '.config/rspack/rspack.config.ts';
20
15
 
21
- /**
22
- * Checks if an AST node is a regex matching @grafana/ui
23
- * The pattern in the AST is "^@grafana\/ui" (backslash-escaped forward slash)
24
- */
25
- function isGrafanaUiRegex(element: recast.types.namedTypes.ASTNode): boolean {
26
- // Handle RegExpLiteral (TypeScript parser)
27
- if (element.type === 'RegExpLiteral') {
28
- const regexNode = element as recast.types.namedTypes.RegExpLiteral;
29
- return regexNode.pattern === '^@grafana\\/ui' && regexNode.flags === 'i';
30
- }
31
- // Handle Literal with regex property (other parsers)
32
- if (element.type === 'Literal' && 'regex' in element && element.regex) {
33
- const regex = element.regex as { pattern: string; flags: string };
34
- return regex.pattern === '^@grafana\\/ui' && regex.flags === 'i';
35
- }
36
- return false;
16
+ export const schema = v.object({});
17
+ type BundleGrafanaUIOptions = v.InferOutput<typeof schema>;
18
+
19
+ export default function bundleGrafanaUI(context: Context, _options: BundleGrafanaUIOptions): Context {
20
+ additionsDebug('Running bundle-grafana-ui addition...');
21
+
22
+ // Ensure minimum Grafana version requirement
23
+ ensureMinGrafanaVersion(context);
24
+
25
+ // Update externals array using the shared utility
26
+ updateExternalsArray(context, createBundleGrafanaUIModifier());
27
+
28
+ // Update bundler resolve configuration to handle ESM imports
29
+ updateResolveExtensionsSimple(context);
30
+
31
+ // Update module rules directly with simple string manipulation
32
+ updateModuleRulesSimple(context);
33
+
34
+ return context;
37
35
  }
38
36
 
39
37
  /**
40
- * Checks if an AST node is a regex matching @grafana/data
41
- * The pattern in the AST is "^@grafana\/data" (backslash-escaped forward slash)
38
+ * Checks if an AST node is a regex matching a Grafana package pattern
39
+ * @param element - The AST node to check
40
+ * @param pattern - The regex pattern to match (e.g., "^@grafana\\/ui" or "^@grafana\\/data")
42
41
  */
43
- function isGrafanaDataRegex(element: recast.types.namedTypes.ASTNode): boolean {
42
+ function isGrafanaRegex(element: recast.types.namedTypes.ASTNode, pattern: string): boolean {
44
43
  // Handle RegExpLiteral (TypeScript parser)
45
44
  if (element.type === 'RegExpLiteral') {
46
45
  const regexNode = element as recast.types.namedTypes.RegExpLiteral;
47
- return regexNode.pattern === '^@grafana\\/data' && regexNode.flags === 'i';
46
+ return regexNode.pattern === pattern && regexNode.flags === 'i';
48
47
  }
49
48
  // Handle Literal with regex property (other parsers)
50
49
  if (element.type === 'Literal' && 'regex' in element && element.regex) {
51
50
  const regex = element.regex as { pattern: string; flags: string };
52
- return regex.pattern === '^@grafana\\/data' && regex.flags === 'i';
51
+ return regex.pattern === pattern && regex.flags === 'i';
53
52
  }
54
53
  return false;
55
54
  }
@@ -58,7 +57,7 @@ function isGrafanaDataRegex(element: recast.types.namedTypes.ASTNode): boolean {
58
57
  * Removes /^@grafana\/ui/i regex from externals array and adds 'react-inlinesvg'
59
58
  * @returns true if changes were made, false otherwise
60
59
  */
61
- function modifyExternalsArray(externalsArray: recast.types.namedTypes.ArrayExpression): boolean {
60
+ function removeGrafanaUiAndAddReactInlineSvg(externalsArray: recast.types.namedTypes.ArrayExpression): boolean {
62
61
  let hasChanges = false;
63
62
  let hasGrafanaUiExternal = false;
64
63
  let hasReactInlineSvg = false;
@@ -70,7 +69,7 @@ function modifyExternalsArray(externalsArray: recast.types.namedTypes.ArrayExpre
70
69
  }
71
70
 
72
71
  // Check for /^@grafana\/ui/i regex
73
- if (isGrafanaUiRegex(element)) {
72
+ if (isGrafanaRegex(element, '^@grafana\\/ui')) {
74
73
  hasGrafanaUiExternal = true;
75
74
  }
76
75
 
@@ -91,7 +90,7 @@ function modifyExternalsArray(externalsArray: recast.types.namedTypes.ArrayExpre
91
90
  if (!element) {
92
91
  return true;
93
92
  }
94
- return !isGrafanaUiRegex(element);
93
+ return !isGrafanaRegex(element, '^@grafana\\/ui');
95
94
  });
96
95
  hasChanges = true;
97
96
  additionsDebug('Removed /^@grafana\\/ui/i from externals array');
@@ -103,7 +102,7 @@ function modifyExternalsArray(externalsArray: recast.types.namedTypes.ArrayExpre
103
102
  let insertIndex = -1;
104
103
  for (let i = 0; i < externalsArray.elements.length; i++) {
105
104
  const element = externalsArray.elements[i];
106
- if (element && isGrafanaDataRegex(element)) {
105
+ if (element && isGrafanaRegex(element, '^@grafana\\/data')) {
107
106
  insertIndex = i + 1;
108
107
  break;
109
108
  }
@@ -128,302 +127,108 @@ function modifyExternalsArray(externalsArray: recast.types.namedTypes.ArrayExpre
128
127
  */
129
128
  function createBundleGrafanaUIModifier(): ExternalsArrayModifier {
130
129
  return (externalsArray: recast.types.namedTypes.ArrayExpression) => {
131
- return modifyExternalsArray(externalsArray);
130
+ return removeGrafanaUiAndAddReactInlineSvg(externalsArray);
132
131
  };
133
132
  }
134
133
 
135
134
  /**
136
- * Updates the bundler's resolve configuration to handle ESM imports from @grafana/ui
137
- * - Adds '.mjs' to resolve.extensions
138
- * - Adds fullySpecified: false to allow extensionless imports in node_modules
135
+ * Updates resolve extensions to add .mjs using simple string manipulation
136
+ * This is a simplified approach that may not handle all edge cases, but is much simpler
139
137
  */
140
- function updateBundlerResolveConfig(context: Context): void {
141
- // Try rspack config first (newer structure)
142
- if (context.doesFileExist(RSPACK_CONFIG_PATH)) {
143
- additionsDebug(`Found ${RSPACK_CONFIG_PATH}, updating resolve configuration...`);
144
- const rspackContent = context.getFile(RSPACK_CONFIG_PATH);
145
- if (rspackContent) {
146
- try {
147
- const ast = recast.parse(rspackContent, {
148
- parser: typeScriptParser,
149
- });
150
-
151
- let hasChanges = false;
152
-
153
- recast.visit(ast, {
154
- visitObjectExpression(path) {
155
- const { node } = path;
156
- const properties = node.properties;
157
-
158
- if (properties) {
159
- for (const prop of properties) {
160
- if (prop && (prop.type === 'Property' || prop.type === 'ObjectProperty')) {
161
- const key = 'key' in prop ? prop.key : null;
162
- const value = 'value' in prop ? prop.value : null;
163
-
164
- // Find the resolve property
165
- if (
166
- key &&
167
- key.type === 'Identifier' &&
168
- key.name === 'resolve' &&
169
- value &&
170
- value.type === 'ObjectExpression'
171
- ) {
172
- hasChanges = updateResolveObject(value) || hasChanges;
173
- }
174
-
175
- // Find the module property to add .mjs rule
176
- if (
177
- key &&
178
- key.type === 'Identifier' &&
179
- key.name === 'module' &&
180
- value &&
181
- value.type === 'ObjectExpression'
182
- ) {
183
- hasChanges = updateModuleRules(value) || hasChanges;
184
- }
185
- }
186
- }
187
- }
188
-
189
- return this.traverse(path);
190
- },
191
- });
192
-
193
- if (hasChanges) {
194
- const output = recast.print(ast, {
195
- tabWidth: 2,
196
- trailingComma: true,
197
- lineTerminator: '\n',
198
- });
199
- context.updateFile(RSPACK_CONFIG_PATH, output.code);
200
- additionsDebug(`Updated ${RSPACK_CONFIG_PATH}`);
201
- }
202
- } catch (error) {
203
- additionsDebug(`Error updating ${RSPACK_CONFIG_PATH}:`, error);
204
- }
205
- }
138
+ function updateResolveExtensionsSimple(context: Context): void {
139
+ const configPath = context.doesFileExist(RSPACK_CONFIG_PATH)
140
+ ? RSPACK_CONFIG_PATH
141
+ : context.doesFileExist(WEBPACK_CONFIG_PATH)
142
+ ? WEBPACK_CONFIG_PATH
143
+ : null;
144
+
145
+ if (!configPath) {
206
146
  return;
207
147
  }
208
148
 
209
- // Fall back to webpack config (legacy structure)
210
- if (context.doesFileExist(WEBPACK_CONFIG_PATH)) {
211
- additionsDebug(`Found ${WEBPACK_CONFIG_PATH}, updating resolve configuration...`);
212
- const webpackContent = context.getFile(WEBPACK_CONFIG_PATH);
213
- if (webpackContent) {
214
- try {
215
- const ast = recast.parse(webpackContent, {
216
- parser: typeScriptParser,
217
- });
218
-
219
- let hasChanges = false;
220
-
221
- recast.visit(ast, {
222
- visitObjectExpression(path) {
223
- const { node } = path;
224
- const properties = node.properties;
225
-
226
- if (properties) {
227
- for (const prop of properties) {
228
- if (prop && (prop.type === 'Property' || prop.type === 'ObjectProperty')) {
229
- const key = 'key' in prop ? prop.key : null;
230
- const value = 'value' in prop ? prop.value : null;
231
-
232
- // Find the resolve property
233
- if (
234
- key &&
235
- key.type === 'Identifier' &&
236
- key.name === 'resolve' &&
237
- value &&
238
- value.type === 'ObjectExpression'
239
- ) {
240
- hasChanges = updateResolveObject(value) || hasChanges;
241
- }
242
-
243
- // Find the module property to add .mjs rule
244
- if (
245
- key &&
246
- key.type === 'Identifier' &&
247
- key.name === 'module' &&
248
- value &&
249
- value.type === 'ObjectExpression'
250
- ) {
251
- hasChanges = updateModuleRules(value) || hasChanges;
252
- }
253
- }
254
- }
255
- }
256
-
257
- return this.traverse(path);
258
- },
259
- });
260
-
261
- if (hasChanges) {
262
- const output = recast.print(ast, {
263
- tabWidth: 2,
264
- trailingComma: true,
265
- lineTerminator: '\n',
266
- });
267
- context.updateFile(WEBPACK_CONFIG_PATH, output.code);
268
- additionsDebug(`Updated ${WEBPACK_CONFIG_PATH}`);
269
- }
270
- } catch (error) {
271
- additionsDebug(`Error updating ${WEBPACK_CONFIG_PATH}:`, error);
272
- }
273
- }
149
+ const content = context.getFile(configPath);
150
+ if (!content) {
151
+ return;
274
152
  }
275
- }
276
153
 
277
- /**
278
- * Updates module rules to add a rule for .mjs files in node_modules with resolve.fullySpecified: false
279
- */
280
- function updateModuleRules(moduleObject: recast.types.namedTypes.ObjectExpression): boolean {
281
- if (!moduleObject.properties) {
282
- return false;
154
+ // Check if .mjs already exists
155
+ if (content.includes("'.mjs'") || content.includes('".mjs"')) {
156
+ return;
283
157
  }
284
158
 
285
- let hasChanges = false;
286
- let hasMjsRule = false;
287
- let rulesProperty: recast.types.namedTypes.Property | null = null;
288
-
289
- // Find the rules property
290
- for (const prop of moduleObject.properties) {
291
- if (!prop || (prop.type !== 'Property' && prop.type !== 'ObjectProperty')) {
292
- continue;
293
- }
294
-
295
- const key = 'key' in prop ? prop.key : null;
296
- const value = 'value' in prop ? prop.value : null;
297
-
298
- if (key && key.type === 'Identifier' && key.name === 'rules' && value && value.type === 'ArrayExpression') {
299
- rulesProperty = prop as recast.types.namedTypes.Property;
300
- // Check if .mjs rule already exists
301
- for (const element of value.elements) {
302
- if (element && element.type === 'ObjectExpression' && element.properties) {
303
- for (const ruleProp of element.properties) {
304
- if (
305
- ruleProp &&
306
- (ruleProp.type === 'Property' || ruleProp.type === 'ObjectProperty') &&
307
- 'key' in ruleProp &&
308
- ruleProp.key.type === 'Identifier' &&
309
- ruleProp.key.name === 'test'
310
- ) {
311
- const testValue = 'value' in ruleProp ? ruleProp.value : null;
312
- if (testValue) {
313
- // Check for RegExpLiteral with .mjs pattern
314
- if (testValue.type === 'RegExpLiteral' && 'pattern' in testValue && testValue.pattern === '\\.mjs$') {
315
- hasMjsRule = true;
316
- break;
317
- }
318
- // Check for Literal with regex property
319
- if (
320
- testValue.type === 'Literal' &&
321
- 'regex' in testValue &&
322
- testValue.regex &&
323
- typeof testValue.regex === 'object' &&
324
- 'pattern' in testValue.regex &&
325
- testValue.regex.pattern === '\\.mjs$'
326
- ) {
327
- hasMjsRule = true;
328
- break;
329
- }
330
- // Check for string literal containing .mjs
331
- if (
332
- testValue.type === 'Literal' &&
333
- 'value' in testValue &&
334
- typeof testValue.value === 'string' &&
335
- testValue.value.includes('.mjs')
336
- ) {
337
- hasMjsRule = true;
338
- break;
339
- }
340
- }
341
- }
342
- }
343
- }
344
- if (hasMjsRule) {
345
- break;
346
- }
347
- }
348
- break;
159
+ // Add .mjs to extensions array
160
+ const updated = content.replace(/(extensions:\s*\[)([^\]]+)(\])/, (match, prefix, extensions, suffix) => {
161
+ if (extensions.includes('.mjs')) {
162
+ return match;
349
163
  }
350
- }
164
+ return `${prefix}${extensions}, '.mjs'${suffix}`;
165
+ });
351
166
 
352
- // Add .mjs rule if missing (insert at position 1, after imports-loader rule which must be first)
353
- if (!hasMjsRule && rulesProperty && 'value' in rulesProperty) {
354
- const rulesArray = rulesProperty.value as recast.types.namedTypes.ArrayExpression;
355
- const mjsRule = builders.objectExpression([
356
- builders.property('init', builders.identifier('test'), builders.literal(/\.mjs$/)),
357
- builders.property('init', builders.identifier('include'), builders.literal(/node_modules/)),
358
- builders.property(
359
- 'init',
360
- builders.identifier('resolve'),
361
- builders.objectExpression([
362
- builders.property('init', builders.identifier('fullySpecified'), builders.literal(false)),
363
- ])
364
- ),
365
- builders.property('init', builders.identifier('type'), builders.literal('javascript/auto')),
366
- ]);
367
- // Insert at position 1 (second position) to keep imports-loader first
368
- rulesArray.elements.splice(1, 0, mjsRule);
369
- hasChanges = true;
370
- additionsDebug('Added module rule for .mjs files in node_modules with resolve.fullySpecified: false');
167
+ if (updated !== content) {
168
+ context.updateFile(configPath, updated);
169
+ additionsDebug("Added '.mjs' to resolve.extensions");
371
170
  }
372
-
373
- return hasChanges;
374
171
  }
375
172
 
376
173
  /**
377
- * Updates a resolve object expression to add .mjs extension
378
- * Note: We don't set fullySpecified: false globally because .mjs files need
379
- * rule-level configuration to override ESM's strict fully-specified import requirements
174
+ * Updates module rules to add .mjs rule using simple string manipulation
175
+ * This is a simplified approach that may not handle all edge cases, but is much simpler
380
176
  */
381
- function updateResolveObject(resolveObject: recast.types.namedTypes.ObjectExpression): boolean {
382
- if (!resolveObject.properties) {
383
- return false;
177
+ function updateModuleRulesSimple(context: Context): void {
178
+ const configPath = context.doesFileExist(RSPACK_CONFIG_PATH)
179
+ ? RSPACK_CONFIG_PATH
180
+ : context.doesFileExist(WEBPACK_CONFIG_PATH)
181
+ ? WEBPACK_CONFIG_PATH
182
+ : null;
183
+
184
+ if (!configPath) {
185
+ return;
384
186
  }
385
187
 
386
- let hasChanges = false;
387
- let hasMjsExtension = false;
388
- let extensionsProperty: recast.types.namedTypes.Property | null = null;
188
+ const content = context.getFile(configPath);
189
+ if (!content) {
190
+ return;
191
+ }
389
192
 
390
- // Check current state
391
- for (const prop of resolveObject.properties) {
392
- if (!prop || (prop.type !== 'Property' && prop.type !== 'ObjectProperty')) {
393
- continue;
394
- }
193
+ // Check if rule already exists
194
+ if (content.includes('test: /\\.mjs$') || content.includes('test: /\\\\.mjs$')) {
195
+ return;
196
+ }
395
197
 
396
- const key = 'key' in prop ? prop.key : null;
397
- const value = 'value' in prop ? prop.value : null;
398
-
399
- if (key && key.type === 'Identifier') {
400
- if (key.name === 'extensions' && value && value.type === 'ArrayExpression') {
401
- extensionsProperty = prop as recast.types.namedTypes.Property;
402
- // Check if .mjs is already in the extensions array
403
- for (const element of value.elements) {
404
- if (
405
- element &&
406
- (element.type === 'Literal' || element.type === 'StringLiteral') &&
407
- 'value' in element &&
408
- element.value === '.mjs'
409
- ) {
410
- hasMjsExtension = true;
411
- break;
412
- }
413
- }
198
+ const mjsRule = `{
199
+ test: /\\.mjs$/,
200
+ include: /node_modules/,
201
+ resolve: {
202
+ fullySpecified: false,
203
+ },
204
+ type: 'javascript/auto',
205
+ },`;
206
+
207
+ // Simple approach: find rules array and insert after first rule
208
+ let updated = content;
209
+
210
+ // Case 1: Empty array - insert at start
211
+ if (content.match(/rules:\s*\[\s*\]/)) {
212
+ updated = content.replace(/(rules:\s*\[\s*)(\])/, `$1${mjsRule}\n $2`);
213
+ }
214
+ // Case 2: Find first rule and insert after it
215
+ else {
216
+ // Match: rules: [ { ... }, and insert mjs rule after the first rule
217
+ // The regex finds the first complete rule object (balanced braces)
218
+ updated = content.replace(/(rules:\s*\[\s*)(\{[\s\S]*?\}),(\s*)/, (match, prefix, firstRule, suffix) => {
219
+ // Check if we already inserted (avoid double insertion)
220
+ if (match.includes('test: /\\.mjs$')) {
221
+ return match;
414
222
  }
415
- }
223
+ // Insert mjs rule after first rule
224
+ return `${prefix}${firstRule},\n ${mjsRule}${suffix}`;
225
+ });
416
226
  }
417
227
 
418
- // Add .mjs to extensions if missing
419
- if (!hasMjsExtension && extensionsProperty && 'value' in extensionsProperty) {
420
- const extensionsArray = extensionsProperty.value as recast.types.namedTypes.ArrayExpression;
421
- extensionsArray.elements.push(builders.literal('.mjs'));
422
- hasChanges = true;
423
- additionsDebug("Added '.mjs' to resolve.extensions");
228
+ if (updated !== content) {
229
+ context.updateFile(configPath, updated);
230
+ additionsDebug('Added module rule for .mjs files in node_modules with resolve.fullySpecified: false');
424
231
  }
425
-
426
- return hasChanges;
427
232
  }
428
233
 
429
234
  /**
@@ -471,18 +276,3 @@ function ensureMinGrafanaVersion(context: Context): void {
471
276
  additionsDebug(`Error updating ${PLUGIN_JSON_PATH}:`, error);
472
277
  }
473
278
  }
474
-
475
- export default function bundleGrafanaUI(context: Context, _options: BundleGrafanaUIOptions): Context {
476
- additionsDebug('Running bundle-grafana-ui addition...');
477
-
478
- // Ensure minimum Grafana version requirement
479
- ensureMinGrafanaVersion(context);
480
-
481
- // Update externals array using the shared utility
482
- updateExternalsArray(context, createBundleGrafanaUIModifier());
483
-
484
- // Update bundler resolve configuration to handle ESM imports
485
- updateBundlerResolveConfig(context);
486
-
487
- return context;
488
- }
@@ -0,0 +1,180 @@
1
+ import * as recast from 'recast';
2
+ import * as typeScriptParser from 'recast/parsers/typescript.js';
3
+
4
+ import type { Context } from './context.js';
5
+ import { additionsDebug } from './utils.js';
6
+
7
+ const WEBPACK_CONFIG_PATH = '.config/webpack/webpack.config.ts';
8
+ const RSPACK_CONFIG_PATH = '.config/rspack/rspack.config.ts';
9
+
10
+ /**
11
+ * Type for a function that modifies a resolve object expression
12
+ * @param resolveObject - The AST node representing the resolve configuration
13
+ * @returns true if changes were made, false otherwise
14
+ */
15
+ export type ResolveModifier = (resolveObject: recast.types.namedTypes.ObjectExpression) => boolean;
16
+
17
+ /**
18
+ * Type for a function that modifies a module rules array
19
+ * @param moduleObject - The AST node representing the module configuration
20
+ * @returns true if changes were made, false otherwise
21
+ */
22
+ export type ModuleRulesModifier = (moduleObject: recast.types.namedTypes.ObjectExpression) => boolean;
23
+
24
+ /**
25
+ * Updates the bundler's resolve and module configuration.
26
+ *
27
+ * This utility handles both webpack and rspack configurations, preferring rspack when both exist.
28
+ *
29
+ * @param context - The codemod context
30
+ * @param resolveModifier - Optional function to modify the resolve configuration
31
+ * @param moduleRulesModifier - Optional function to modify the module rules configuration
32
+ */
33
+ export function updateBundlerConfig(
34
+ context: Context,
35
+ resolveModifier?: ResolveModifier,
36
+ moduleRulesModifier?: ModuleRulesModifier
37
+ ): void {
38
+ if (!resolveModifier && !moduleRulesModifier) {
39
+ return;
40
+ }
41
+
42
+ // Try rspack config first (newer structure)
43
+ if (context.doesFileExist(RSPACK_CONFIG_PATH)) {
44
+ additionsDebug(`Found ${RSPACK_CONFIG_PATH}, updating bundler configuration...`);
45
+ const rspackContent = context.getFile(RSPACK_CONFIG_PATH);
46
+ if (rspackContent) {
47
+ try {
48
+ const ast = recast.parse(rspackContent, {
49
+ parser: typeScriptParser,
50
+ });
51
+
52
+ let hasChanges = false;
53
+
54
+ recast.visit(ast, {
55
+ visitObjectExpression(path) {
56
+ const { node } = path;
57
+ const properties = node.properties;
58
+
59
+ if (properties) {
60
+ for (const prop of properties) {
61
+ if (prop && (prop.type === 'Property' || prop.type === 'ObjectProperty')) {
62
+ const key = 'key' in prop ? prop.key : null;
63
+ const value = 'value' in prop ? prop.value : null;
64
+
65
+ // Find the resolve property
66
+ if (
67
+ resolveModifier &&
68
+ key &&
69
+ key.type === 'Identifier' &&
70
+ key.name === 'resolve' &&
71
+ value &&
72
+ value.type === 'ObjectExpression'
73
+ ) {
74
+ hasChanges = resolveModifier(value) || hasChanges;
75
+ }
76
+
77
+ // Find the module property
78
+ if (
79
+ moduleRulesModifier &&
80
+ key &&
81
+ key.type === 'Identifier' &&
82
+ key.name === 'module' &&
83
+ value &&
84
+ value.type === 'ObjectExpression'
85
+ ) {
86
+ hasChanges = moduleRulesModifier(value) || hasChanges;
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ return this.traverse(path);
93
+ },
94
+ });
95
+
96
+ if (hasChanges) {
97
+ const output = recast.print(ast, {
98
+ tabWidth: 2,
99
+ trailingComma: true,
100
+ lineTerminator: '\n',
101
+ });
102
+ context.updateFile(RSPACK_CONFIG_PATH, output.code);
103
+ additionsDebug(`Updated ${RSPACK_CONFIG_PATH}`);
104
+ }
105
+ } catch (error) {
106
+ additionsDebug(`Error updating ${RSPACK_CONFIG_PATH}:`, error);
107
+ }
108
+ }
109
+ return;
110
+ }
111
+
112
+ // Fall back to webpack config (legacy structure)
113
+ if (context.doesFileExist(WEBPACK_CONFIG_PATH)) {
114
+ additionsDebug(`Found ${WEBPACK_CONFIG_PATH}, updating bundler configuration...`);
115
+ const webpackContent = context.getFile(WEBPACK_CONFIG_PATH);
116
+ if (webpackContent) {
117
+ try {
118
+ const ast = recast.parse(webpackContent, {
119
+ parser: typeScriptParser,
120
+ });
121
+
122
+ let hasChanges = false;
123
+
124
+ recast.visit(ast, {
125
+ visitObjectExpression(path) {
126
+ const { node } = path;
127
+ const properties = node.properties;
128
+
129
+ if (properties) {
130
+ for (const prop of properties) {
131
+ if (prop && (prop.type === 'Property' || prop.type === 'ObjectProperty')) {
132
+ const key = 'key' in prop ? prop.key : null;
133
+ const value = 'value' in prop ? prop.value : null;
134
+
135
+ // Find the resolve property
136
+ if (
137
+ resolveModifier &&
138
+ key &&
139
+ key.type === 'Identifier' &&
140
+ key.name === 'resolve' &&
141
+ value &&
142
+ value.type === 'ObjectExpression'
143
+ ) {
144
+ hasChanges = resolveModifier(value) || hasChanges;
145
+ }
146
+
147
+ // Find the module property
148
+ if (
149
+ moduleRulesModifier &&
150
+ key &&
151
+ key.type === 'Identifier' &&
152
+ key.name === 'module' &&
153
+ value &&
154
+ value.type === 'ObjectExpression'
155
+ ) {
156
+ hasChanges = moduleRulesModifier(value) || hasChanges;
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ return this.traverse(path);
163
+ },
164
+ });
165
+
166
+ if (hasChanges) {
167
+ const output = recast.print(ast, {
168
+ tabWidth: 2,
169
+ trailingComma: true,
170
+ lineTerminator: '\n',
171
+ });
172
+ context.updateFile(WEBPACK_CONFIG_PATH, output.code);
173
+ additionsDebug(`Updated ${WEBPACK_CONFIG_PATH}`);
174
+ }
175
+ } catch (error) {
176
+ additionsDebug(`Error updating ${WEBPACK_CONFIG_PATH}:`, error);
177
+ }
178
+ }
179
+ }
180
+ }
@@ -21,7 +21,8 @@ import { externals } from '../bundler/externals';
21
21
  const { SubresourceIntegrityPlugin } = rspack.experiments;
22
22
  const pluginJson = getPluginJson();
23
23
  const cpVersion = getCPConfigVersion();
24
-
24
+ const logoPaths = Array.from(new Set([pluginJson.info?.logos?.large, pluginJson.info?.logos?.small])).filter(Boolean);
25
+ const screenshotPaths = pluginJson.info?.screenshots?.map((s: { path: string }) => s.path) || [];
25
26
  const virtualPublicPath = new RspackVirtualModulePlugin({
26
27
  'grafana-public-path': `
27
28
  import amdMetaModule from 'amd-module';
@@ -161,14 +162,13 @@ const config = async (env): Promise<Configuration> => {
161
162
  { from: 'plugin.json', to: '.' },
162
163
  { from: '../LICENSE', to: '.' },
163
164
  { from: '../CHANGELOG.md', to: '.', force: true },
164
- { from: '**/*.json', to: '.' }, // TODO<Add an error for checking the basic structure of the repo>
165
- { from: '**/*.svg', to: '.', noErrorOnMissing: true }, // Optional
166
- { from: '**/*.png', to: '.', noErrorOnMissing: true }, // Optional
167
- { from: '**/*.html', to: '.', noErrorOnMissing: true }, // Optional
168
- { from: 'img/**/*', to: '.', noErrorOnMissing: true }, // Optional
169
- { from: 'libs/**/*', to: '.', noErrorOnMissing: true }, // Optional
170
- { from: 'static/**/*', to: '.', noErrorOnMissing: true }, // Optional
171
- { from: '**/query_help.md', to: '.', noErrorOnMissing: true }, // Optional
165
+ { from: '**/*.json', to: '.' },
166
+ { from: '**/query_help.md', to: '.', noErrorOnMissing: true },
167
+ ...logoPaths.map((logoPath) => ({ from: logoPath, to: logoPath })),
168
+ ...screenshotPaths.map((screenshotPath) => ({
169
+ from: screenshotPath,
170
+ to: screenshotPath,
171
+ })),
172
172
  ],
173
173
  }),
174
174
  // Replace certain template-variables in the README and plugin.json
@@ -24,7 +24,8 @@ import { externals } from '../bundler/externals.ts';
24
24
  const pluginJson = getPluginJson();
25
25
  const cpVersion = getCPConfigVersion();
26
26
  const pluginVersion = getPackageJson().version;
27
-
27
+ const logoPaths = Array.from(new Set([pluginJson.info?.logos?.large, pluginJson.info?.logos?.small])).filter(Boolean);
28
+ const screenshotPaths = pluginJson.info?.screenshots?.map((s: { path: string }) => s.path) || [];
28
29
  const virtualPublicPath = new VirtualModulesPlugin({
29
30
  'node_modules/grafana-public-path.js': `
30
31
  import amdMetaModule from 'amd-module';
@@ -174,13 +175,12 @@ const config = async (env: Env): Promise<Configuration> => {
174
175
  { from: '../LICENSE', to: '.' },
175
176
  { from: '../CHANGELOG.md', to: '.', force: true },
176
177
  { from: '**/*.json', to: '.' },
177
- { from: '**/*.svg', to: '.', noErrorOnMissing: true },
178
- { from: '**/*.png', to: '.', noErrorOnMissing: true },
179
- { from: '**/*.html', to: '.', noErrorOnMissing: true },
180
- { from: 'img/**/*', to: '.', noErrorOnMissing: true },
181
- { from: 'libs/**/*', to: '.', noErrorOnMissing: true },
182
- { from: 'static/**/*', to: '.', noErrorOnMissing: true },
183
178
  { from: '**/query_help.md', to: '.', noErrorOnMissing: true },
179
+ ...logoPaths.map((logoPath) => ({ from: logoPath, to: logoPath })),
180
+ ...screenshotPaths.map((screenshotPath) => ({
181
+ from: screenshotPath,
182
+ to: screenshotPath,
183
+ })),
184
184
  ],
185
185
  }),
186
186
  // Replace certain template-variables in the README and plugin.json