// @ts-check // Deep-module enforcement for dependency-cruiser. // // Each package under the packages root is a DEEP MODULE: a lot of behaviour // behind a small interface. A package's PUBLIC SURFACE is its ENTRY POINTS — // the files at the package root. Implementation lives in SUBFOLDERS and is // private — by convention `lib/` for implementation and `tests/` for tests, // though any subfolder is private. A package may expose several small entry // points (index.ts, client.ts, server.ts, …) — prefer that over one giant // barrel index. // // The only thing you should ever need to edit here is PACKAGES_ROOT. /** Where packages live. One immediate child dir per package (flat, no nesting). */ const PACKAGES_ROOT = "src/packages"; // --- derived patterns (no need to edit) ------------------------------------- const R = PACKAGES_ROOT; /** * A package's private internals: anything nested inside a package subfolder. * The package's root files are its entry points and are NOT matched here — * they stay importable from outside. */ const PACKAGE_INTERNALS = `^${R}/[^/]+/[^/]+/`; /** @type {import('dependency-cruiser').IConfiguration} */ module.exports = { forbidden: [ { name: "entrypoint-boundary-from-app", comment: "App/root code may import a package's entry points (its root files), but nothing inside its subfolders.", severity: "error", from: { pathNot: `^${R}/` }, // importer is NOT inside any package to: { path: PACKAGE_INTERNALS }, }, { name: "entrypoint-boundary-across-packages", comment: "A package's own files import each other freely, but may reach OTHER packages only through their entry points — never their internals.", severity: "error", // importer is inside a package ($1), but is not a test file from: { path: `^${R}/([^/]+)/`, pathNot: `^${R}/[^/]+/tests/` }, to: { path: PACKAGE_INTERNALS, pathNot: `^${R}/$1/`, // same package → intra-package freedom }, }, { name: "tests-through-entrypoints", comment: "A package's tests exercise it through its entry points like everyone else: they may import any package's entry points and their own tests/ fixtures, but never any package's internals — not even their own.", severity: "error", from: { path: `^${R}/([^/]+)/tests/` }, // a test file, in package $1 to: { path: PACKAGE_INTERNALS, pathNot: `^${R}/$1/tests/`, // own tests/ fixtures → allowed }, }, { name: "tests-folder-is-private", comment: "A package's tests/ folder is reachable only from tests — nothing else may import fixtures.", severity: "error", from: { pathNot: `^${R}/[^/]+/tests/` }, // importer is not itself a test to: { path: `^${R}/[^/]+/tests/` }, }, { name: "no-circular", comment: "No dependency cycles. Scope to `^${R}/` if you want to allow cycles outside packages.", severity: "error", from: {}, to: { circular: true }, }, // --- Layering (optional, off by default) ---------------------------------- // Interface-hiding controls HOW you import (through the entry points). // Layering controls WHICH packages may depend on which. Add your own rules // here, e.g.: // // { // name: "ui-may-not-depend-on-billing", // severity: "error", // from: { path: `^${R}/ui/` }, // to: { path: `^${R}/billing/` }, // }, ], options: { doNotFollow: { path: "node_modules" }, tsConfig: { fileName: "tsconfig.json" }, enhancedResolveOptions: { extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], }, }, };