@grafana/create-plugin 6.7.1-canary.2370.20798217827.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,8 +1,8 @@
1
1
  var defaultAdditions = [
2
2
  {
3
- name: "example-addition",
4
- description: "Adds an example addition to the plugin",
5
- scriptPath: import.meta.resolve("./scripts/example-addition.js")
3
+ name: "bundle-grafana-ui",
4
+ description: "Configures the plugin to bundle @grafana/ui instead of using the external provided by Grafana",
5
+ scriptPath: import.meta.resolve("./scripts/bundle-grafana-ui/index.js")
6
6
  }
7
7
  ];
8
8
 
@@ -0,0 +1,230 @@
1
+ import * as v from 'valibot';
2
+ import * as recast from 'recast';
3
+ import { coerce, gte } from 'semver';
4
+ import { additionsDebug } from '../../../utils.js';
5
+ import { updateBundlerConfig } from '../../../utils.bundler-config.js';
6
+ import { updateExternalsArray } from '../../../utils.externals.js';
7
+
8
+ const { builders } = recast.types;
9
+ const PLUGIN_JSON_PATH = "src/plugin.json";
10
+ const MIN_GRAFANA_VERSION = "10.2.0";
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
+ }
19
+ function isGrafanaUiRegex(element) {
20
+ if (element.type === "RegExpLiteral") {
21
+ const regexNode = element;
22
+ return regexNode.pattern === "^@grafana\\/ui" && regexNode.flags === "i";
23
+ }
24
+ if (element.type === "Literal" && "regex" in element && element.regex) {
25
+ const regex = element.regex;
26
+ return regex.pattern === "^@grafana\\/ui" && regex.flags === "i";
27
+ }
28
+ return false;
29
+ }
30
+ function isGrafanaDataRegex(element) {
31
+ if (element.type === "RegExpLiteral") {
32
+ const regexNode = element;
33
+ return regexNode.pattern === "^@grafana\\/data" && regexNode.flags === "i";
34
+ }
35
+ if (element.type === "Literal" && "regex" in element && element.regex) {
36
+ const regex = element.regex;
37
+ return regex.pattern === "^@grafana\\/data" && regex.flags === "i";
38
+ }
39
+ return false;
40
+ }
41
+ function removeGrafanaUiAndAddReactInlineSvg(externalsArray) {
42
+ let hasChanges = false;
43
+ let hasGrafanaUiExternal = false;
44
+ let hasReactInlineSvg = false;
45
+ for (const element of externalsArray.elements) {
46
+ if (!element) {
47
+ continue;
48
+ }
49
+ if (isGrafanaUiRegex(element)) {
50
+ hasGrafanaUiExternal = true;
51
+ }
52
+ if ((element.type === "Literal" || element.type === "StringLiteral") && "value" in element && typeof element.value === "string" && element.value === "react-inlinesvg") {
53
+ hasReactInlineSvg = true;
54
+ }
55
+ }
56
+ if (hasGrafanaUiExternal) {
57
+ externalsArray.elements = externalsArray.elements.filter((element) => {
58
+ if (!element) {
59
+ return true;
60
+ }
61
+ return !isGrafanaUiRegex(element);
62
+ });
63
+ hasChanges = true;
64
+ additionsDebug("Removed /^@grafana\\/ui/i from externals array");
65
+ }
66
+ if (!hasReactInlineSvg) {
67
+ let insertIndex = -1;
68
+ for (let i = 0; i < externalsArray.elements.length; i++) {
69
+ const element = externalsArray.elements[i];
70
+ if (element && isGrafanaDataRegex(element)) {
71
+ insertIndex = i + 1;
72
+ break;
73
+ }
74
+ }
75
+ if (insertIndex >= 0) {
76
+ externalsArray.elements.splice(insertIndex, 0, builders.literal("react-inlinesvg"));
77
+ } else {
78
+ externalsArray.elements.push(builders.literal("react-inlinesvg"));
79
+ }
80
+ hasChanges = true;
81
+ additionsDebug("Added 'react-inlinesvg' to externals array");
82
+ }
83
+ return hasChanges;
84
+ }
85
+ function createBundleGrafanaUIModifier() {
86
+ return (externalsArray) => {
87
+ return removeGrafanaUiAndAddReactInlineSvg(externalsArray);
88
+ };
89
+ }
90
+ function createResolveModifier() {
91
+ return (resolveObject) => {
92
+ if (!resolveObject.properties) {
93
+ return false;
94
+ }
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;
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
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
+ };
124
+ }
125
+ function createModuleRulesModifier() {
126
+ return (moduleObject) => {
127
+ if (!moduleObject.properties) {
128
+ return false;
129
+ }
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
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ if (hasMjsRule) {
164
+ break;
165
+ }
166
+ }
167
+ break;
168
+ }
169
+ }
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
+ };
190
+ }
191
+ function ensureMinGrafanaVersion(context) {
192
+ if (!context.doesFileExist(PLUGIN_JSON_PATH)) {
193
+ additionsDebug(`${PLUGIN_JSON_PATH} not found, skipping version check`);
194
+ return;
195
+ }
196
+ const pluginJsonRaw = context.getFile(PLUGIN_JSON_PATH);
197
+ if (!pluginJsonRaw) {
198
+ return;
199
+ }
200
+ try {
201
+ const pluginJson = JSON.parse(pluginJsonRaw);
202
+ if (!pluginJson.dependencies) {
203
+ pluginJson.dependencies = {};
204
+ }
205
+ const currentGrafanaDep = pluginJson.dependencies.grafanaDependency || ">=9.0.0";
206
+ const currentVersion = coerce(currentGrafanaDep.replace(/^[><=]+/, ""));
207
+ const minVersion = coerce(MIN_GRAFANA_VERSION);
208
+ if (!currentVersion || !minVersion || !gte(currentVersion, minVersion)) {
209
+ const oldVersion = pluginJson.dependencies.grafanaDependency || "not set";
210
+ pluginJson.dependencies.grafanaDependency = `>=${MIN_GRAFANA_VERSION}`;
211
+ context.updateFile(PLUGIN_JSON_PATH, JSON.stringify(pluginJson, null, 2));
212
+ additionsDebug(
213
+ `Updated grafanaDependency from "${oldVersion}" to ">=${MIN_GRAFANA_VERSION}" - bundling @grafana/ui requires Grafana ${MIN_GRAFANA_VERSION} or higher`
214
+ );
215
+ console.log(
216
+ `
217
+ \u26A0\uFE0F Updated grafanaDependency to ">=${MIN_GRAFANA_VERSION}" because bundling @grafana/ui is only supported from Grafana ${MIN_GRAFANA_VERSION} onwards.
218
+ `
219
+ );
220
+ } else {
221
+ additionsDebug(
222
+ `grafanaDependency "${currentGrafanaDep}" already meets minimum requirement of ${MIN_GRAFANA_VERSION}`
223
+ );
224
+ }
225
+ } catch (error) {
226
+ additionsDebug(`Error updating ${PLUGIN_JSON_PATH}:`, error);
227
+ }
228
+ }
229
+
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 };
@@ -0,0 +1,116 @@
1
+ import * as recast from 'recast';
2
+ import * as typeScriptParser from 'recast/parsers/typescript.js';
3
+ import { additionsDebug } from './utils.js';
4
+
5
+ const EXTERNALS_PATH = ".config/bundler/externals.ts";
6
+ const WEBPACK_CONFIG_PATH = ".config/webpack/webpack.config.ts";
7
+ function updateExternalsArray(context, modifier) {
8
+ if (context.doesFileExist(EXTERNALS_PATH)) {
9
+ additionsDebug(`Found ${EXTERNALS_PATH}, updating externals array...`);
10
+ const externalsContent = context.getFile(EXTERNALS_PATH);
11
+ if (externalsContent) {
12
+ try {
13
+ const ast = recast.parse(externalsContent, {
14
+ parser: typeScriptParser
15
+ });
16
+ let hasChanges = false;
17
+ recast.visit(ast, {
18
+ visitVariableDeclarator(path) {
19
+ const { node } = path;
20
+ if (node.id.type === "Identifier" && node.id.name === "externals" && node.init && node.init.type === "ArrayExpression") {
21
+ additionsDebug("Found externals array in externals.ts");
22
+ if (modifier(node.init)) {
23
+ hasChanges = true;
24
+ }
25
+ }
26
+ return this.traverse(path);
27
+ }
28
+ });
29
+ if (hasChanges) {
30
+ const output = recast.print(ast, {
31
+ tabWidth: 2,
32
+ trailingComma: true,
33
+ lineTerminator: "\n"
34
+ });
35
+ context.updateFile(EXTERNALS_PATH, output.code);
36
+ additionsDebug(`Updated ${EXTERNALS_PATH}`);
37
+ return true;
38
+ }
39
+ return false;
40
+ } catch (error) {
41
+ additionsDebug(`Error updating ${EXTERNALS_PATH}:`, error);
42
+ return false;
43
+ }
44
+ }
45
+ }
46
+ additionsDebug(`Checking for ${WEBPACK_CONFIG_PATH}...`);
47
+ if (context.doesFileExist(WEBPACK_CONFIG_PATH)) {
48
+ additionsDebug(`Found ${WEBPACK_CONFIG_PATH}, checking for inline externals...`);
49
+ const webpackContent = context.getFile(WEBPACK_CONFIG_PATH);
50
+ if (webpackContent) {
51
+ try {
52
+ const ast = recast.parse(webpackContent, {
53
+ parser: typeScriptParser
54
+ });
55
+ let hasChanges = false;
56
+ let foundExternals = false;
57
+ recast.visit(ast, {
58
+ visitObjectExpression(path) {
59
+ const { node } = path;
60
+ const properties = node.properties;
61
+ if (properties) {
62
+ for (const prop of properties) {
63
+ if (prop && (prop.type === "Property" || prop.type === "ObjectProperty")) {
64
+ const key = "key" in prop ? prop.key : null;
65
+ const value = "value" in prop ? prop.value : null;
66
+ if (key && key.type === "Identifier" && key.name === "externals" && value && value.type === "ArrayExpression") {
67
+ foundExternals = true;
68
+ additionsDebug("Found externals property in webpack.config.ts");
69
+ if (modifier(value)) {
70
+ hasChanges = true;
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ return this.traverse(path);
77
+ },
78
+ visitProperty(path) {
79
+ const { node } = path;
80
+ if (node.key && node.key.type === "Identifier" && node.key.name === "externals" && node.value && node.value.type === "ArrayExpression") {
81
+ if (!foundExternals) {
82
+ foundExternals = true;
83
+ additionsDebug("Found externals property in webpack.config.ts (via visitProperty)");
84
+ }
85
+ if (modifier(node.value)) {
86
+ hasChanges = true;
87
+ }
88
+ }
89
+ return this.traverse(path);
90
+ }
91
+ });
92
+ if (!foundExternals) {
93
+ additionsDebug("No externals property found in webpack.config.ts");
94
+ }
95
+ if (hasChanges) {
96
+ const output = recast.print(ast, {
97
+ tabWidth: 2,
98
+ trailingComma: true,
99
+ lineTerminator: "\n"
100
+ });
101
+ context.updateFile(WEBPACK_CONFIG_PATH, output.code);
102
+ additionsDebug(`Updated ${WEBPACK_CONFIG_PATH}`);
103
+ return true;
104
+ }
105
+ return false;
106
+ } catch (error) {
107
+ additionsDebug(`Error updating ${WEBPACK_CONFIG_PATH}:`, error);
108
+ return false;
109
+ }
110
+ }
111
+ }
112
+ additionsDebug("No externals configuration found");
113
+ return false;
114
+ }
115
+
116
+ export { updateExternalsArray };
@@ -205,6 +205,6 @@ function sortObjectByKeys(obj) {
205
205
  return Object.keys(obj).sort().reduce((acc, key) => ({ ...acc, [key]: obj[key] }), {});
206
206
  }
207
207
  const migrationsDebug = debug.extend("migrations");
208
- debug.extend("additions");
208
+ const additionsDebug = debug.extend("additions");
209
209
 
210
- export { addDependenciesToPackageJson, flushChanges, formatFiles, installNPMDependencies, isVersionGreater, migrationsDebug, printChanges, readJsonFile, removeDependenciesFromPackageJson };
210
+ export { addDependenciesToPackageJson, additionsDebug, flushChanges, formatFiles, installNPMDependencies, isVersionGreater, migrationsDebug, printChanges, readJsonFile, removeDependenciesFromPackageJson };
package/dist/constants.js CHANGED
@@ -30,7 +30,6 @@ const EXTRA_TEMPLATE_VARIABLES = {
30
30
  grafanaImage: "grafana-enterprise"
31
31
  };
32
32
  const DEFAULT_FEATURE_FLAGS = {
33
- bundleGrafanaUI: false,
34
33
  useExperimentalRspack: false,
35
34
  useExperimentalUpdates: true
36
35
  };
@@ -1,4 +1,4 @@
1
- import { DEFAULT_FEATURE_FLAGS, EXTRA_TEMPLATE_VARIABLES, PLUGIN_TYPES, TEMPLATE_PATHS, EXPORT_PATH_PREFIX } from '../constants.js';
1
+ import { EXTRA_TEMPLATE_VARIABLES, PLUGIN_TYPES, TEMPLATE_PATHS, EXPORT_PATH_PREFIX } from '../constants.js';
2
2
  import { isFile, getExportFileName, filterOutCommonFiles, isFileStartingWith } from './utils.files.js';
3
3
  import { getPackageManagerFromUserAgent, getPackageManagerInstallCmd, getPackageManagerWithFallback } from './utils.packageManager.js';
4
4
  import { normalizeId, renderHandlebarsTemplate } from './utils.handlebars.js';
@@ -56,7 +56,6 @@ function renderTemplateFromFile(templateFile, data) {
56
56
  function getTemplateData(cliArgs) {
57
57
  const { features } = getConfig();
58
58
  const currentVersion = CURRENT_APP_VERSION;
59
- const bundleGrafanaUI = features.bundleGrafanaUI ?? DEFAULT_FEATURE_FLAGS.bundleGrafanaUI;
60
59
  const isAppType = (pluginType) => pluginType === PLUGIN_TYPES.app || pluginType === PLUGIN_TYPES.scenes;
61
60
  const isNPM = (packageManagerName) => packageManagerName === "npm";
62
61
  const frontendBundler = features.useExperimentalRspack ? "rspack" : "webpack";
@@ -77,7 +76,6 @@ function getTemplateData(cliArgs) {
77
76
  isAppType: isAppType(cliArgs.pluginType),
78
77
  isNPM: isNPM(packageManagerName),
79
78
  version: currentVersion,
80
- bundleGrafanaUI,
81
79
  scenesVersion: "^6.10.4",
82
80
  useExperimentalRspack: Boolean(features.useExperimentalRspack),
83
81
  frontendBundler
@@ -98,7 +96,6 @@ function getTemplateData(cliArgs) {
98
96
  isAppType: isAppType(pluginJson.type),
99
97
  isNPM: isNPM(packageManagerName),
100
98
  version: currentVersion,
101
- bundleGrafanaUI,
102
99
  scenesVersion: "^6.10.4",
103
100
  pluginExecutable: pluginJson.executable,
104
101
  useExperimentalRspack: Boolean(features.useExperimentalRspack),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/create-plugin",
3
- "version": "6.7.1-canary.2370.20798217827.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": "bb9cbddbe1ddc57c7343dcaa5671238c128be212"
58
+ "gitHead": "7dc0a04f78794e1d23d24b13fa2dfaa5a4312bc3"
59
59
  }
@@ -2,8 +2,8 @@ import { Codemod } from '../types.js';
2
2
 
3
3
  export default [
4
4
  {
5
- name: 'example-addition',
6
- description: 'Adds an example addition to the plugin',
7
- scriptPath: import.meta.resolve('./scripts/example-addition.js'),
5
+ name: 'bundle-grafana-ui',
6
+ description: 'Configures the plugin to bundle @grafana/ui instead of using the external provided by Grafana',
7
+ scriptPath: import.meta.resolve('./scripts/bundle-grafana-ui/index.js'),
8
8
  },
9
9
  ] satisfies Codemod[];
@@ -0,0 +1,68 @@
1
+ # Bundle Grafana UI Addition
2
+
3
+ Configures a Grafana plugin to bundle `@grafana/ui` instead of using the version of `@grafana/ui` provided by the Grafana runtime environment.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx @grafana/create-plugin add bundle-grafana-ui
9
+ ```
10
+
11
+ ## Requirements
12
+
13
+ - **Grafana >= 10.2.0**: Bundling `@grafana/ui` is only supported from Grafana 10.2.0 onwards. If your plugin's `grafanaDependency` is set to a version lower than 10.2.0, the script will automatically update it to `>=10.2.0` and display a warning message.
14
+
15
+ ## What This Addition Does
16
+
17
+ By default, Grafana plugins use `@grafana/ui` as an external dependency provided by Grafana at runtime. This addition modifies your plugin's bundler configuration to include `@grafana/ui` in your plugin bundle instead.
18
+
19
+ This addition:
20
+
21
+ 1. **Updates `src/plugin.json`** - Ensures `grafanaDependency` is set to `>=10.2.0` or higher
22
+ 2. **Removes `/^@grafana\/ui/i` from externals** - This tells the bundler to include `@grafana/ui` in your plugin bundle rather than expecting Grafana to provide it
23
+ 3. **Adds `'react-inlinesvg'` to externals** - Since `@grafana/ui` uses `react-inlinesvg` internally and Grafana provides it at runtime, we add it to externals to avoid bundling it twice
24
+ 4. **Updates bundler resolve configuration** - Adds `.mjs` to `resolve.extensions` and sets `resolve.fullySpecified: false` to handle ESM imports from `@grafana/ui` and its dependencies (e.g., `rc-picker`, `ol/format/WKT`)
25
+
26
+ ## When to Use This
27
+
28
+ Consider bundling `@grafana/ui` when:
29
+
30
+ - You want to ensure consistent behavior across different Grafana versions
31
+ - You're experiencing compatibility issues with the Grafana-provided `@grafana/ui`
32
+
33
+ ## Trade-offs
34
+
35
+ **Pros:**
36
+
37
+ - Full control over the `@grafana/ui` version your plugin uses
38
+ - Consistent behavior regardless of Grafana version
39
+
40
+ **Cons:**
41
+
42
+ - Larger plugin bundle size
43
+ - Potential visual inconsistencies if your bundled version differs significantly from Grafana's version
44
+ - You'll need to manually update `@grafana/ui` in your plugin dependencies
45
+
46
+ ## Files Modified
47
+
48
+ ```
49
+ your-plugin/
50
+ ├── src/
51
+ │ └── plugin.json # Modified: grafanaDependency updated if needed
52
+ ├── .config/
53
+ │ ├── bundler/
54
+ │ │ └── externals.ts # Modified: removes @grafana/ui, adds react-inlinesvg
55
+ │ └── rspack/
56
+ │ └── rspack.config.ts # Modified: resolve.extensions and resolve.fullySpecified updated
57
+ ```
58
+
59
+ Or for legacy structure:
60
+
61
+ ```
62
+ your-plugin/
63
+ ├── src/
64
+ │ └── plugin.json # Modified: grafanaDependency updated if needed
65
+ ├── .config/
66
+ │ └── webpack/
67
+ │ └── webpack.config.ts # Modified: externals array and resolve configuration updated
68
+ ```