From 5d59cf29d8c7f4252f326db690cf044c6d617569 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:14:21 +0000 Subject: [PATCH] fix: format docker-compose error objects as readable messages Co-authored-by: neilime <314088+neilime@users.noreply.github.com> Signed-off-by: Emilien Escalle --- dist/index.js | 67 ++++++++- dist/post.js | 67 ++++++++- eslint.config.mjs | 3 + package-lock.json | 84 +++-------- package.json | 40 +----- src/services/docker-compose.service.test.ts | 148 ++++++++++++++++++++ src/services/docker-compose.service.ts | 78 ++++++++++- 7 files changed, 371 insertions(+), 116 deletions(-) create mode 100644 eslint.config.mjs diff --git a/dist/index.js b/dist/index.js index 81ed5d9..71714b4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -33176,18 +33176,28 @@ class DockerComposeService { ...this.getCommonOptions(optionsInputs), commandOptions: upFlags, }; - if (services.length > 0) { - await (0, docker_compose_1.upMany)(services, options); - return; + try { + if (services.length > 0) { + await (0, docker_compose_1.upMany)(services, options); + return; + } + await (0, docker_compose_1.upAll)(options); + } + catch (error) { + throw this.formatDockerComposeError(error); } - await (0, docker_compose_1.upAll)(options); } async down({ downFlags, ...optionsInputs }) { const options = { ...this.getCommonOptions(optionsInputs), commandOptions: downFlags, }; - await (0, docker_compose_1.down)(options); + try { + await (0, docker_compose_1.down)(options); + } + catch (error) { + throw this.formatDockerComposeError(error); + } } async logs({ services, ...optionsInputs }) { const options = { @@ -33212,6 +33222,53 @@ class DockerComposeService { }, }; } + /** + * Formats docker-compose errors into proper Error objects with readable messages + */ + formatDockerComposeError(error) { + // If it's already an Error, return it + if (error instanceof Error) { + return error; + } + // Handle docker-compose result objects + if (this.isDockerComposeResult(error)) { + const parts = []; + // Add exit code information + if (error.exitCode !== null) { + parts.push(`Docker Compose command failed with exit code ${error.exitCode}`); + } + else { + parts.push("Docker Compose command failed"); + } + // Add error stream output if available + if (error.err && error.err.trim()) { + parts.push("\nError output:"); + parts.push(error.err.trim()); + } + // Add standard output if available and different from error output + if (error.out && error.out.trim() && error.out !== error.err) { + parts.push("\nStandard output:"); + parts.push(error.out.trim()); + } + return new Error(parts.join("\n")); + } + // Handle string errors + if (typeof error === "string") { + return new Error(error); + } + // Fallback for unknown error types + return new Error(JSON.stringify(error)); + } + /** + * Type guard to check if an object is a docker-compose result + */ + isDockerComposeResult(error) { + return (typeof error === "object" && + error !== null && + "exitCode" in error && + "err" in error && + "out" in error); + } } exports.DockerComposeService = DockerComposeService; diff --git a/dist/post.js b/dist/post.js index f5070d2..03d517b 100644 --- a/dist/post.js +++ b/dist/post.js @@ -26253,18 +26253,28 @@ class DockerComposeService { ...this.getCommonOptions(optionsInputs), commandOptions: upFlags, }; - if (services.length > 0) { - await (0, docker_compose_1.upMany)(services, options); - return; + try { + if (services.length > 0) { + await (0, docker_compose_1.upMany)(services, options); + return; + } + await (0, docker_compose_1.upAll)(options); + } + catch (error) { + throw this.formatDockerComposeError(error); } - await (0, docker_compose_1.upAll)(options); } async down({ downFlags, ...optionsInputs }) { const options = { ...this.getCommonOptions(optionsInputs), commandOptions: downFlags, }; - await (0, docker_compose_1.down)(options); + try { + await (0, docker_compose_1.down)(options); + } + catch (error) { + throw this.formatDockerComposeError(error); + } } async logs({ services, ...optionsInputs }) { const options = { @@ -26289,6 +26299,53 @@ class DockerComposeService { }, }; } + /** + * Formats docker-compose errors into proper Error objects with readable messages + */ + formatDockerComposeError(error) { + // If it's already an Error, return it + if (error instanceof Error) { + return error; + } + // Handle docker-compose result objects + if (this.isDockerComposeResult(error)) { + const parts = []; + // Add exit code information + if (error.exitCode !== null) { + parts.push(`Docker Compose command failed with exit code ${error.exitCode}`); + } + else { + parts.push("Docker Compose command failed"); + } + // Add error stream output if available + if (error.err && error.err.trim()) { + parts.push("\nError output:"); + parts.push(error.err.trim()); + } + // Add standard output if available and different from error output + if (error.out && error.out.trim() && error.out !== error.err) { + parts.push("\nStandard output:"); + parts.push(error.out.trim()); + } + return new Error(parts.join("\n")); + } + // Handle string errors + if (typeof error === "string") { + return new Error(error); + } + // Fallback for unknown error types + return new Error(JSON.stringify(error)); + } + /** + * Type guard to check if an object is a docker-compose result + */ + isDockerComposeResult(error) { + return (typeof error === "object" && + error !== null && + "exitCode" in error && + "err" in error && + "out" in error); + } } exports.DockerComposeService = DockerComposeService; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..ae49398 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,3 @@ +import { default as tsDevToolsCore } from "@ts-dev-tools/core/dist/eslint-plugin-ts-dev-tools/index.js"; + +export default tsDevToolsCore.default; diff --git a/package-lock.json b/package-lock.json index 728ab72..63f9f69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -147,6 +147,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1516,7 +1517,6 @@ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -1534,7 +1534,6 @@ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -1592,7 +1591,6 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, - "peer": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -1608,7 +1606,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, - "peer": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -1622,7 +1619,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "peer": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -1635,7 +1631,6 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -1663,7 +1658,6 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -1703,7 +1697,6 @@ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -1814,7 +1807,6 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -1829,7 +1821,6 @@ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -1845,7 +1836,6 @@ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -1861,7 +1851,6 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -2016,6 +2005,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -2199,6 +2189,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.0.0", @@ -2685,7 +2676,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -3168,9 +3158,9 @@ } }, "node_modules/@ts-dev-tools/core/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==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -3914,7 +3904,6 @@ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*" } @@ -4186,6 +4175,7 @@ "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -4217,6 +4207,7 @@ "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.40.0", @@ -4257,6 +4248,7 @@ "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.40.0", "@typescript-eslint/types": "8.40.0", @@ -4764,6 +4756,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5065,7 +5058,6 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, - "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -5087,7 +5079,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -5104,7 +5095,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -5121,7 +5111,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -5131,7 +5120,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, - "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -5173,7 +5161,6 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, - "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -5238,6 +5225,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", @@ -5455,8 +5443,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/cli-cursor": { "version": "5.0.0", @@ -5677,6 +5664,7 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -5721,7 +5709,6 @@ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -5928,7 +5915,6 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -6224,6 +6210,7 @@ "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -6901,7 +6888,6 @@ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -6921,7 +6907,6 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, - "peer": true, "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -8359,7 +8344,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -8448,7 +8432,6 @@ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, - "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -8463,7 +8446,6 @@ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -8495,7 +8477,6 @@ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -8529,7 +8510,6 @@ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -8575,7 +8555,6 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -8591,7 +8570,6 @@ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, - "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -8604,7 +8582,6 @@ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -8621,7 +8598,6 @@ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -8639,7 +8615,6 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -8649,7 +8624,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -8675,7 +8649,6 @@ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, - "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -8689,7 +8662,6 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -8705,7 +8677,6 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -8726,7 +8697,6 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -8758,7 +8728,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -8768,7 +8737,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -8789,7 +8757,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, - "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -8803,7 +8770,6 @@ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -8836,7 +8802,6 @@ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -8870,7 +8835,6 @@ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -8919,7 +8883,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -8937,7 +8900,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -8950,7 +8912,6 @@ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -8970,7 +8931,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -8986,7 +8946,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9086,6 +9045,7 @@ "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz", "integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==", "dev": true, + "peer": true, "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^3.0.0", @@ -9157,7 +9117,6 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -10163,6 +10122,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10205,7 +10165,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -10220,7 +10179,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -10269,7 +10227,6 @@ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, - "peer": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -10301,8 +10258,7 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ], - "peer": true + ] }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -10445,7 +10401,6 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true, - "peer": true, "engines": { "node": ">=10" } @@ -10760,8 +10715,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/slash": { "version": "3.0.0", @@ -11434,6 +11388,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11844,7 +11799,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" diff --git a/package.json b/package.json index cb2e06f..7fab39a 100644 --- a/package.json +++ b/package.json @@ -85,43 +85,13 @@ "**/src/**/*.[jt]s?(x)" ] }, - "eslintConfig": { - "root": true, - "parser": "@typescript-eslint/parser", - "plugins": [ - "@typescript-eslint", - "jest" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:jest/recommended", - "prettier" - ], - "env": { - "es2021": true - }, - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 12, - "sourceType": "module" - }, - "settings": { - "jest": { - "version": "detect" - } - }, - "ignorePatterns": [ - "dist", - "node_modules" - ] - }, "prettier": { "semi": true, "printWidth": 100, - "trailingComma": "es5" + "trailingComma": "es5", + "plugins": [ + "@prettier/plugin-oxc" + ] }, "commitlint": { "extends": [ @@ -140,6 +110,6 @@ } }, "tsDevTools": { - "version": "20220617100200-prettier-cache" + "version": "20250623095500-add-prettier-oxc" } } diff --git a/src/services/docker-compose.service.test.ts b/src/services/docker-compose.service.test.ts index 9564d77..2778380 100644 --- a/src/services/docker-compose.service.test.ts +++ b/src/services/docker-compose.service.test.ts @@ -112,6 +112,130 @@ describe("DockerComposeService", () => { }, }); }); + + it("should throw formatted error when upAll fails with docker-compose result", async () => { + const upInputs: UpInputs = { + dockerFlags: [], + composeFiles: ["docker-compose.yml"], + services: [], + composeFlags: [], + upFlags: [], + cwd: "/current/working/dir", + serviceLogger: jest.fn(), + }; + + const dockerComposeError = { + exitCode: 1, + err: "Error: unable to pull image\nfailed to resolve reference", + out: "", + }; + + upAllMock.mockRejectedValue(dockerComposeError); + + await expect(service.up(upInputs)).rejects.toThrow( + "Docker Compose command failed with exit code 1" + ); + await expect(service.up(upInputs)).rejects.toThrow("unable to pull image"); + }); + + it("should throw formatted error when upMany fails with docker-compose result", async () => { + const upInputs: UpInputs = { + dockerFlags: [], + composeFiles: ["docker-compose.yml"], + services: ["web"], + composeFlags: [], + upFlags: [], + cwd: "/current/working/dir", + serviceLogger: jest.fn(), + }; + + const dockerComposeError = { + exitCode: 1, + err: "Service 'web' failed to start", + out: "Starting web...", + }; + + upManyMock.mockRejectedValue(dockerComposeError); + + await expect(service.up(upInputs)).rejects.toThrow( + "Docker Compose command failed with exit code 1" + ); + await expect(service.up(upInputs)).rejects.toThrow("Service 'web' failed to start"); + await expect(service.up(upInputs)).rejects.toThrow("Starting web..."); + }); + + it("should pass through docker-compose result without exit code", async () => { + const upInputs: UpInputs = { + dockerFlags: [], + composeFiles: ["docker-compose.yml"], + services: [], + composeFlags: [], + upFlags: [], + cwd: "/current/working/dir", + serviceLogger: jest.fn(), + }; + + const dockerComposeError = { + exitCode: null, + err: "Some error without exit code", + out: "", + }; + + upAllMock.mockRejectedValue(dockerComposeError); + + await expect(service.up(upInputs)).rejects.toThrow("Some error without exit code"); + }); + + it("should pass through standard Error objects", async () => { + const upInputs: UpInputs = { + dockerFlags: [], + composeFiles: ["docker-compose.yml"], + services: [], + composeFlags: [], + upFlags: [], + cwd: "/current/working/dir", + serviceLogger: jest.fn(), + }; + + const standardError = new Error("Standard error message"); + upAllMock.mockRejectedValue(standardError); + + await expect(service.up(upInputs)).rejects.toThrow("Standard error message"); + }); + + it("should pass through error strings", async () => { + const upInputs: UpInputs = { + dockerFlags: [], + composeFiles: ["docker-compose.yml"], + services: [], + composeFlags: [], + upFlags: [], + cwd: "/current/working/dir", + serviceLogger: jest.fn(), + }; + + const unknownError = "Some unknown error"; + upAllMock.mockRejectedValue(unknownError); + + await expect(service.up(upInputs)).rejects.toThrow("Some unknown error"); + }); + + it("should handle unknown error types gracefully", async () => { + const upInputs: UpInputs = { + dockerFlags: [], + composeFiles: ["docker-compose.yml"], + services: [], + composeFlags: [], + upFlags: [], + cwd: "/current/working/dir", + serviceLogger: jest.fn(), + }; + + const unknownError = { unexpected: "error format" }; + upAllMock.mockRejectedValue(unknownError); + + await expect(service.up(upInputs)).rejects.toThrow(JSON.stringify(unknownError)); + }); }); describe("down", () => { @@ -139,6 +263,30 @@ describe("DockerComposeService", () => { callback: expect.any(Function), }); }); + + it("should throw formatted error when down fails with docker-compose result", async () => { + const downInputs: DownInputs = { + dockerFlags: [], + composeFiles: [], + composeFlags: [], + downFlags: [], + cwd: "/current/working/dir", + serviceLogger: jest.fn(), + }; + + const dockerComposeError = { + exitCode: 1, + err: "Error stopping containers", + out: "", + }; + + downMock.mockRejectedValue(dockerComposeError); + + await expect(service.down(downInputs)).rejects.toThrow( + "Docker Compose command failed with exit code 1" + ); + await expect(service.down(downInputs)).rejects.toThrow("Error stopping containers"); + }); }); describe("logs", () => { diff --git a/src/services/docker-compose.service.ts b/src/services/docker-compose.service.ts index 5f3bbde..e14f3e8 100644 --- a/src/services/docker-compose.service.ts +++ b/src/services/docker-compose.service.ts @@ -2,6 +2,7 @@ import { down, IDockerComposeLogOptions, IDockerComposeOptions, + IDockerComposeResult, logs, upAll, upMany, @@ -27,12 +28,16 @@ export class DockerComposeService { commandOptions: upFlags, }; - if (services.length > 0) { - await upMany(services, options); - return; - } + try { + if (services.length > 0) { + await upMany(services, options); + return; + } - await upAll(options); + await upAll(options); + } catch (error) { + throw this.formatDockerComposeError(error); + } } async down({ downFlags, ...optionsInputs }: DownInputs): Promise { @@ -41,7 +46,11 @@ export class DockerComposeService { commandOptions: downFlags, }; - await down(options); + try { + await down(options); + } catch (error) { + throw this.formatDockerComposeError(error); + } } async logs({ services, ...optionsInputs }: LogsInputs): Promise<{ @@ -79,4 +88,61 @@ export class DockerComposeService { }, }; } + + /** + * Formats docker-compose errors into proper Error objects with readable messages + */ + private formatDockerComposeError(error: unknown): Error { + // If it's already an Error, return it + if (error instanceof Error) { + return error; + } + + // Handle docker-compose result objects + if (this.isDockerComposeResult(error)) { + const parts: string[] = []; + + // Add exit code information + if (error.exitCode !== null) { + parts.push(`Docker Compose command failed with exit code ${error.exitCode}`); + } else { + parts.push("Docker Compose command failed"); + } + + // Add error stream output if available + if (error.err && error.err.trim()) { + parts.push("\nError output:"); + parts.push(error.err.trim()); + } + + // Add standard output if available and different from error output + if (error.out && error.out.trim() && error.out !== error.err) { + parts.push("\nStandard output:"); + parts.push(error.out.trim()); + } + + return new Error(parts.join("\n")); + } + + // Handle string errors + if (typeof error === "string") { + return new Error(error); + } + + // Fallback for unknown error types + return new Error(JSON.stringify(error)); + } + + /** + * Type guard to check if an object is a docker-compose result + */ + private isDockerComposeResult(error: unknown): error is IDockerComposeResult { + return ( + typeof error === "object" && + error !== null && + "exitCode" in error && + "err" in error && + "out" in error + ); + } }