From a1dae7180d23f7c3a84ea80780139b3ff76df041 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 9 Oct 2025 15:11:17 +0300 Subject: [PATCH] feat(eslint): relax frontend imports for nfs allow frontend plugins to import from other frontend plugins with same plugin id to allow plugin overrides without unnecessary eslint warning. relates to #31372 Signed-off-by: Hellgren Heikki --- .changeset/hungry-needles-clean.md | 8 +++++++ .../docs/rules/no-mixed-plugin-imports.md | 21 ++++++++++++++++++- packages/eslint-plugin/lib/getPackages.js | 2 +- .../rules/no-mixed-plugin-imports.js | 13 ++++++++++++ .../packages/bar-override/package.json | 19 +++++++++++++++++ .../monorepo/packages/bar/package.json | 3 ++- .../monorepo/packages/foo/package.json | 3 ++- 7 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 .changeset/hungry-needles-clean.md create mode 100644 packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar-override/package.json diff --git a/.changeset/hungry-needles-clean.md b/.changeset/hungry-needles-clean.md new file mode 100644 index 0000000000..6bab8f238e --- /dev/null +++ b/.changeset/hungry-needles-clean.md @@ -0,0 +1,8 @@ +--- +'@backstage/eslint-plugin': patch +--- + +Allow frontend plugin to import from another frontend plugin with same plugin id. + +This prevents the ESLint rule from incorrectly flagging these imports in the new frontend system +where plugin override requires cross-plugin imports. diff --git a/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md b/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md index e8570054e4..26a332d105 100644 --- a/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md +++ b/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md @@ -1,6 +1,25 @@ # @backstage/no-mixed-plugin-imports -Disallow mixed imports between backstage plugins. +This rule ensures that imports between Backstage plugins are consistent with their intended usage. + +The rule checks the `backstage.role` field in the `package.json` of both the importing and target packages to determine if they are compatible. + +Plugin roles include: + +- `frontend-plugin` (or `frontend` for short) +- `backend-plugin` (or `backend` for short) +- `web-library` (or `react` for short) +- `node-library` (or `node` for short) +- `common-library` (or `common` for short) + +Prohibited imports include: + +- A `frontend` plugin importing directly from another `frontend`, `backend`, or `node` package. Instead, it should import from the corresponding `react` or `common` package. + - With an exception with the new frontend system where frontend plugins with the same plugin id are allowed to import from each other. +- A `backend` plugin importing directly from another `backend`, `frontend`, or `react` package. Instead, it should import from the corresponding `node` or `common` package. +- A `react` package importing from `frontend`, `backend`, or `node` packages. +- A `node` package importing from `frontend`, `backend`, or `react` packages +- A `common` package importing directly from any other plugin package. ## Usage diff --git a/packages/eslint-plugin/lib/getPackages.js b/packages/eslint-plugin/lib/getPackages.js index e90536ff52..68739d358b 100644 --- a/packages/eslint-plugin/lib/getPackages.js +++ b/packages/eslint-plugin/lib/getPackages.js @@ -21,7 +21,7 @@ const manypkg = require('@manypkg/get-packages'); /** * @typedef ExtendedPackage - * @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record, files?: Array, backstage?: { inline?: boolean, role?: string } }}} packageJson + * @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record, files?: Array, backstage?: { inline?: boolean, role?: string, pluginId?: string } }}} packageJson */ /** diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js index b5e79b509e..cf8b3bde0b 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -146,11 +146,24 @@ module.exports = { } const sourceRole = pkg.packageJson.backstage?.role; + const sourcePluginId = pkg.packageJson.backstage?.pluginId; const targetRole = targetPackage.packageJson.backstage?.role; + const targetPluginId = targetPackage.packageJson.backstage?.pluginId; if (!sourceRole || !targetRole) { return; } + // Allow frontend plugins to import from other frontend plugins with the same pluginId for NFS + if ( + sourceRole === 'frontend-plugin' && + targetRole === 'frontend-plugin' && + sourcePluginId && + targetPluginId && + sourcePluginId === targetPluginId + ) { + return; + } + if ( roleRules.some( rule => diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar-override/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar-override/package.json new file mode 100644 index 0000000000..ff8996a0a2 --- /dev/null +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar-override/package.json @@ -0,0 +1,19 @@ +{ + "name": "@internal/bar-override", + "backstage": { + "role": "frontend-plugin", + "pluginId": "bar" + }, + "exports": { + ".": "./src/index.ts", + "./BarPage": "./src/components/Bar.tsx", + "./package.json": "./package.json" + }, + "dependencies": { + "inline-dep": "*", + "react-router": "*" + }, + "devDependencies": { + "react-router-dom": "*" + } +} diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json index ba55598dc2..bd60f93ce6 100644 --- a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json @@ -1,7 +1,8 @@ { "name": "@internal/bar", "backstage": { - "role": "frontend-plugin" + "role": "frontend-plugin", + "pluginId": "bar" }, "exports": { ".": "./src/index.ts", diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json index e8e4d4ba70..66be5cecf2 100644 --- a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json @@ -1,7 +1,8 @@ { "name": "@internal/foo", "backstage": { - "role": "frontend-plugin" + "role": "frontend-plugin", + "pluginId": "foo" }, "files": [ "dist",