96 lines
3.6 KiB
JavaScript
96 lines
3.6 KiB
JavaScript
// @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"],
|
|
},
|
|
},
|
|
};
|