From 063b2d39ce85ab6a48b5874d4ad377f60854f470 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 13 Jun 2025 01:46:04 +0300 Subject: [PATCH 1/5] feat: eslint rule to check forbidden plugin imports basically verify-local-dependencies.js but done during linting also in the 3rd party repositories. Signed-off-by: Hellgren Heikki --- .changeset/cold-crews-peel.md | 5 + .changeset/mean-pillows-enter.md | 12 ++ .eslintrc.js | 18 ++- package.json | 1 + packages/canon/.eslintrc.js | 1 + packages/eslint-plugin/README.md | 1 + .../docs/rules/no-mixed-plugin-imports.md | 67 ++++++++ packages/eslint-plugin/index.js | 2 + .../rules/no-mixed-plugin-imports.js | 149 ++++++++++++++++++ .../monorepo/packages/foo/package.json | 13 +- .../src/no-mixed-plugin-imports.test.ts | 68 ++++++++ .../package.json | 1 - .../gitlabGroupEnsureExists.examples.test.ts | 22 +-- .../actions/gitlabGroupEnsureExists.test.ts | 22 +-- .../gitlabIssueCreate.examples.test.ts | 22 +-- .../src/actions/gitlabIssueCreate.test.ts | 22 +-- .../actions/gitlabIssueEdit.examples.test.ts | 22 +-- .../src/actions/gitlabIssueEdit.test.ts | 22 +-- .../gitlabPipelineTrigger.examples.test.ts | 22 +-- .../src/actions/gitlabPipelineTrigger.test.ts | 22 +-- yarn.lock | 4 +- 21 files changed, 429 insertions(+), 89 deletions(-) create mode 100644 .changeset/cold-crews-peel.md create mode 100644 .changeset/mean-pillows-enter.md create mode 100644 packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md create mode 100644 packages/eslint-plugin/rules/no-mixed-plugin-imports.js create mode 100644 packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts diff --git a/.changeset/cold-crews-peel.md b/.changeset/cold-crews-peel.md new file mode 100644 index 0000000000..465982920e --- /dev/null +++ b/.changeset/cold-crews-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Fixed dependency to frontend package from tests diff --git a/.changeset/mean-pillows-enter.md b/.changeset/mean-pillows-enter.md new file mode 100644 index 0000000000..7775e2648f --- /dev/null +++ b/.changeset/mean-pillows-enter.md @@ -0,0 +1,12 @@ +--- +'@backstage/eslint-plugin': patch +--- + +Added new eslint rule to restrict mixed plugin imports. + +New rule `@backstage/no-mixed-plugin-imports` disallows mixed imports between plugins that are mixing +the backstage architecture. This rule forces that: + +- No imports from frontend plugins to backend plugins or other frontend plugins. +- No imports from backend plugins to frontend plugins or other backend plugins. +- No imports from common plugins to frontend or backend plugins. diff --git a/.eslintrc.js b/.eslintrc.js index 409b820783..58e14ee2d1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,8 +18,24 @@ var path = require('path'); module.exports = { root: true, - plugins: ['@spotify', 'notice', 'react', 'testing-library'], + plugins: ['@spotify', 'notice', 'react', 'testing-library', '@backstage'], rules: { + '@backstage/no-mixed-plugin-imports': [ + 'error', + { + // TODO: Fix these either by right role or by moving things to new packages + excludedTargetPackages: [ + '@backstage/test-utils', + '@backstage/config-loader', + '@backstage/plugin-catalog', + '@backstage/plugin-permission-backend', + '@backstage/plugin-app-backend', + '@backstage/plugin-techdocs', + '@backstage/plugin-app', + '@backstage/plugin-catalog-backend' + ], + } + ], 'react/react-in-jsx-scope': 'off', 'notice/notice': [ 'error', diff --git a/package.json b/package.json index fc773cb0d0..0b827a0ce3 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,7 @@ "@backstage/codemods": "workspace:*", "@backstage/create-app": "workspace:*", "@backstage/e2e-test-utils": "workspace:*", + "@backstage/eslint-plugin": "workspace:*", "@backstage/repo-tools": "workspace:*", "@changesets/cli": "^2.14.0", "@octokit/rest": "^19.0.3", diff --git a/packages/canon/.eslintrc.js b/packages/canon/.eslintrc.js index f7c0e8cbd1..9638ff6e45 100644 --- a/packages/canon/.eslintrc.js +++ b/packages/canon/.eslintrc.js @@ -3,6 +3,7 @@ module.exports = { extends: ['plugin:storybook/recommended'], rules: { 'react/forbid-elements': 'off', + '@backstage/no-mixed-plugin-imports': 'off' }, }; diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 600b3268e1..e30b9e5de3 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -41,3 +41,4 @@ The following rules are provided by this plugin: | [@backstage/no-relative-monorepo-imports](./docs/rules/no-relative-monorepo-imports.md) | Forbid relative imports that reach outside of the package in a monorepo. | | [@backstage/no-undeclared-imports](./docs/rules/no-undeclared-imports.md) | Forbid imports of external packages that have not been declared in the appropriate dependencies field in `package.json`. | | [@backstage/no-top-level-material-ui-4-imports](./docs/rules/no-top-level-material-ui-4-imports.md) | Forbid top level import from Material UI v4 packages. | +| [@backstage/no-mixed-plugin-imports](./docs/rules/no-mixed-plugin-imports.md) | Disallow mixed 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 new file mode 100644 index 0000000000..e8570054e4 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md @@ -0,0 +1,67 @@ +# @backstage/no-mixed-plugin-imports + +Disallow mixed imports between backstage plugins. + +## Usage + +Add the rules as follows, it has no options: + +```js +"@backstage/no-mixed-plugin-imports": ["error"] +``` + +## Rule Details + +Given the following two target packages: + +```json +{ + "name": "@backstage/plugin-foo", + "backstage": { + "role": "frontend-plugin" + } +} +``` + +```json +{ + "name": "@backstage/plugin-bar", + "backstage": { + "role": "frontend-plugin" + } +} +``` + +### Fail + +```ts +import { FooCard } from '@backstage/plugin-foo'; +``` + +### Pass + +```ts +import { FooCard } from '@backstage/plugin-foo-react'; +``` + +## Options + +You can ignore specific target packages or files by adding them to the options in the `.eslintrc.js` file: + +```js +{ + rules: { + '@backstage/no-mixed-plugin-imports': [ + 'error', + { + excludedTargetPackages: [ + '@backstage/plugin-foo', + ], + excludedFiles: [ + '**/*.{test,spec}.[jt]s?(x)' + ], + } + ] + } +} +``` diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js index af1a8891c3..a1e38e4269 100644 --- a/packages/eslint-plugin/index.js +++ b/packages/eslint-plugin/index.js @@ -22,6 +22,7 @@ module.exports = { '@backstage/no-forbidden-package-imports': 'error', '@backstage/no-relative-monorepo-imports': 'error', '@backstage/no-undeclared-imports': 'error', + '@backstage/no-mixed-plugin-imports': 'error', }, }, }, @@ -30,5 +31,6 @@ module.exports = { 'no-relative-monorepo-imports': require('./rules/no-relative-monorepo-imports'), 'no-undeclared-imports': require('./rules/no-undeclared-imports'), 'no-top-level-material-ui-4-imports': require('./rules/no-top-level-material-ui-4-imports'), + 'no-mixed-plugin-imports': require('./rules/no-mixed-plugin-imports'), }, }; diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js new file mode 100644 index 0000000000..90d3448b7a --- /dev/null +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -0,0 +1,149 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +const visitImports = require('../lib/visitImports'); +const getPackages = require('../lib/getPackages'); +const minimatch = require('minimatch'); + +const roleRules = [ + { + sourceRole: ['frontend-plugin', 'web-library'], + targetRole: [ + 'backend-plugin', + 'node-library', + 'backend-plugin-module', + 'frontend-plugin', + ], + }, + { + sourceRole: ['backend-plugin', 'node-library', 'backend-plugin-module'], + targetRole: ['frontend-plugin', 'web-library', 'backend-plugin'], + }, + { + sourceRole: ['common-library'], + targetRole: [ + 'frontend-plugin', + 'web-library', + 'backend-plugin', + 'node-library', + 'backend-plugin-module', + ], + }, +]; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + messages: { + forbidden: + '{{sourcePackage}} ({{sourceRole}}) uses forbidden import from {{targetPackage}} ({{targetRole}}).', + }, + docs: { + description: 'Disallow mixed plugin imports.', + url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md', + }, + schema: [ + { + type: 'object', + properties: { + excludedTargetPackages: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + }, + excludedFiles: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + }, + create(context) { + const packages = getPackages(context.cwd); + if (!packages) { + return {}; + } + + const filePath = context.physicalFilename + ? context.physicalFilename + : context.filename; + + const pkg = packages.byPath(filePath); + if (!pkg) { + return {}; + } + + const options = context.options[0] || {}; + const ignoreTargetPackages = options.excludedTargetPackages || []; + const ignorePatterns = options.excludedFiles || [ + '**/*.{test,spec}.[jt]s?(x)', + '**/dev/index.[jt]s?(x)', + ]; + + if ( + ignorePatterns.some(pattern => + new minimatch.Minimatch(pattern).match(context.filename), + ) + ) { + return {}; + } + + return visitImports(context, (node, imp) => { + if (imp.type !== 'internal') { + return; + } + + const targetPackage = imp.package; + const targetName = targetPackage.packageJson.name; + const sourceName = pkg.packageJson.name; + if (sourceName === targetName) { + return; + } + + const sourceRole = pkg.packageJson.backstage?.role; + const targetRole = targetPackage.packageJson.backstage?.role; + if (!sourceRole || !targetRole) { + return; + } + + if ( + roleRules.some( + rule => + rule.sourceRole.includes(sourceRole) && + rule.targetRole.includes(targetRole) && + !ignoreTargetPackages.includes(targetName), + ) + ) { + context.report({ + node: node, + messageId: 'forbidden', + data: { + sourcePackage: pkg.packageJson.name || imp.package.dir, + sourceRole, + targetPackage: targetPackage.packageJson.name || imp.package.dir, + targetRole, + }, + }); + } + }); + }, +}; 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 563ec35120..e8e4d4ba70 100644 --- a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json @@ -1,5 +1,12 @@ { "name": "@internal/foo", + "backstage": { + "role": "frontend-plugin" + }, + "files": [ + "dist", + "type-utils" + ], "dependencies": { "@internal/bar": "1.0.0" }, @@ -8,9 +15,5 @@ }, "peerDependencies": { "react": "*" - }, - "files": [ - "dist", - "type-utils" - ] + } } diff --git a/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts b/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts new file mode 100644 index 0000000000..45764ca71a --- /dev/null +++ b/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RuleTester } from 'eslint'; +import path from 'path'; +import rule from '../rules/no-mixed-plugin-imports'; + +const RULE = 'no-mixed-plugin-imports'; +const FIXTURE = path.resolve(__dirname, '__fixtures__/monorepo'); + +const ERR = ( + sourcePackage: string, + sourceRole: string, + targetPackage: string, + targetRole: string, +) => ({ + message: `${sourcePackage} (${sourceRole}) uses forbidden import from ${targetPackage} (${targetRole}).`, +}); + +// cwd must be restored +const origDir = process.cwd(); +afterAll(() => { + process.chdir(origDir); +}); +process.chdir(FIXTURE); + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + }, +}); + +ruleTester.run(RULE, rule, { + valid: [ + { + code: `import '@internal/inline'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + }, + ], + invalid: [ + { + code: `import '@internal/foo'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR( + '@internal/bar', + 'frontend-plugin', + '@internal/foo', + 'frontend-plugin', + ), + ], + }, + ], +}); diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 4a451ce6fa..0d822bbd63 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -59,7 +59,6 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" } } diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index 4d8e0fd0c0..92892b787b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; import { examples } from './gitlabGroupEnsureExists.examples'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Groups: { @@ -50,15 +50,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts index 3d703eb91f..8e52ee999e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; import { getClient } from '../util'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Groups: { @@ -45,15 +45,17 @@ describe('gitlab:group:ensureExists', () => { jest.clearAllMocks(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts index 917ba61d91..858adb4b99 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createGitlabIssueAction } from './gitlabIssueCreate'; import { examples } from './gitlabIssueCreate.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -46,15 +46,17 @@ describe('gitlab:issues:create', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'sample-token', - apiBaseUrl: 'https://gitlab.com/api/v1', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'sample-token', + apiBaseUrl: 'https://gitlab.com/api/v1', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts index d5aa2d1e17..e09bfaf249 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { IssueType } from '../commonGitlabConfig'; import { createGitlabIssueAction } from './gitlabIssueCreate'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -45,15 +45,17 @@ describe('gitlab:issues:create', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts index 7e50eac66c..fa53609c19 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts @@ -15,11 +15,11 @@ */ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { editGitlabIssueAction } from './gitlabIssueEdit'; import { examples } from './gitlabIssueEdit.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -46,15 +46,17 @@ describe('gitlab:issue:edit', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts index 9604fe8fe2..a16236a509 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts @@ -15,10 +15,10 @@ */ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { IssueType } from '../commonGitlabConfig'; import { editGitlabIssueAction } from './gitlabIssueEdit'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -45,15 +45,17 @@ describe('gitlab:issue:edit', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts index 53cbfb0b87..41535479fe 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; import { examples } from './gitlabPipelineTrigger.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { PipelineTriggerTokens: { @@ -49,15 +49,17 @@ describe('gitlab:pipeline:trigger', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'glpat-abcdef', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts index 3803198ec8..89498c52f2 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { PipelineTriggerTokens: { @@ -47,15 +47,17 @@ describe('gitlab:pipeline:trigger', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'glpat-abcdef', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/yarn.lock b/yarn.lock index ab101b1be1..d3fc3ea273 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4598,7 +4598,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/eslint-plugin@workspace:^, @backstage/eslint-plugin@workspace:packages/eslint-plugin": +"@backstage/eslint-plugin@workspace:*, @backstage/eslint-plugin@workspace:^, @backstage/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@backstage/eslint-plugin@workspace:packages/eslint-plugin" dependencies: @@ -7702,7 +7702,6 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" - "@backstage/core-app-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" @@ -45419,6 +45418,7 @@ __metadata: "@backstage/create-app": "workspace:*" "@backstage/e2e-test-utils": "workspace:*" "@backstage/errors": "workspace:^" + "@backstage/eslint-plugin": "workspace:*" "@backstage/repo-tools": "workspace:*" "@changesets/cli": "npm:^2.14.0" "@manypkg/get-packages": "npm:^1.1.3" From 2788a08dad826cca465248c6a96a6e0d2a0149f6 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 13 Jun 2025 08:23:29 +0300 Subject: [PATCH 2/5] fix: tsc errors and rest of the tests Signed-off-by: Hellgren Heikki --- packages/eslint-plugin/lib/getPackages.js | 2 +- .../rules/no-mixed-plugin-imports.js | 23 ++-- .../src/no-undeclared-imports.test.ts | 19 +--- .../gitlabGroupEnsureExists.examples.test.ts | 100 ++++++++++-------- 4 files changed, 77 insertions(+), 67 deletions(-) diff --git a/packages/eslint-plugin/lib/getPackages.js b/packages/eslint-plugin/lib/getPackages.js index 3b46f41a54..e90536ff52 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 } }}} packageJson + * @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record, files?: Array, backstage?: { inline?: boolean, role?: 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 90d3448b7a..5b7cb9de7d 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -20,6 +20,17 @@ const visitImports = require('../lib/visitImports'); const getPackages = require('../lib/getPackages'); const minimatch = require('minimatch'); +/** @typedef {import('../lib/getPackages.js').ExtendedPackage} ExtendedPackage */ + +/** + * @param {string} pattern + * @param {string} filePath + * @returns {boolean} + */ +const matchesPattern = (pattern, filePath) => { + return new minimatch.Minimatch(pattern).match(filePath); +}; + const roleRules = [ { sourceRole: ['frontend-plugin', 'web-library'], @@ -87,23 +98,22 @@ module.exports = { ? context.physicalFilename : context.filename; + /** @type {ExtendedPackage | undefined} */ const pkg = packages.byPath(filePath); if (!pkg) { return {}; } const options = context.options[0] || {}; + /** @type {string[]} */ const ignoreTargetPackages = options.excludedTargetPackages || []; + /** @type {string[]} */ const ignorePatterns = options.excludedFiles || [ '**/*.{test,spec}.[jt]s?(x)', '**/dev/index.[jt]s?(x)', ]; - if ( - ignorePatterns.some(pattern => - new minimatch.Minimatch(pattern).match(context.filename), - ) - ) { + if (ignorePatterns.some(pattern => matchesPattern(pattern, filePath))) { return {}; } @@ -112,8 +122,9 @@ module.exports = { return; } + /** @type {ExtendedPackage | undefined} */ const targetPackage = imp.package; - const targetName = targetPackage.packageJson.name; + const targetName = targetPackage?.packageJson.name; const sourceName = pkg.packageJson.name; if (sourceName === targetName) { return; diff --git a/packages/eslint-plugin/src/no-undeclared-imports.test.ts b/packages/eslint-plugin/src/no-undeclared-imports.test.ts index ee56c34926..7127d2f344 100644 --- a/packages/eslint-plugin/src/no-undeclared-imports.test.ts +++ b/packages/eslint-plugin/src/no-undeclared-imports.test.ts @@ -24,6 +24,7 @@ jest.mock('child_process', () => ({ const RULE = 'no-undeclared-imports'; const FIXTURE = joinPath(__dirname, '__fixtures__/monorepo'); +// const ERR_UNDECLARED = ( name: string, @@ -246,26 +247,14 @@ ruleTester.run(RULE, rule, { }, { code: `import 'react-dom'`, - output: `import 'directive:add-import:dependencies:react-dom'`, + output: `import 'directive:add-import:peerDependencies:react-dom'`, filename: joinPath(FIXTURE, 'packages/foo/src/index.ts'), errors: [ ERR_UNDECLARED( 'react-dom', - 'dependencies', + 'peerDependencies', joinPath('packages', 'foo'), - ), - ], - }, - { - code: `import 'react-dom'`, - output: `import 'directive:add-import:devDependencies:react-dom'`, - filename: joinPath(FIXTURE, 'packages/foo/src/index.test.ts'), - errors: [ - ERR_UNDECLARED( - 'react-dom', - 'devDependencies', - joinPath('packages', 'foo'), - '--dev', + '--peer', ), ], }, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index 92892b787b..449c5980a4 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -93,15 +93,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -140,15 +142,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -172,15 +176,17 @@ describe('gitlab:group:ensureExists', () => { }); it(`Should ${examples[3].description}`, async () => { - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -215,15 +221,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -266,15 +274,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3/group4', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); From e84e7c55bfa3b0d9c69390862c85f1c30f8fd0b8 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 13 Jun 2025 11:00:58 +0300 Subject: [PATCH 3/5] feat: add suggestions for fixes for mixed plugins rule Signed-off-by: Hellgren Heikki --- .eslintrc.js | 6 +- .../rules/no-mixed-plugin-imports.js | 55 +++++++++++++++++++ .../catalog-backend-module-logs/package.json | 1 + yarn.lock | 1 + 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 58e14ee2d1..787edae242 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,12 +24,10 @@ module.exports = { 'error', { // TODO: Fix these either by right role or by moving things to new packages + // TODO: Additionally remove the '**/*.{test,spec}.[jt]s?(x)' from `excludedFiles` in `no-mixed-plugin-imports` + // and fix the errors excludedTargetPackages: [ - '@backstage/test-utils', - '@backstage/config-loader', '@backstage/plugin-catalog', - '@backstage/plugin-permission-backend', - '@backstage/plugin-app-backend', '@backstage/plugin-techdocs', '@backstage/plugin-app', '@backstage/plugin-catalog-backend' diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js index 5b7cb9de7d..0ed4367ce2 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -64,11 +64,18 @@ module.exports = { messages: { forbidden: '{{sourcePackage}} ({{sourceRole}}) uses forbidden import from {{targetPackage}} ({{targetRole}}).', + useReactPlugin: + 'Use web library {{targetPackage}}-react or common library instead.', + useNodePlugin: + 'Use node library {{targetPackage}}-node or common library instead.', + removeImport: + 'Remove this import to avoid mixed plugin imports. Fix the code by refactoring it to use the correct plugin type.', }, docs: { description: 'Disallow mixed plugin imports.', url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md', }, + hasSuggestions: true, schema: [ { type: 'object', @@ -144,6 +151,53 @@ module.exports = { !ignoreTargetPackages.includes(targetName), ) ) { + const suggest = []; + + if ( + (sourceRole === 'frontend-plugin' || sourceRole === 'web-library') && + targetRole === 'frontend-plugin' + ) { + suggest.push({ + messageId: 'useReactPlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/'$/, "-react'"); + return fixer.replaceText(imp.node, newImport); + }, + }); + } else if ( + (sourceRole === 'backend-plugin' || + sourceRole === 'backend-plugin-module') && + targetRole === 'backend-plugin' + ) { + suggest.push({ + messageId: 'useNodePlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/-backend'$/, "-node'"); + return fixer.replaceText(imp.node, newImport); + }, + }); + } else { + suggest.push({ + messageId: 'removeImport', + /** @param {import('eslint').Rule.RuleFixer} _fixer */ + fix(_fixer) { + // Not a fixable case, just give a suggestion to remove the import + }, + }); + } + context.report({ node: node, messageId: 'forbidden', @@ -153,6 +207,7 @@ module.exports = { targetPackage: targetPackage.packageJson.name || imp.package.dir, targetRole, }, + suggest, }); } }); diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 481ffba3ad..3fad09bcb1 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -35,6 +35,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index d3fc3ea273..ebb1d05132 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5995,6 +5995,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-events-backend-test-utils": "workspace:^" "@backstage/plugin-events-node": "workspace:^" languageName: unknown From 0a254aa6a08c8ebbb09235fdb4942058c5c245b2 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 13 Jun 2025 11:14:38 +0300 Subject: [PATCH 4/5] chore: make mixed plugin imports warning instead error Signed-off-by: Hellgren Heikki --- .changeset/mean-pillows-enter.md | 3 +++ packages/eslint-plugin/index.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.changeset/mean-pillows-enter.md b/.changeset/mean-pillows-enter.md index 7775e2648f..500c9af486 100644 --- a/.changeset/mean-pillows-enter.md +++ b/.changeset/mean-pillows-enter.md @@ -10,3 +10,6 @@ the backstage architecture. This rule forces that: - No imports from frontend plugins to backend plugins or other frontend plugins. - No imports from backend plugins to frontend plugins or other backend plugins. - No imports from common plugins to frontend or backend plugins. + +The current recommended configuration is giving a warning for mixed imports. This is to be changed in +the future to an error so please adjust your workspace accordingly. diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js index a1e38e4269..58b597a438 100644 --- a/packages/eslint-plugin/index.js +++ b/packages/eslint-plugin/index.js @@ -22,7 +22,7 @@ module.exports = { '@backstage/no-forbidden-package-imports': 'error', '@backstage/no-relative-monorepo-imports': 'error', '@backstage/no-undeclared-imports': 'error', - '@backstage/no-mixed-plugin-imports': 'error', + '@backstage/no-mixed-plugin-imports': 'warn', }, }, }, From 2974c086193160a5ec529723ddb5d9f4604efcdc Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Mon, 16 Jun 2025 22:24:07 +0300 Subject: [PATCH 5/5] fix: review findings removed default exclude targets so that tests are also in the scope. added new includedFiles option that defaults to all in src/ directories added more ignored packages to local config to make this pass, needs fixes later on Signed-off-by: Hellgren Heikki --- .eslintrc.js | 10 +++-- .../rules/no-mixed-plugin-imports.js | 44 ++++++++++++++++--- .../catalog-backend-module-logs/package.json | 1 - yarn.lock | 1 - 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 787edae242..54f9e69576 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,13 +24,17 @@ module.exports = { 'error', { // TODO: Fix these either by right role or by moving things to new packages - // TODO: Additionally remove the '**/*.{test,spec}.[jt]s?(x)' from `excludedFiles` in `no-mixed-plugin-imports` - // and fix the errors excludedTargetPackages: [ '@backstage/plugin-catalog', '@backstage/plugin-techdocs', '@backstage/plugin-app', - '@backstage/plugin-catalog-backend' + '@backstage/plugin-catalog-backend', + '@backstage/test-utils', + '@backstage/plugin-auth-backend', + '@backstage/plugin-permission-backend', + '@backstage/plugin-kubernetes-backend', + '@backstage/config-loader', + '@backstage/plugin-app-backend' ], } ], diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js index 0ed4367ce2..b5e79b509e 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -68,6 +68,7 @@ module.exports = { 'Use web library {{targetPackage}}-react or common library instead.', useNodePlugin: 'Use node library {{targetPackage}}-node or common library instead.', + useCommonPlugin: 'Use common library {{targetPackage}}-common instead.', removeImport: 'Remove this import to avoid mixed plugin imports. Fix the code by refactoring it to use the correct plugin type.', }, @@ -90,6 +91,11 @@ module.exports = { items: { type: 'string' }, uniqueItems: true, }, + includedFiles: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + }, }, additionalProperties: false, }, @@ -115,12 +121,14 @@ module.exports = { /** @type {string[]} */ const ignoreTargetPackages = options.excludedTargetPackages || []; /** @type {string[]} */ - const ignorePatterns = options.excludedFiles || [ - '**/*.{test,spec}.[jt]s?(x)', - '**/dev/index.[jt]s?(x)', - ]; + const excludePatterns = options.excludedFiles || []; + /** @type {string[]} */ + const includePatterns = options.includedFiles || ['**/src/**']; - if (ignorePatterns.some(pattern => matchesPattern(pattern, filePath))) { + if ( + !includePatterns.some(pattern => matchesPattern(pattern, filePath)) || + excludePatterns.some(pattern => matchesPattern(pattern, filePath)) + ) { return {}; } @@ -170,6 +178,19 @@ module.exports = { return fixer.replaceText(imp.node, newImport); }, }); + suggest.push({ + messageId: 'useCommonPlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/'$/, "-common'"); + return fixer.replaceText(imp.node, newImport); + }, + }); } else if ( (sourceRole === 'backend-plugin' || sourceRole === 'backend-plugin-module') && @@ -188,6 +209,19 @@ module.exports = { return fixer.replaceText(imp.node, newImport); }, }); + suggest.push({ + messageId: 'useCommonPlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/-backend'$/, '-common'); + return fixer.replaceText(imp.node, newImport); + }, + }); } else { suggest.push({ messageId: 'removeImport', diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 3fad09bcb1..481ffba3ad 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -35,7 +35,6 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", - "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index ebb1d05132..d3fc3ea273 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5995,7 +5995,6 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" - "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-events-backend-test-utils": "workspace:^" "@backstage/plugin-events-node": "workspace:^" languageName: unknown