diff --git a/docs/shared/monorepo-tags.md b/docs/shared/monorepo-tags.md index ac769ceae9..8cf9d5ca08 100644 --- a/docs/shared/monorepo-tags.md +++ b/docs/shared/monorepo-tags.md @@ -327,7 +327,7 @@ A common example of this is for backend projects that use NestJS and frontend pr } ``` -Another common example is ensuring that util libraries stay framework-free by banning imports from these frameworks. A workspace using React would have a configuration like this. +Another common example is ensuring that util libraries stay framework-free by banning imports from these frameworks. You can use wildcard `*` to match multiple projects e.g. `react*` would match `react`, but also `react-dom`, `react-native` etc. You can also have multiple wildcards e.g. `*react*` would match any package with word `react` in it's name. A workspace using React would have a configuration like this. ```jsonc { @@ -339,10 +339,10 @@ Another common example is ensuring that util libraries stay framework-free by ba "allow": [], // update depConstraints based on your tags "depConstraints": [ - // projects tagged with "type:ui" can't import from "react" or "react-dom" + // projects tagged with "type:ui" can't import from "react" or related projects { "sourceTag": "type:ui", - "bannedExternalImports": ["react", "react-dom"] + "bannedExternalImports": ["*react*"] } ] } diff --git a/nx-dev/nx-dev/public/documentation/latest/shared/monorepo-tags.md b/nx-dev/nx-dev/public/documentation/latest/shared/monorepo-tags.md index ac769ceae9..8cf9d5ca08 100644 --- a/nx-dev/nx-dev/public/documentation/latest/shared/monorepo-tags.md +++ b/nx-dev/nx-dev/public/documentation/latest/shared/monorepo-tags.md @@ -327,7 +327,7 @@ A common example of this is for backend projects that use NestJS and frontend pr } ``` -Another common example is ensuring that util libraries stay framework-free by banning imports from these frameworks. A workspace using React would have a configuration like this. +Another common example is ensuring that util libraries stay framework-free by banning imports from these frameworks. You can use wildcard `*` to match multiple projects e.g. `react*` would match `react`, but also `react-dom`, `react-native` etc. You can also have multiple wildcards e.g. `*react*` would match any package with word `react` in it's name. A workspace using React would have a configuration like this. ```jsonc { @@ -339,10 +339,10 @@ Another common example is ensuring that util libraries stay framework-free by ba "allow": [], // update depConstraints based on your tags "depConstraints": [ - // projects tagged with "type:ui" can't import from "react" or "react-dom" + // projects tagged with "type:ui" can't import from "react" or related projects { "sourceTag": "type:ui", - "bannedExternalImports": ["react", "react-dom"] + "bannedExternalImports": ["*react*"] } ] } diff --git a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts index 5fceb3a317..e31d20aaf8 100644 --- a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts +++ b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts @@ -260,6 +260,30 @@ describe('Enforce Module Boundaries (eslint)', () => { version: '0.0.0', }, }, + 'npm:npm-package2': { + name: 'npm:npm-package2', + type: 'npm', + data: { + packageName: 'npm-package2', + version: '0.0.0', + }, + }, + 'npm:1npm-package': { + name: 'npm:1npm-package', + type: 'npm', + data: { + packageName: '1npm-package', + version: '0.0.0', + }, + }, + 'npm:npm-awesome-package': { + name: 'npm:npm-awesome-package', + type: 'npm', + data: { + packageName: 'npm-awesome-package', + version: '0.0.0', + }, + }, }, dependencies: {}, }; @@ -331,6 +355,30 @@ describe('Enforce Module Boundaries (eslint)', () => { expect(failures[1].message).toEqual(message); }); + it('should allow wildcards for defining forbidden npm packages', () => { + const failures = runRule( + { + depConstraints: [ + { sourceTag: 'api', bannedExternalImports: ['npm-*ge'] }, + ], + }, + `${process.cwd()}/proj/libs/api/src/index.ts`, + ` + import 'npm-package'; + import 'npm-awesome-package'; + import 'npm-package2'; + import '1npm-package'; + `, + graph + ); + + const message = (packageName) => + `A project tagged with "api" is not allowed to import the "${packageName}" package`; + expect(failures.length).toEqual(2); + expect(failures[0].message).toEqual(message('npm-package')); + expect(failures[1].message).toEqual(message('npm-awesome-package')); + }); + it('should error when the target library is untagged', () => { const failures = runRule( depConstraints, diff --git a/packages/workspace/src/utils/runtime-lint-utils.ts b/packages/workspace/src/utils/runtime-lint-utils.ts index 32bbb4d389..022875c063 100644 --- a/packages/workspace/src/utils/runtime-lint-utils.ts +++ b/packages/workspace/src/utils/runtime-lint-utils.ts @@ -187,10 +187,22 @@ export function hasBannedImport( c.bannedExternalImports.length ); return depConstraints.find((constraint) => - constraint.bannedExternalImports.includes(target.data.packageName) + constraint.bannedExternalImports.some((importDefinition) => + parseImportWildcards(importDefinition).test(target.data.packageName) + ) ); } +/** + * Maps import with wildcards to regex pattern + * @param importDefinition + * @returns + */ +function parseImportWildcards(importDefinition: string): RegExp { + const mappedWildcards = importDefinition.split('*').join('.*'); + return new RegExp(`^${new RegExp(mappedWildcards).source}$`); +} + /** * Verifies whether the given node has an architect builder attached * @param projectGraph the node to verify