fix: format docker-compose error objects as readable messages

Co-authored-by: neilime <314088+neilime@users.noreply.github.com>
Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-02 19:14:21 +00:00 committed by Emilien Escalle
parent e9bb59d794
commit 5d59cf29d8
7 changed files with 371 additions and 116 deletions

67
dist/index.js generated vendored
View File

@ -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;

67
dist/post.js generated vendored
View File

@ -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;

3
eslint.config.mjs Normal file
View File

@ -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;

84
package-lock.json generated
View File

@ -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"

View File

@ -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"
}
}

View File

@ -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", () => {

View File

@ -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<void> {
@ -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
);
}
}