[Migration 0-2] Enable ESLint import/no-cycle + boundaries rules (#3349)

Enable ESLint import/no-cycle and architecture boundary rules

Add eslint-plugin-boundaries with element-types matching ARCH_REDESIGN §8,
enable import/no-cycle (error in client/, warn in lib/ and ui/), extend
consistent-default-export-name to client/**/*.tsx, and document §10
types/api.ts enforcement as a follow-up.
This commit is contained in:
tom goriunov
2026-03-02 10:54:15 +02:00
committed by GitHub
parent 2beb6adac6
commit d451ff9d30
3 changed files with 179 additions and 1 deletions
+116 -1
View File
@@ -3,6 +3,7 @@ import jsPlugin from '@eslint/js';
import nextJsPlugin from '@next/eslint-plugin-next';
import stylisticPlugin from '@stylistic/eslint-plugin';
import reactQueryPlugin from '@tanstack/eslint-plugin-query';
import boundariesPlugin from 'eslint-plugin-boundaries';
import consistentDefaultExportNamePlugin from 'eslint-plugin-consistent-default-export-name';
import importPlugin from 'eslint-plugin-import';
import importHelpersPlugin from 'eslint-plugin-import-helpers';
@@ -57,6 +58,20 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const gitignorePath = path.resolve(__dirname, '.gitignore');
/** @see client/ARCH_REDESIGN.md §8 — dependency layers for migrated code */
const ARCH_BOUNDARY_ELEMENTS = [
{ type: 'client-api', pattern: 'client/api/**', mode: 'full' },
{ type: 'client-slices', pattern: 'client/slices/**', mode: 'full' },
{ type: 'client-features', pattern: 'client/features/**', mode: 'full' },
{ type: 'client-shared', pattern: 'client/shared/**', mode: 'full' },
{ type: 'client-shell', pattern: 'client/shell/**', mode: 'full' },
{ type: 'configs', pattern: 'configs/**', mode: 'full' },
{ type: 'toolkit', pattern: 'toolkit/**', mode: 'full' },
{ type: 'nextjs', pattern: 'nextjs/**', mode: 'full' },
{ type: 'legacy-lib', pattern: 'lib/**', mode: 'full' },
{ type: 'legacy-ui', pattern: 'ui/**', mode: 'full' },
];
/** @type {import('eslint').Linter.Config[]} */
export default tseslint.config(
includeIgnoreFile(gitignorePath),
@@ -72,7 +87,12 @@ export default tseslint.config(
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
{ settings: { react: { version: 'detect' } } },
{
settings: {
react: { version: 'detect' },
'boundaries/elements': ARCH_BOUNDARY_ELEMENTS,
},
},
jsPlugin.configs.recommended,
@@ -288,6 +308,100 @@ export default tseslint.config(
},
},
{
files: [ 'client/**' ],
plugins: {
'import': importPlugin,
},
rules: {
'import/no-cycle': [ 'error', { maxDepth: 10 } ],
},
},
{
files: [ 'lib/**', 'ui/**' ],
plugins: {
'import': importPlugin,
},
rules: {
'import/no-cycle': 'warn',
},
},
/*
* ARCH_REDESIGN.md §10 — public type surface: consumers should import slice/feature types only from
* `types/api.ts`. eslint-plugin-boundaries can enforce this with `capture` on element patterns and
* targeted `disallow` rules; that is left for a follow-up once more slices/features exist under client/.
* Type-only cross-layer imports are approximated via per-rule `importKind: 'type'` (v4 has no `allowTypeImports` option).
*/
{
files: [
'client/**/*.{ts,tsx}',
'configs/**/*.{ts,tsx,mjs}',
],
plugins: {
boundaries: boundariesPlugin,
},
rules: {
'boundaries/element-types': [ 'error', {
'default': 'disallow',
rules: [
{
from: 'client-api',
allow: [ 'client-shared', 'legacy-lib' ],
},
{
from: 'client-api',
allow: [ 'client-slices', 'client-features' ],
importKind: 'type',
},
{
from: 'client-slices',
allow: [ 'client-api', 'client-shared', 'client-slices', 'legacy-lib', 'legacy-ui', 'toolkit', 'configs' ],
},
{
from: 'client-slices',
allow: [ 'client-features' ],
importKind: 'type',
},
{
from: 'client-features',
allow: [ 'client-api', 'client-shared', 'client-slices', 'client-features', 'legacy-lib', 'legacy-ui', 'toolkit', 'configs' ],
},
{
from: 'client-shared',
allow: [ 'client-shared', 'legacy-lib', 'legacy-ui', 'toolkit', 'configs' ],
},
{
from: 'client-shared',
allow: [ 'client-slices' ],
importKind: 'type',
},
{
from: 'client-shell',
allow: [
'client-api',
'client-slices',
'client-features',
'client-shared',
'client-shell',
'legacy-lib',
'legacy-ui',
'toolkit',
'nextjs',
'configs',
],
},
{
from: 'configs',
// `configs` must not import `client/*`; `lib/*` stays allowed until config-owned types move out of legacy paths
allow: [ 'configs', 'legacy-lib' ],
},
],
} ],
},
},
{
plugins: {
'import-helpers': importHelpersPlugin,
@@ -338,6 +452,7 @@ export default tseslint.config(
},
files: [
'ui/**/[A-Z]*.tsx',
'client/**/*.tsx',
],
ignores: [
'**/*.pw.*',
+1
View File
@@ -179,6 +179,7 @@
"dotenv-cli": "^6.0.0",
"eslint": "9.39.2",
"eslint-config-next": "16.1.7",
"eslint-plugin-boundaries": "^4.2.2",
"eslint-plugin-consistent-default-export-name": "^0.0.15",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-import-helpers": "2.0.1",
+62
View File
@@ -417,6 +417,9 @@ importers:
eslint-config-next:
specifier: 16.1.7
version: 16.1.7(@typescript-eslint/[email protected]([email protected])([email protected]))([email protected])([email protected])
eslint-plugin-boundaries:
specifier: ^4.2.2
version: 4.2.2(@typescript-eslint/[email protected]([email protected])([email protected]))([email protected])
eslint-plugin-consistent-default-export-name:
specifier: ^0.0.15
version: 0.0.15
@@ -8532,6 +8535,33 @@ packages:
eslint-import-resolver-webpack:
optional: true
[email protected]:
resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: '*'
eslint-import-resolver-node: '*'
eslint-import-resolver-typescript: '*'
eslint-import-resolver-webpack: '*'
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
eslint:
optional: true
eslint-import-resolver-node:
optional: true
eslint-import-resolver-typescript:
optional: true
eslint-import-resolver-webpack:
optional: true
[email protected]:
resolution: {integrity: sha512-cjwpZqkCXgfz953bc74uDetOtGVxwgMgNZ7hAKi6Oxck+x4oY6Z/9DzgPqAYhtQdSNHFVg+vhft/lSL+snPMQg==}
engines: {node: '>=14.0.0'}
peerDependencies:
eslint: '>=6.0.0'
[email protected]:
resolution: {integrity: sha512-gqW7dnJbWMxI5H6/Pyz6Sl/vBMwOktePMI2iuuKPb4N82uvemUkfaWhsRZCKndSzpIVaCZ9wdspCVO1tm0wXJQ==}
engines: {node: '>=0.10.0'}
@@ -10289,6 +10319,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==}
[email protected]:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
engines: {node: '>=8.6'}
[email protected]:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -25836,6 +25870,29 @@ snapshots:
transitivePeerDependencies:
- supports-color
[email protected](@typescript-eslint/[email protected]([email protected])([email protected]))([email protected])([email protected]):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.57.1([email protected])([email protected])
eslint: 9.39.2
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
- supports-color
[email protected](@typescript-eslint/[email protected]([email protected])([email protected]))([email protected]):
dependencies:
chalk: 4.1.2
eslint: 9.39.2
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.1(@typescript-eslint/[email protected]([email protected])([email protected]))([email protected])([email protected])
micromatch: 4.0.7
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
[email protected]:
dependencies:
lodash: 4.17.23
@@ -28037,6 +28094,11 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
[email protected]:
dependencies:
braces: 3.0.3