diff --git a/.github/actions/npm-setup/action.yml b/.github/actions/npm-setup/action.yml index 215b06cbb..6f6739a39 100644 --- a/.github/actions/npm-setup/action.yml +++ b/.github/actions/npm-setup/action.yml @@ -1,12 +1,15 @@ name: "NPM Setup" -description: "Sets up Node.js and installs NPM dependencies with caching" +description: "Sets up JS runtime and installs NPM dependencies with caching" inputs: runner: description: "Runner to use" required: true - node-version: - description: "Node.js version to use" + js-runtime: + description: "JS runtime to use" + required: true + js-runtime-version: + description: "JS runtime version to use" required: true workspace: description: "Key for the cache" @@ -16,14 +19,27 @@ outputs: workspace_path: description: "Full path to the workspace directory" value: ${{ steps.set-env.outputs.workspace_path }} + cmd: + description: "Command prefix (e.g. 'node' or 'bun')" + value: ${{ steps.set-env.outputs.cmd }} + script_cmd: + description: "Command prefix to run package scripts (e.g. 'npm run' or 'bun run')" + value: ${{ steps.set-env.outputs.script_cmd }} runs: using: "composite" steps: - - name: Install NodeJS ${{ inputs.node-version }} + - name: Install NodeJS ${{ inputs.js-runtime-version }} + if: ${{ inputs.js-runtime == 'node' }} uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.js-runtime-version }} + + - name: Install Bun ${{ inputs.js-runtime-version }} + if: ${{ inputs.js-runtime == 'bun' }} + uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ inputs.js-runtime-version }} - name: Set cache configuration shell: bash @@ -46,11 +62,19 @@ runs: echo "workspace_path=packages/modules/${{ inputs.workspace }}" >> "$GITHUB_OUTPUT" fi + if [ "${{ inputs.js-runtime }}" = "node" ]; then + echo "cmd=node" >> "$GITHUB_OUTPUT" + echo "script_cmd=npm" >> "$GITHUB_OUTPUT" + elif [ "${{ inputs.js-runtime }}" = "bun" ]; then + echo "cmd=bun" >> "$GITHUB_OUTPUT" + echo "script_cmd=bun" >> "$GITHUB_OUTPUT" + fi + - uses: actions/cache/restore@v4 id: npm-cache with: path: ${{ env.CACHE_PATHS }} - key: ${{ inputs.runner }}-node-${{ inputs.node-version }}-${{ inputs.workspace }}-${{ hashFiles('package-lock.json') }} + key: ${{ inputs.runner }}-runtime-${{ inputs.js-runtime-version }}-${{ inputs.workspace }}-${{ hashFiles('package-lock.json') }} - name: Install dependencies if: steps.npm-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 7644dd321..42d146c05 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -52,22 +52,25 @@ jobs: strategy: fail-fast: false matrix: + js-runtime: + - { name: node, version: 24.x } module: ${{ fromJSON(needs.detect-modules.outputs.modules) }} runs-on: ubuntu-22.04 steps: - name: Code checkout uses: actions/checkout@v5 - - name: Install Node and Dependencies - id: npm-install-modules + - name: Install ${{ matrix.js-runtime.name }} and Dependencies + id: npm-install uses: ./.github/actions/npm-setup with: runner: ubuntu-22.04 - node-version: 24.x + js-runtime: ${{ matrix.js-runtime.name }} + js-runtime-version: ${{ matrix.js-runtime.version }} workspace: "${{ matrix.module }}" - name: Code linting env: - WORKSPACE_PATH: ${{ steps.npm-install-modules.outputs.workspace_path }} - run: npm run lint:ci + WORKSPACE_PATH: ${{ steps.npm-install.outputs.workspace_path }} + run: ${{ steps.npm-install.outputs.script_cmd }} run lint:ci compile: if: ${{ needs.detect-modules.outputs.modules_count > 0 }} @@ -78,23 +81,26 @@ jobs: strategy: fail-fast: false matrix: + js-runtime: + - { name: node, version: 24.x } module: ${{ fromJSON(needs.detect-modules.outputs.modules) }} runs-on: ubuntu-22.04 steps: - name: Code checkout uses: actions/checkout@v5 - - name: Install Node and Dependencies + - name: Install ${{ matrix.js-runtime.name }} and Dependencies id: npm-install uses: ./.github/actions/npm-setup with: runner: ubuntu-22.04 - node-version: 24.x + js-runtime: ${{ matrix.js-runtime.name }} + js-runtime-version: ${{ matrix.js-runtime.version }} workspace: "${{ matrix.module }}" - name: Compile run: | - npm run build --ignore-scripts --workspace packages/testcontainers -- --project tsconfig.json + ${{ steps.npm-install.outputs.script_cmd }} run build --ignore-scripts --workspace packages/testcontainers -- --project tsconfig.json if [ "${{ matrix.module }}" != "testcontainers" ]; then - npm run build --ignore-scripts --workspace ${{ steps.npm-install.outputs.workspace_path }} -- --project tsconfig.json --noEmit + ${{ steps.npm-install.outputs.script_cmd }} run build --ignore-scripts --workspace ${{ steps.npm-install.outputs.workspace_path }} -- --project tsconfig.json --noEmit fi smoke-test: @@ -107,31 +113,62 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x, 24.x] + js-runtime: + - { name: node, version: 20.x } + - { name: node, version: 22.x } + - { name: node, version: 24.x } + - { name: bun, version: 1.3.0 } runs-on: ubuntu-22.04 steps: - name: Code checkout uses: actions/checkout@v5 - - name: Install Node ${{ matrix.node-version }} and Dependencies + - name: Install ${{ matrix.js-runtime.name }} ${{ matrix.js-runtime-version }} and Dependencies + id: npm-install uses: ./.github/actions/npm-setup with: runner: ubuntu-22.04 - node-version: ${{ matrix.node-version }} + js-runtime: ${{ matrix.js-runtime.name }} + js-runtime-version: ${{ matrix.js-runtime.version }} workspace: "testcontainers" - name: Build testcontainers run: npm run build --workspace packages/testcontainers - name: Remove dev dependencies run: npm prune --omit=dev --workspace packages/testcontainers - name: Run CommonJS module smoke test - run: node packages/testcontainers/smoke-test.js + run: ${{ steps.npm-install.outputs.cmd }} packages/testcontainers/smoke-test.js env: DEBUG: "testcontainers*" - name: Run ES module smoke test - run: node packages/testcontainers/smoke-test.mjs + run: ${{ steps.npm-install.outputs.cmd }} packages/testcontainers/smoke-test.mjs env: DEBUG: "testcontainers*" - test: + test-docker: + if: ${{ needs.detect-modules.outputs.modules_count > 0 }} + name: Tests + needs: + - detect-modules + - lint + - compile + - smoke-test + strategy: + fail-fast: false + matrix: + js-runtime: + - { name: node, version: 20.x } + - { name: node, version: 22.x } + - { name: node, version: 24.x } + - { name: bun, version: 1.3.0 } + module: ${{ fromJSON(needs.detect-modules.outputs.modules) }} + uses: ./.github/workflows/test-template.yml + with: + runner: ubuntu-22.04 + js-runtime: ${{ matrix.js-runtime.name }} + js-runtime-version: ${{ matrix.js-runtime.version }} + container-runtime: docker + workspace: "${{ matrix.module }}" + + test-podman: if: ${{ needs.detect-modules.outputs.modules_count > 0 }} name: Tests needs: @@ -142,14 +179,18 @@ jobs: strategy: fail-fast: false matrix: + js-runtime: + - { name: node, version: 20.x } + - { name: node, version: 22.x } + - { name: node, version: 24.x } + - { name: bun, version: 1.3.0 } module: ${{ fromJSON(needs.detect-modules.outputs.modules) }} - node-version: [20.x, 22.x, 24.x] - container-runtime: [docker, podman] uses: ./.github/workflows/test-template.yml with: runner: ubuntu-22.04 - node-version: ${{ matrix.node-version }} - container-runtime: ${{ matrix.container-runtime }} + js-runtime: ${{ matrix.js-runtime.name }} + js-runtime-version: ${{ matrix.js-runtime.version }} + container-runtime: podman workspace: "${{ matrix.module }}" end: @@ -160,7 +201,8 @@ jobs: - lint - compile - smoke-test - - test + - test-docker + - test-podman runs-on: ubuntu-22.04 steps: - name: Check if any jobs failed diff --git a/.github/workflows/test-template.yml b/.github/workflows/test-template.yml index d78bc6350..19f1d7029 100644 --- a/.github/workflows/test-template.yml +++ b/.github/workflows/test-template.yml @@ -4,7 +4,10 @@ on: runner: required: true type: string - node-version: + js-runtime: + required: true + type: string + js-runtime-version: required: true type: string container-runtime: @@ -45,15 +48,16 @@ jobs: - name: Code checkout uses: actions/checkout@v5 - - name: Install Node ${{ inputs.node-version }} and Dependencies + - name: Install ${{ inputs.js-runtime }} ${{ inputs.js-runtime-version }} and Dependencies id: npm-install uses: ./.github/actions/npm-setup with: runner: ${{ inputs.runner }} - node-version: ${{ inputs.node-version }} + js-runtime: ${{ inputs.js-runtime }} + js-runtime-version: ${{ inputs.js-runtime-version }} workspace: "${{ inputs.workspace }}" - name: Run tests - run: npm run test:ci -- --coverage.include=${{ steps.npm-install.outputs.workspace_path }} ${{ steps.npm-install.outputs.workspace_path }} + run: ${{ steps.npm-install.outputs.script_cmd }} run test:ci -- --coverage.include=${{ steps.npm-install.outputs.workspace_path }} ${{ steps.npm-install.outputs.workspace_path }} env: CI: true diff --git a/packages/testcontainers/src/common/index.ts b/packages/testcontainers/src/common/index.ts index 594e54477..f1b1d3cd6 100644 --- a/packages/testcontainers/src/common/index.ts +++ b/packages/testcontainers/src/common/index.ts @@ -1,8 +1,10 @@ export { withFileLock } from "./file-lock"; export { hash } from "./hash"; export { Logger, buildLog, composeLog, containerLog, execLog, log, pullLog } from "./logger"; -export { IntervalRetry, Retry } from "./retry"; +export { IntervalRetry } from "./retry"; +export type { Retry } from "./retry"; export { streamToString } from "./streams"; export * from "./time"; export * from "./type-guards"; -export { RandomUuid, Uuid, randomUuid } from "./uuid"; +export { RandomUuid, randomUuid } from "./uuid"; +export type { Uuid } from "./uuid"; diff --git a/packages/testcontainers/src/container-runtime/clients/container/docker-container-client.ts b/packages/testcontainers/src/container-runtime/clients/container/docker-container-client.ts index ce22b023a..97efcac66 100644 --- a/packages/testcontainers/src/container-runtime/clients/container/docker-container-client.ts +++ b/packages/testcontainers/src/container-runtime/clients/container/docker-container-client.ts @@ -291,9 +291,14 @@ export class DockerContainerClient implements ContainerClient { log.debug(`Removing container...`, { containerId: container.id }); await container.remove({ v: opts?.removeVolumes }); log.debug(`Removed container`, { containerId: container.id }); - } catch (err) { - log.error(`Failed to remove container: ${err}`, { containerId: container.id }); - throw err; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + if (err.statusCode === 404) { + log.warn(`Failed to remove container as it no longer exists: ${err}`, { containerId: container.id }); + } else { + log.error(`Failed to remove container: ${err}`, { containerId: container.id }); + throw err; + } } } diff --git a/packages/testcontainers/src/container-runtime/index.ts b/packages/testcontainers/src/container-runtime/index.ts index d0332c0b5..e329b5a7f 100644 --- a/packages/testcontainers/src/container-runtime/index.ts +++ b/packages/testcontainers/src/container-runtime/index.ts @@ -1,6 +1,6 @@ export { getAuthConfig } from "./auth/get-auth-config"; export { ContainerRuntimeClient, getContainerRuntimeClient } from "./clients/client"; export { parseComposeContainerName } from "./clients/compose/parse-compose-container-name"; -export { ComposeDownOptions, ComposeExecutableOptions, ComposeOptions } from "./clients/compose/types"; -export { HostIp } from "./clients/types"; +export type { ComposeDownOptions, ComposeExecutableOptions, ComposeOptions } from "./clients/compose/types"; +export type { HostIp } from "./clients/types"; export { ImageName } from "./image-name"; diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts index 40273b48b..a53d6f713 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts @@ -1,5 +1,6 @@ import { ContainerInfo } from "dockerode"; -import { containerLog, log, RandomUuid, Uuid } from "../common"; +import type { Uuid } from "../common"; +import { containerLog, log, RandomUuid } from "../common"; import { ComposeOptions, getContainerRuntimeClient, parseComposeContainerName } from "../container-runtime"; import { StartedGenericContainer } from "../generic-container/started-generic-container"; import { getReaper } from "../reaper/reaper"; diff --git a/packages/testcontainers/src/generic-container/generic-container-builder.ts b/packages/testcontainers/src/generic-container/generic-container-builder.ts index a084108dd..55d4be6ad 100644 --- a/packages/testcontainers/src/generic-container/generic-container-builder.ts +++ b/packages/testcontainers/src/generic-container/generic-container-builder.ts @@ -1,6 +1,7 @@ import type { ImageBuildOptions } from "dockerode"; import path from "path"; -import { log, RandomUuid, Uuid } from "../common"; +import type { Uuid } from "../common"; +import { log, RandomUuid } from "../common"; import { getAuthConfig, getContainerRuntimeClient, ImageName } from "../container-runtime"; import { getReaper } from "../reaper/reaper"; import { AuthConfig, BuildArgs, RegistryConfig } from "../types"; diff --git a/packages/testcontainers/src/index.ts b/packages/testcontainers/src/index.ts index 71e65489f..5755424f3 100644 --- a/packages/testcontainers/src/index.ts +++ b/packages/testcontainers/src/index.ts @@ -1,4 +1,5 @@ -export { IntervalRetry, RandomUuid, Retry, Uuid, log, randomUuid } from "./common"; +export { IntervalRetry, RandomUuid, log, randomUuid } from "./common"; +export type { Retry, Uuid } from "./common"; export { ContainerRuntimeClient, ImageName, getContainerRuntimeClient } from "./container-runtime"; export { DockerComposeEnvironment } from "./docker-compose-environment/docker-compose-environment"; export { DownedDockerComposeEnvironment } from "./docker-compose-environment/downed-docker-compose-environment"; diff --git a/packages/testcontainers/src/network/network.ts b/packages/testcontainers/src/network/network.ts index be22802f9..dda5cec80 100644 --- a/packages/testcontainers/src/network/network.ts +++ b/packages/testcontainers/src/network/network.ts @@ -1,5 +1,6 @@ import Dockerode from "dockerode"; -import { log, RandomUuid, Uuid } from "../common"; +import type { Uuid } from "../common"; +import { log, RandomUuid } from "../common"; import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime"; import { getReaper } from "../reaper/reaper"; import { createLabels, LABEL_TESTCONTAINERS_SESSION_ID } from "../utils/labels"; diff --git a/packages/testcontainers/src/reaper/reaper.ts b/packages/testcontainers/src/reaper/reaper.ts index b558b3e69..a339d4350 100644 --- a/packages/testcontainers/src/reaper/reaper.ts +++ b/packages/testcontainers/src/reaper/reaper.ts @@ -32,7 +32,7 @@ export async function getReaper(client: ContainerRuntimeClient): Promise const reaperContainer = await findReaperContainer(client); sessionId = reaperContainer?.Labels[LABEL_TESTCONTAINERS_SESSION_ID] ?? new RandomUuid().nextUuid(); - if (process.env.TESTCONTAINERS_RYUK_DISABLED === "true") { + if (process.env.TESTCONTAINERS_RYUK_DISABLED === "true" || process.versions.bun) { return new DisabledReaper(sessionId, ""); } else if (reaperContainer) { return await useExistingReaper(reaperContainer, sessionId, client.info.containerRuntime.host);