fix: avoid buffering compose logs during cleanup

Co-authored-by: neilime <314088+neilime@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-08-07 09:27:55 +00:00 committed by Emilien Escalle
parent 26f805aa21
commit 75b024b03b
4 changed files with 175 additions and 17 deletions

33
dist/index.js generated vendored
View File

@ -31409,20 +31409,42 @@ var DockerComposeService = class {
composeFiles: optionsInputs.composeFiles,
commandArgs: services
});
return await new Promise((resolve2, reject) => {
return new Promise((resolve2) => {
let settled = false;
const childProcess = spawn2("docker", commandArgs, {
cwd: optionsInputs.cwd
});
childProcess.on("error", reject);
childProcess.on("error", (error2) => {
if (settled) {
return;
}
settled = true;
resolve2({
error: `Unable to collect docker compose logs: ${error2.message}`,
output: ""
});
});
if (!childProcess.stdout || !childProcess.stderr) {
settled = true;
resolve2({
error: "Unable to collect docker compose logs: stdout/stderr unavailable",
output: ""
});
return;
}
childProcess.stdout.on("data", (chunk) => {
optionsInputs.serviceLogger(chunk.toString());
});
childProcess.stderr.on("data", (chunk) => {
optionsInputs.serviceLogger(chunk.toString());
});
childProcess.on("close", (exitCode) => {
childProcess.on("close", (exitCode, signal) => {
if (settled) {
return;
}
settled = true;
resolve2({
error: exitCode && exitCode !== 0 ? `Docker Compose logs command failed with exit code ${exitCode}` : "",
error: signal ? `Docker Compose logs command failed with signal ${signal}` : exitCode !== null && exitCode !== 0 ? `Docker Compose logs command failed with exit code ${exitCode}` : "",
output: ""
});
});
@ -31446,6 +31468,9 @@ var DockerComposeService = class {
}
};
}
/**
* Builds docker CLI arguments in the order expected by `docker compose`.
*/
getDockerComposeCommandArgs(command, {
dockerFlags,
composeFlags,

33
dist/post.js generated vendored
View File

@ -27146,20 +27146,42 @@ var DockerComposeService = class {
composeFiles: optionsInputs.composeFiles,
commandArgs: services
});
return await new Promise((resolve, reject) => {
return new Promise((resolve) => {
let settled = false;
const childProcess = spawn("docker", commandArgs, {
cwd: optionsInputs.cwd
});
childProcess.on("error", reject);
childProcess.on("error", (error2) => {
if (settled) {
return;
}
settled = true;
resolve({
error: `Unable to collect docker compose logs: ${error2.message}`,
output: ""
});
});
if (!childProcess.stdout || !childProcess.stderr) {
settled = true;
resolve({
error: "Unable to collect docker compose logs: stdout/stderr unavailable",
output: ""
});
return;
}
childProcess.stdout.on("data", (chunk) => {
optionsInputs.serviceLogger(chunk.toString());
});
childProcess.stderr.on("data", (chunk) => {
optionsInputs.serviceLogger(chunk.toString());
});
childProcess.on("close", (exitCode) => {
childProcess.on("close", (exitCode, signal) => {
if (settled) {
return;
}
settled = true;
resolve({
error: exitCode && exitCode !== 0 ? `Docker Compose logs command failed with exit code ${exitCode}` : "",
error: signal ? `Docker Compose logs command failed with signal ${signal}` : exitCode !== null && exitCode !== 0 ? `Docker Compose logs command failed with exit code ${exitCode}` : "",
output: ""
});
});
@ -27183,6 +27205,9 @@ var DockerComposeService = class {
}
};
}
/**
* Builds docker CLI arguments in the order expected by `docker compose`.
*/
getDockerComposeCommandArgs(command, {
dockerFlags,
composeFlags,

View File

@ -440,5 +440,85 @@ describe("DockerComposeService", () => {
output: "",
});
});
it("should return a non-fatal error message when logs command is terminated by a signal", async () => {
const logsInputs = {
dockerFlags: [] as string[],
composeFiles: ["docker-compose.yml"] as string[],
services: [] as string[],
composeFlags: [] as string[],
cwd: "/current/working/dir",
serviceLogger: vi.fn(),
};
const childProcess = new EventEmitter() as EventEmitter & {
stdout: EventEmitter;
stderr: EventEmitter;
};
childProcess.stdout = new EventEmitter();
childProcess.stderr = new EventEmitter();
spawnMock.mockReturnValue(childProcess);
const logsPromise = service.logs(logsInputs);
childProcess.emit("close", null, "SIGTERM");
await expect(logsPromise).resolves.toEqual({
error: "Docker Compose logs command failed with signal SIGTERM",
output: "",
});
});
it("should return a non-fatal error message when spawning logs fails", async () => {
const logsInputs = {
dockerFlags: [] as string[],
composeFiles: ["docker-compose.yml"] as string[],
services: [] as string[],
composeFlags: [] as string[],
cwd: "/current/working/dir",
serviceLogger: vi.fn(),
};
const childProcess = new EventEmitter() as EventEmitter & {
stdout: EventEmitter;
stderr: EventEmitter;
};
childProcess.stdout = new EventEmitter();
childProcess.stderr = new EventEmitter();
spawnMock.mockReturnValue(childProcess);
const logsPromise = service.logs(logsInputs);
childProcess.emit("error", new Error("spawn ENOENT"));
await expect(logsPromise).resolves.toEqual({
error: "Unable to collect docker compose logs: spawn ENOENT",
output: "",
});
});
it("should return a non-fatal error message when output streams are unavailable", async () => {
const logsInputs = {
dockerFlags: [] as string[],
composeFiles: ["docker-compose.yml"] as string[],
services: [] as string[],
composeFlags: [] as string[],
cwd: "/current/working/dir",
serviceLogger: vi.fn(),
};
const childProcess = new EventEmitter() as EventEmitter & {
stdout: EventEmitter | null;
stderr: EventEmitter | null;
};
childProcess.stdout = null;
childProcess.stderr = null;
spawnMock.mockReturnValue(childProcess);
await expect(service.logs(logsInputs)).resolves.toEqual({
error: "Unable to collect docker compose logs: stdout/stderr unavailable",
output: "",
});
});
});
});

View File

@ -66,24 +66,49 @@ export class DockerComposeService {
commandArgs: services,
});
return await new Promise((resolve, reject) => {
return new Promise((resolve) => {
let settled = false;
const childProcess = spawn("docker", commandArgs, {
cwd: optionsInputs.cwd,
});
childProcess.on("error", reject);
childProcess.stdout.on("data", (chunk: Buffer) => {
childProcess.on("error", (error) => {
if (settled) {
return;
}
settled = true;
resolve({
error: `Unable to collect docker compose logs: ${error.message}`,
output: "",
});
});
if (!childProcess.stdout || !childProcess.stderr) {
settled = true;
resolve({
error: "Unable to collect docker compose logs: stdout/stderr unavailable",
output: "",
});
return;
}
childProcess.stdout.on("data", (chunk: Buffer | string) => {
optionsInputs.serviceLogger(chunk.toString());
});
childProcess.stderr.on("data", (chunk: Buffer) => {
childProcess.stderr.on("data", (chunk: Buffer | string) => {
optionsInputs.serviceLogger(chunk.toString());
});
childProcess.on("close", (exitCode) => {
childProcess.on("close", (exitCode, signal) => {
if (settled) {
return;
}
settled = true;
resolve({
error:
exitCode && exitCode !== 0
? `Docker Compose logs command failed with exit code ${exitCode}`
: "",
signal
? `Docker Compose logs command failed with signal ${signal}`
: exitCode !== null && exitCode !== 0
? `Docker Compose logs command failed with exit code ${exitCode}`
: "",
output: "",
});
});
@ -109,8 +134,11 @@ export class DockerComposeService {
};
}
/**
* Builds docker CLI arguments in the order expected by `docker compose`.
*/
private getDockerComposeCommandArgs(
command: string,
command: "logs",
{
dockerFlags,
composeFlags,