This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
||||
// The layer rules of this repository, generated from the folder tree so a module added
|
||||
// tomorrow is covered without anybody editing a list (app-template spec §2). Read by
|
||||
// .dependency-cruiser.cjs and by test/boundaries.test.ts.
|
||||
"use strict"
|
||||
|
||||
const fs = require("node:fs")
|
||||
const path = require("node:path")
|
||||
|
||||
/** The module ids under modules/, which is the only place a domain module lives. */
|
||||
function listModules(root) {
|
||||
const dir = path.join(root, "modules")
|
||||
if (!fs.existsSync(dir)) return []
|
||||
return fs
|
||||
.readdirSync(dir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => entry.name)
|
||||
.sort()
|
||||
}
|
||||
|
||||
/** What a module's core may never reach: an engine, an SDK or a framework (spec §2). */
|
||||
const DRIVERS_AND_FRAMEWORKS = [
|
||||
"^(kysely|pg|pg-boss|mongodb|hono)$",
|
||||
"^(react|react-dom|next)(/|$)",
|
||||
"^@conusai-kit/(data-kysely|data-mongo|data-convex|jobs-pgboss|platform|ui|i18n|auth)(/|$)",
|
||||
"^@conusai-sdk/",
|
||||
]
|
||||
|
||||
function buildForbidden(moduleIds) {
|
||||
const rules = [
|
||||
{
|
||||
name: "no-circular",
|
||||
severity: "error",
|
||||
comment: "A cycle between files makes the order they run in an accident. Move the shared part down a layer.",
|
||||
from: {},
|
||||
to: { circular: true },
|
||||
},
|
||||
{
|
||||
name: "worker-entry-is-the-workers-own",
|
||||
severity: "error",
|
||||
comment:
|
||||
"@conusai-kit/core/worker mints system actors. Only apps/worker may import it, so no transport can act as one (kit-core system-actor.ts).",
|
||||
from: { pathNot: "^apps/worker/" },
|
||||
// The resolved path, not the specifier: dependency-cruiser matches what a module resolves
|
||||
// to, and in a pnpm store that is .../@conusai-kit/core/dist/worker.js.
|
||||
to: { path: "@conusai-kit/core/dist/worker\\." },
|
||||
},
|
||||
{
|
||||
name: "core-imports-no-engine-sdk-or-framework",
|
||||
severity: "error",
|
||||
comment:
|
||||
"A use case under modules/<id>/core is pure TypeScript over the ports of @conusai-kit/core|data|tenancy, so it runs on every profile and needs no database to test (spec §2).",
|
||||
from: { path: "^modules/[^/]+/core/" },
|
||||
to: { path: DRIVERS_AND_FRAMEWORKS },
|
||||
},
|
||||
{
|
||||
name: "contract-is-contract-only",
|
||||
severity: "error",
|
||||
comment:
|
||||
"A module's contract/ is what other modules import, so it may not reach back into its own core, adapters or ui.",
|
||||
from: { path: "^modules/([^/]+)/contract/" },
|
||||
to: { path: "^modules/$1/(core|adapters|ui)/" },
|
||||
},
|
||||
{
|
||||
name: "ui-talks-to-contracts-not-to-core",
|
||||
severity: "error",
|
||||
comment:
|
||||
"A module's pages read and write through the data facade and its own contract, never its use cases (spec §6.3).",
|
||||
from: { path: "^modules/([^/]+)/ui/" },
|
||||
to: { path: "^modules/$1/(core|adapters)/" },
|
||||
},
|
||||
{
|
||||
name: "a-module-never-imports-an-app",
|
||||
severity: "error",
|
||||
comment: "apps/* are composition roots: they import modules, never the other way round.",
|
||||
from: { path: "^modules/" },
|
||||
to: { path: "^apps/" },
|
||||
},
|
||||
{
|
||||
name: "config-is-below-everything",
|
||||
severity: "error",
|
||||
comment: "config/ is read by apps and modules, so it may not import either of them.",
|
||||
from: { path: "^config/" },
|
||||
to: { path: "^(apps|modules)/" },
|
||||
},
|
||||
]
|
||||
|
||||
// One rule per module, from the folder tree: a module reaches another only through its
|
||||
// contract/, and the message names both modules.
|
||||
for (const id of moduleIds) {
|
||||
const others = moduleIds.filter((other) => other !== id)
|
||||
if (others.length === 0) continue
|
||||
rules.push({
|
||||
name: `module-${id}-crosses-only-contracts`,
|
||||
severity: "error",
|
||||
comment: `modules/${id} may import another module's contract/ and nothing else of it (spec §2, §4.4).`,
|
||||
from: { path: `^modules/${id}/` },
|
||||
to: { path: `^modules/(${others.join("|")})/(?!contract/)` },
|
||||
})
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
module.exports = { DRIVERS_AND_FRAMEWORKS, buildForbidden, listModules }
|
||||
Reference in New Issue
Block a user