diff --git a/.dockerignore b/.dockerignore index 4e78aa5..f350568 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,6 @@ .github .idea dist -lib/types -!lib/types/FileInfo.ts node_modules out junit.xml \ No newline at end of file diff --git a/.oxlintrc.json b/.oxlintrc.json index a0db916..01bda0a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,12 +1,26 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["import", "typescript", "unicorn"], + "plugins": [ + "import", + "unicorn", + "node", + "oxc", + "eslint", + "promise", + "vitest", + "jsdoc" + ], "env": { "node": true, "es6": true }, "globals": {}, "settings": {}, + "categories": { + "correctness": "error", + "perf": "error", + "suspicious": "warn" + }, "rules": { "eqeqeq": "error", "import/no-cycle": "error", @@ -18,7 +32,89 @@ "varsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_" } - ] + ], + "no-await-in-loop": "off", + "jsdoc/check-tag-names": [ + "warn", + { + "typed": true, + "enableFixer": false + } + ], + "jsdoc/require-yields": "off" }, - "overrides": [] + "overrides": [ + { + "files": ["*.ts"], + "plugins": ["typescript"], + "rules": { + "typescript/no-floating-promises": [ + "error", + { + "ignoreVoid": true, + "ignoreIIFE": true + } + ], + "typescript/no-misused-promises": [ + "error", + { + "checksVoidReturn": false, + "checksConditionals": true + } + ], + "typescript/await-thenable": "error", + "typescript/no-array-delete": "error", + "typescript/no-base-to-string": "error", + "typescript/no-confusing-void-expression": "error", + "typescript/no-duplicate-type-constituents": "error", + "typescript/no-for-in-array": "error", + "typescript/no-implied-eval": "error", + "typescript/no-meaningless-void-operator": "error", + "typescript/no-misused-spread": "error", + "typescript/no-mixed-enums": "error", + "typescript/no-redundant-type-constituents": "error", + "typescript/no-unnecessary-boolean-literal-compare": "error", + "typescript/no-unnecessary-template-expression": "error", + "typescript/no-unnecessary-type-arguments": "error", + "typescript/no-unnecessary-type-assertion": "off", + "typescript/no-extraneous-class": "off", + "typescript/no-unsafe-type-assertion": "off", + "typescript/no-unsafe-enum-comparison": "error", + "typescript/no-unsafe-return": "error", + "typescript/no-unsafe-unary-minus": "error", + "typescript/non-nullable-type-assertion-style": "error", + "typescript/only-throw-error": "error", + "typescript/prefer-promise-reject-errors": "error", + "typescript/prefer-reduce-type-parameter": "error", + "typescript/prefer-return-this-type": "error", + "typescript/related-getter-setter-pairs": "error", + "typescript/require-array-sort-compare": "error", + "typescript/require-await": "error", + "typescript/restrict-plus-operands": "error", + "typescript/restrict-template-expressions": "error", + "typescript/return-await": ["error", "always"], + "typescript/switch-exhaustiveness-check": "error", + "typescript/unbound-method": "error", + "typescript/use-unknown-in-catch-callback-variable": "error" + } + }, + // Disable errors with `any | null` types + { + "files": [ + "lib/types/ContainerStatsResponse.ts", + "lib/types/SystemVersionComponentsInner.ts" + ], + "plugins": ["typescript"], + "rules": { + "typescript/no-redundant-type-constituents": "off" + } + }, + // Disable errors with `@` character used within jsdoc description + { + "files": ["lib/types/ExecConfig.ts", "lib/docker-client.ts"], + "rules": { + "check-tag-names": "off" + } + } + ] } diff --git a/lib/docker-client.ts b/lib/docker-client.ts index f6f822b..c4bc303 100644 --- a/lib/docker-client.ts +++ b/lib/docker-client.ts @@ -2,10 +2,10 @@ import { createConnection } from 'node:net'; import { promises as fsPromises } from 'node:fs'; import { join } from 'node:path'; import { homedir } from 'node:os'; -import type { Agent, Response } from 'undici'; +import type { Agent } from 'undici'; import type { SecureContextOptions } from 'node:tls'; import { connect as tlsConnect } from 'node:tls'; -import type { AuthConfig, BuildInfo, Platform } from './types/index.js'; +import type { AuthConfig, Platform } from './types/index.js'; import * as types from './types/index.js'; import { APPLICATION_JSON, @@ -66,6 +66,7 @@ export class DockerClient { } catch (error) { throw new Error( `Failed to create Docker client for ${dockerHost}: ${getErrorMessage(error)}`, + { cause: error }, ); } } else if (dockerHost.startsWith('tcp:')) { @@ -101,6 +102,7 @@ export class DockerClient { } catch (error) { throw new Error( `Failed to create Docker client for ${dockerHost}: ${getErrorMessage(error)}`, + { cause: error }, ); } } else if (dockerHost.startsWith('ssh:')) { @@ -112,6 +114,7 @@ export class DockerClient { } catch (error) { throw new Error( `Failed to create SSH Docker client for ${dockerHost}: ${getErrorMessage(error)}`, + { cause: error }, ); } } else { @@ -179,7 +182,7 @@ export class DockerClient { } catch { // TLS directory doesn't exist, certificates remain undefined } - return DockerClient.fromDockerHost( + return await DockerClient.fromDockerHost( dockerHost, certificates, userAgent, @@ -201,6 +204,7 @@ export class DockerClient { if (isFileNotFoundError(error)) { throw new Error( `Docker contexts directory not found: ${contextsDir}`, + { cause: error }, ); } throw error; @@ -237,14 +241,14 @@ export class DockerClient { if (config.currentContext) { // Use the specified current context - return DockerClient.fromDockerContext( + return await DockerClient.fromDockerContext( config.currentContext, userAgent, headers, ); } else { // No current context specified, use default - return DockerClient.fromDockerHost( + return await DockerClient.fromDockerHost( 'unix:/var/run/docker.sock', undefined, userAgent, @@ -258,6 +262,7 @@ export class DockerClient { } else if (error instanceof SyntaxError) { throw new Error( `Invalid JSON in Docker config file: ${configPath}`, + { cause: error }, ); } throw error; @@ -332,7 +337,7 @@ export class DockerClient { const response = await this.api.head('/_ping', { accept: 'text/plain', }); - return response.headers.get('api-version') as string; + return response.headers.get('api-version')!; } /** @@ -1039,33 +1044,34 @@ export class DockerClient { * Build an image * * @param buildContext A tar archive compressed with one of the following algorithms: identity (no compression), gzip, bzip2, xz. - * @param dockerfile Path within the build context to the `Dockerfile`. This is ignored if `remote` is specified and points to an external `Dockerfile`. - * @param t A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default `latest` value is assumed. You can provide several `t` parameters. - * @param extrahosts Extra hosts to add to /etc/hosts - * @param remote A Git repository URI or HTTP/HTTPS context URI. If the URI points to a single text file, the file's contents are placed into a file called `Dockerfile` and the image is built from that file. If the URI points to a tarball, the file is downloaded by the daemon and the contents therein used as the context for the build. If the URI points to a tarball and the `dockerfile` parameter is also specified, there must be a file with the corresponding path inside the tarball. - * @param q Suppress verbose build output. - * @param nocache Do not use the cache when building the image. - * @param cachefrom JSON array of images used for build cache resolution. - * @param pull Attempt to pull the image even if an older image exists locally. - * @param rm Remove intermediate containers after a successful build. - * @param forcerm Always remove intermediate containers, even upon failure. - * @param memory Set memory limit for build. - * @param memswap Total memory (memory + swap). Set as `-1` to disable swap. - * @param cpushares CPU shares (relative weight). - * @param cpusetcpus CPUs in which to allow execution (e.g., `0-3`, `0,1`). - * @param cpuperiod The length of a CPU period in microseconds. - * @param cpuquota Microseconds of CPU time that the container can get in a CPU period. - * @param buildargs JSON map of string pairs for build-time variables. Users pass these values at build-time. Docker uses the buildargs as the environment context for commands run via the `Dockerfile` RUN instruction, or for variable expansion in other `Dockerfile` instructions. This is not meant for passing secret values. For example, the build arg `FOO=bar` would become `{\"FOO\":\"bar\"}` in JSON. This would result in the query parameter `buildargs={\"FOO\":\"bar\"}`. Note that `{\"FOO\":\"bar\"}` should be URI component encoded. [Read more about the buildargs instruction.](https://docs.docker.com/engine/reference/builder/#arg) - * @param shmsize Size of `/dev/shm` in bytes. The size must be greater than 0. If omitted the system uses 64MB. - * @param squash Squash the resulting images layers into a single layer. *(Experimental release only.)* - * @param labels Arbitrary key/value labels to set on the image, as a JSON map of string pairs. - * @param networkmode Sets the networking mode for the run commands during build. Supported standard values are: `bridge`, `host`, `none`, and `container:<name|id>`. Any other value is taken as a custom network\'s name or ID to which this container should connect to. - * @param contentType - * @param xRegistryConfig This is a base64-encoded JSON object with auth configurations for multiple registries that a build may refer to. The key is a registry URL, and the value is an auth configuration object, [as described in the authentication section](#section/Authentication). For example: ``` { \"docker.example.com\": { \"username\": \"janedoe\", \"password\": \"hunter2\" }, \"https://index.docker.io/v1/\": { \"username\": \"mobydock\", \"password\": \"conta1n3rize14\" } } ``` Only the registry domain name (and port if not the default 443) are required. However, for legacy reasons, the Docker Hub registry must be specified with both a `https://` prefix and a `/v1/` suffix even though Docker will prefer to use the v2 registry API. - * @param platform Platform in the format os[/arch[/variant]] - * @param target Target build stage - * @param outputs BuildKit output configuration in the format of a stringified JSON array of objects. Each object must have two top-level properties: `Type` and `Attrs`. The `Type` property must be set to \'moby\'. The `Attrs` property is a map of attributes for the BuildKit output configuration. See https://docs.docker.com/build/exporters/oci-docker/ for more information. Example: ``` [{\"Type\":\"moby\",\"Attrs\":{\"type\":\"image\",\"force-compression\":\"true\",\"compression\":\"zstd\"}}] ``` - * @param version Version of the builder backend to use. - `1` is the first generation classic (deprecated) builder in the Docker daemon (default) - `2` is [BuildKit](https://github.com/moby/buildkit) + * @param options + * @param options.dockerfile Path within the build context to the `Dockerfile`. This is ignored if `remote` is specified and points to an external `Dockerfile`. + * @param options.t A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default `latest` value is assumed. You can provide several `t` parameters. + * @param options.extrahosts Extra hosts to add to /etc/hosts + * @param options.remote A Git repository URI or HTTP/HTTPS context URI. If the URI points to a single text file, the file's contents are placed into a file called `Dockerfile` and the image is built from that file. If the URI points to a tarball, the file is downloaded by the daemon and the contents therein used as the context for the build. If the URI points to a tarball and the `dockerfile` parameter is also specified, there must be a file with the corresponding path inside the tarball. + * @param options.q Suppress verbose build output. + * @param options.nocache Do not use the cache when building the image. + * @param options.cachefrom JSON array of images used for build cache resolution. + * @param options.pull Attempt to pull the image even if an older image exists locally. + * @param options.rm Remove intermediate containers after a successful build. + * @param options.forcerm Always remove intermediate containers, even upon failure. + * @param options.memory Set memory limit for build. + * @param options.memswap Total memory (memory + swap). Set as `-1` to disable swap. + * @param options.cpushares CPU shares (relative weight). + * @param options.cpusetcpus CPUs in which to allow execution (e.g., `0-3`, `0,1`). + * @param options.cpuperiod The length of a CPU period in microseconds. + * @param options.cpuquota Microseconds of CPU time that the container can get in a CPU period. + * @param options.buildargs JSON map of string pairs for build-time variables. Users pass these values at build-time. Docker uses the buildargs as the environment context for commands run via the `Dockerfile` RUN instruction, or for variable expansion in other `Dockerfile` instructions. This is not meant for passing secret values. For example, the build arg `FOO=bar` would become `{\"FOO\":\"bar\"}` in JSON. This would result in the query parameter `buildargs={\"FOO\":\"bar\"}`. Note that `{\"FOO\":\"bar\"}` should be URI component encoded. [Read more about the buildargs instruction.](https://docs.docker.com/engine/reference/builder/#arg) + * @param options.shmsize Size of `/dev/shm` in bytes. The size must be greater than 0. If omitted the system uses 64MB. + * @param options.squash Squash the resulting images layers into a single layer. *(Experimental release only.)* + * @param options.labels Arbitrary key/value labels to set on the image, as a JSON map of string pairs. + * @param options.networkmode Sets the networking mode for the run commands during build. Supported standard values are: `bridge`, `host`, `none`, and `container:<name|id>`. Any other value is taken as a custom network\'s name or ID to which this container should connect to. + * @param options.contentType + * @param options.xRegistryConfig This is a base64-encoded JSON object with auth configurations for multiple registries that a build may refer to. The key is a registry URL, and the value is an auth configuration object, [as described in the authentication section](#section/Authentication). For example: ``` { \"docker.example.com\": { \"username\": \"janedoe\", \"password\": \"hunter2\" }, \"https://index.docker.io/v1/\": { \"username\": \"mobydock\", \"password\": \"conta1n3rize14\" } } ``` Only the registry domain name (and port if not the default 443) are required. However, for legacy reasons, the Docker Hub registry must be specified with both a `https://` prefix and a `/v1/` suffix even though Docker will prefer to use the v2 registry API. + * @param options.platform Platform in the format os[/arch[/variant]] + * @param options.target Target build stage + * @param options.outputs BuildKit output configuration in the format of a stringified JSON array of objects. Each object must have two top-level properties: `Type` and `Attrs`. The `Type` property must be set to \'moby\'. The `Attrs` property is a map of attributes for the BuildKit output configuration. See https://docs.docker.com/build/exporters/oci-docker/ for more information. Example: ``` [{\"Type\":\"moby\",\"Attrs\":{\"type\":\"image\",\"force-compression\":\"true\",\"compression\":\"zstd\"}}] ``` + * @param options.version Version of the builder backend to use. - `1` is the first generation classic (deprecated) builder in the Docker daemon (default) - `2` is [BuildKit](https://github.com/moby/buildkit) */ public async *imageBuild( buildContext: ReadableStream, diff --git a/lib/filter.ts b/lib/filter.ts index 56a9829..210d1a2 100644 --- a/lib/filter.ts +++ b/lib/filter.ts @@ -1,11 +1,11 @@ export class Filter { private data: Map> = new Map(); - set(key: string, values: string[]): Filter { + set(key: string, values: string[]): this { this.data.set(key, new Set(values)); return this; } - add(key: string, value: string): Filter { + add(key: string, value: string): this { if (!this.data.has(key)) { this.data.set(key, new Set()); } diff --git a/lib/http.ts b/lib/http.ts index 8ddd611..a61f369 100644 --- a/lib/http.ts +++ b/lib/http.ts @@ -67,7 +67,7 @@ function _getErrorMessageFromResp( if (contentType?.includes(APPLICATION_JSON) && body) { const jsonBody = JSON.parse(body); if (jsonBody.message) { - return jsonBody.message; + return jsonBody.message as string; } } return res.statusMessage; @@ -253,7 +253,7 @@ export class HTTPClient { if (Array.isArray(header)) { content = header[0] || ''; } else { - content = header as string; + content = header; } return { diff --git a/lib/registry.ts b/lib/registry.ts index 95b8a31..ae2aa99 100644 --- a/lib/registry.ts +++ b/lib/registry.ts @@ -107,9 +107,9 @@ export class RegistryClient { /** * Close the client and cleanup resources */ - public close(): void { + public async close(): Promise { if (this.agent) { - this.agent.destroy(); + await this.agent.destroy(); } } @@ -131,7 +131,7 @@ export class RegistryClient { reference: string, ): Promise { const url = `${this.baseUrl}/v2/${repository}/manifests/${reference}`; - const headers = await this.buildHeaders({ + const headers = this.buildHeaders({ Accept: [ MediaTypes.OCI_MANIFEST_V1, MediaTypes.OCI_INDEX_V1, @@ -164,7 +164,7 @@ export class RegistryClient { reference: string, ): Promise { const url = `${this.baseUrl}/v2/${repository}/manifests/${reference}`; - const headers = await this.buildHeaders({ + const headers = this.buildHeaders({ Accept: [ MediaTypes.OCI_MANIFEST_V1, MediaTypes.OCI_INDEX_V1, @@ -215,7 +215,7 @@ export class RegistryClient { ): Promise { const url = `${this.baseUrl}/v2/${repository}/manifests/${reference}`; const body = JSON.stringify(manifest); - const headers = await this.buildHeaders({ + const headers = this.buildHeaders({ 'Content-Type': manifest.mediaType || MediaTypes.OCI_MANIFEST_V1, 'Content-Length': String(Buffer.byteLength(body)), }); @@ -257,7 +257,7 @@ export class RegistryClient { } const url = `${this.baseUrl}/v2/${repository}/manifests/${digest}`; - const headers = await this.buildHeaders(); + const headers = this.buildHeaders(); const response = await this.fetch(url, { method: 'DELETE', @@ -284,7 +284,7 @@ export class RegistryClient { digest: string, ): Promise { const url = `${this.baseUrl}/v2/${repository}/blobs/${digest}`; - const headers = await this.buildHeaders(); + const headers = this.buildHeaders(); const response = await this.fetch(url, { headers }); await this.handleResponse(response); @@ -316,7 +316,7 @@ export class RegistryClient { digest: string, ): Promise { const url = `${this.baseUrl}/v2/${repository}/blobs/${digest}`; - const headers = await this.buildHeaders(); + const headers = this.buildHeaders(); const response = await this.fetch(url, { method: 'HEAD', @@ -339,7 +339,7 @@ export class RegistryClient { */ public async deleteBlob(repository: string, digest: string): Promise { const url = `${this.baseUrl}/v2/${repository}/blobs/${digest}`; - const headers = await this.buildHeaders(); + const headers = this.buildHeaders(); const response = await this.fetch(url, { method: 'DELETE', @@ -362,7 +362,7 @@ export class RegistryClient { */ public async initiateUpload(repository: string): Promise { const url = `${this.baseUrl}/v2/${repository}/blobs/uploads/`; - const headers = await this.buildHeaders(); + const headers = this.buildHeaders(); const response = await this.fetch(url, { method: 'POST', @@ -409,7 +409,7 @@ export class RegistryClient { const url = new URL(uploadUrl); url.searchParams.set('digest', digest); - const headers = await this.buildHeaders({ + const headers = this.buildHeaders({ 'Content-Type': 'application/octet-stream', }); @@ -463,7 +463,7 @@ export class RegistryClient { url.searchParams.set('last', last); } - const headers = await this.buildHeaders(); + const headers = this.buildHeaders(); const response = await this.fetch(url.toString(), { headers }); await this.handleResponse(response); @@ -485,7 +485,7 @@ export class RegistryClient { */ public async checkVersion(): Promise { const url = `${this.baseUrl}/v2/`; - const headers = await this.buildHeaders(); + const headers = this.buildHeaders(); const response = await this.fetch(url, { headers }); return response.status === 200; @@ -512,7 +512,7 @@ export class RegistryClient { */ public async ping(): Promise { const url = `${this.baseUrl}/v2/`; - const headers = await this.buildHeaders(); + const headers = this.buildHeaders(); const response = await this.fetch(url, { headers }); @@ -526,9 +526,9 @@ export class RegistryClient { /** * Build request headers with authentication */ - private async buildHeaders( + private buildHeaders( additional?: Record, - ): Promise> { + ): Record { const headers: Record = { 'User-Agent': this.userAgent, ...additional, @@ -574,9 +574,9 @@ export class RegistryClient { if (error instanceof Error && error.message.includes('401')) { // Try to handle auth challenge and retry await this.handleAuthChallenge(url); - return await fetch(url, { + return fetch(url, { ...fetchOptions, - headers: await this.buildHeaders( + headers: this.buildHeaders( options?.headers as Record, ), }); diff --git a/lib/tls.ts b/lib/tls.ts index a995618..1b8558f 100644 --- a/lib/tls.ts +++ b/lib/tls.ts @@ -46,6 +46,7 @@ export class TLS { } catch (error) { throw new Error( `Failed to load TLS certificates from ${certPath}: ${getErrorMessage(error)}`, + { cause: error }, ); } } diff --git a/lib/util.ts b/lib/util.ts index 1741bde..b3c3223 100644 --- a/lib/util.ts +++ b/lib/util.ts @@ -1,5 +1,9 @@ +function isObject(value: unknown): value is object { + return value !== null && typeof value === 'object'; +} + export function isFileNotFoundError(error: unknown): boolean { - return (error as NodeJS.ErrnoException)?.code === 'ENOENT'; + return isObject(error) && 'code' in error && error.code === 'ENOENT'; } export function getErrorMessage(error: unknown): string | undefined { @@ -13,7 +17,7 @@ export function getErrorMessage(error: unknown): string | undefined { return error.message; } if ( - typeof error === 'object' && + isObject(error) && 'message' in error && typeof error.message === 'string' ) { diff --git a/makefile b/makefile index c3c66da..4681105 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,8 @@ .PHONY: install install: npm install + cd test-integration/cjs-project && npm install + cd test-integration/esm-project && npm install .PHONY: build build: install diff --git a/test-integration/cjs-project/import.test.js b/test-integration/cjs-project/import.test.js index 875ec9a..682d5b3 100644 --- a/test-integration/cjs-project/import.test.js +++ b/test-integration/cjs-project/import.test.js @@ -1,4 +1,5 @@ -import { test, assert } from 'vitest'; +const test = require('node:test'); +const assert = require('node:assert/strict'); const { DockerClient } = require('@docker/node-sdk'); diff --git a/test-integration/cjs-project/main.js b/test-integration/cjs-project/main.js index f5d0f63..7a0166c 100644 --- a/test-integration/cjs-project/main.js +++ b/test-integration/cjs-project/main.js @@ -1,6 +1,7 @@ const { DockerClient } = require('@docker/node-sdk'); const fs = require('fs'); const os = require('os'); +const { Writable } = require('node:stream'); async function main() { try { @@ -11,17 +12,14 @@ async function main() { const v = await docker.systemVersion(); console.dir(v, { depth: null }); - const ctr = await docker - .containerCreate({ - Image: 'alpine', - }) - .then((value) => { - console.dir(value, { depth: null }); - return value.Id; - }); + const container = await docker.containerCreate({ + Image: 'alpine', + }); + + console.dir(container, { depth: null }); const out = fs.createWriteStream(os.tmpdir() + '/test.tar'); - await docker.containerExport(ctr, out); + await docker.containerExport(container.Id, Writable.toWeb(out)); await docker.close(); } catch (error) { diff --git a/test-integration/cjs-project/package.json b/test-integration/cjs-project/package.json index ff0e607..32db796 100644 --- a/test-integration/cjs-project/package.json +++ b/test-integration/cjs-project/package.json @@ -3,13 +3,12 @@ "version": "1.0.0", "private": true, "scripts": { - "test": "vitest run", + "test": "node --test --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=junit.xml", "start": "node main.js" }, "type": "commonjs", "main": "main.js", "dependencies": { - "@docker/node-sdk": "file:../..", - "vitest": "^3.2.4" + "@docker/node-sdk": "file:../.." } } diff --git a/test-integration/cjs-project/vitest.config.js b/test-integration/cjs-project/vitest.config.js index 70a73f5..0f9911d 100644 --- a/test-integration/cjs-project/vitest.config.js +++ b/test-integration/cjs-project/vitest.config.js @@ -1,4 +1,4 @@ -/// +/// import { defineConfig } from 'vitest/config'; export default defineConfig({ diff --git a/test-integration/esm-project/main.ts b/test-integration/esm-project/main.ts index c7df522..5398925 100644 --- a/test-integration/esm-project/main.ts +++ b/test-integration/esm-project/main.ts @@ -11,17 +11,14 @@ try { const v = await docker.systemVersion(); console.dir(v, { depth: null }); - const ctr = await docker - .containerCreate({ - Image: 'alpine', - }) - .then((value) => { - console.dir(value, { depth: null }); - return value.Id; - }); + const container = await docker.containerCreate({ + Image: 'alpine', + }); + + console.dir(container, { depth: null }); const out = createWriteStream(tmpdir() + '/test.tar'); - await docker.containerExport(ctr, Writable.toWeb(out)); + await docker.containerExport(container.Id, Writable.toWeb(out)); await docker.close(); } catch (error: any) { diff --git a/test-integration/esm-project/vitest.config.ts b/test-integration/esm-project/vitest.config.ts index 75d073a..522f428 100644 --- a/test-integration/esm-project/vitest.config.ts +++ b/test-integration/esm-project/vitest.config.ts @@ -1,4 +1,3 @@ -/// import { defineConfig } from 'vitest/config'; export default defineConfig({ diff --git a/test/build.test.ts b/test/build.test.ts index 0181300..d286d74 100644 --- a/test/build.test.ts +++ b/test/build.test.ts @@ -1,7 +1,6 @@ import { expect, test } from 'vitest'; import { DockerClient } from '../lib/docker-client.js'; import { pack as createTarPack } from 'tar-stream'; -import { fail } from 'node:assert'; import { Readable } from 'node:stream'; test('imageBuild: build image from Dockerfile with tar-stream context', async () => { @@ -22,23 +21,19 @@ COPY test.txt /test.txt let builtImage: string | undefined; - try { - for await (const buildInfo of client.imageBuild( - Readable.toWeb(pack, { strategy: { highWaterMark: 16384 } }), - { - tag: `${testImageName}:${testTag}`, - rm: true, - forcerm: true, - }, - )) { - console.log(` Build event: ${JSON.stringify(buildInfo)}`); - // Capture the built image ID when buildinfo.id == 'moby.image.id' - if (buildInfo.id === 'moby.image.id') { - builtImage = buildInfo.aux?.ID; - } + for await (const buildInfo of client.imageBuild( + Readable.toWeb(pack, { strategy: { highWaterMark: 16384 } }), + { + tag: `${testImageName}:${testTag}`, + rm: true, + forcerm: true, + }, + )) { + console.log(` Build event: ${JSON.stringify(buildInfo)}`); + // Capture the built image ID when buildinfo.id == 'moby.image.id' + if (buildInfo.id === 'moby.image.id') { + builtImage = buildInfo.aux?.ID; } - } catch (error: any) { - fail(error); } expect(builtImage).toBeDefined(); diff --git a/test/multiplexed-stream.test.ts b/test/multiplexed-stream.test.ts index ec65b4a..e6e4e70 100644 --- a/test/multiplexed-stream.test.ts +++ b/test/multiplexed-stream.test.ts @@ -25,7 +25,7 @@ function createMultiplexedMessage(streamType: number, content: string): Buffer { return Buffer.concat([header, contentBuffer]); } -test('should write stdout message to stdout stream', async () => { +test('should write stdout message to stdout stream', () => { const { stream: stdout, data: stdoutData } = createMockStream(); const { stream: stderr, data: stderrData } = createMockStream(); @@ -39,7 +39,7 @@ test('should write stdout message to stdout stream', async () => { assert.deepEqual(stderrData.length, 0); }); -test('should write stderr message to stderr stream', async () => { +test('should write stderr message to stderr stream', () => { const { stream: stdout, data: stdoutData } = createMockStream(); const { stream: stderr, data: stderrData } = createMockStream(); @@ -53,7 +53,7 @@ test('should write stderr message to stderr stream', async () => { assert.deepEqual(stdoutData.length, 0); }); -test('should ignore unknown stream types', async () => { +test('should ignore unknown stream types', () => { const { stream: stdout, data: stdoutData } = createMockStream(); const { stream: stderr, data: stderrData } = createMockStream(); @@ -66,7 +66,7 @@ test('should ignore unknown stream types', async () => { assert.deepEqual(stderrData.length, 0); }); -test('should handle multiple messages in single chunk', async () => { +test('should handle multiple messages in single chunk', () => { const { stream: stdout, data: stdoutData } = createMockStream(); const { stream: stderr, data: stderrData } = createMockStream(); @@ -83,7 +83,7 @@ test('should handle multiple messages in single chunk', async () => { assert.deepEqual(stderrData[0]?.toString(), 'First stderr'); }); -test('should handle incomplete messages across multiple chunks', async () => { +test('should handle incomplete messages across multiple chunks', () => { const { stream: stdout, data: stdoutData } = createMockStream(); const { stream: stderr, data: _ } = createMockStream(); @@ -103,7 +103,7 @@ test('should handle incomplete messages across multiple chunks', async () => { assert.deepEqual(stdoutData[0]?.toString(), 'Split message'); }); -test('should handle empty content', async () => { +test('should handle empty content', () => { const { stream: stdout, data: stdoutData } = createMockStream(); const { stream: stderr, data: _ } = createMockStream(); @@ -116,7 +116,7 @@ test('should handle empty content', async () => { assert.deepEqual(stdoutData[0]?.toString(), ''); }); -test('should handle very short incomplete chunks', async () => { +test('should handle very short incomplete chunks', () => { const { stream: stdout, data: stdoutData } = createMockStream(); const { stream: stderr, data: stderrData } = createMockStream(); @@ -129,7 +129,7 @@ test('should handle very short incomplete chunks', async () => { assert.deepEqual(stderrData.length, 0); }); -test('should handle large content', async () => { +test('should handle large content', () => { const { stream: stdout, data: stdoutData } = createMockStream(); const { stream: stderr, data: _ } = createMockStream();