From e2af797539beca1e85bdefb9cf265d6cb95b6cfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:14:59 +0000 Subject: [PATCH] Fix Docker Compose installation when not present on self-hosted runners Co-authored-by: neilime <314088+neilime@users.noreply.github.com> --- dist/index.js | 24 ++++- .../docker-compose-installer.service.test.ts | 101 ++++++++++++++++++ .../docker-compose-installer.service.ts | 27 +++-- 3 files changed, 141 insertions(+), 11 deletions(-) diff --git a/dist/index.js b/dist/index.js index 59cc085..0bfb371 100644 --- a/dist/index.js +++ b/dist/index.js @@ -47951,6 +47951,10 @@ class DockerComposeInstallerService { async install({ composeVersion, cwd, githubToken }) { const currentVersion = await this.version({ cwd }); if (!composeVersion) { + // If no version is specified and Docker Compose is not installed, throw an error + if (!currentVersion) { + throw new Error("Docker Compose is not installed and no compose-version was specified. Please specify a compose-version to install."); + } return currentVersion; } if (currentVersion === composeVersion) { @@ -47963,13 +47967,23 @@ class DockerComposeInstallerService { composeVersion = await this.getLatestVersion(githubToken); } await this.installVersion(composeVersion); - return this.version({ cwd }); + const installedVersion = await this.version({ cwd }); + if (!installedVersion) { + throw new Error("Failed to verify Docker Compose installation"); + } + return installedVersion; } async version({ cwd }) { - const result = await (0,dist.version)({ - cwd, - }); - return result.data.version; + try { + const result = await (0,dist.version)({ + cwd, + }); + return result.data.version; + } + catch { + // If version check fails (e.g., Docker Compose not installed), return null + return null; + } } async getLatestVersion(githubToken) { const octokit = getOctokit(githubToken); diff --git a/src/services/docker-compose-installer.service.test.ts b/src/services/docker-compose-installer.service.test.ts index 5e66b54..00173d9 100644 --- a/src/services/docker-compose-installer.service.test.ts +++ b/src/services/docker-compose-installer.service.test.ts @@ -57,6 +57,107 @@ describe("DockerComposeInstallerService", () => { expect(manualInstallerAdapterMock.install).not.toHaveBeenCalled(); }); + it("should throw an error when Docker Compose is not installed and no version is provided", async () => { + // Arrange + versionMock.mockRejectedValue(new Error("Docker Compose not found")); + + // Act & Assert + await expect( + service.install({ + composeVersion: null, + cwd: "/path/to/cwd", + githubToken: null, + }) + ).rejects.toThrow( + "Docker Compose is not installed and no compose-version was specified. Please specify a compose-version to install." + ); + + expect(manualInstallerAdapterMock.install).not.toHaveBeenCalled(); + }); + + it("should install Docker Compose when it is not installed and a version is specified", async () => { + // Arrange + versionMock.mockRejectedValueOnce(new Error("Docker Compose not found")); + + const expectedVersion = "v2.20.0"; + versionMock.mockResolvedValueOnce({ + exitCode: 0, + out: "", + err: "", + data: { + version: expectedVersion, + }, + }); + + Object.defineProperty(process, "platform", { + value: "linux", + }); + + // Act + const result = await service.install({ + composeVersion: expectedVersion, + cwd: "/path/to/cwd", + githubToken: null, + }); + + // Assert + expect(result).toBe(expectedVersion); + expect(manualInstallerAdapterMock.install).toHaveBeenCalledWith(expectedVersion); + }); + + it("should install Docker Compose when it is not installed and latest version is requested", async () => { + // Arrange + versionMock.mockRejectedValueOnce(new Error("Docker Compose not found")); + + const latestVersion = "v2.30.0"; + + const mockClient = mockAgent.get("https://api.github.com"); + mockClient + .intercept({ + path: "/repos/docker/compose/releases/latest", + method: "GET", + }) + .reply( + 200, + { + tag_name: latestVersion, + }, + { + headers: { + "content-type": "application/json", + }, + } + ); + setGlobalDispatcher(mockClient); + + versionMock.mockResolvedValueOnce({ + exitCode: 0, + out: "", + err: "", + data: { + version: latestVersion, + }, + }); + + Object.defineProperty(process, "platform", { + value: "linux", + }); + Object.defineProperty(globalThis, "fetch", { + value: jest.fn(), + }); + + // Act + const result = await service.install({ + composeVersion: "latest", + cwd: "/path/to/cwd", + githubToken: "token", + }); + + // Assert + expect(result).toBe(latestVersion); + expect(manualInstallerAdapterMock.install).toHaveBeenCalledWith(latestVersion); + }); + it("should not install anything when expected version is already installed", async () => { // Arrange versionMock.mockResolvedValue({ diff --git a/src/services/docker-compose-installer.service.ts b/src/services/docker-compose-installer.service.ts index dba3fa7..ef44162 100644 --- a/src/services/docker-compose-installer.service.ts +++ b/src/services/docker-compose-installer.service.ts @@ -20,6 +20,12 @@ export class DockerComposeInstallerService { const currentVersion = await this.version({ cwd }); if (!composeVersion) { + // If no version is specified and Docker Compose is not installed, throw an error + if (!currentVersion) { + throw new Error( + "Docker Compose is not installed and no compose-version was specified. Please specify a compose-version to install." + ); + } return currentVersion; } @@ -36,14 +42,23 @@ export class DockerComposeInstallerService { await this.installVersion(composeVersion); - return this.version({ cwd }); + const installedVersion = await this.version({ cwd }); + if (!installedVersion) { + throw new Error("Failed to verify Docker Compose installation"); + } + return installedVersion; } - private async version({ cwd }: VersionInputs): Promise { - const result = await version({ - cwd, - }); - return result.data.version; + private async version({ cwd }: VersionInputs): Promise { + try { + const result = await version({ + cwd, + }); + return result.data.version; + } catch { + // If version check fails (e.g., Docker Compose not installed), return null + return null; + } } private async getLatestVersion(githubToken: string): Promise {