Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .agents/skills/add-module/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
name: add-module
description: Adds a new Testcontainers module under packages/modules (container class, tests, Dockerfile image pin, docs page, mkdocs nav), or brings a contributor's module PR up to the repo's conventions. Use when asked to add, create, port or support a new container or service module (e.g. "add a RustFS module", "port the Java Pulsar module"), or when adding a second image or class to an existing module.
argument-hint: "[module name]"
---

# Add a module

Start by copying a small, recent module (`packages/modules/mosquitto` and `docs/modules/mosquitto.md`) and adapting it. Copying keeps the boilerplate current. The rules below are what reviewers keep flagging on module PRs.

## Before writing code

- Check the Java and Go modules for the image, ports, wait strategy and defaults, and for whether they split major versions into separate classes.
- Pin a concrete, current, multi-arch tag in the module `Dockerfile`, never `latest` or a floating major. `docker manifest inspect <image:tag>` should list both amd64 and arm64.
- The client library used in the tests goes in `devDependencies` (`npm install -w @testcontainers/<name> --save-dev <client>`). Users bring their own client. Add a runtime dependency only if the container class itself needs one.

## Container class

- The constructor sets exposed ports, the wait strategy and `withStartupTimeout(120_000)`. Setting the wait strategy there lets users override it.
- Prefer listening-port, health-check or HTTP waits over log regexes. Log output changes between image versions. Shell-less images need a health-check or HTTP wait.
- Zero config must work: `new XContainer(IMAGE).start()` gives a container a client can connect to. Prefer defaults to getters that can return `undefined`.
- Validate `with*` inputs, and fail fast on half-set config (for example a username without a password) instead of waiting out the startup timeout.
- Started-container getters call `getMappedPort()` when they're invoked. A restarted container can get different host ports.
- Incompatible major versions (different ports, auth or startup) get separate classes, not image-tag parsing.
- Keep it small: no getters that exist only for tests, and no speculative options.

## Tests

- Every test does a real client round trip, such as write then read, or publish then receive. Asserting that getters or connection strings look right proves nothing. Cover the default path and each option that changes behaviour.
- Use `await using`, with one container per test. Tests run concurrently.
- Import from the container file, not `./index`. Read images with `getImage(__dirname, index)` (AGENTS.md).
- Wrap the part a user would copy in `// name {` … `// }` markers inside the `it` body. The docs include these blocks.

## Docs and finish

- Adapt the mosquitto docs page. Examples come only from test blocks via `codeinclude`. Keep the "substitute `IMAGE`" line.
- Add the page to the `mkdocs.yml` Modules nav in alphabetical order.
- Verify per AGENTS.md, including `npx vitest run packages/modules/<name>`.
- The diff should contain only the module directory, the docs page, `mkdocs.yml`, and the lockfile entries for the new workspace and client.
- Open the PR with `open-pr`: title `Add <Name> module`, labels `enhancement` + `minor`.
50 changes: 50 additions & 0 deletions .agents/skills/diagnose-ci/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: diagnose-ci
description: Diagnoses red or flaky GitHub Actions checks in testcontainers-node from the job-matrix pattern and failed logs, classifies the cause (dependency resolution, image or SDK change, Podman-only flake, real regression, flaky test) and fixes or routes it. Use when CI, a workflow run or a job (Lint, Compile, Smoke tests, Tests) is failing or flaky, e.g. "why is CI red", "is this flaky", "fix the failing test in CI".
argument-hint: "[PR number or run id]"
---

# Diagnose CI

`checks.yml` decides which packages to run with `.github/scripts/changed-modules.mjs`. A change to one module runs only that module. A change to core or to root config runs every package. Docs-only changes run nothing.

Each selected package then runs:

1. Lint
2. Compile
3. Tests, across Node 22/24 × Docker/Podman

The smoke tests run only when core is selected. In CI, Vitest retries a failing test 3 times, so a red test failed four times in a row. A test that "passed on retry" is still flaky.

## Read the shape first

```bash
gh pr checks <N>
gh run view <run-id> --log-failed | head -300
```

| Pattern | Likely cause |
| --- | --- |
| Every Lint job red | `npm ci` failed, usually a peer-dependency conflict after a bump → `update-dependencies` |
| Smoke tests red | The built package doesn't load under CJS, ESM, Jest or Bun. Often an ESM-only runtime dependency → `update-dependencies` |
| Every Tests job for one module red | Its image is gone or moved, or the client SDK changed → `update-dependencies` |
| Only Podman jobs red, with health-check or startup timeouts on heavy images | Known Podman slowness. If it's unrelated to the diff, rerun with `gh run rerun <run-id> --failed` |
| The same test red on every runtime and Node version | A real regression or a deterministic test bug. Reproduce it locally |

Before blaming the PR, check whether `main` is red in the same place: `gh run list --branch main --workflow checks.yml --limit 5`.

## Fix a flaky test

Treat a flake as a bug and use red-green (AGENTS.md):

1. Reproduce it by looping the single file:

```bash
for i in $(seq 1 20); do npx vitest run <file> || break; done
```

2. Find the race. Usual suspects:
- The port is open before the service is actually ready. Wait using the client's own readiness check.
- State shared between concurrent tests.
- A timeout too tight for a slow image.
3. Fix the cause. Don't just raise retries or timeouts.
61 changes: 61 additions & 0 deletions .agents/skills/open-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
name: open-pr
description: Verifies, commits, pushes and opens a pull request in testcontainers-node following the repo's title, label and PR-body conventions. Use when work is ready to ship ("open a PR", "commit this", "push and raise a PR", "write the PR description") or when choosing a PR title or labels.
---

# Open a PR

Release Drafter turns PR titles into release notes and labels into the version bump (`.github/release-drafter.yml`). Titles and labels matter as much as the code.

Before committing, pushing or opening anything, get the user's approval of the diff, commit message, title and body (AGENTS.md).

## Before committing

- Branch from an up-to-date `main`.
- Run the checks in AGENTS.md "Verification". For bug fixes, keep the red-green output.
- Check that `git diff --stat main...HEAD` shows only the files you intended, and that the lockfile changes only the entries you intended.
- If you changed GitHub Actions, Node or npm versions, or the publish automation, also dry-run the publish workflow against your branch:

```bash
gh workflow run npm-publish.yml --ref <branch> -f version=<next version>
```

## Title

Write it as an imperative release-note line about the user-visible change. Don't use conventional-commit prefixes, agent names or branch names.

| Good | Bad |
| --- | --- |
| `Add Mosquitto module` | `Adding module mosquitto`, `feat(mosquitto): add module` |
| `Fix container exec output truncation` | `Fixed exec truncation`, `fix: exec` |

## Labels

Every PR gets exactly one change-type label and one semver label.

| Change | Labels |
| --- | --- |
| Feature or new module | `enhancement` + `minor` |
| Bug fix | `bug` + `patch` |
| Breaking change (removed or renamed export, changed default, ESM-only runtime dependency, higher Node floor) | type label + `major` |
| Docs only | `documentation` + `patch` |
| Dependency update | `dependencies` + its user-facing impact |
| CI, tooling, tests, refactors | `maintenance` + `patch` |

## Body

Include:

- **Summary:** what changed and why. Link to the Java or Go implementation if you borrowed from it.
- **Verification:** the commands you ran and their results, including red-green evidence for fixes.
- **Not breaking** (unless the PR is labelled `major`): why the change is backward compatible.
- `Closes #<issue>`, only if the PR fully resolves that issue.

Write the body to a file and pass it with `--body-file`. Inline `--body` mangles backticks.

```bash
git push -u origin <branch>
gh pr create --base main --title "<title>" --body-file <path> --label <type> --label <semver>
```

Open it ready for review. Only open it as a draft if the user asks.
68 changes: 68 additions & 0 deletions .agents/skills/publish-release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
name: publish-release
description: Prepares, dry-runs, publishes and verifies a testcontainers-node npm release (testcontainers plus every @testcontainers/* module), and recovers from a failed publish. Use when cutting, preparing, dry-running or publishing a release, reviewing the draft release notes, or fixing a failed publish run.
argument-hint: "[version]"
disable-model-invocation: true
---

# Publish a release

Release Drafter keeps a draft GitHub release current as PRs merge. Publishing that draft triggers `npm-publish.yml`, which:

1. bumps every workspace to the new version
2. commits `v<version>` to `main` and pushes it
3. runs `npm publish --ws`

Running the same workflow manually (`workflow_dispatch`) is a dry run. An npm version can never be republished, so get explicit user approval before publishing.

Copy this checklist and track progress:

```
- [ ] Draft reviewed (labels, version, titles)
- [ ] No hidden breaking changes
- [ ] Dry run green
- [ ] User approved publishing
- [ ] Published and verified on npm
```

## 1. Review the draft

1. Read the draft with `gh release view v<draft>`.
2. List the PRs merged since the last release: `gh pr list --state merged --search "merged:>=<last release date>" --json number,title,labels`.
3. Check:
- Every PR has a type label and a semver label. Without a type label, a PR is missing from the notes. Without a semver label, it counts as patch.
- The version is right for the highest semver label.
- The titles read as release notes. Fix a title on the PR itself.

## 2. Look for hidden breaking changes

- Diff runtime dependencies since the last tag: `git diff v<last>..main -- 'packages/**/package.json'`.
- Flag any new major version that is ESM-only (AGENTS.md).
- Also check whether `engines.node` changed, or exports were removed or renamed.

## 3. Dry run

The version must be plain `x.y.z`: no `v` prefix and no trailing dot.

```bash
gh workflow run npm-publish.yml --ref main -f version=<x.y.z>
gh run watch $(gh run list --workflow npm-publish.yml --limit 1 --json databaseId --jq '.[0].databaseId')
```

If the dry run fails, fix the cause in a normal PR first.

## 4. Publish and verify

After approval, publish the draft (`gh release edit v<x.y.z> --draft=false --latest`) and watch the run. Then confirm:

- `main` has the `v<x.y.z>` commit.
- `npm view testcontainers version` and `npm view @testcontainers/<module> version` (spot-check a few modules) report `x.y.z`.

## Recovery

- **Version commit pushed but nothing published:**
1. Revert the `v<x.y.z>` commit on `main`. A plain re-run fails with nothing to commit.
2. Fix the cause.
3. Re-run the publish.
- **Only some packages published:** a published version can't be republished. Ship a new patch release for all packages. Never unpublish without the user's explicit decision.
- **A regression shipped:** fix forward with a patch release.
64 changes: 64 additions & 0 deletions .agents/skills/review-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
name: review-pr
description: Reviews a testcontainers-node pull request (own or a contributor's) against the maintainer's recurring review feedback and drafts terse inline comments for approval. Use when asked to review, check, look over or give feedback on a PR, PR number, branch or diff, for a self-review before opening a PR, and when the @claude GitHub Action is asked to review.
argument-hint: "[PR number]"
---

# Review a PR

Aim to raise in one pass everything the maintainer would otherwise raise over several rounds.

## Gather

```bash
gh pr view <N> --json title,body,labels,headRefName,comments,reviews
gh api repos/testcontainers/testcontainers-node/pulls/<N>/comments # inline review comments
gh pr diff <N>
gh pr checks <N>
```

- Read the linked issue, and read the surrounding code as well as the hunks.
- Treat earlier review rounds as context. Check that previously requested changes were made, and don't repeat points already raised.
- If CI is red, find out why first (`diagnose-ci`).
- For fork PRs, `gh pr checks` can look green while the workflows wait for approval. Check `gh run list --branch <head>`. If CI hasn't run, say so.
- For a new module, also apply the `add-module` rules.

## What to look for

- **Title and labels:** they follow `open-pr`, and the semver label matches the real impact.
- **Tests:**
- Each test can actually fail. Flag tests that only read back getters, check a string's shape, or assert `toBeDefined()` on an API that returns 200 on errors.
- Each option has a test that would fail if the option never reached the container.
- Bug fixes come with red-green evidence.
- New cases extend the nearest existing test file with the same setup (real Docker or a mocked client) rather than adding new files.
- **Concurrency:** no shared containers, `process.env` or spies without `{ concurrent: false }`. Use `await using`. No skipping when Docker is unavailable.
- **Completeness:** a fix applied to one path is also applied to its siblings, for example `restart()` next to `start()`, or the reuse path next to the create path.
- **Docs:** public API changes are documented, and module examples use `codeinclude` (AGENTS.md).
- **Design:**
- Zero-config defaults work, and invalid or half-set config fails fast.
- The wait strategy is set in the constructor, and waits are robust (listening ports, health check or HTTP rather than log regexes).
- Images are pinned in the module `Dockerfile`.
- **Scope:**
- Flag new files for a few lines of logic, dead fallbacks, duplicated constants, getters that exist only for tests, and tests of third-party behaviour.
- Flag unrelated dependency bumps; leave those to Dependabot.
- **Dependencies:** runtime dependencies load from CommonJS (AGENTS.md), and well-established libraries or built-ins are preferred.
- **Breaking changes:** renamed exports, changed defaults and lowered timeouts all count. They need `major` or a non-breaking alternative.
- **Claims:** check root-cause explanations and "Java/Go does X" statements against the actual code. AI-written PR descriptions are often confidently wrong.

## Write the comments

- Anchor each comment on the line it concerns, one problem per comment. Findings outside the diff hunks, such as an untouched sibling path, go in the review body. Say what's wrong, add a sentence of context if it helps, then say what to do instead. Skip restated background and severity labels.
- Keep the review body to the few must-address points, plus any high-level design note. A short thanks, a numbered list of required changes, then "Nits" matches the maintainer's style.
- Before calling something a convention, check the rest of the repo. Prefer "the other modules do X" to broad claims.
- If there's nothing worth raising, say so.

## Post

**Locally:** show the user every comment (path, line, text), the review body, and any suggested title or label changes. Post nothing until they approve. Then post a single review:

```bash
gh api --method POST repos/testcontainers/testcontainers-node/pulls/<N>/reviews --input review.json
# {"event":"COMMENT","body":"...","comments":[{"path":"...","line":42,"side":"RIGHT","body":"..."}]}
```

**As the `@claude` GitHub Action:** the triggering comment is the approval, so post the inline comments directly. Put any title or label suggestions in your reply; never change labels, approve or merge.
Loading