diff --git a/package-lock.json b/package-lock.json index 225a581..7793e5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,32 +18,17 @@ "@types/node": "^24.5.2", "@types/ssh2": "^1.15.5", "@types/tar-stream": "^3.1.4", - "@vitest/coverage-v8": "^3.2.4", "oxlint": "^1.16.0", "oxlint-tsgolint": "^0.2.0", "prettier": "^3.6.2", "tsdown": "^0.15.6", - "typescript": "^5.9.2", - "vitest": "^3.2.4" + "tsx": "^4.20.6", + "typescript": "^5.9.2" }, "engines": { "node": "^18.0.0 || ^20.0.0 || >=22.0.0" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/generator": { "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", @@ -111,16 +96,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@borewit/text-codec": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", @@ -756,16 +731,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1200,17 +1165,6 @@ "win32" ] }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@quansync/fs": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-0.1.5.tgz", @@ -1469,314 +1423,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", - "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", - "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", - "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", - "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", - "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", - "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", - "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", - "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", - "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", - "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", - "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", - "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", - "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", - "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", - "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", - "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", - "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", - "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", - "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", - "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", - "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", - "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@tokenizer/inflate": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", @@ -1821,38 +1467,14 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "node_modules/@types/node": { + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", - "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.14.0" + "undici-types": "~7.14.0" } }, "node_modules/@types/ssh2": { @@ -1892,155 +1514,6 @@ "@types/node": "*" } }, - "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -2112,16 +1585,6 @@ "safer-buffer": "~2.1.0" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/ast-kit": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.1.3.tgz", @@ -2152,18 +1615,6 @@ "node": ">=4" } }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.5.tgz", - "integrity": "sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.30", - "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2197,13 +1648,6 @@ } } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/bare-events": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.0.tgz", @@ -2280,16 +1724,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -2348,23 +1782,6 @@ "node": ">= 0.4" } }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2389,16 +1806,6 @@ "dev": true, "license": "MIT" }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2665,16 +2072,6 @@ } } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -2820,13 +2217,6 @@ "node": ">= 0.4" } }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -2964,16 +2354,6 @@ "node": ">=4.0" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2993,16 +2373,6 @@ "bare-events": "^2.7.0" } }, - "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -3351,13 +2721,6 @@ "dev": true, "license": "MIT" }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -3508,60 +2871,6 @@ "dev": true, "license": "ISC" }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/iterare": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", @@ -3598,13 +2907,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -3675,13 +2977,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", @@ -3702,34 +2997,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3820,25 +3087,6 @@ "license": "MIT", "optional": true }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -4048,23 +3296,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", @@ -4078,35 +3309,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/prettier": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", @@ -4333,48 +3535,6 @@ } } }, - "node_modules/rollup": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", - "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.4", - "@rollup/rollup-android-arm64": "4.52.4", - "@rollup/rollup-darwin-arm64": "4.52.4", - "@rollup/rollup-darwin-x64": "4.52.4", - "@rollup/rollup-freebsd-arm64": "4.52.4", - "@rollup/rollup-freebsd-x64": "4.52.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", - "@rollup/rollup-linux-arm-musleabihf": "4.52.4", - "@rollup/rollup-linux-arm64-gnu": "4.52.4", - "@rollup/rollup-linux-arm64-musl": "4.52.4", - "@rollup/rollup-linux-loong64-gnu": "4.52.4", - "@rollup/rollup-linux-ppc64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-musl": "4.52.4", - "@rollup/rollup-linux-s390x-gnu": "4.52.4", - "@rollup/rollup-linux-x64-gnu": "4.52.4", - "@rollup/rollup-linux-x64-musl": "4.52.4", - "@rollup/rollup-openharmony-arm64": "4.52.4", - "@rollup/rollup-win32-arm64-msvc": "4.52.4", - "@rollup/rollup-win32-ia32-msvc": "4.52.4", - "@rollup/rollup-win32-x64-gnu": "4.52.4", - "@rollup/rollup-win32-x64-msvc": "4.52.4", - "fsevents": "~2.3.2" - } - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -4471,13 +3631,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -4543,16 +3696,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ssh2": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", @@ -4570,20 +3713,6 @@ "nan": "^2.23.0" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, - "license": "MIT" - }, "node_modules/streamx": { "version": "2.23.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", @@ -4663,19 +3792,6 @@ "node": ">=8" } }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/strtok3": { "version": "10.3.4", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", @@ -4717,98 +3833,6 @@ "streamx": "^2.15.0" } }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/test-exclude/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -4825,13 +3849,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyexec": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", @@ -4856,36 +3873,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/token-types": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", @@ -4985,6 +3972,26 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -5093,184 +4100,6 @@ "dev": true, "license": "MIT" }, - "node_modules/vite": { - "version": "7.1.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", - "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/debug": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -5315,23 +4144,6 @@ "node": ">= 8" } }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index e622567..2a07ee9 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "scripts": { "generate": "openapi-generator-cli generate -i swagger.yaml -c openapi-config.yaml -o lib && npm run format.types", "build": "npm run generate && tsdown", - "test": "vitest run --coverage", + "test": "node --import tsx --experimental-test-coverage --test 'test/**/*.test.ts'", "typecheck": "tsc --noEmit", "lint": "oxlint --type-aware", "format": "prettier --write .", @@ -57,13 +57,12 @@ "@types/node": "^24.5.2", "@types/ssh2": "^1.15.5", "@types/tar-stream": "^3.1.4", - "@vitest/coverage-v8": "^3.2.4", "oxlint": "^1.16.0", "oxlint-tsgolint": "^0.2.0", "prettier": "^3.6.2", "tsdown": "^0.15.6", - "typescript": "^5.9.2", - "vitest": "^3.2.4" + "tsx": "^4.20.6", + "typescript": "^5.9.2" }, "engines": { "node": "^18.0.0 || ^20.0.0 || >=22.0.0" diff --git a/test-integration/cjs-project/import.test.js b/test-integration/cjs-project/import.test.js index 682d5b3..e0c09ab 100644 --- a/test-integration/cjs-project/import.test.js +++ b/test-integration/cjs-project/import.test.js @@ -4,13 +4,13 @@ const assert = require('node:assert/strict'); const { DockerClient } = require('@docker/node-sdk'); test('CJS module should import correctly', () => { - assert.equal(typeof DockerClient, 'function'); - assert.equal(typeof DockerClient.fromDockerConfig, 'function'); + assert.strictEqual(typeof DockerClient, 'function'); + assert.strictEqual(typeof DockerClient.fromDockerConfig, 'function'); }); test('CJS module should import functional client', async () => { const docker = await DockerClient.fromDockerConfig(); const apiVersion = await docker.systemPing(); - assert.ok(apiVersion); + assert.notStrictEqual(apiVersion, null); console.log(` Docker API version: ${apiVersion}`); }); diff --git a/test/build.test.ts b/test/build.test.ts index 2b6b946..4ab5739 100644 --- a/test/build.test.ts +++ b/test/build.test.ts @@ -1,54 +1,64 @@ -import { expect, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { DockerClient } from '../lib/docker-client.js'; import { pack as createTarPack } from 'tar-stream'; import { Readable } from 'node:stream'; -test('imageBuild: build image from Dockerfile with tar-stream context', async () => { - const client = await DockerClient.fromDockerConfig(); - const testImageName = 'test-build-image'; - const testTag = 'latest'; +test( + 'imageBuild: build image from Dockerfile with tar-stream context', + { timeout: 60000 }, + async () => { + const client = await DockerClient.fromDockerConfig(); + const testImageName = 'test-build-image'; + const testTag = 'latest'; - try { - const pack = createTarPack(); - pack.entry( - { name: 'Dockerfile' }, - `FROM scratch + try { + const pack = createTarPack(); + pack.entry( + { name: 'Dockerfile' }, + `FROM scratch COPY test.txt /test.txt `, - ); - pack.entry({ name: 'test.txt' }, 'Hello from Docker build test!'); - pack.finalize(); + ); + pack.entry({ name: 'test.txt' }, 'Hello from Docker build test!'); + pack.finalize(); - const builtImage = await client - .imageBuild( - Readable.toWeb(pack, { strategy: { highWaterMark: 16384 } }), - { - tag: `${testImageName}:${testTag}`, - rm: true, - forcerm: true, - }, - ) - .wait(); + const builtImage = await client + .imageBuild( + Readable.toWeb(pack, { + strategy: { highWaterMark: 16384 }, + }), + { + tag: `${testImageName}:${testTag}`, + rm: true, + forcerm: true, + }, + ) + .wait(); - // Inspect the built builtImage to confirm it was created successfully - console.log(` Inspecting built image ${builtImage}`); - const imageInspect = await client.imageInspect(builtImage || ''); - console.log(' Image found! Build was successful.'); + // Inspect the built builtImage to confirm it was created successfully + console.log(` Inspecting built image ${builtImage}`); + const imageInspect = await client.imageInspect(builtImage || ''); + console.log(' Image found! Build was successful.'); - expect(imageInspect.RepoTags).toContain(`${testImageName}:${testTag}`); - console.log(` Image size: ${imageInspect.Size} bytes`); - } finally { - // Clean up: delete the test image - console.log(' Cleaning up test image...'); - try { - await client.imageDelete(`${testImageName}:${testTag}`, { - force: true, - }); - console.log(' Test image deleted successfully'); - } catch (cleanupError) { - console.log( - ` Warning: Failed to delete test image: ${(cleanupError as any)?.message}`, + assert.notStrictEqual( + imageInspect.RepoTags?.includes(`${testImageName}:${testTag}`), + false, ); + console.log(` Image size: ${imageInspect.Size} bytes`); + } finally { + // Clean up: delete the test image + console.log(' Cleaning up test image...'); + try { + await client.imageDelete(`${testImageName}:${testTag}`, { + force: true, + }); + console.log(' Test image deleted successfully'); + } catch (cleanupError) { + console.log( + ` Warning: Failed to delete test image: ${(cleanupError as any)?.message}`, + ); + } } - } -}, 60000); // 60 second timeout + }, +); diff --git a/test/concurrency.test.ts b/test/concurrency.test.ts index e5b2ae3..85927f0 100644 --- a/test/concurrency.test.ts +++ b/test/concurrency.test.ts @@ -1,41 +1,46 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { DockerClient } from '../lib/docker-client.js'; -test('concurrent requests should execute in parallel', async () => { - const client = await DockerClient.fromDockerConfig(); - const startTime = Date.now(); - - // Make 5 concurrent API calls - const promises = [ - client.systemPing(), - client.systemInfo(), - client.systemVersion(), - client.containerList({ all: true }), - client.imageList(), - ]; - - // Execute all requests concurrently - const results = await Promise.all(promises); - const totalTime = Date.now() - startTime; - - // Verify all requests completed successfully - assert.isNotNull(results[0]); // systemPing result - assert.isNotNull(results[1]); // systemInfo result - assert.isNotNull(results[2]); // systemVersion result - assert.isNotNull(results[3]); // containerList result - assert.isNotNull(results[4]); // imageList result - - console.log(` Completed 5 concurrent requests in ${totalTime}ms`); - - // Concurrent requests should be faster than sequential ones - // This is a rough check - concurrent should typically be < 80% of sequential time - assert.isTrue( - totalTime < 10000, - 'Concurrent requests should complete within reasonable time', - ); -}, 15000); +test( + 'concurrent requests should execute in parallel', + { timeout: 15000 }, + async () => { + const client = await DockerClient.fromDockerConfig(); + const startTime = Date.now(); + + // Make 5 concurrent API calls + const promises = [ + client.systemPing(), + client.systemInfo(), + client.systemVersion(), + client.containerList({ all: true }), + client.imageList(), + ]; + + // Execute all requests concurrently + const results = await Promise.all(promises); + const totalTime = Date.now() - startTime; + + // Verify all requests completed successfully + assert.notStrictEqual(results[0], null); // systemPing result + assert.notStrictEqual(results[1], null); // systemInfo result + assert.notStrictEqual(results[2], null); // systemVersion result + assert.notStrictEqual(results[3], null); // containerList result + assert.notStrictEqual(results[4], null); // imageList result + + console.log(` Completed 5 concurrent requests in ${totalTime}ms`); + + // Concurrent requests should be faster than sequential ones + // This is a rough check - concurrent should typically be < 80% of sequential time + assert.ok( + totalTime < 10000, + 'Concurrent requests should complete within reasonable time', + ); + }, +); -test('high concurrency stress test', async () => { +test('high concurrency stress test', { timeout: 20000 }, async () => { const client = await DockerClient.fromDockerConfig(); const startTime = Date.now(); @@ -48,20 +53,24 @@ test('high concurrency stress test', async () => { // Verify all requests completed successfully results.forEach((result, index) => { - assert.isNotNull(result, `Request ${index} should return a result`); + assert.notStrictEqual( + result, + null, + `Request ${index} should return a result`, + ); }); console.log(` Completed 20 concurrent ping requests in ${totalTime}ms`); console.log(` Average time per request: ${(totalTime / 20).toFixed(1)}ms`); // All requests should complete within reasonable time - assert.isTrue( + assert.ok( totalTime < 15000, 'High concurrency requests should complete within reasonable time', ); -}, 20000); +}); -test('mixed concurrent operations', async () => { +test('mixed concurrent operations', { timeout: 18000 }, async () => { const client = await DockerClient.fromDockerConfig(); // Test different types of concurrent operations @@ -86,8 +95,9 @@ test('mixed concurrent operations', async () => { // Verify all requests completed successfully results.forEach((result, index) => { - assert.isNotNull( + assert.notStrictEqual( result, + null, `Mixed operation ${index} should return a result`, ); }); @@ -95,8 +105,8 @@ test('mixed concurrent operations', async () => { console.log(` Completed 10 mixed concurrent operations in ${totalTime}ms`); // Should handle mixed operations efficiently - assert.isTrue( + assert.ok( totalTime < 12000, 'Mixed concurrent operations should complete efficiently', ); -}, 18000); +}); diff --git a/test/container.test.ts b/test/container.test.ts index 6cfe9bb..963b22e 100644 --- a/test/container.test.ts +++ b/test/container.test.ts @@ -1,4 +1,5 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { DockerClient } from '../lib/docker-client.js'; import { Filter } from '../lib/filter.js'; import { Writable } from 'node:stream'; @@ -6,376 +7,402 @@ import { Logger } from '../lib/logs.js'; // Test Docker Container API functionality -test('should receive container stdout on attach', async () => { - const client = await DockerClient.fromDockerConfig(); - let containerId: string | undefined; - - try { - // Pull alpine image first - console.log(' Pulling alpine image...'); - await client - .imageCreate({ - fromImage: 'alpine', - tag: 'latest', - }) - .wait(); - - // Create container with echo command - console.log(' Creating Alpine container with echo command...'); - const createResponse = await client.containerCreate({ - Image: 'alpine', - Cmd: ['echo', 'hello'], - Labels: { - 'test.type': 'container-test', - }, - }); - - containerId = createResponse.Id; - assert.isNotNull(containerId); - console.log(` Container created: ${containerId.substring(0, 12)}`); - - // Set up streams to capture output - const stdoutData: string[] = []; - const stderrData: string[] = []; - - // Attach to container before starting - console.log(' Attaching to container...'); - const attachPromise = client.containerAttach( - containerId, - new Logger((line: string) => { - stdoutData.push(line); - }), - new Logger((line: string) => { - stderrData.push(line); - }), - { - stream: true, - stdout: true, - stderr: false, - }, - ); - - // Start the container - console.log(' Starting container...'); - await client.containerStart(containerId); - console.log(' Container started'); - - // Wait for the attach operation to complete - await attachPromise; - console.log(' Attach completed'); - - // Wait for container to finish - console.log(' Waiting for container to finish...'); - const waitResult = await client.containerWait(containerId); - console.log( - ` Container finished with exit code: ${waitResult.StatusCode}`, - ); - - // Verify the output - console.log(' Verifying output...'); - console.log(` Captured stdout data: ${JSON.stringify(stdoutData)}`); - console.log(` Captured stderr data: ${JSON.stringify(stderrData)}`); - - // Check that we received "hello" in stdout - const allStdout = stdoutData.join(''); - assert.include(allStdout, 'hello', 'Should receive "hello" in stdout'); - - // Verify container exited successfully - assert.equal( - waitResult.StatusCode, - 0, - 'Container should exit with code 0', - ); - - console.log(' ✓ Test passed: received expected output'); - } finally { - // Clean up: delete container - if (containerId) { - console.log(' Cleaning up container...'); - try { - await client.containerDelete(containerId, { force: true }); - console.log(' Container deleted successfully'); - } catch (deleteError) { - console.log( - ` Warning: Failed to delete container: ${(deleteError as any)?.message}`, - ); +test( + 'should receive container stdout on attach', + { timeout: 30000 }, + async () => { + const client = await DockerClient.fromDockerConfig(); + let containerId: string | undefined; + + try { + // Pull alpine image first + console.log(' Pulling alpine image...'); + await client + .imageCreate({ + fromImage: 'alpine', + tag: 'latest', + }) + .wait(); + + // Create container with echo command + console.log(' Creating Alpine container with echo command...'); + const createResponse = await client.containerCreate({ + Image: 'alpine', + Cmd: ['echo', 'hello'], + Labels: { + 'test.type': 'container-test', + }, + }); + + containerId = createResponse.Id; + assert.notStrictEqual(containerId, null); + console.log( + ` Container created: ${containerId.substring(0, 12)}`, + ); + + // Set up streams to capture output + const stdoutData: string[] = []; + const stderrData: string[] = []; + + // Attach to container before starting + console.log(' Attaching to container...'); + const attachPromise = client.containerAttach( + containerId, + new Logger((line: string) => { + stdoutData.push(line); + }), + new Logger((line: string) => { + stderrData.push(line); + }), + { + stream: true, + stdout: true, + stderr: false, + }, + ); + + // Start the container + console.log(' Starting container...'); + await client.containerStart(containerId); + console.log(' Container started'); + + // Wait for the attach operation to complete + await attachPromise; + console.log(' Attach completed'); + + // Wait for container to finish + console.log(' Waiting for container to finish...'); + const waitResult = await client.containerWait(containerId); + console.log( + ` Container finished with exit code: ${waitResult.StatusCode}`, + ); + + // Verify the output + console.log(' Verifying output...'); + console.log( + ` Captured stdout data: ${JSON.stringify(stdoutData)}`, + ); + console.log( + ` Captured stderr data: ${JSON.stringify(stderrData)}`, + ); + + // Check that we received "hello" in stdout + const allStdout = stdoutData.join(''); + assert.ok( + allStdout.includes('hello'), + 'Should receive "hello" in stdout', + ); + + // Verify container exited successfully + assert.strictEqual( + waitResult.StatusCode, + 0, + 'Container should exit with code 0', + ); + + console.log(' ✓ Test passed: received expected output'); + } finally { + // Clean up: delete container + if (containerId) { + console.log(' Cleaning up container...'); + try { + await client.containerDelete(containerId, { force: true }); + console.log(' Container deleted successfully'); + } catch (deleteError) { + console.log( + ` Warning: Failed to delete container: ${(deleteError as any)?.message}`, + ); + } } } - } -}, 30000); // 30 second timeout - -test('should collect container output using containerLogs', async () => { - const client = await DockerClient.fromDockerConfig(); - let containerId: string | undefined; - - try { - // Pull alpine image first (should be cached from previous test) - console.log(' Pulling alpine image...'); - await client - .imageCreate({ - fromImage: 'alpine', - tag: 'latest', - }) - .wait(); - - // Create container with a command that produces multiple lines of output - console.log(' Creating Alpine container with multi-line output...'); - const createResponse = await client.containerCreate({ - Image: 'alpine', - Cmd: ['sh', '-c', 'echo "line1"; echo "line2"; echo "line3"'], - Labels: { - 'test.type': 'container-logs-test', - }, - }); - - containerId = createResponse.Id; - assert.isNotNull(containerId); - console.log(` Container created: ${containerId.substring(0, 12)}`); - - // Start the container and let it finish - console.log(' Starting container...'); - await client.containerStart(containerId); - console.log(' Container started'); - - // Wait for container to finish - console.log(' Waiting for container to finish...'); - const waitResult = await client.containerWait(containerId); - console.log( - ` Container finished with exit code: ${waitResult.StatusCode}`, - ); - - // Set up streams to capture logs - const stdoutLogsData: string[] = []; - const stderrLogsData: string[] = []; - - const stdoutLogsStream = new Writable({ - write( - chunk: any, - encoding: BufferEncoding, - callback: (error?: Error | null) => void, - ) { - const data = chunk.toString(); - stdoutLogsData.push(data); - console.log(` STDOUT LOGS: ${JSON.stringify(data)}`); - callback(); - }, - }); - - const stderrLogsStream = new Writable({ - write( - chunk: any, - encoding: BufferEncoding, - callback: (error?: Error | null) => void, - ) { - const data = chunk.toString(); - stderrLogsData.push(data); - console.log(` STDERR LOGS: ${JSON.stringify(data)}`); - callback(); - }, - }); - - // Get container logs - console.log(' Fetching container logs...'); - await client.containerLogs( - containerId, - stdoutLogsStream, - stderrLogsStream, - { - stdout: true, - stderr: true, - }, - ); - console.log(' Logs retrieved'); - - // Give a moment for the streams to finish processing - await new Promise((resolve) => setTimeout(resolve, 100)); - - // Verify the output - console.log(' Verifying logs...'); - console.log( - ` Captured stdout logs: ${JSON.stringify(stdoutLogsData)}`, - ); - console.log( - ` Captured stderr logs: ${JSON.stringify(stderrLogsData)}`, - ); - - // Check that we received all expected lines in stdout - const allStdoutLogs = stdoutLogsData.join(''); - assert.include( - allStdoutLogs, - 'line1', - 'Should receive "line1" in stdout logs', - ); - assert.include( - allStdoutLogs, - 'line2', - 'Should receive "line2" in stdout logs', - ); - assert.include( - allStdoutLogs, - 'line3', - 'Should receive "line3" in stdout logs', - ); - - // Verify container exited successfully - assert.equal( - waitResult.StatusCode, - 0, - 'Container should exit with code 0', - ); - - console.log(' ✓ Test passed: received all expected log lines'); - } finally { - // Clean up: delete container - if (containerId) { - console.log(' Cleaning up container...'); - try { - await client.containerDelete(containerId, { force: true }); - console.log(' Container deleted successfully'); - } catch (deleteError) { - console.log( - ` Warning: Failed to delete container: ${(deleteError as any)?.message}`, - ); + }, +); + +test( + 'should collect container output using containerLogs', + { timeout: 30000 }, + async () => { + const client = await DockerClient.fromDockerConfig(); + let containerId: string | undefined; + + try { + // Pull alpine image first (should be cached from previous test) + console.log(' Pulling alpine image...'); + await client + .imageCreate({ + fromImage: 'alpine', + tag: 'latest', + }) + .wait(); + + // Create container with a command that produces multiple lines of output + console.log( + ' Creating Alpine container with multi-line output...', + ); + const createResponse = await client.containerCreate({ + Image: 'alpine', + Cmd: ['sh', '-c', 'echo "line1"; echo "line2"; echo "line3"'], + Labels: { + 'test.type': 'container-logs-test', + }, + }); + + containerId = createResponse.Id; + assert.notStrictEqual(containerId, null); + console.log( + ` Container created: ${containerId.substring(0, 12)}`, + ); + + // Start the container and let it finish + console.log(' Starting container...'); + await client.containerStart(containerId); + console.log(' Container started'); + + // Wait for container to finish + console.log(' Waiting for container to finish...'); + const waitResult = await client.containerWait(containerId); + console.log( + ` Container finished with exit code: ${waitResult.StatusCode}`, + ); + + // Set up streams to capture logs + const stdoutLogsData: string[] = []; + const stderrLogsData: string[] = []; + + const stdoutLogsStream = new Writable({ + write( + chunk: any, + encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ) { + const data = chunk.toString(); + stdoutLogsData.push(data); + console.log(` STDOUT LOGS: ${JSON.stringify(data)}`); + callback(); + }, + }); + + const stderrLogsStream = new Writable({ + write( + chunk: any, + encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ) { + const data = chunk.toString(); + stderrLogsData.push(data); + console.log(` STDERR LOGS: ${JSON.stringify(data)}`); + callback(); + }, + }); + + // Get container logs + console.log(' Fetching container logs...'); + await client.containerLogs( + containerId, + stdoutLogsStream, + stderrLogsStream, + { + stdout: true, + stderr: true, + }, + ); + console.log(' Logs retrieved'); + + // Give a moment for the streams to finish processing + await new Promise((resolve) => setTimeout(resolve, 100)); + + // Verify the output + console.log(' Verifying logs...'); + console.log( + ` Captured stdout logs: ${JSON.stringify(stdoutLogsData)}`, + ); + console.log( + ` Captured stderr logs: ${JSON.stringify(stderrLogsData)}`, + ); + + // Check that we received all expected lines in stdout + const allStdoutLogs = stdoutLogsData.join(''); + assert.ok( + allStdoutLogs.includes('line1'), + 'Should receive "line1" in stdout logs', + ); + assert.ok( + allStdoutLogs.includes('line2'), + 'Should receive "line2" in stdout logs', + ); + assert.ok( + allStdoutLogs.includes('line3'), + 'Should receive "line3" in stdout logs', + ); + + // Verify container exited successfully + assert.strictEqual( + waitResult.StatusCode, + 0, + 'Container should exit with code 0', + ); + + console.log(' ✓ Test passed: received all expected log lines'); + } finally { + // Clean up: delete container + if (containerId) { + console.log(' Cleaning up container...'); + try { + await client.containerDelete(containerId, { force: true }); + console.log(' Container deleted successfully'); + } catch (deleteError) { + console.log( + ` Warning: Failed to delete container: ${(deleteError as any)?.message}`, + ); + } } } - } -}, 30000); // 30 second timeout - -test('container lifecycle should work end-to-end', async () => { - const client = await DockerClient.fromDockerConfig(); - let containerId: string | undefined; - - try { - await client - .imageCreate({ - fromImage: 'nginx', - tag: 'latest', - }) - .wait(); - - console.log(' Creating nginx container...'); - // Create container with label - const createResponse = await client.containerCreate( - { - Image: 'nginx', - Labels: { - 'test.type': 'e2e', + }, +); + +test( + 'container lifecycle should work end-to-end', + { timeout: 30000 }, + async () => { + const client = await DockerClient.fromDockerConfig(); + let containerId: string | undefined; + + try { + await client + .imageCreate({ + fromImage: 'nginx', + tag: 'latest', + }) + .wait(); + + console.log(' Creating nginx container...'); + // Create container with label + const createResponse = await client.containerCreate( + { + Image: 'nginx', + Labels: { + 'test.type': 'e2e', + }, + }, + { + name: 'e2e-test-container', }, - }, - { - name: 'e2e-test-container', - }, - ); - containerId = createResponse.Id; - assert.isNotNull(containerId); - console.log(` Container created: ${containerId?.substring(0, 12)}`); - - // Test container listing with label filter - console.log(' Testing container filter by label...'); - const filteredContainers = await client.containerList({ - all: true, - filters: new Filter().add('label', 'test.type=e2e'), - }); - assert.isNotNull(filteredContainers); - const foundContainer = filteredContainers.find( - (c) => c.Id === containerId, - ); - assert.isNotNull(foundContainer); - console.log( - ` Found ${filteredContainers.length} container(s) with label test.type=e2e`, - ); - - // Start container - console.log(' Starting container...'); - await client.containerStart(containerId); - console.log(' Container started'); - - // Resize TTY - console.log(' Resizing container TTY...'); - await client.containerResize(containerId, 24, 80); - console.log(' TTY resized successfully'); - - // Inspect container - console.log(' Inspecting container...'); - const inspectResponse = await client.containerInspect(containerId); - assert.isNotNull(inspectResponse); - assert.equal(inspectResponse.State?.Running, true); - console.log(` Container state: ${inspectResponse.State?.Status}`); - - // Pause container - console.log(' Pausing container...'); - await client.containerPause(containerId); - console.log(' Container paused'); - - // Verify paused state - const pausedInspect = await client.containerInspect(containerId); - assert.equal(pausedInspect.State?.Paused, true); - console.log( - ` Verified paused state: ${pausedInspect.State?.Status}`, - ); - - // Unpause container - console.log(' Unpausing container...'); - await client.containerUnpause(containerId); - console.log(' Container unpaused'); - - // Verify running state - const unpausedInspect = await client.containerInspect(containerId); - assert.equal(unpausedInspect.State?.Running, true); - assert.equal(unpausedInspect.State?.Paused, false); - console.log( - ` Verified running state: ${unpausedInspect.State?.Status}`, - ); - - // Stop container - console.log(' Stopping container...'); - await client.containerStop(containerId, { timeout: 1 }); - console.log(' Container stopped'); - - // Restart container - console.log(' Restarting container...'); - await client.containerRestart(containerId, { timeout: 1 }); - console.log(' Container restarted'); - - // Verify running again - const restartedInspect = await client.containerInspect(containerId); - assert.equal(restartedInspect.State?.Running, true); - console.log( - ` Verified restarted state: ${restartedInspect.State?.Status}`, - ); - - // Kill container - console.log(' Killing container...'); - await client.containerKill(containerId, { signal: 'SIGKILL' }); - console.log(' Container killed'); - - // Final inspect - console.log(' Final inspection...'); - const finalInspect = await client.containerInspect(containerId); - assert.equal(finalInspect.State?.Running, false); - - // Verify stopped container is found in container list - console.log(' Verifying stopped container in list...'); - const allContainers = await client.containerList({ all: true }); - const stoppedContainer = allContainers.find( - (c) => c.Id === containerId, - ); - assert.isNotNull(stoppedContainer); - console.log( - ` Found stopped container in list: ${stoppedContainer?.Id?.substring(0, 12)}`, - ); - } finally { - // Clean up: delete container - if (containerId) { - console.log(' Deleting container...'); - try { - await client.containerDelete(containerId, { force: true }); - console.log(' Container deleted successfully'); - } catch (deleteError) { - console.log( - ` Warning: Failed to delete container: ${(deleteError as any)?.message}`, - ); + ); + containerId = createResponse.Id; + assert.notStrictEqual(containerId, null); + console.log( + ` Container created: ${containerId?.substring(0, 12)}`, + ); + + // Test container listing with label filter + console.log(' Testing container filter by label...'); + const filteredContainers = await client.containerList({ + all: true, + filters: new Filter().add('label', 'test.type=e2e'), + }); + assert.notStrictEqual(filteredContainers, null); + const foundContainer = filteredContainers.find( + (c) => c.Id === containerId, + ); + assert.notStrictEqual(foundContainer, null); + console.log( + ` Found ${filteredContainers.length} container(s) with label test.type=e2e`, + ); + + // Start container + console.log(' Starting container...'); + await client.containerStart(containerId); + console.log(' Container started'); + + // Resize TTY + console.log(' Resizing container TTY...'); + await client.containerResize(containerId, 24, 80); + console.log(' TTY resized successfully'); + + // Inspect container + console.log(' Inspecting container...'); + const inspectResponse = await client.containerInspect(containerId); + assert.notStrictEqual(inspectResponse, null); + assert.strictEqual(inspectResponse.State?.Running, true); + console.log( + ` Container state: ${inspectResponse.State?.Status}`, + ); + + // Pause container + console.log(' Pausing container...'); + await client.containerPause(containerId); + console.log(' Container paused'); + + // Verify paused state + const pausedInspect = await client.containerInspect(containerId); + assert.strictEqual(pausedInspect.State?.Paused, true); + console.log( + ` Verified paused state: ${pausedInspect.State?.Status}`, + ); + + // Unpause container + console.log(' Unpausing container...'); + await client.containerUnpause(containerId); + console.log(' Container unpaused'); + + // Verify running state + const unpausedInspect = await client.containerInspect(containerId); + assert.strictEqual(unpausedInspect.State?.Running, true); + assert.strictEqual(unpausedInspect.State?.Paused, false); + console.log( + ` Verified running state: ${unpausedInspect.State?.Status}`, + ); + + // Stop container + console.log(' Stopping container...'); + await client.containerStop(containerId, { timeout: 1 }); + console.log(' Container stopped'); + + // Restart container + console.log(' Restarting container...'); + await client.containerRestart(containerId, { timeout: 1 }); + console.log(' Container restarted'); + + // Verify running again + const restartedInspect = await client.containerInspect(containerId); + assert.strictEqual(restartedInspect.State?.Running, true); + console.log( + ` Verified restarted state: ${restartedInspect.State?.Status}`, + ); + + // Kill container + console.log(' Killing container...'); + await client.containerKill(containerId, { signal: 'SIGKILL' }); + console.log(' Container killed'); + + // Final inspect + console.log(' Final inspection...'); + const finalInspect = await client.containerInspect(containerId); + assert.strictEqual(finalInspect.State?.Running, false); + + // Verify stopped container is found in container list + console.log(' Verifying stopped container in list...'); + const allContainers = await client.containerList({ all: true }); + const stoppedContainer = allContainers.find( + (c) => c.Id === containerId, + ); + assert.notStrictEqual(stoppedContainer, null); + console.log( + ` Found stopped container in list: ${stoppedContainer?.Id?.substring(0, 12)}`, + ); + } finally { + // Clean up: delete container + if (containerId) { + console.log(' Deleting container...'); + try { + await client.containerDelete(containerId, { force: true }); + console.log(' Container deleted successfully'); + } catch (deleteError) { + console.log( + ` Warning: Failed to delete container: ${(deleteError as any)?.message}`, + ); + } } } - } -}, 30000); + }, +); diff --git a/test/exec.test.ts b/test/exec.test.ts index 4594de1..275947a 100644 --- a/test/exec.test.ts +++ b/test/exec.test.ts @@ -1,4 +1,5 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { DockerClient } from '../lib/docker-client.js'; import { Logger } from '../lib/logs.js'; @@ -29,7 +30,7 @@ test('should execute ps command in running container and capture output', async }); containerId = createResponse.Id; - assert.isNotNull(containerId); + assert.notStrictEqual(containerId, null); console.log(` Container created: ${containerId.substring(0, 12)}`); // Start the container @@ -46,7 +47,7 @@ test('should execute ps command in running container and capture output', async }); const execId = execResponse.Id; - assert.isNotNull(execId); + assert.notStrictEqual(execId, null); console.log(` Exec instance created: ${execId.substring(0, 12)}`); // Set up streams to capture output @@ -73,9 +74,8 @@ test('should execute ps command in running container and capture output', async // Check that we received process information in stdout const allStdout = stdoutData.join('\n'); - assert.include( - allStdout, - 'sleep', + assert.ok( + allStdout.includes('sleep'), 'Should find sleep process in ps output', ); @@ -84,8 +84,12 @@ test('should execute ps command in running container and capture output', async const execInfo = await client.execInspect(execId); console.log(` Exec exit code: ${execInfo.ExitCode}`); - assert.equal(execInfo.ExitCode, 0, 'Exec should complete successfully'); - assert.equal( + assert.strictEqual( + execInfo.ExitCode, + 0, + 'Exec should complete successfully', + ); + assert.strictEqual( execInfo.Running, false, 'Exec should not be running anymore', diff --git a/test/filter.test.ts b/test/filter.test.ts index eb24b70..ea4613c 100644 --- a/test/filter.test.ts +++ b/test/filter.test.ts @@ -1,4 +1,5 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { Filter } from '../lib/filter.js'; test('Filter should serialize single key with single value to JSON', () => { @@ -6,7 +7,7 @@ test('Filter should serialize single key with single value to JSON', () => { filter.add('type', 'container'); const expected = { type: { container: true } }; - assert.deepEqual(filter.toJSON(), expected); + assert.deepStrictEqual(filter.toJSON(), expected); }); test('Filter should serialize single key with multiple values to JSON', () => { @@ -15,7 +16,7 @@ test('Filter should serialize single key with multiple values to JSON', () => { filter.add('type', 'image'); const expected = { type: { container: true, image: true } }; - assert.deepEqual(filter.toJSON(), expected); + assert.deepStrictEqual(filter.toJSON(), expected); }); test('Filter should serialize multiple keys to JSON', () => { @@ -29,10 +30,10 @@ test('Filter should serialize multiple keys to JSON', () => { type: { container: true, image: true }, status: { running: true, stopped: true }, }; - assert.deepEqual(filter.toJSON(), expected); + assert.deepStrictEqual(filter.toJSON(), expected); }); test('Filter should serialize empty filter to empty object', () => { const filter = new Filter(); - assert.deepEqual(filter.toJSON(), {}); + assert.deepStrictEqual(filter.toJSON(), {}); }); diff --git a/test/image.test.ts b/test/image.test.ts index 45c5127..87387c3 100644 --- a/test/image.test.ts +++ b/test/image.test.ts @@ -1,155 +1,178 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { DockerClient } from '../lib/docker-client.js'; import { Filter } from '../lib/filter.js'; import { Readable } from 'node:stream'; import type { NotFoundError } from '../lib/http.js'; -test('image lifecycle: create container, commit image, export/import, inspect, and prune', async () => { - const client = await DockerClient.fromDockerConfig(); - let containerId: string | undefined; - const testImageName = 'test'; - - try { - // Step 1: Pull alpine image and create container - console.log(' Pulling alpine image...'); - await client - .imageCreate({ - fromImage: 'alpine', - tag: 'latest', - }) - .wait(); - - console.log(' Creating Alpine container...'); - const createResponse = await client.containerCreate({ - Image: 'alpine', - Cmd: ['echo', 'test container'], - Labels: { - 'test.type': 'image-test', - }, - }); - - console.dir(createResponse, { depth: null }); - containerId = createResponse.Id; - assert.isNotNull(containerId); - console.log(` Container created: ${containerId.substring(0, 12)}`); - - // Step 2: Commit container as new image with label - console.log(' Committing container as new image...'); - const commitResponse = await client.imageCommit(containerId, { - repo: testImageName, - tag: 'latest', - changes: 'LABEL test=true', - }); - - assert.isNotNull(commitResponse.Id); - console.log( - ` Image committed: ${commitResponse.Id.substring(0, 19)}`, - ); - - // Verify the committed image exists - console.log(' Verifying committed image exists...'); - const images = await client.imageList({ - filters: new Filter().add('label', 'test=true'), - }); - const testImage = images.find((img) => - img.RepoTags?.includes(`${testImageName}:latest`), - ); - assert.isNotNull(testImage, 'Test image should exist after commit'); - console.log( - ` Found committed image: ${testImage!.Id!.substring(0, 19)}`, - ); - - // Step 3: Get image as tar file - console.log(' Exporting image as tar file...'); - const tarData: Uint8Array[] = []; - - await client.imageGet( - testImageName, - new WritableStream({ - write(chunk: Uint8Array, _controller) { - // console.log(` Writing chunk: ${chunk.length} bytes`); - tarData.push(chunk); +test( + 'image lifecycle: create container, commit image, export/import, inspect, and prune', + { timeout: 60000 }, + async () => { + const client = await DockerClient.fromDockerConfig(); + let containerId: string | undefined; + const testImageName = 'test'; + + try { + // Step 1: Pull alpine image and create container + console.log(' Pulling alpine image...'); + await client + .imageCreate({ + fromImage: 'alpine', + tag: 'latest', + }) + .wait(); + + console.log(' Creating Alpine container...'); + const createResponse = await client.containerCreate({ + Image: 'alpine', + Cmd: ['echo', 'test container'], + Labels: { + 'test.type': 'image-test', }, - }), - ); - - // Write tar data to file - const tarBuffer = Buffer.concat(tarData); - console.log( - ` Image exported to tar file: (${tarBuffer.length} bytes)`, - ); - - // Step 4: Delete the test image - console.log(' Deleting test image...'); - await client.imageDelete(testImageName, { force: true }); - console.log(' Test image deleted'); - - // Verify image is deleted - const imagesAfterDelete = await client.imageList(); - const deletedImage = imagesAfterDelete.find((img) => - img.RepoTags?.includes(`${testImageName}:latest`), - ); - assert.isUndefined(deletedImage, 'Test image should be deleted'); - console.log(' Verified image deletion'); - - // Step 5: Load image from tar file - console.log(' Loading image from tar file...'); - await client.imageLoad(Readable.toWeb(Readable.from(tarBuffer))); - console.log(' Image loaded from tar file'); - - // Step 6: Inspect the loaded image to confirm successful load - console.log(' Inspecting loaded image...'); - const inspectResponse = await client.imageInspect(testImageName); - assert.isNotNull( - inspectResponse, - 'Should be able to inspect loaded image', - ); - assert.isNotNull( - inspectResponse.Config?.Labels?.['test'], - 'Image should have test=true label', - ); - assert.equal( - inspectResponse.Config?.Labels?.['test'], - 'true', - 'Label should be "true"', - ); - console.log( - ` Image inspection successful: ${inspectResponse.Id!.substring(0, 19)}`, - ); - console.log( - ` Verified label test=${inspectResponse.Config?.Labels?.['test']}`, - ); - - // Verify the image exists in the list again - const imagesAfterLoad = await client.imageList(); - const loadedImage = imagesAfterLoad.find((img) => - img.RepoTags?.includes(`${testImageName}:latest`), - ); - assert.isNotNull(loadedImage, 'Test image should exist after load'); - console.log(' Verified loaded image in image list'); - } finally { - // Clean up: delete container - if (containerId) { - console.log(' Cleaning up container...'); - try { - await client.containerDelete(containerId, { force: true }); - console.log(' Container deleted successfully'); - } catch (err) { - if ((err as NotFoundError)?.name === 'NotFoundError') { - console.log(' Container already deleted or not found'); - } else { - console.log( - ` Warning: Failed to delete container: ${(err as any)?.message}`, - ); + }); + + console.dir(createResponse, { depth: null }); + containerId = createResponse.Id; + assert.notStrictEqual(containerId, null); + console.log( + ` Container created: ${containerId.substring(0, 12)}`, + ); + + // Step 2: Commit container as new image with label + console.log(' Committing container as new image...'); + const commitResponse = await client.imageCommit(containerId, { + repo: testImageName, + tag: 'latest', + changes: 'LABEL test=true', + }); + + assert.notStrictEqual(commitResponse.Id, null); + console.log( + ` Image committed: ${commitResponse.Id.substring(0, 19)}`, + ); + + // Verify the committed image exists + console.log(' Verifying committed image exists...'); + const images = await client.imageList({ + filters: new Filter().add('label', 'test=true'), + }); + const testImage = images.find((img) => + img.RepoTags?.includes(`${testImageName}:latest`), + ); + assert.notStrictEqual( + testImage, + null, + 'Test image should exist after commit', + ); + console.log( + ` Found committed image: ${testImage!.Id!.substring(0, 19)}`, + ); + + // Step 3: Get image as tar file + console.log(' Exporting image as tar file...'); + const tarData: Uint8Array[] = []; + + await client.imageGet( + testImageName, + new WritableStream({ + write(chunk: Uint8Array, _controller) { + // console.log(` Writing chunk: ${chunk.length} bytes`); + tarData.push(chunk); + }, + }), + ); + + // Write tar data to file + const tarBuffer = Buffer.concat(tarData); + console.log( + ` Image exported to tar file: (${tarBuffer.length} bytes)`, + ); + + // Step 4: Delete the test image + console.log(' Deleting test image...'); + await client.imageDelete(testImageName, { force: true }); + console.log(' Test image deleted'); + + // Verify image is deleted + const imagesAfterDelete = await client.imageList(); + const deletedImage = imagesAfterDelete.find((img) => + img.RepoTags?.includes(`${testImageName}:latest`), + ); + assert.strictEqual( + deletedImage, + undefined, + 'Test image should be deleted', + ); + console.log(' Verified image deletion'); + + // Step 5: Load image from tar file + console.log(' Loading image from tar file...'); + await client.imageLoad(Readable.toWeb(Readable.from(tarBuffer))); + console.log(' Image loaded from tar file'); + + // Step 6: Inspect the loaded image to confirm successful load + console.log(' Inspecting loaded image...'); + const inspectResponse = await client.imageInspect(testImageName); + assert.notStrictEqual( + inspectResponse, + null, + 'Should be able to inspect loaded image', + ); + assert.notStrictEqual( + inspectResponse.Config?.Labels?.['test'], + null, + 'Image should have test=true label', + ); + assert.strictEqual( + inspectResponse.Config?.Labels?.['test'], + 'true', + 'Label should be "true"', + ); + console.log( + ` Image inspection successful: ${inspectResponse.Id!.substring(0, 19)}`, + ); + console.log( + ` Verified label test=${inspectResponse.Config?.Labels?.['test']}`, + ); + + // Verify the image exists in the list again + const imagesAfterLoad = await client.imageList(); + const loadedImage = imagesAfterLoad.find((img) => + img.RepoTags?.includes(`${testImageName}:latest`), + ); + assert.notStrictEqual( + loadedImage, + null, + 'Test image should exist after load', + ); + console.log(' Verified loaded image in image list'); + } finally { + // Clean up: delete container + if (containerId) { + console.log(' Cleaning up container...'); + try { + await client.containerDelete(containerId, { force: true }); + console.log(' Container deleted successfully'); + } catch (err) { + if ((err as NotFoundError)?.name === 'NotFoundError') { + console.log( + ' Container already deleted or not found', + ); + } else { + console.log( + ` Warning: Failed to delete container: ${(err as any)?.message}`, + ); + } } } - } - // Clean up: ensure test image is deleted - try { - await client.imageDelete(testImageName, { force: true }); - } catch (deleteError) { - // Ignore error - image might already be deleted + // Clean up: ensure test image is deleted + try { + await client.imageDelete(testImageName, { force: true }); + } catch (deleteError) { + // Ignore error - image might already be deleted + } } - } -}, 60000); // 60 second timeout for this comprehensive test + }, +); diff --git a/test/logs.test.ts b/test/logs.test.ts index f51d804..98563f8 100644 --- a/test/logs.test.ts +++ b/test/logs.test.ts @@ -1,4 +1,5 @@ -import { assert, test, describe } from 'vitest'; +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; import { Logger } from '../lib/logs.js'; describe('Logger', () => { @@ -11,7 +12,7 @@ describe('Logger', () => { logger.write('line1\nline2\nline3\n'); logger.end(); - assert.deepEqual(lines, ['line1', 'line2', 'line3']); + assert.deepStrictEqual(lines, ['line1', 'line2', 'line3']); }); test('should handle partial lines across multiple writes', () => { @@ -25,7 +26,7 @@ describe('Logger', () => { logger.write('\nline3\n'); logger.end(); - assert.deepEqual(lines, ['partial line1', 'line2', 'line3']); + assert.deepStrictEqual(lines, ['partial line1', 'line2', 'line3']); }); test('should handle remaining buffer on end', () => { @@ -37,7 +38,7 @@ describe('Logger', () => { logger.write('line without newline'); logger.end(); - assert.deepEqual(lines, ['line without newline']); + assert.deepStrictEqual(lines, ['line without newline']); }); test('should handle empty lines', () => { @@ -49,7 +50,7 @@ describe('Logger', () => { logger.write('line1\n\nline3\n'); logger.end(); - assert.deepEqual(lines, ['line1', '', 'line3']); + assert.deepStrictEqual(lines, ['line1', '', 'line3']); }); test('should handle only newlines', () => { @@ -61,6 +62,6 @@ describe('Logger', () => { logger.write('\n\n\n'); logger.end(); - assert.deepEqual(lines, ['', '', '']); + assert.deepStrictEqual(lines, ['', '', '']); }); }); diff --git a/test/multiplexed-stream.test.ts b/test/multiplexed-stream.test.ts index e6e4e70..db4ce1f 100644 --- a/test/multiplexed-stream.test.ts +++ b/test/multiplexed-stream.test.ts @@ -1,4 +1,5 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { demultiplexStream } from '../lib/multiplexed-stream.js'; import { Writable } from 'node:stream'; @@ -34,9 +35,9 @@ test('should write stdout message to stdout stream', () => { demuxStream.write(message); - assert.deepEqual(stdoutData.length, 1); - assert.deepEqual(stdoutData[0]?.toString(), 'Hello stdout'); - assert.deepEqual(stderrData.length, 0); + assert.deepStrictEqual(stdoutData.length, 1); + assert.deepStrictEqual(stdoutData[0]?.toString(), 'Hello stdout'); + assert.deepStrictEqual(stderrData.length, 0); }); test('should write stderr message to stderr stream', () => { @@ -48,9 +49,9 @@ test('should write stderr message to stderr stream', () => { demuxStream.write(message); - assert.deepEqual(stderrData.length, 1); - assert.deepEqual(stderrData[0]?.toString(), 'Hello stderr'); - assert.deepEqual(stdoutData.length, 0); + assert.deepStrictEqual(stderrData.length, 1); + assert.deepStrictEqual(stderrData[0]?.toString(), 'Hello stderr'); + assert.deepStrictEqual(stdoutData.length, 0); }); test('should ignore unknown stream types', () => { @@ -62,8 +63,8 @@ test('should ignore unknown stream types', () => { demuxStream.write(message); - assert.deepEqual(stdoutData.length, 0); - assert.deepEqual(stderrData.length, 0); + assert.deepStrictEqual(stdoutData.length, 0); + assert.deepStrictEqual(stderrData.length, 0); }); test('should handle multiple messages in single chunk', () => { @@ -77,10 +78,10 @@ test('should handle multiple messages in single chunk', () => { demuxStream.write(combined); - assert.deepEqual(stdoutData.length, 1); - assert.deepEqual(stdoutData[0]?.toString(), 'First stdout'); - assert.deepEqual(stderrData.length, 1); - assert.deepEqual(stderrData[0]?.toString(), 'First stderr'); + assert.deepStrictEqual(stdoutData.length, 1); + assert.deepStrictEqual(stdoutData[0]?.toString(), 'First stdout'); + assert.deepStrictEqual(stderrData.length, 1); + assert.deepStrictEqual(stderrData[0]?.toString(), 'First stderr'); }); test('should handle incomplete messages across multiple chunks', () => { @@ -93,14 +94,14 @@ test('should handle incomplete messages across multiple chunks', () => { // Send first half const firstHalf = message.subarray(0, 10); demuxStream.write(firstHalf); - assert.deepEqual(stdoutData.length, 0); // Should not write yet + assert.deepStrictEqual(stdoutData.length, 0); // Should not write yet // Send second half const secondHalf = message.subarray(10); demuxStream.write(secondHalf); - assert.deepEqual(stdoutData.length, 1); - assert.deepEqual(stdoutData[0]?.toString(), 'Split message'); + assert.deepStrictEqual(stdoutData.length, 1); + assert.deepStrictEqual(stdoutData[0]?.toString(), 'Split message'); }); test('should handle empty content', () => { @@ -112,8 +113,8 @@ test('should handle empty content', () => { demuxStream.write(message); - assert.deepEqual(stdoutData.length, 1); - assert.deepEqual(stdoutData[0]?.toString(), ''); + assert.deepStrictEqual(stdoutData.length, 1); + assert.deepStrictEqual(stdoutData[0]?.toString(), ''); }); test('should handle very short incomplete chunks', () => { @@ -125,8 +126,8 @@ test('should handle very short incomplete chunks', () => { // Send only 4 bytes (less than minimum header size of 8) demuxStream.write(new TextEncoder().encode('test')); - assert.deepEqual(stdoutData.length, 0); - assert.deepEqual(stderrData.length, 0); + assert.deepStrictEqual(stdoutData.length, 0); + assert.deepStrictEqual(stderrData.length, 0); }); test('should handle large content', () => { @@ -139,6 +140,6 @@ test('should handle large content', () => { demuxStream.write(message); - assert.deepEqual(stdoutData.length, 1); - assert.deepEqual(stdoutData[0]?.toString(), largeContent); + assert.deepStrictEqual(stdoutData.length, 1); + assert.deepStrictEqual(stdoutData[0]?.toString(), largeContent); }); diff --git a/test/network.test.ts b/test/network.test.ts index 7c89c5b..a913719 100644 --- a/test/network.test.ts +++ b/test/network.test.ts @@ -1,4 +1,5 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { DockerClient } from '../lib/docker-client.js'; test('network lifecycle: create, inspect, list, delete', async () => { @@ -22,26 +23,26 @@ test('network lifecycle: create, inspect, list, delete', async () => { }, }); - assert.isNotNull(createdNetwork); - assert.isNotNull(createdNetwork.Id); + assert.notStrictEqual(createdNetwork, null); + assert.notStrictEqual(createdNetwork.Id, null); console.log(` Created network: ${networkName} (${createdNetwork.Id})`); // 2. Inspect network const inspectedNetwork = await client.networkInspect(networkName); - assert.isNotNull(inspectedNetwork); - assert.equal(inspectedNetwork.Name, networkName); - assert.equal(inspectedNetwork.Driver, 'bridge'); - assert.equal(inspectedNetwork.Labels?.test, 'lifecycle'); - assert.equal(inspectedNetwork.IPAM?.Driver, 'default'); + assert.notStrictEqual(inspectedNetwork, null); + assert.strictEqual(inspectedNetwork.Name, networkName); + assert.strictEqual(inspectedNetwork.Driver, 'bridge'); + assert.strictEqual(inspectedNetwork.Labels?.test, 'lifecycle'); + assert.strictEqual(inspectedNetwork.IPAM?.Driver, 'default'); console.log(` Inspected network: ${inspectedNetwork.Name}`); // 3. List networks and verify our network exists const networkList = await client.networkList(); - assert.isNotNull(networkList); + assert.notStrictEqual(networkList, null); const foundNetwork = networkList.find((n) => n.Name === networkName); - assert.isNotNull(foundNetwork); - assert.equal(foundNetwork?.Name, networkName); - assert.equal(foundNetwork?.Driver, 'bridge'); + assert.notStrictEqual(foundNetwork, null); + assert.strictEqual(foundNetwork?.Name, networkName); + assert.strictEqual(foundNetwork?.Driver, 'bridge'); console.log(` Found network in list: ${foundNetwork?.Name}`); // 4. Delete network @@ -51,9 +52,9 @@ test('network lifecycle: create, inspect, list, delete', async () => { // 5. Verify network is deleted by trying to inspect (should fail) try { await client.networkInspect(networkName); - assert.fail('Network should not exist after deletion'); + throw new Error('Network should not exist after deletion'); } catch (error: any) { - assert.equal(error.name, 'NotFoundError'); + assert.strictEqual(error.name, 'NotFoundError'); console.log(` Confirmed network deletion: ${networkName}`); } }); diff --git a/test/system.test.ts b/test/system.test.ts index a9d7e82..fc38d37 100644 --- a/test/system.test.ts +++ b/test/system.test.ts @@ -1,4 +1,5 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { DockerClient } from '../lib/docker-client.js'; // Test Docker System API connectivity and information @@ -6,22 +7,22 @@ import { DockerClient } from '../lib/docker-client.js'; test('systemPing should return API version', async () => { const client = await DockerClient.fromDockerConfig(); const apiVersion = await client.systemPing(); - assert.isNotNull(apiVersion); + assert.notStrictEqual(apiVersion, null); console.log(` Docker API version: ${apiVersion}`); }); test('systemInfo should return system information', async () => { const client = await DockerClient.fromDockerConfig(); const info = await client.systemInfo(); - assert.isNotNull(info); - assert.isNotNull(info.ID); + assert.notStrictEqual(info, null); + assert.notStrictEqual(info.ID, null); console.log(` Docker system ID: ${info.ID}`); }); test('systemVersion should return version information', async () => { const client = await DockerClient.fromDockerConfig(); const version = await client.systemVersion(); - assert.isNotNull(version); - assert.isNotNull(version.Version); + assert.notStrictEqual(version, null); + assert.notStrictEqual(version.Version, null); console.log(` Docker version: ${version.Version}`); }); diff --git a/test/util.test.ts b/test/util.test.ts index 71f7df0..545491d 100644 --- a/test/util.test.ts +++ b/test/util.test.ts @@ -1,48 +1,49 @@ -import { assert, test, describe } from 'vitest'; +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; import { getErrorMessage } from '../lib/util.js'; describe('utils', () => { describe('getErrorMessage', () => { test('should return undefined for null', () => { const result = getErrorMessage(null); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return undefined for undefined', () => { const result = getErrorMessage(undefined); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return undefined for false', () => { const result = getErrorMessage(false); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return undefined for 0', () => { const result = getErrorMessage(0); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return undefined for empty string', () => { const result = getErrorMessage(''); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return string when input is a string', () => { const result = getErrorMessage('error message'); - assert.equal(result, 'error message'); + assert.strictEqual(result, 'error message'); }); test('should return Error message property', () => { const error = new Error('test error'); const result = getErrorMessage(error); - assert.equal(result, 'test error'); + assert.strictEqual(result, 'test error'); }); test('should handle TypeError', () => { const error = new TypeError('type error message'); const result = getErrorMessage(error); - assert.equal(result, 'type error message'); + assert.strictEqual(result, 'type error message'); }); test('should handle custom Error subclass', () => { @@ -55,19 +56,19 @@ describe('utils', () => { const error = new CustomError('custom error message'); const result = getErrorMessage(error); - assert.equal(result, 'custom error message'); + assert.strictEqual(result, 'custom error message'); }); test('should extract message from object with message property', () => { const errorObj = { message: 'nested error message' }; const result = getErrorMessage(errorObj); - assert.equal(result, 'nested error message'); + assert.strictEqual(result, 'nested error message'); }); test('should handle deeply nested message property', () => { const errorObj = { message: { message: 'deeply nested error' } }; const result = getErrorMessage(errorObj); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should handle object with message property containing Error', () => { @@ -75,53 +76,53 @@ describe('utils', () => { message: new Error('error in message property'), }; const result = getErrorMessage(errorObj); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return undefined for object without message property', () => { const obj = { code: 'ENOENT', path: '/some/path' }; const result = getErrorMessage(obj); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return undefined for number', () => { const result = getErrorMessage(42); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return undefined for boolean true', () => { const result = getErrorMessage(true); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should return undefined for array', () => { const result = getErrorMessage(['error', 'array']); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should handle circular reference in nested messages', () => { const errorObj: any = { message: null }; errorObj.message = errorObj; const result = getErrorMessage(errorObj); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should handle null message property', () => { const errorObj = { message: null }; const result = getErrorMessage(errorObj); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should handle undefined message property', () => { const errorObj = { message: undefined }; const result = getErrorMessage(errorObj); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should handle empty string message property', () => { const errorObj = { message: '' }; const result = getErrorMessage(errorObj); - assert.equal(result, ''); + assert.strictEqual(result, ''); }); test('should handle complex circular reference chain', () => { @@ -130,7 +131,7 @@ describe('utils', () => { obj1.message = obj2; obj2.message = obj1; const result = getErrorMessage(obj1); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); test('should handle self-referencing object with other properties', () => { @@ -141,7 +142,7 @@ describe('utils', () => { }; errorObj.message = errorObj; const result = getErrorMessage(errorObj); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); }); }); diff --git a/test/volume.test.ts b/test/volume.test.ts index 8fcf610..5e42f6c 100644 --- a/test/volume.test.ts +++ b/test/volume.test.ts @@ -1,4 +1,5 @@ -import { assert, test } from 'vitest'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; import { DockerClient } from '../lib/docker-client.js'; // Test Docker Volume API functionality @@ -16,24 +17,24 @@ test('volume lifecycle: create, inspect, list, delete', async () => { }, }); - assert.isNotNull(createdVolume); - assert.equal(createdVolume.Name, volumeName); + assert.notStrictEqual(createdVolume, null); + assert.strictEqual(createdVolume.Name, volumeName); console.log(` Created volume: ${createdVolume.Name}`); // 2. Inspect volume const inspectedVolume = await client.volumeInspect(volumeName); - assert.isNotNull(inspectedVolume); - assert.equal(inspectedVolume.Name, volumeName); - assert.equal(inspectedVolume.Driver, 'local'); - assert.equal(inspectedVolume.Labels?.test, 'lifecycle'); + assert.notStrictEqual(inspectedVolume, null); + assert.strictEqual(inspectedVolume.Name, volumeName); + assert.strictEqual(inspectedVolume.Driver, 'local'); + assert.strictEqual(inspectedVolume.Labels?.test, 'lifecycle'); console.log(` Inspected volume: ${inspectedVolume.Name}`); // 3. List volumes and verify our volume exists const volumeList = await client.volumeList(); - assert.isNotNull(volumeList.Volumes); + assert.notStrictEqual(volumeList.Volumes, null); const foundVolume = volumeList.Volumes?.find((v) => v.Name === volumeName); - assert.isNotNull(foundVolume); - assert.equal(foundVolume?.Name, volumeName); + assert.notStrictEqual(foundVolume, null); + assert.strictEqual(foundVolume?.Name, volumeName); console.log(` Found volume in list: ${foundVolume?.Name}`); // 4. Delete volume @@ -43,9 +44,9 @@ test('volume lifecycle: create, inspect, list, delete', async () => { // 5. Verify volume is deleted by trying to inspect (should fail) try { await client.volumeInspect(volumeName); - assert.fail('Volume should not exist after deletion'); + throw new Error('Volume should not exist after deletion'); } catch (error: any) { - assert.equal(error.name, 'NotFoundError'); + assert.strictEqual(error.name, 'NotFoundError'); console.log(` Confirmed volume deletion: ${volumeName}`); } });