From c7d9210e35b5e51bd6fc5a1a3b15bda84aae3ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20S=C3=BC=C3=9F?= Date: Tue, 26 May 2026 10:45:56 +0200 Subject: [PATCH 1/2] refactor: remove dependency on glob for created plugins --- .../templates/common/.config/bundler/utils.ts | 19 ++++++++++--------- .../templates/common/_package.json | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/create-plugin/templates/common/.config/bundler/utils.ts b/packages/create-plugin/templates/common/.config/bundler/utils.ts index 3b13b19379..7011c5e0a9 100644 --- a/packages/create-plugin/templates/common/.config/bundler/utils.ts +++ b/packages/create-plugin/templates/common/.config/bundler/utils.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import process from 'process'; import os from 'os'; import path from 'path'; -import { glob } from 'glob'; +import { glob } from 'node:fs/promises'; import { SOURCE_DIR } from './constants.ts'; export function isWSL() { @@ -46,14 +46,15 @@ export function hasReadme() { // Support bundling nested plugins by finding all plugin.json files in src directory // then checking for a sibling module.[jt]sx? file. export async function getEntries() { - const pluginsJson = await glob('**/src/**/plugin.json', { absolute: true }); - - const plugins = await Promise.all( - pluginsJson.map((pluginJson) => { - const folder = path.dirname(pluginJson); - return glob(`${folder}/module.{ts,tsx,js,jsx}`, { absolute: true }); - }) - ); + const plugins: string[][] = []; + for await (const pluginJson of glob('**/src/**/plugin.json')) { + const folder = path.dirname(pluginJson); + const modules: string[] = []; + for await (const module of glob(`${folder}/module.{ts,tsx,js,jsx}`)) { + modules.push(path.resolve(module)); + } + plugins.push(modules); + } return plugins.reduce>((result, modules) => { return modules.reduce((innerResult, module) => { diff --git a/packages/create-plugin/templates/common/_package.json b/packages/create-plugin/templates/common/_package.json index 716da43535..48b753fb40 100644 --- a/packages/create-plugin/templates/common/_package.json +++ b/packages/create-plugin/templates/common/_package.json @@ -45,7 +45,6 @@ "eslint-plugin-react-hooks": "^7.0.0", "eslint-webpack-plugin": "^5.0.0",{{#unless useExperimentalRspack}} "fork-ts-checker-webpack-plugin": "^9.1.0",{{/unless}} - "glob": "^11.1.0", "identity-obj-proxy": "^3.0.0", "imports-loader": "^5.0.0", "jest": "^29.7.0", From 6da8a0ad2b481fd9db63e7c8c32f29bdb230ea1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20S=C3=BC=C3=9F?= Date: Wed, 27 May 2026 12:34:58 +0200 Subject: [PATCH 2/2] chore: add codemod for glob replacement --- .../src/codemods/migrations/migrations.ts | 6 ++ ...012-webpack-remove-glob-dependency.test.ts | 65 +++++++++++++ .../012-webpack-remove-glob-dependency.ts | 93 +++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 packages/create-plugin/src/codemods/migrations/scripts/012-webpack-remove-glob-dependency.test.ts create mode 100644 packages/create-plugin/src/codemods/migrations/scripts/012-webpack-remove-glob-dependency.ts diff --git a/packages/create-plugin/src/codemods/migrations/migrations.ts b/packages/create-plugin/src/codemods/migrations/migrations.ts index 5652dbee0b..5d9e928846 100644 --- a/packages/create-plugin/src/codemods/migrations/migrations.ts +++ b/packages/create-plugin/src/codemods/migrations/migrations.ts @@ -78,6 +78,12 @@ export default [ 'Security: replace insecure inline `npx --yes @grafana/sign-plugin@latest` sign script with a locked @grafana/sign-plugin devDependency to prevent arbitrary code execution from a compromised @latest publish.', scriptPath: import.meta.resolve('./scripts/011-secure-sign-script.js'), }, + { + name: '012-webpack-remove-glob-dependency', + version: '7.4.1', + description: 'Removes a dependency on glob as the same functionality can be found in the nodejs standard library', + scriptPath: import.meta.resolve('./scripts/012-webpack-remove-glob-dependency.js'), + }, // Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run // for those written before the switch to updates as migrations. ] satisfies Migration[]; diff --git a/packages/create-plugin/src/codemods/migrations/scripts/012-webpack-remove-glob-dependency.test.ts b/packages/create-plugin/src/codemods/migrations/scripts/012-webpack-remove-glob-dependency.test.ts new file mode 100644 index 0000000000..6de31dc000 --- /dev/null +++ b/packages/create-plugin/src/codemods/migrations/scripts/012-webpack-remove-glob-dependency.test.ts @@ -0,0 +1,65 @@ +import migrate from './012-webpack-remove-glob-dependency.js'; +import { createDefaultContext } from '../../test-utils.js'; + +const originalContent = ` +import { glob } from 'glob'; + +// Support bundling nested plugins by finding all plugin.json files in src directory +// then checking for a sibling module.[jt]sx? file. +export async function getEntries() { + // mock content +} +`; + +describe('Migration - webpack remove glob dependency', () => { + test('should be idempotent', async () => { + const context = createDefaultContext(); + context.addFile('.config/webpack/utils.ts', originalContent); + + // run the migration once + const updated = migrate(context); + + // subsequent runs should be idempotent + await expect(migrate).toBeIdempotent(updated); + }); + + test('should replace function in both locations', () => { + const context = createDefaultContext(); + + context.addFile('.config/webpack/utils.ts', originalContent); + context.addFile('.config/bundler/utils.ts', originalContent); + + const updatedContext = migrate(context); + const bundlerUtilsContent = updatedContext.getFile('.config/bundler/utils.ts'); + const webpackUtilsContent = updatedContext.getFile('.config/webpack/utils.ts'); + + for (const content of [bundlerUtilsContent, webpackUtilsContent]) { + expect(content).not.toContain('mock content'); + expect(content).toContain('for await (const module of glob('); + } + }); + test('should remove dependency from package.json', () => { + const context = createDefaultContext(); + + context.addFile('.config/webpack/utils.ts', originalContent); + context.updateFile( + 'package.json', + JSON.stringify({ + devDependencies: { + '@grafana/eslint-config': '^9.0.0', + '@grafana/plugin-e2e': '^3.6.1', + '@grafana/tsconfig': '^2.0.1', + '@rspack/core': '^1.6.0', + '@stylistic/eslint-plugin-ts': '^4.4.0', + '@swc/core': '^1.15.0', + '@swc/helpers': '^0.5.0', + '@swc/jest': '^0.2.0', + glob: '11.0.0', + }, + }) + ); + const updatedContext = migrate(context); + const updatedPackageJSON = updatedContext.getFile('package.json'); + expect(updatedPackageJSON).not.toContain('glob'); + }); +}); diff --git a/packages/create-plugin/src/codemods/migrations/scripts/012-webpack-remove-glob-dependency.ts b/packages/create-plugin/src/codemods/migrations/scripts/012-webpack-remove-glob-dependency.ts new file mode 100644 index 0000000000..3549853cba --- /dev/null +++ b/packages/create-plugin/src/codemods/migrations/scripts/012-webpack-remove-glob-dependency.ts @@ -0,0 +1,93 @@ +import type { Context } from '../../context.js'; +import * as recast from 'recast'; +import * as typeScriptParser from 'recast/parsers/typescript.js'; +import { removeDependenciesFromPackageJson } from '../../utils.js'; + +const targetFnBody = (() => { + const raw = `export async function getEntries() { + const plugins: string[][] = []; + for await (const pluginJson of glob('**/src/**/plugin.json')) { + const folder = path.dirname(pluginJson); + const modules: string[] = []; + for await (const module of glob(\`\${folder}/module.{ts,tsx,js,jsx}\`)) { + modules.push(path.resolve(module)); + } + plugins.push(modules); + } + + return plugins.reduce>((result, modules) => { + return modules.reduce((innerResult, module) => { + const pluginPath = path.dirname(module); + const pluginName = path.relative(process.cwd(), pluginPath).replace(/src\\/?/i, ''); + const entryName = pluginName === '' ? 'module' : \`\${pluginName}/module\`; + + innerResult[entryName] = module; + return innerResult; + }, result); + }, {}); +}`; + const ast = recast.parse(raw, { + parser: typeScriptParser, + }); + let out: any = undefined; + recast.visit(ast, { + visitFunctionDeclaration(path) { + const { node } = path; + out = node.body; + return false; + }, + }); + return out; +})(); + +function updateUtils(context: Context, target: string) { + const webpackConfigContent = context.getFile(target); + if (!webpackConfigContent) { + return; + } + + const ast = recast.parse(webpackConfigContent, { + parser: typeScriptParser, + }); + + let hasChanges = true; + recast.visit(ast, { + visitImportDeclaration(path) { + const { node } = path; + if (node.source.value === 'glob') { + node.source.value = 'node:fs/promises'; + hasChanges = true; + } + return this.traverse(path); + }, + visitFunctionDeclaration(path) { + const { node } = path; + if (node.id && node.id.name === 'getEntries') { + node.body = targetFnBody; + } + return this.traverse(path); + }, + }); + + // Only update the file if we made changes + if (hasChanges) { + const output = recast.print(ast, { + tabWidth: 2, + trailingComma: true, + lineTerminator: '\n', + }); + context.updateFile(target, output.code); + } +} + +export default function migrate(context: Context) { + for (const f of ['.config/webpack/utils.ts', '.config/bundler/utils.ts']) { + if (context.doesFileExist(f)) { + updateUtils(context, f); + } + } + + removeDependenciesFromPackageJson(context, [], ['glob']); + + return context; +}