mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 06:45:51 +00:00
47f2883ca2
Encode the feature-folder layering contract as a CI gate: lib is the floor, store/ui must not import features/app, cross-feature only via the barrel, no cycles. dependency-cruiser resolves the @/ alias via tsconfig; tests excluded. Land as a ratchet: the 738 current violations are seeded into .dependency-cruiser-known-violations.json and ignored via --ignore-known, so HEAD is green and any NEW violation fails CI. Regenerate the baseline as the drain removes an exception; the count is the tracked residual debt. Wired via `npm run dep:check` into a new dependency-cruiser job in eslint.yml and the ci-ok required-check aggregate.
92 lines
3.5 KiB
JavaScript
92 lines
3.5 KiB
JavaScript
// Architecture / layering guard for the feature-folder structure (PR #1225, group A1).
|
|
//
|
|
// Encodes the layering contract the restructure established:
|
|
//
|
|
// lib (feature-free infra: api clients, format, i18n, util, media, server, navigation)
|
|
// ▲
|
|
// store / ui (cross-cutting global stores; domain-agnostic primitives) — may import lib
|
|
// ▲
|
|
// cover / music-network (top-level domains — peers; may import lib, store, ui)
|
|
// ▲
|
|
// features/<x> (may import lib, store, ui, cover, music-network, other features via barrel only)
|
|
// ▲
|
|
// app (shell + bridges — may import anything)
|
|
//
|
|
// A lower layer may NOT import a higher one. Cross-feature access goes through the
|
|
// `@/features/<x>` barrel only, never a deep path. No import cycles anywhere.
|
|
//
|
|
// Ratchet: current known violations (residual legacy dirs + documented inversions) are
|
|
// captured in `.dependency-cruiser-known-violations.json` and ignored via `--ignore-known`
|
|
// (see `npm run dep:check`). Any NEW violation fails CI. As the drain (group E) removes an
|
|
// exception, regenerate the baseline so the count ratchets toward zero.
|
|
|
|
/** @type {import('dependency-cruiser').IConfiguration} */
|
|
module.exports = {
|
|
forbidden: [
|
|
{
|
|
name: 'no-circular',
|
|
severity: 'error',
|
|
comment: 'No import cycles anywhere under src/ — they make the module graph impossible to reason about.',
|
|
from: {},
|
|
to: { circular: true },
|
|
},
|
|
{
|
|
name: 'lib-is-the-floor',
|
|
severity: 'error',
|
|
comment:
|
|
'lib/** is feature-free infra and must not import a higher layer ' +
|
|
'(features, store, ui, app, cover, music-network).',
|
|
from: { path: '^src/lib/' },
|
|
to: { path: '^src/(features|store|ui|app|cover|music-network)/' },
|
|
},
|
|
{
|
|
name: 'no-core-to-feature',
|
|
severity: 'error',
|
|
comment:
|
|
'store/** and ui/** are cross-cutting core and must not import features or the app shell ' +
|
|
'(the inversions the seams removed — keep them out).',
|
|
from: { path: '^src/(store|ui)/' },
|
|
to: { path: '^src/(features|app)/' },
|
|
},
|
|
{
|
|
name: 'no-deep-cross-feature',
|
|
severity: 'error',
|
|
comment:
|
|
'A feature must reach another feature only through its `@/features/<x>` barrel (index), ' +
|
|
'never a deep path. Same-feature deep imports are fine.',
|
|
from: { path: '^src/features/([^/]+)/' },
|
|
to: {
|
|
path: '^src/features/([^/]+)/[^/]+',
|
|
pathNot: [
|
|
'^src/features/$1/', // same feature — allowed
|
|
'^src/features/[^/]+/index\\.(ts|tsx)$', // the barrel — allowed
|
|
],
|
|
},
|
|
},
|
|
],
|
|
options: {
|
|
doNotFollow: { path: 'node_modules' },
|
|
// Type-only edges are tolerated by the iron rule (erased at runtime), but the ratchet
|
|
// records whatever exists today regardless of kind; new edges of any kind fail.
|
|
tsPreCompilationDeps: true,
|
|
tsConfig: { fileName: 'tsconfig.json' },
|
|
enhancedResolveOptions: {
|
|
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
|
|
mainFields: ['module', 'main', 'types', 'typings'],
|
|
},
|
|
// The layering contract is about the production module graph. Tests, test helpers,
|
|
// ambient declarations and non-src trees are not part of it.
|
|
exclude: {
|
|
path: [
|
|
'\\.test\\.(ts|tsx)$',
|
|
'^src/test/',
|
|
'\\.d\\.ts$',
|
|
'^src/vite-env',
|
|
],
|
|
},
|
|
reporterOptions: {
|
|
text: { highlightFocused: true },
|
|
},
|
|
},
|
|
};
|