@grafana/create-plugin 6.8.0-canary.2356.20813241719.0 → 6.8.0-canary.2356.20813734793.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.
@@ -1,16 +1,21 @@
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';
5
+ import { updateBundlerConfig } from '../../../utils.bundler-config.js';
6
6
  import { updateExternalsArray } from '../../../utils.externals.js';
7
7
 
8
8
  const { builders } = recast.types;
9
- const schema = v.object({});
10
9
  const PLUGIN_JSON_PATH = "src/plugin.json";
11
10
  const MIN_GRAFANA_VERSION = "10.2.0";
12
- const WEBPACK_CONFIG_PATH = ".config/webpack/webpack.config.ts";
13
- const RSPACK_CONFIG_PATH = ".config/rspack/rspack.config.ts";
11
+ const schema = v.object({});
12
+ function bundleGrafanaUI(context, _options) {
13
+ additionsDebug("Running bundle-grafana-ui addition...");
14
+ ensureMinGrafanaVersion(context);
15
+ updateExternalsArray(context, createBundleGrafanaUIModifier());
16
+ updateBundlerConfig(context, createResolveModifier(), createModuleRulesModifier());
17
+ return context;
18
+ }
14
19
  function isGrafanaUiRegex(element) {
15
20
  if (element.type === "RegExpLiteral") {
16
21
  const regexNode = element;
@@ -33,7 +38,7 @@ function isGrafanaDataRegex(element) {
33
38
  }
34
39
  return false;
35
40
  }
36
- function modifyExternalsArray(externalsArray) {
41
+ function removeGrafanaUiAndAddReactInlineSvg(externalsArray) {
37
42
  let hasChanges = false;
38
43
  let hasGrafanaUiExternal = false;
39
44
  let hasReactInlineSvg = false;
@@ -79,196 +84,109 @@ function modifyExternalsArray(externalsArray) {
79
84
  }
80
85
  function createBundleGrafanaUIModifier() {
81
86
  return (externalsArray) => {
82
- return modifyExternalsArray(externalsArray);
87
+ return removeGrafanaUiAndAddReactInlineSvg(externalsArray);
83
88
  };
84
89
  }
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
- }
90
+ function createResolveModifier() {
91
+ return (resolveObject) => {
92
+ if (!resolveObject.properties) {
93
+ return false;
128
94
  }
129
- return;
130
- }
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
- }
95
+ let hasChanges = false;
96
+ let hasMjsExtension = false;
97
+ let extensionsProperty = null;
98
+ for (const prop of resolveObject.properties) {
99
+ if (!prop || prop.type !== "Property" && prop.type !== "ObjectProperty") {
100
+ continue;
101
+ }
102
+ const key = "key" in prop ? prop.key : null;
103
+ const value = "value" in prop ? prop.value : null;
104
+ if (key && key.type === "Identifier") {
105
+ if (key.name === "extensions" && value && value.type === "ArrayExpression") {
106
+ extensionsProperty = prop;
107
+ for (const element of value.elements) {
108
+ if (element && (element.type === "Literal" || element.type === "StringLiteral") && "value" in element && element.value === ".mjs") {
109
+ hasMjsExtension = true;
110
+ break;
157
111
  }
158
- return this.traverse(path);
159
112
  }
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
113
  }
170
- } catch (error) {
171
- additionsDebug(`Error updating ${WEBPACK_CONFIG_PATH}:`, error);
172
114
  }
173
115
  }
174
- }
116
+ if (!hasMjsExtension && extensionsProperty && "value" in extensionsProperty) {
117
+ const extensionsArray = extensionsProperty.value;
118
+ extensionsArray.elements.push(builders.literal(".mjs"));
119
+ hasChanges = true;
120
+ additionsDebug("Added '.mjs' to resolve.extensions");
121
+ }
122
+ return hasChanges;
123
+ };
175
124
  }
176
- function updateModuleRules(moduleObject) {
177
- if (!moduleObject.properties) {
178
- return false;
179
- }
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;
125
+ function createModuleRulesModifier() {
126
+ return (moduleObject) => {
127
+ if (!moduleObject.properties) {
128
+ return false;
186
129
  }
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;
130
+ let hasChanges = false;
131
+ let hasMjsRule = false;
132
+ let rulesProperty = null;
133
+ for (const prop of moduleObject.properties) {
134
+ if (!prop || prop.type !== "Property" && prop.type !== "ObjectProperty") {
135
+ continue;
136
+ }
137
+ const key = "key" in prop ? prop.key : null;
138
+ const value = "value" in prop ? prop.value : null;
139
+ if (key && key.type === "Identifier" && key.name === "rules" && value && value.type === "ArrayExpression") {
140
+ rulesProperty = prop;
141
+ for (const element of value.elements) {
142
+ if (element && element.type === "ObjectExpression" && element.properties) {
143
+ for (const ruleProp of element.properties) {
144
+ if (ruleProp && (ruleProp.type === "Property" || ruleProp.type === "ObjectProperty") && "key" in ruleProp && ruleProp.key.type === "Identifier" && ruleProp.key.name === "test") {
145
+ const testValue = "value" in ruleProp ? ruleProp.value : null;
146
+ if (testValue) {
147
+ if (testValue.type === "RegExpLiteral" && "pattern" in testValue && testValue.pattern === "\\.mjs$") {
148
+ hasMjsRule = true;
149
+ break;
150
+ }
151
+ if (testValue.type === "Literal" && "regex" in testValue && testValue.regex && typeof testValue.regex === "object" && "pattern" in testValue.regex && testValue.regex.pattern === "\\.mjs$") {
152
+ hasMjsRule = true;
153
+ break;
154
+ }
155
+ if (testValue.type === "Literal" && "value" in testValue && typeof testValue.value === "string" && testValue.value.includes(".mjs")) {
156
+ hasMjsRule = true;
157
+ break;
158
+ }
208
159
  }
209
160
  }
210
161
  }
211
162
  }
212
- }
213
- if (hasMjsRule) {
214
- break;
215
- }
216
- }
217
- break;
218
- }
219
- }
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
- }
240
- function updateResolveObject(resolveObject) {
241
- if (!resolveObject.properties) {
242
- return false;
243
- }
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;
163
+ if (hasMjsRule) {
259
164
  break;
260
165
  }
261
166
  }
167
+ break;
262
168
  }
263
169
  }
264
- }
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");
270
- }
271
- return hasChanges;
170
+ if (!hasMjsRule && rulesProperty && "value" in rulesProperty) {
171
+ const rulesArray = rulesProperty.value;
172
+ const mjsRule = builders.objectExpression([
173
+ builders.property("init", builders.identifier("test"), builders.literal(/\.mjs$/)),
174
+ builders.property("init", builders.identifier("include"), builders.literal(/node_modules/)),
175
+ builders.property(
176
+ "init",
177
+ builders.identifier("resolve"),
178
+ builders.objectExpression([
179
+ builders.property("init", builders.identifier("fullySpecified"), builders.literal(false))
180
+ ])
181
+ ),
182
+ builders.property("init", builders.identifier("type"), builders.literal("javascript/auto"))
183
+ ]);
184
+ rulesArray.elements.splice(1, 0, mjsRule);
185
+ hasChanges = true;
186
+ additionsDebug("Added module rule for .mjs files in node_modules with resolve.fullySpecified: false");
187
+ }
188
+ return hasChanges;
189
+ };
272
190
  }
273
191
  function ensureMinGrafanaVersion(context) {
274
192
  if (!context.doesFileExist(PLUGIN_JSON_PATH)) {
@@ -308,12 +226,5 @@ function ensureMinGrafanaVersion(context) {
308
226
  additionsDebug(`Error updating ${PLUGIN_JSON_PATH}:`, error);
309
227
  }
310
228
  }
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
229
 
319
230
  export { bundleGrafanaUI as default, schema };
@@ -0,0 +1,102 @@
1
+ import * as recast from 'recast';
2
+ import * as typeScriptParser from 'recast/parsers/typescript.js';
3
+ import { additionsDebug } from './utils.js';
4
+
5
+ const WEBPACK_CONFIG_PATH = ".config/webpack/webpack.config.ts";
6
+ const RSPACK_CONFIG_PATH = ".config/rspack/rspack.config.ts";
7
+ function updateBundlerConfig(context, resolveModifier, moduleRulesModifier) {
8
+ if (!resolveModifier && !moduleRulesModifier) {
9
+ return;
10
+ }
11
+ if (context.doesFileExist(RSPACK_CONFIG_PATH)) {
12
+ additionsDebug(`Found ${RSPACK_CONFIG_PATH}, updating bundler configuration...`);
13
+ const rspackContent = context.getFile(RSPACK_CONFIG_PATH);
14
+ if (rspackContent) {
15
+ try {
16
+ const ast = recast.parse(rspackContent, {
17
+ parser: typeScriptParser
18
+ });
19
+ let hasChanges = false;
20
+ recast.visit(ast, {
21
+ visitObjectExpression(path) {
22
+ const { node } = path;
23
+ const properties = node.properties;
24
+ if (properties) {
25
+ for (const prop of properties) {
26
+ if (prop && (prop.type === "Property" || prop.type === "ObjectProperty")) {
27
+ const key = "key" in prop ? prop.key : null;
28
+ const value = "value" in prop ? prop.value : null;
29
+ if (resolveModifier && key && key.type === "Identifier" && key.name === "resolve" && value && value.type === "ObjectExpression") {
30
+ hasChanges = resolveModifier(value) || hasChanges;
31
+ }
32
+ if (moduleRulesModifier && key && key.type === "Identifier" && key.name === "module" && value && value.type === "ObjectExpression") {
33
+ hasChanges = moduleRulesModifier(value) || hasChanges;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return this.traverse(path);
39
+ }
40
+ });
41
+ if (hasChanges) {
42
+ const output = recast.print(ast, {
43
+ tabWidth: 2,
44
+ trailingComma: true,
45
+ lineTerminator: "\n"
46
+ });
47
+ context.updateFile(RSPACK_CONFIG_PATH, output.code);
48
+ additionsDebug(`Updated ${RSPACK_CONFIG_PATH}`);
49
+ }
50
+ } catch (error) {
51
+ additionsDebug(`Error updating ${RSPACK_CONFIG_PATH}:`, error);
52
+ }
53
+ }
54
+ return;
55
+ }
56
+ if (context.doesFileExist(WEBPACK_CONFIG_PATH)) {
57
+ additionsDebug(`Found ${WEBPACK_CONFIG_PATH}, updating bundler configuration...`);
58
+ const webpackContent = context.getFile(WEBPACK_CONFIG_PATH);
59
+ if (webpackContent) {
60
+ try {
61
+ const ast = recast.parse(webpackContent, {
62
+ parser: typeScriptParser
63
+ });
64
+ let hasChanges = false;
65
+ recast.visit(ast, {
66
+ visitObjectExpression(path) {
67
+ const { node } = path;
68
+ const properties = node.properties;
69
+ if (properties) {
70
+ for (const prop of properties) {
71
+ if (prop && (prop.type === "Property" || prop.type === "ObjectProperty")) {
72
+ const key = "key" in prop ? prop.key : null;
73
+ const value = "value" in prop ? prop.value : null;
74
+ if (resolveModifier && key && key.type === "Identifier" && key.name === "resolve" && value && value.type === "ObjectExpression") {
75
+ hasChanges = resolveModifier(value) || hasChanges;
76
+ }
77
+ if (moduleRulesModifier && key && key.type === "Identifier" && key.name === "module" && value && value.type === "ObjectExpression") {
78
+ hasChanges = moduleRulesModifier(value) || hasChanges;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ return this.traverse(path);
84
+ }
85
+ });
86
+ if (hasChanges) {
87
+ const output = recast.print(ast, {
88
+ tabWidth: 2,
89
+ trailingComma: true,
90
+ lineTerminator: "\n"
91
+ });
92
+ context.updateFile(WEBPACK_CONFIG_PATH, output.code);
93
+ additionsDebug(`Updated ${WEBPACK_CONFIG_PATH}`);
94
+ }
95
+ } catch (error) {
96
+ additionsDebug(`Error updating ${WEBPACK_CONFIG_PATH}:`, error);
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ export { updateBundlerConfig };
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.20813734793.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": "7dc0a04f78794e1d23d24b13fa2dfaa5a4312bc3"
59
59
  }
@@ -1,22 +1,34 @@
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';
7
6
  import { additionsDebug } from '../../../utils.js';
7
+ import { updateBundlerConfig, type ModuleRulesModifier, type ResolveModifier } from '../../../utils.bundler-config.js';
8
8
  import { updateExternalsArray, type ExternalsArrayModifier } from '../../../utils.externals.js';
9
9
 
10
10
  const { builders } = recast.types;
11
11
 
12
- export const schema = v.object({});
12
+ const PLUGIN_JSON_PATH = 'src/plugin.json';
13
+ const MIN_GRAFANA_VERSION = '10.2.0';
13
14
 
15
+ export const schema = v.object({});
14
16
  type BundleGrafanaUIOptions = v.InferOutput<typeof schema>;
15
17
 
16
- const PLUGIN_JSON_PATH = 'src/plugin.json';
17
- const MIN_GRAFANA_VERSION = '10.2.0';
18
- const WEBPACK_CONFIG_PATH = '.config/webpack/webpack.config.ts';
19
- const RSPACK_CONFIG_PATH = '.config/rspack/rspack.config.ts';
18
+ export default function bundleGrafanaUI(context: Context, _options: BundleGrafanaUIOptions): Context {
19
+ additionsDebug('Running bundle-grafana-ui addition...');
20
+
21
+ // Ensure minimum Grafana version requirement
22
+ ensureMinGrafanaVersion(context);
23
+
24
+ // Update externals array using the shared utility
25
+ updateExternalsArray(context, createBundleGrafanaUIModifier());
26
+
27
+ // Update bundler resolve configuration to handle ESM imports
28
+ updateBundlerConfig(context, createResolveModifier(), createModuleRulesModifier());
29
+
30
+ return context;
31
+ }
20
32
 
21
33
  /**
22
34
  * Checks if an AST node is a regex matching @grafana/ui
@@ -58,7 +70,7 @@ function isGrafanaDataRegex(element: recast.types.namedTypes.ASTNode): boolean {
58
70
  * Removes /^@grafana\/ui/i regex from externals array and adds 'react-inlinesvg'
59
71
  * @returns true if changes were made, false otherwise
60
72
  */
61
- function modifyExternalsArray(externalsArray: recast.types.namedTypes.ArrayExpression): boolean {
73
+ function removeGrafanaUiAndAddReactInlineSvg(externalsArray: recast.types.namedTypes.ArrayExpression): boolean {
62
74
  let hasChanges = false;
63
75
  let hasGrafanaUiExternal = false;
64
76
  let hasReactInlineSvg = false;
@@ -128,302 +140,163 @@ function modifyExternalsArray(externalsArray: recast.types.namedTypes.ArrayExpre
128
140
  */
129
141
  function createBundleGrafanaUIModifier(): ExternalsArrayModifier {
130
142
  return (externalsArray: recast.types.namedTypes.ArrayExpression) => {
131
- return modifyExternalsArray(externalsArray);
143
+ return removeGrafanaUiAndAddReactInlineSvg(externalsArray);
132
144
  };
133
145
  }
134
146
 
135
147
  /**
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
148
+ * Creates a modifier function for updateBundlerConfig that adds '.mjs' to resolve.extensions
139
149
  */
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
- }
150
+ function createResolveModifier(): ResolveModifier {
151
+ return (resolveObject: recast.types.namedTypes.ObjectExpression): boolean => {
152
+ if (!resolveObject.properties) {
153
+ return false;
205
154
  }
206
- return;
207
- }
208
155
 
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
- }
156
+ let hasChanges = false;
157
+ let hasMjsExtension = false;
158
+ let extensionsProperty: recast.types.namedTypes.Property | null = null;
242
159
 
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);
160
+ // Check current state
161
+ for (const prop of resolveObject.properties) {
162
+ if (!prop || (prop.type !== 'Property' && prop.type !== 'ObjectProperty')) {
163
+ continue;
272
164
  }
273
- }
274
- }
275
- }
276
165
 
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;
283
- }
166
+ const key = 'key' in prop ? prop.key : null;
167
+ const value = 'value' in prop ? prop.value : null;
284
168
 
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) {
169
+ if (key && key.type === 'Identifier') {
170
+ if (key.name === 'extensions' && value && value.type === 'ArrayExpression') {
171
+ extensionsProperty = prop as recast.types.namedTypes.Property;
172
+ // Check if .mjs is already in the extensions array
173
+ for (const element of value.elements) {
304
174
  if (
305
- ruleProp &&
306
- (ruleProp.type === 'Property' || ruleProp.type === 'ObjectProperty') &&
307
- 'key' in ruleProp &&
308
- ruleProp.key.type === 'Identifier' &&
309
- ruleProp.key.name === 'test'
175
+ element &&
176
+ (element.type === 'Literal' || element.type === 'StringLiteral') &&
177
+ 'value' in element &&
178
+ element.value === '.mjs'
310
179
  ) {
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
- }
180
+ hasMjsExtension = true;
181
+ break;
341
182
  }
342
183
  }
343
184
  }
344
- if (hasMjsRule) {
345
- break;
346
- }
347
185
  }
348
- break;
349
186
  }
350
- }
351
187
 
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');
371
- }
188
+ // Add .mjs to extensions if missing
189
+ if (!hasMjsExtension && extensionsProperty && 'value' in extensionsProperty) {
190
+ const extensionsArray = extensionsProperty.value as recast.types.namedTypes.ArrayExpression;
191
+ extensionsArray.elements.push(builders.literal('.mjs'));
192
+ hasChanges = true;
193
+ additionsDebug("Added '.mjs' to resolve.extensions");
194
+ }
372
195
 
373
- return hasChanges;
196
+ return hasChanges;
197
+ };
374
198
  }
375
199
 
376
200
  /**
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
201
+ * Creates a modifier function for updateBundlerConfig that adds a module rule for .mjs files
202
+ * in node_modules with resolve.fullySpecified: false
380
203
  */
381
- function updateResolveObject(resolveObject: recast.types.namedTypes.ObjectExpression): boolean {
382
- if (!resolveObject.properties) {
383
- return false;
384
- }
204
+ function createModuleRulesModifier(): ModuleRulesModifier {
205
+ return (moduleObject: recast.types.namedTypes.ObjectExpression): boolean => {
206
+ if (!moduleObject.properties) {
207
+ return false;
208
+ }
385
209
 
386
- let hasChanges = false;
387
- let hasMjsExtension = false;
388
- let extensionsProperty: recast.types.namedTypes.Property | null = null;
210
+ let hasChanges = false;
211
+ let hasMjsRule = false;
212
+ let rulesProperty: recast.types.namedTypes.Property | null = null;
389
213
 
390
- // Check current state
391
- for (const prop of resolveObject.properties) {
392
- if (!prop || (prop.type !== 'Property' && prop.type !== 'ObjectProperty')) {
393
- continue;
394
- }
214
+ // Find the rules property
215
+ for (const prop of moduleObject.properties) {
216
+ if (!prop || (prop.type !== 'Property' && prop.type !== 'ObjectProperty')) {
217
+ continue;
218
+ }
395
219
 
396
- const key = 'key' in prop ? prop.key : null;
397
- const value = 'value' in prop ? prop.value : null;
220
+ const key = 'key' in prop ? prop.key : null;
221
+ const value = 'value' in prop ? prop.value : null;
398
222
 
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
223
+ if (key && key.type === 'Identifier' && key.name === 'rules' && value && value.type === 'ArrayExpression') {
224
+ rulesProperty = prop as recast.types.namedTypes.Property;
225
+ // Check if .mjs rule already exists
403
226
  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;
227
+ if (element && element.type === 'ObjectExpression' && element.properties) {
228
+ for (const ruleProp of element.properties) {
229
+ if (
230
+ ruleProp &&
231
+ (ruleProp.type === 'Property' || ruleProp.type === 'ObjectProperty') &&
232
+ 'key' in ruleProp &&
233
+ ruleProp.key.type === 'Identifier' &&
234
+ ruleProp.key.name === 'test'
235
+ ) {
236
+ const testValue = 'value' in ruleProp ? ruleProp.value : null;
237
+ if (testValue) {
238
+ // Check for RegExpLiteral with .mjs pattern
239
+ if (testValue.type === 'RegExpLiteral' && 'pattern' in testValue && testValue.pattern === '\\.mjs$') {
240
+ hasMjsRule = true;
241
+ break;
242
+ }
243
+ // Check for Literal with regex property
244
+ if (
245
+ testValue.type === 'Literal' &&
246
+ 'regex' in testValue &&
247
+ testValue.regex &&
248
+ typeof testValue.regex === 'object' &&
249
+ 'pattern' in testValue.regex &&
250
+ testValue.regex.pattern === '\\.mjs$'
251
+ ) {
252
+ hasMjsRule = true;
253
+ break;
254
+ }
255
+ // Check for string literal containing .mjs
256
+ if (
257
+ testValue.type === 'Literal' &&
258
+ 'value' in testValue &&
259
+ typeof testValue.value === 'string' &&
260
+ testValue.value.includes('.mjs')
261
+ ) {
262
+ hasMjsRule = true;
263
+ break;
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+ if (hasMjsRule) {
411
270
  break;
412
271
  }
413
272
  }
273
+ break;
414
274
  }
415
275
  }
416
- }
417
276
 
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");
424
- }
277
+ // Add .mjs rule if missing (insert at position 1, after imports-loader rule which must be first)
278
+ if (!hasMjsRule && rulesProperty && 'value' in rulesProperty) {
279
+ const rulesArray = rulesProperty.value as recast.types.namedTypes.ArrayExpression;
280
+ const mjsRule = builders.objectExpression([
281
+ builders.property('init', builders.identifier('test'), builders.literal(/\.mjs$/)),
282
+ builders.property('init', builders.identifier('include'), builders.literal(/node_modules/)),
283
+ builders.property(
284
+ 'init',
285
+ builders.identifier('resolve'),
286
+ builders.objectExpression([
287
+ builders.property('init', builders.identifier('fullySpecified'), builders.literal(false)),
288
+ ])
289
+ ),
290
+ builders.property('init', builders.identifier('type'), builders.literal('javascript/auto')),
291
+ ]);
292
+ // Insert at position 1 (second position) to keep imports-loader first
293
+ rulesArray.elements.splice(1, 0, mjsRule);
294
+ hasChanges = true;
295
+ additionsDebug('Added module rule for .mjs files in node_modules with resolve.fullySpecified: false');
296
+ }
425
297
 
426
- return hasChanges;
298
+ return hasChanges;
299
+ };
427
300
  }
428
301
 
429
302
  /**
@@ -471,18 +344,3 @@ function ensureMinGrafanaVersion(context: Context): void {
471
344
  additionsDebug(`Error updating ${PLUGIN_JSON_PATH}:`, error);
472
345
  }
473
346
  }
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
+ }