@grafana/create-plugin 6.7.1-canary.2370.20798217827.0 → 6.8.0-canary.2356.20813241719.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/dist/codemods/additions/additions.js +3 -3
- package/dist/codemods/additions/scripts/bundle-grafana-ui/index.js +319 -0
- package/dist/codemods/utils.externals.js +116 -0
- package/dist/codemods/utils.js +2 -2
- package/dist/constants.js +0 -1
- package/dist/utils/utils.templates.js +1 -4
- package/package.json +2 -2
- package/src/codemods/additions/additions.ts +3 -3
- package/src/codemods/additions/scripts/bundle-grafana-ui/README.md +68 -0
- package/src/codemods/additions/scripts/bundle-grafana-ui/index.test.ts +511 -0
- package/src/codemods/additions/scripts/bundle-grafana-ui/index.ts +488 -0
- package/src/codemods/utils.externals.test.ts +87 -0
- package/src/codemods/utils.externals.ts +181 -0
- package/src/constants.ts +0 -1
- package/src/types.ts +0 -1
- package/src/utils/tests/utils.config.test.ts +3 -25
- package/src/utils/utils.config.ts +0 -1
- package/src/utils/utils.templates.ts +1 -10
- package/templates/common/.config/webpack/BuildModeWebpackPlugin.ts +1 -1
- package/templates/common/.cprc.json +4 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
var defaultAdditions = [
|
|
2
2
|
{
|
|
3
|
-
name: "
|
|
4
|
-
description: "
|
|
5
|
-
scriptPath: import.meta.resolve("./scripts/
|
|
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,319 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
import * as recast from 'recast';
|
|
3
|
+
import * as typeScriptParser from 'recast/parsers/typescript.js';
|
|
4
|
+
import { coerce, gte } from 'semver';
|
|
5
|
+
import { additionsDebug } from '../../../utils.js';
|
|
6
|
+
import { updateExternalsArray } from '../../../utils.externals.js';
|
|
7
|
+
|
|
8
|
+
const { builders } = recast.types;
|
|
9
|
+
const schema = v.object({});
|
|
10
|
+
const PLUGIN_JSON_PATH = "src/plugin.json";
|
|
11
|
+
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";
|
|
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;
|
|
24
|
+
}
|
|
25
|
+
function isGrafanaDataRegex(element) {
|
|
26
|
+
if (element.type === "RegExpLiteral") {
|
|
27
|
+
const regexNode = element;
|
|
28
|
+
return regexNode.pattern === "^@grafana\\/data" && regexNode.flags === "i";
|
|
29
|
+
}
|
|
30
|
+
if (element.type === "Literal" && "regex" in element && element.regex) {
|
|
31
|
+
const regex = element.regex;
|
|
32
|
+
return regex.pattern === "^@grafana\\/data" && regex.flags === "i";
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function modifyExternalsArray(externalsArray) {
|
|
37
|
+
let hasChanges = false;
|
|
38
|
+
let hasGrafanaUiExternal = false;
|
|
39
|
+
let hasReactInlineSvg = false;
|
|
40
|
+
for (const element of externalsArray.elements) {
|
|
41
|
+
if (!element) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (isGrafanaUiRegex(element)) {
|
|
45
|
+
hasGrafanaUiExternal = true;
|
|
46
|
+
}
|
|
47
|
+
if ((element.type === "Literal" || element.type === "StringLiteral") && "value" in element && typeof element.value === "string" && element.value === "react-inlinesvg") {
|
|
48
|
+
hasReactInlineSvg = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (hasGrafanaUiExternal) {
|
|
52
|
+
externalsArray.elements = externalsArray.elements.filter((element) => {
|
|
53
|
+
if (!element) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return !isGrafanaUiRegex(element);
|
|
57
|
+
});
|
|
58
|
+
hasChanges = true;
|
|
59
|
+
additionsDebug("Removed /^@grafana\\/ui/i from externals array");
|
|
60
|
+
}
|
|
61
|
+
if (!hasReactInlineSvg) {
|
|
62
|
+
let insertIndex = -1;
|
|
63
|
+
for (let i = 0; i < externalsArray.elements.length; i++) {
|
|
64
|
+
const element = externalsArray.elements[i];
|
|
65
|
+
if (element && isGrafanaDataRegex(element)) {
|
|
66
|
+
insertIndex = i + 1;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (insertIndex >= 0) {
|
|
71
|
+
externalsArray.elements.splice(insertIndex, 0, builders.literal("react-inlinesvg"));
|
|
72
|
+
} else {
|
|
73
|
+
externalsArray.elements.push(builders.literal("react-inlinesvg"));
|
|
74
|
+
}
|
|
75
|
+
hasChanges = true;
|
|
76
|
+
additionsDebug("Added 'react-inlinesvg' to externals array");
|
|
77
|
+
}
|
|
78
|
+
return hasChanges;
|
|
79
|
+
}
|
|
80
|
+
function createBundleGrafanaUIModifier() {
|
|
81
|
+
return (externalsArray) => {
|
|
82
|
+
return modifyExternalsArray(externalsArray);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
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
|
+
}
|
|
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
|
+
}
|
|
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
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
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;
|
|
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;
|
|
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;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
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;
|
|
272
|
+
}
|
|
273
|
+
function ensureMinGrafanaVersion(context) {
|
|
274
|
+
if (!context.doesFileExist(PLUGIN_JSON_PATH)) {
|
|
275
|
+
additionsDebug(`${PLUGIN_JSON_PATH} not found, skipping version check`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const pluginJsonRaw = context.getFile(PLUGIN_JSON_PATH);
|
|
279
|
+
if (!pluginJsonRaw) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
const pluginJson = JSON.parse(pluginJsonRaw);
|
|
284
|
+
if (!pluginJson.dependencies) {
|
|
285
|
+
pluginJson.dependencies = {};
|
|
286
|
+
}
|
|
287
|
+
const currentGrafanaDep = pluginJson.dependencies.grafanaDependency || ">=9.0.0";
|
|
288
|
+
const currentVersion = coerce(currentGrafanaDep.replace(/^[><=]+/, ""));
|
|
289
|
+
const minVersion = coerce(MIN_GRAFANA_VERSION);
|
|
290
|
+
if (!currentVersion || !minVersion || !gte(currentVersion, minVersion)) {
|
|
291
|
+
const oldVersion = pluginJson.dependencies.grafanaDependency || "not set";
|
|
292
|
+
pluginJson.dependencies.grafanaDependency = `>=${MIN_GRAFANA_VERSION}`;
|
|
293
|
+
context.updateFile(PLUGIN_JSON_PATH, JSON.stringify(pluginJson, null, 2));
|
|
294
|
+
additionsDebug(
|
|
295
|
+
`Updated grafanaDependency from "${oldVersion}" to ">=${MIN_GRAFANA_VERSION}" - bundling @grafana/ui requires Grafana ${MIN_GRAFANA_VERSION} or higher`
|
|
296
|
+
);
|
|
297
|
+
console.log(
|
|
298
|
+
`
|
|
299
|
+
\u26A0\uFE0F Updated grafanaDependency to ">=${MIN_GRAFANA_VERSION}" because bundling @grafana/ui is only supported from Grafana ${MIN_GRAFANA_VERSION} onwards.
|
|
300
|
+
`
|
|
301
|
+
);
|
|
302
|
+
} else {
|
|
303
|
+
additionsDebug(
|
|
304
|
+
`grafanaDependency "${currentGrafanaDep}" already meets minimum requirement of ${MIN_GRAFANA_VERSION}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
} catch (error) {
|
|
308
|
+
additionsDebug(`Error updating ${PLUGIN_JSON_PATH}:`, error);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
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
|
+
|
|
319
|
+
export { bundleGrafanaUI as default, schema };
|
|
@@ -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 };
|
package/dist/codemods/utils.js
CHANGED
|
@@ -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
3
|
+
"version": "6.8.0-canary.2356.20813241719.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": "
|
|
58
|
+
"gitHead": "a1f3d6909efb7fabc801bc7cc221803fffe0cc2f"
|
|
59
59
|
}
|
|
@@ -2,8 +2,8 @@ import { Codemod } from '../types.js';
|
|
|
2
2
|
|
|
3
3
|
export default [
|
|
4
4
|
{
|
|
5
|
-
name: '
|
|
6
|
-
description: '
|
|
7
|
-
scriptPath: import.meta.resolve('./scripts/
|
|
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
|
+
```
|