From f1acf7827dc57ed31fe1993686b3c1637e4190c0 Mon Sep 17 00:00:00 2001 From: gnezim Date: Tue, 14 Apr 2026 22:47:26 +0300 Subject: [PATCH] Add eslint-plugin-boundaries with layered dependency rules --- eslint.config.js | 54 +++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 122 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) diff --git a/eslint.config.js b/eslint.config.js index fbf01305..12f4ac46 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,6 +3,7 @@ import js from "@eslint/js"; import tseslint from "typescript-eslint"; import unusedImports from "eslint-plugin-unused-imports"; +import boundaries from "eslint-plugin-boundaries"; export default [ { @@ -52,4 +53,57 @@ export default [ "no-console": ["warn", { allow: ["warn", "error"] }], }, }, + { + files: ["src/**/*.{ts,tsx}"], + plugins: { + boundaries, + }, + settings: { + "boundaries/elements": [ + { type: "routes", pattern: "src/routes/*" }, + { type: "mf", pattern: "src/mf/*" }, + { type: "features", pattern: "src/features/*", capture: ["feature"] }, + { type: "ui", pattern: "src/ui/*" }, + { type: "shared", pattern: "src/shared/*" }, + { type: "observability", pattern: "src/observability/*" }, + { type: "i18n", pattern: "src/i18n/*" }, + { type: "env", pattern: "src/env/*" }, + ], + }, + rules: { + // Design spec ยง1.2 layered dependency direction: + // features/ cannot import routes/ or mf/ + // ui/ cannot import features/ + // shared/ cannot import features/, routes/, mf/, observability/ + // observability/ cannot import features/, routes/, mf/ + "boundaries/element-types": [ + "error", + { + default: "allow", + rules: [ + { + from: "features", + disallow: ["routes", "mf"], + message: "Features must not import from routes/ or mf/. Use the HostContract or a shared module instead.", + }, + { + from: "ui", + disallow: ["features", "routes", "mf"], + message: "UI layer must not import from features/, routes/, or mf/. UI is consumed by features, not the other way around.", + }, + { + from: "shared", + disallow: ["features", "routes", "mf", "observability"], + message: "Shared modules must not import from features/, routes/, mf/, or observability/.", + }, + { + from: "observability", + disallow: ["features", "routes", "mf"], + message: "Observability modules must not import from features/, routes/, or mf/.", + }, + ], + }, + ], + }, + }, ]; diff --git a/package.json b/package.json index 924ae828..4ca7e5e6 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@typescript-eslint/parser": "^8.0.0", "@vitest/ui": "^3.0.0", "eslint": "^9.0.0", + "eslint-plugin-boundaries": "^5.0.0", "eslint-plugin-unused-imports": "^4.0.0", "typescript": "^5.5.0", "typescript-eslint": "^8.58.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae208b4f..f8ffc51a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,9 @@ importers: eslint: specifier: ^9.0.0 version: 9.39.4(jiti@2.6.1) + eslint-plugin-boundaries: + specifier: ^5.0.0 + version: 5.4.0(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-unused-imports: specifier: ^4.0.0 version: 4.4.1(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)) @@ -666,6 +669,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@boundaries/elements@1.2.0': + resolution: {integrity: sha512-W65Gum02liMd3hmNrLmDBX1u5BmRMcunouFjLXyhxHnNY4YlK1kTxsgfflZ5XBGSnPnO0MkiUzAcoGzYrlx0RQ==} + engines: {node: '>=18.18'} + '@bufbuild/protobuf@2.11.0': resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} @@ -2976,6 +2983,14 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -3150,6 +3165,36 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + 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 + + eslint-plugin-boundaries@5.4.0: + resolution: {integrity: sha512-6SQmEhXCqGrrxm9YiM24SC95CqrVi2MUOm5SDrfquceh/os8MIAvZYsDU69zvtCSb1S6UbNEmdioi1gCDc8+VQ==} + engines: {node: '>=18.18'} + peerDependencies: + eslint: '>=6.0.0' + eslint-plugin-unused-imports@4.4.1: resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==} peerDependencies: @@ -3433,6 +3478,11 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -5267,6 +5317,11 @@ packages: ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -5470,6 +5525,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6317,6 +6375,20 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@boundaries/elements@1.2.0(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))': + dependencies: + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.4(jiti@2.6.1)) + handlebars: 4.7.8 + is-core-module: 2.16.1 + micromatch: 4.0.8 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + '@bufbuild/protobuf@2.11.0': {} '@colordx/core@5.0.3': {} @@ -9139,6 +9211,10 @@ snapshots: data-uri-to-buffer@4.0.1: {} + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -9363,6 +9439,38 @@ snapshots: escape-string-regexp@4.0.0: {} + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.4(jiti@2.6.1)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-boundaries@5.4.0(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)): + dependencies: + '@boundaries/elements': 1.2.0(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)) + chalk: 4.1.2 + eslint: 9.39.4(jiti@2.6.1) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.4(jiti@2.6.1)) + micromatch: 4.0.8 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)): dependencies: eslint: 9.39.4(jiti@2.6.1) @@ -9674,6 +9782,15 @@ snapshots: graceful-fs@4.2.11: {} + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -11503,6 +11620,9 @@ snapshots: ufo@1.6.3: {} + uglify-js@3.19.3: + optional: true + undici-types@7.16.0: {} undici@7.24.7: {} @@ -11734,6 +11854,8 @@ snapshots: word-wrap@1.2.5: {} + wordwrap@1.0.0: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0