/** * Architecture enforcement. * * The layering described in src/ports/types.js is worth nothing if it is only a * comment. These rules make it a build failure: * * core/ imports nothing outside core/ (pure: no DOM, no I/O, no time) * ports/ imports nothing at all (contracts only) * adapters/ may import ports/ and core/ (the impure edge) * ui/ may import ports/ and core/ (rendering only) * app/ may import anything (composition root) * * The arrow never points from core/ outward. That single rule is what keeps the * ranker testable in Node with no browser, and it is checked on every run of * `npm run verify`. */ module.exports = { forbidden: [ { name: 'core-stays-pure', severity: 'error', comment: 'core/ must not import from any other layer. It is the only part of the ' + 'system that can be reasoned about in isolation, and that property is ' + 'load-bearing: it is why the ranking can be tested deterministically.', from: { path: '^src/core' }, to: { path: '^src/(adapters|ui|app|ports)' }, }, { name: 'core-touches-no-platform', severity: 'error', comment: 'core/ must not reach for browser or Node globals. If a ranking function ' + 'reads the clock or storage directly it stops being reproducible.', from: { path: '^src/core' }, to: { dependencyTypes: ['core', 'npm'] }, }, { name: 'ports-are-contracts-only', severity: 'error', comment: 'ports/ declares types. It must import nothing.', from: { path: '^src/ports' }, to: { path: '.' }, }, { name: 'ui-does-no-io', severity: 'error', comment: 'ui/ renders. It must not import adapters — anything that talks to the ' + 'network, storage or the observer belongs behind the composition root.', from: { path: '^src/ui' }, to: { path: '^src/(adapters|app)' }, }, { name: 'adapters-never-import-ui', severity: 'error', comment: 'The impure edge must not depend on rendering.', from: { path: '^src/adapters' }, to: { path: '^src/(ui|app)' }, }, { name: 'no-cycles', severity: 'error', comment: 'A cycle means the boundaries are not real.', from: {}, to: { circular: true }, }, { name: 'no-orphans', severity: 'warn', comment: 'Unreachable module — either wire it up or delete it.', from: { orphan: true, pathNot: ['^src/ports/types\\.js$'] }, to: {}, }, ], options: { doNotFollow: { path: 'node_modules' }, exclude: { path: '(node_modules|dist|\\.source)' }, tsPreCompilationDeps: false, }, };