diff --git a/.dependency-cruiser.json b/.dependency-cruiser.json index ea20ba8..f3ffd9d 100644 --- a/.dependency-cruiser.json +++ b/.dependency-cruiser.json @@ -191,6 +191,19 @@ "pathNot": "^src/adapters/($1/|platform/|browser-file-storage/result\\.ts$|cross-context-invalidation/index\\.ts$)" } }, + { + "name": "adapter-groups-are-reached-through-their-barrel", + "comment": "어댑터 그룹의 공개 표면은 그 그룹의 index.ts다. 그룹 바깥(bootstrap, features, presentation)은 배럴만 import한다. 배럴이 없던 시절 bootstrap은 어댑터 내부 파일 15곳을 직접 겨눴고, 그래서 어떤 파일이 공개이고 어떤 파일이 내부 헬퍼인지 아무 데도 적혀 있지 않았다. 출발점에서 src/adapters를 뺀 이유는 어댑터끼리의 간선은 바로 위 adapters-do-not-know-other-concrete-adapters가 이미 담당하고, 커널(platform/**)은 파일 단위로 공유되기 때문이다 — scripts/check-adapter-inventory.ts가 네 소비자에게 platform/abortable-operation.ts로 해석되는 specifier를 직접 요구한다. 도착점에서 1단계 중첩 index.ts를 허용한 이유는 storage/indexeddb와 storage/opfs가 각자 독립적으로 제거 가능한 런타임이고(scripts/test-browser-file-storage-runtime-removal.ts), 그래서 각자의 배럴이 곧 경계이기 때문이다.", + "severity": "error", + "from": { + "path": "^src/", + "pathNot": "^src/adapters/" + }, + "to": { + "path": "^src/adapters/[^/]+/", + "pathNot": "^src/adapters/[^/]+/index\\.ts$|^src/adapters/[^/]+/[^/]+/index\\.ts$" + } + }, { "name": "no-circular-dependencies", "severity": "error", diff --git a/scripts/check-architecture.ts b/scripts/check-architecture.ts index 6b2957a..ee4c14f 100644 --- a/scripts/check-architecture.ts +++ b/scripts/check-architecture.ts @@ -824,12 +824,13 @@ async function runGraphFixtureChecks(): Promise { "tests/fixtures/architecture/dependency-graph", ); const allowedRoot = resolve(fixtureRoot, "allowed"); - const [allowedGraph, unresolvedGraph, layerGraph, cycleGraph] = + const [allowedGraph, unresolvedGraph, layerGraph, cycleGraph, barrelGraph] = await Promise.all([ analyzeSourceGraph(allowedRoot, "src"), analyzeSourceGraph(resolve(fixtureRoot, "unresolved"), "src"), analyzeSourceGraph(resolve(fixtureRoot, "layer"), "src"), analyzeSourceGraph(resolve(fixtureRoot, "cycle"), "src"), + analyzeSourceGraph(resolve(fixtureRoot, "barrel"), "src"), ]); const allowedFiles = new Set(await listFiles(allowedRoot)); const allowedSourceFile = resolve( @@ -850,6 +851,21 @@ async function runGraphFixtureChecks(): Promise { ), ); const assertions = [ + { + name: "deep adapter import from outside the group is rejected", + passed: blockingViolations(barrelGraph).some( + ({ rule, source, target }) => + rule === "adapter-groups-are-reached-through-their-barrel" && + source === "src/bootstrap/compose-deep.ts" && + target === "src/adapters/http/client.ts", + ), + }, + { + name: "barrel import from outside the group is accepted", + passed: !blockingViolations(barrelGraph).some( + ({ source }) => source === "src/bootstrap/compose-barrel.ts", + ), + }, { name: "explicit TS specifier resolves to a TS module", passed: allowedGraph.dependencies.some( diff --git a/tests/fixtures/architecture/dependency-graph/barrel/adapters/http/client.ts b/tests/fixtures/architecture/dependency-graph/barrel/adapters/http/client.ts new file mode 100644 index 0000000..3a414bb --- /dev/null +++ b/tests/fixtures/architecture/dependency-graph/barrel/adapters/http/client.ts @@ -0,0 +1,3 @@ +export function createFixtureHttpClient(): string { + return "fixture"; +} diff --git a/tests/fixtures/architecture/dependency-graph/barrel/adapters/http/index.ts b/tests/fixtures/architecture/dependency-graph/barrel/adapters/http/index.ts new file mode 100644 index 0000000..1ca20a3 --- /dev/null +++ b/tests/fixtures/architecture/dependency-graph/barrel/adapters/http/index.ts @@ -0,0 +1 @@ +export { createFixtureHttpClient } from "./client.ts"; diff --git a/tests/fixtures/architecture/dependency-graph/barrel/bootstrap/compose-barrel.ts b/tests/fixtures/architecture/dependency-graph/barrel/bootstrap/compose-barrel.ts new file mode 100644 index 0000000..29f769d --- /dev/null +++ b/tests/fixtures/architecture/dependency-graph/barrel/bootstrap/compose-barrel.ts @@ -0,0 +1,3 @@ +import { createFixtureHttpClient } from "../adapters/http/index.ts"; + +export const barrelComposition = createFixtureHttpClient; diff --git a/tests/fixtures/architecture/dependency-graph/barrel/bootstrap/compose-deep.ts b/tests/fixtures/architecture/dependency-graph/barrel/bootstrap/compose-deep.ts new file mode 100644 index 0000000..590199c --- /dev/null +++ b/tests/fixtures/architecture/dependency-graph/barrel/bootstrap/compose-deep.ts @@ -0,0 +1,3 @@ +import { createFixtureHttpClient } from "../adapters/http/client.ts"; + +export const deepComposition = createFixtureHttpClient;