Skip to content

Enforce import boundaries with Biome - #1491

Open
cristianrgreco wants to merge 2 commits into
mainfrom
claude/enforce-import-boundaries
Open

cristianrgreco wants to merge 2 commits into
mainfrom
claude/enforce-import-boundaries

Conversation

@cristianrgreco

Copy link
Copy Markdown
Collaborator

Summary

Enforces import boundaries with Biome rules that ship with the Biome version we already use (2.5.14). This is the Biome-native alternative to adding dependency-cruiser. The dependency graph is shallow (modules only depend on testcontainers, never on each other), so we only need two rules:

  • suspicious/noImportCycles: error. Applies to the whole repo.
  • style/noRestrictedImports: error. Scoped to packages/modules/** with an override. Modules may only import core through the testcontainers entry point. The rule blocks:
    • testcontainers/** (package deep imports such as testcontainers/src/types or testcontainers/build/...)
    • **/testcontainers/src/** (relative reaches into core source)
    • except **/testcontainers/src/utils/test-helper, which is explicitly allowed. 54 module tests import getImage from it on purpose. It is a test-only, cross-package import, and making it public would be a separate change.

AGENTS.md gets a short note on both rules.

Findings and fixes

Import cycles (10 sites, one loop): suppressed, not refactored

All 10 reports come from one loop in packages/testcontainers/src:

generic-container.ts ↔ reaper/reaper.ts ↔ port-forwarder/port-forwarder.ts ↔ generic-container-builder.ts (plus started-generic-container.ts)

The loop is inherent. Ryuk and sshd are themselves GenericContainers, and fromDockerfile() / build() construct each other. testcontainers-java has the same shape (GenericContainer ↔ ResourceReaper/RyukContainer, PortForwardingContainer). The loop is harmless today because every edge is only used inside functions, never while the module loads.

None of the edges is type-only, so import type would not break the loop. Moving SSHD_IMAGE / getReaperImage() into a leaf module would not break it either: the same files still need PortForwarderInstance, getReaper and GenericContainer at runtime. Breaking it properly would need dependency injection or a registry. That is too big a refactor for a guardrail PR.

So each existing edge gets its own // biome-ignore lint/suspicious/noImportCycles: <reason>. The suppressions are per import, not per file, so a new cycle still fails, even one that goes through these same files (see the guard proof below).

Deep imports (2): fixed without changing the public API

  • weaviate-container.test.ts imported type { Environment } from "testcontainers/src/types". Environment is not exported from index.ts. I removed the annotation, so TypeScript infers the type and withEnvironment() still type-checks it. This snippet appears in docs/modules/weaviate.md, so the rendered example no longer references a type that users cannot import.
  • mongodb-atlas-local-container.test.ts imported IntervalRetry from ../../../testcontainers/src/common. It is already public, so it now comes from "testcontainers".

Possible follow-up (not done here because it changes the public API and needs docs plus a minor label): export Environment, and maybe other types in types.ts, from testcontainers' index.ts.

Verification

Red, on main with the rules enabled (npx biome lint packages):

packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts:2:31 lint/style/noRestrictedImports
packages/modules/weaviate/src/weaviate-container.test.ts:1:34 lint/style/noRestrictedImports
packages/testcontainers/src/generic-container/generic-container-builder.ts:6:27 lint/suspicious/noImportCycles
packages/testcontainers/src/generic-container/generic-container-builder.ts:11:34 lint/suspicious/noImportCycles
packages/testcontainers/src/generic-container/generic-container.ts:10:51 lint/suspicious/noImportCycles
packages/testcontainers/src/generic-container/generic-container.ts:11:43 lint/suspicious/noImportCycles
packages/testcontainers/src/generic-container/generic-container.ts:37:41 lint/suspicious/noImportCycles
packages/testcontainers/src/generic-container/generic-container.ts:39:41 lint/suspicious/noImportCycles
packages/testcontainers/src/generic-container/started-generic-container.ts:9:27 lint/suspicious/noImportCycles
packages/testcontainers/src/port-forwarder/port-forwarder.ts:6:34 lint/suspicious/noImportCycles
packages/testcontainers/src/port-forwarder/port-forwarder.ts:7:27 lint/suspicious/noImportCycles
packages/testcontainers/src/reaper/reaper.ts:6:34 lint/suspicious/noImportCycles
Found 12 errors.

Green, on this branch:

Command Result
npm ci ok, package-lock.json unchanged
npm run format no changes
npm run lint no errors, no fixes applied
npx biome ci --error-on-warnings . exit 0
npm run check-compiles exit 0
npx vitest run packages/modules/weaviate/src/weaviate-container.test.ts packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts 2 files, 9 tests passed

Guard proof. I made these temporary edits, ran npx biome lint packages, then reverted them:

  • New edge into the existing loop: utils/labels.ts imports GenericContainer. Fails, flagging labels.ts:1 and the unsuppressed labels imports in the five loop files.
  • New two-file cycle probe-cycle-a.ts ↔ probe-cycle-b.ts. Fails on both files.
  • redis-container.ts (module runtime code) imports from testcontainers/build/common and ../../../testcontainers/src/common. Fails on both with noRestrictedImports.

Why this is not breaking

The runtime changes are comments only (biome-ignore). The other edits are to biome.json, AGENTS.md, and two test files. Nothing exported from any package changes, and check-compiles passes for core and every module.

Turn on suspicious/noImportCycles and, for packages/modules, style/noRestrictedImports
so modules only import core through the testcontainers entry point (the core test
helper stays allowed).

Suppress the existing GenericContainer <-> reaper/port-forwarder/builder cycle per
import, since Ryuk and sshd are GenericContainers and every edge is only used at
runtime. Fix the two deep imports in the weaviate and mongodb tests without changing
the public API.
@cristianrgreco cristianrgreco added maintenance Improvements that do not change functionality patch Backward compatible bug fix labels Oct 9, 2026
@netlify

netlify Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for testcontainers-node ready!

Name Link
🔨 Latest commit 9eaa101
🔍 Latest deploy log https://app.netlify.com/projects/testcontainers-node/deploys/6ac8bce890d9ca0008dea9cc
😎 Deploy Preview https://deploy-preview-1491--testcontainers-node.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@cristianrgreco
cristianrgreco marked this pull request as ready for review October 10, 2026 13:44
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3da1d920-329f-4e7b-8458-ce129ea74c8c

📥 Commits

Reviewing files that changed from the base of the PR and between 9eaa101 and 3df4fda.


📒 Files selected for processing (2)
  • biome.json
  • packages/testcontainers/src/reaper/reaper.ts

🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/testcontainers/src/reaper/reaper.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.



📝 Walkthrough

Walkthrough

Biome now reports import cycles and restricted imports from Testcontainers internals in module packages, with an exception for the test helper path. Contributor guidance documents these rules. Module tests adjust their imports and type annotations. Core files add suppression comments for existing import cycles; runtime behavior is unchanged.


Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 3df4f

The update tightens lint enforcement and switches module tests to the public API without changing runtime behavior; no actionable merge-blocking risk is evident.

Pre-merge checks | Passed 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: enforcing import boundaries with Biome lint rules.
Description check Passed The description is directly related to the changeset and explains the new Biome rules, existing cycle suppressions, fixed deep imports, and verification results.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 7…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintenance Improvements that do not change functionality patch Backward compatible bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant