Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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',

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what to put here? Should this PR include the version bump or make a best guess?

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[];
Original file line number Diff line number Diff line change
@@ -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');
});
});
Original file line number Diff line number Diff line change
@@ -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<Record<string, string>>((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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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<Record<string, string>>((result, modules) => {
return modules.reduce((innerResult, module) => {
Expand Down
1 change: 0 additions & 1 deletion packages/create-plugin/templates/common/_package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading