Skip to content

Validate published packages in CI - #1490

Draft
cristianrgreco wants to merge 3 commits into
mainfrom
claude/validate-published-packages
Draft

cristianrgreco wants to merge 3 commits into
mainfrom
claude/validate-published-packages

Conversation

@cristianrgreco

Copy link
Copy Markdown
Collaborator

Summary

Nothing in CI checked what we actually publish. The smoke tests cover core's runtime under CJS, ESM, Jest and Bun, but nothing checked type resolution, package metadata or tarball contents for core and the 43 modules.

This PR adds npm run check-packages [-- <package>...] and runs it per changed package in a new Package job in checks.yml. For each package it:

  1. Builds it with its publish config (tsconfig.build.json, plus core's prebuild and the postbuild asset copies) from an empty build directory.
  2. Runs npm pack, so prepack and files apply as they do on publish.
  3. Fails if the tarball contains test-only files (*.test.*, *.spec.*, test-helper*, *-test-utils*, fixtures/, __tests__/, __mocks__/).
  4. Runs publint with --strict on the tarball, so errors and warnings fail the check.
  5. Runs @arethetypeswrong/cli on the tarball with the default strict profile (node10, node16-cjs, node16-esm, bundler) and --no-definitely-typed, so it checks only the types we ship.

The tools follow the check-engines pattern: npm exec --yes --package=<pkg>@<exact version>. They don't add devDependencies, so the lockfile and every CI install stay the same size. Both versions are older than the min-release-age=7 window.

The build has to start from an empty directory. npm run check-compiles builds with tsconfig.json, which emits the tests into the same build directories. Also, tsc skips re-emitting files that its .tsbuildinfo says are up to date, so deleting build on its own isn't enough. In a working tree where check-compiles has run, a plain npm pack -w packages/modules/postgresql ships 10 *.test.js/*.test.d.ts files. Publishing isn't affected, because the publish workflow builds from a fresh npm ci. The check deletes both build and *.tsbuildinfo first.

This automates the manual AGENTS.md rule that test-only helpers under src must be excluded in tsconfig.build.json, so AGENTS.md now points to the check and lists it in the PR checklist.

Where it runs

The check needs the publish build. The Compile job builds core with --project tsconfig.json (which includes the tests) and skips lifecycle scripts, so reusing that job would mean a second clean build in it anyway. The new Package job instead copies the Lint/Compile layout: the same detect-modules matrix, the same npm-setup cache, needs: lint, and it's added to Checks complete. Changes under .github/scripts/ already select all packages, so editing the script checks every package.

Findings

Discovery ran on 12.2.0 (main at c528f29): npm ci, npm run build --ws, then npm pack --ws and each tool against each of the 44 tarballs.

Check testcontainers 43 modules
Test-only files in tarball none none
publint errors / warnings 0 / 0 0 / 0
publint suggestions no "type" no "type", no "engines.node"
attw node10 / node16-cjs / node16-esm / bundler 🟢 / 🟢 (CJS) / 🟢 (CJS) / 🟢 🟢 / 🟢 (CJS) / 🟢 (CJS) / 🟢 for all 43

So the current tarballs are in good shape. The guardrail is there to keep them that way, for example when a new module adds a *-test-utils.ts and forgets the tsconfig.build.json exclude, which today only AGENTS.md prevents.

Per-package results (before this PR)
Package Files Test-only files publint errors/warnings publint suggestions attw node16-cjs attw node16-esm attw bundler
testcontainers 187 0 0 type 🟢 🟢 🟢
@testcontainers/arangodb 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/azure-cosmosdb-emulator 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/azureservicebus 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/azurite 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/cassandra 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/chromadb 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/clickhouse 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/cockroachdb 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/couchbase 13 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/couchdb 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/elasticsearch 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/etcd 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/gcloud 23 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/hivemq 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/influxdb 9 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/k3s 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/kafka 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/kurrentdb 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/localstack 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/mariadb 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/minio 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/mockserver 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/mongodb 9 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/mosquitto 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/mssqlserver 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/mysql 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/nats 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/neo4j 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/ollama 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/opensearch 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/oraclefree 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/postgresql 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/qdrant 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/rabbitmq 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/redis 8 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/redpanda 9 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/s3mock 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/scylladb 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/selenium 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/toxiproxy 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/valkey 8 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/vault 7 0 0 type, engines.node 🟢 🟢 🟢
@testcontainers/weaviate 7 0 0 type, engines.node 🟢 🟢 🟢

Fixed in this PR

Each fix is its own commit, so either can be dropped on its own.

  • "type": "commonjs" on all 44 packages (publint suggestion). The packages already emit CommonJS: without a "type" field Node already treats .js as CommonJS, but it may also run module syntax detection on these files (publint mentions a small performance hit). Declaring the type turns that off and states the existing format. The tsc output is byte-identical before and after for all 44 packages.
  • "engines": { "node": ">= 22.22" } on the 43 modules (publint suggestion), copied from core. package-lock.json changes only by the matching 43 engines entries.

Deferred / not changed

  • No exports field. attw and publint pass without it. Adding exports would block deep imports (for example testcontainers/build/...), which is a breaking change. Recommendation for the next major: add "exports": { ".": "./build/index.js", "./package.json": "./package.json" } (plus explicit "types" if you like). This check would then validate the export map under every resolution mode.
  • @testcontainers/gcloud leaks @google-cloud/spanner types. attw and publint can't see this because they don't resolve other packages. I confirmed it with a clean consumer: install the packed testcontainers, @testcontainers/gcloud and @testcontainers/postgresql tarballs, then run tsc with module: nodenext and skipLibCheck: false. build/spanner-emulator-helper.d.ts fails with TS2307: Cannot find module '@google-cloud/spanner' (and .../build/src/instance) unless the consumer installs spanner. Core and postgresql type-check cleanly. Declare missing module dependencies #1485 already defers this as a possible optional peerDependency because of npm conflicts across spanner majors. This result adds that users with skipLibCheck: false hit it at compile time today. A consumer type-check like this could be a follow-up guardrail, but it needs registry installs per package, so I left it out of this job.
  • Not wired into npm-publish.yml. Every PR and push to main runs the check on the same build config, so I left the release workflow unchanged.

Verification

npm ci                                    # package-lock.json unchanged
npm run check-packages                    # all 44 packages pass with no publint messages and no attw problems (~50s locally)
npm run format                            # no fixes applied
npm run lint                              # no fixes applied
npm run check-engines:root                # "engines" field exactly matches the dependency graph (>= 22.22)
node packages/testcontainers/smoke-test.js && node packages/testcontainers/smoke-test.mjs   # pass with "type": "commonjs"
npx vitest run packages/testcontainers/src/common packages/testcontainers/src/utils packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts   # pass
  • Red/green for the test-file check: I removed "src/test-helper.ts" from packages/modules/kafka/tsconfig.build.json. npm run check-packages -- kafka then fails with Test-only files in the tarball: build/test-helper.d.ts, build/test-helper.js and exits 1. Restoring the exclude makes it pass.
  • Clean-build behaviour: after tsc -b packages/testcontainers packages/modules/postgresql (what check-compiles runs), build/ contains 10 test files. npm run check-packages -- postgresql still passes because it rebuilds from empty.
  • CI install footprint: in a fresh clone I ran npm ci --workspace packages/modules/gcloud --include-workspace-root (what npm-setup installs for a matrix entry), then npm run check-packages -- gcloud. It passes, and the working tree stays clean.
  • npm run check-compiles wasn't needed because no TypeScript sources changed.
  • One generic-container-dockerfile.test.ts run failed on the Reaper session label while other local sessions were using the same Docker daemon. It passed on 3 reruns with this change and on a run without it.

Why this is not breaking

  • The CI job, npm script and AGENTS.md text only affect contributors.
  • "type": "commonjs" is how Node, TypeScript (node16/nodenext), Bun and bundlers already read these packages, since a missing "type" means CommonJS. The emitted files are byte-identical, and the CJS and ESM smoke tests pass.
  • engines.node on modules doesn't narrow support. Every module depends on testcontainers, which already declares >= 22.22, and npm, Yarn and pnpm check engines across the whole dependency tree. An unsupported Node version already gets the same warning, or the same error under engine-strict. publint labels this "may be breaking" in general, but that doesn't apply when a dependency already requires the same range.

The engines commit touches the same package.json files and lockfile area as #1485 (azurite, k3s, selenium), so whichever PR merges second may need a trivial rebase.

Add npm run check-packages, which builds each package from a clean build
directory with its publish config, packs it, and fails if the tarball contains
test-only files or if publint or @arethetypeswrong/cli report problems.
Run it per changed package in a new Package job in the Checks workflow.
Packages already emit CommonJS. Declaring it stops Node.js from running
module syntax detection on the package files, as publint suggests.
Modules depend on testcontainers, which requires Node.js >= 22.22, so
declaring the same range on each module does not narrow support.
@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 34249f5
🔍 Latest deploy log https://app.netlify.com/projects/testcontainers-node/deploys/6ac8bce5a72abf00085d269a
😎 Deploy Preview https://deploy-preview-1490--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.

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