// 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|@tanstack/react-router|@tanstack/react-start)(/|$)", "^@conusai-kit/(data-kysely|data-mongo|data-convex|jobs-pgboss|platform|ui|i18n|auth)(/|$)", "^@conusai-sdk/", ] /** The marker of a screen that is truly domain-specific: `// kit:custom-ui `, near the top of the file. */ const CUSTOM_UI = /^\s*(?:\/\/|\{\/\*|\/\*)\s*kit:custom-ui\s+\S/m /** Every `.tsx` under the folders that hold screens, relative to `root`. */ function listScreens(root) { const found = [] const walk = (dir) => { if (!fs.existsSync(dir)) return for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name) if (entry.isDirectory()) walk(full) else if (entry.name.endsWith(".tsx") && !entry.name.endsWith(".test.tsx")) found.push(path.relative(root, full)) } } walk(path.join(root, "apps/web/src/routes")) walk(path.join(root, "apps/web/src/ui")) for (const id of listModules(root)) walk(path.join(root, "modules", id, "ui")) return found.sort() } /** The screens that declare themselves custom, by the marker in their own first lines. */ function listCustomScreens(root) { return listScreens(root).filter((file) => CUSTOM_UI.test(fs.readFileSync(path.join(root, file), "utf8").split("\n").slice(0, 20).join("\n")), ) } const escaped = (file) => file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") function buildForbidden(moduleIds, customScreens = []) { const rules = [ { name: "ui-comes-from-the-kit", severity: "error", comment: "A screen imports @conusai-kit/ui, never React Aria itself: use a pattern of @conusai-kit/ui/patterns or a primitive, or say why the screen is its own with `// kit:custom-ui `.", from: { path: "^(apps|modules)/", pathNot: customScreens.map((file) => `^${escaped(file)}$`) }, to: { path: "(^|/)(react-aria-components|react-aria|react-stately|@react-aria/[^/]+|@react-stately/[^/]+)(/|$)" }, }, { 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//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, listCustomScreens, listModules, listScreens }