mirror of
https://github.com/hoverkraft-tech/compose-action.git
synced 2026-08-31 23:52:49 +08:00
Compare commits
4 Commits
610019824f
...
6f330cd1dc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f330cd1dc | ||
|
|
75b024b03b | ||
|
|
26f805aa21 | ||
|
|
e56276d1d3 |
3
.github/linters/.codespellrc
vendored
Normal file
3
.github/linters/.codespellrc
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[codespell]
|
||||
# Skip third-party/generated paths and French-locale content files.
|
||||
skip = .git,*.svg,**/package-lock.json,**/dist/**,**/node_modules/**,**/coverage/**,**/junit.xml
|
||||
2
.github/workflows/__check-dist.yml
vendored
2
.github/workflows/__check-dist.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
- name: Build dist/ Directory
|
||||
id: package
|
||||
run: ${{ steps.setup-node.outputs.run-script-command }} package
|
||||
run: npm run package
|
||||
|
||||
# This will fail the workflow if the PR wasn't created by Dependabot.
|
||||
- name: Compare Directories
|
||||
|
||||
85
dist/index.js
generated
vendored
85
dist/index.js
generated
vendored
@ -31403,16 +31403,12 @@ var DockerComposeService = class {
|
||||
}
|
||||
}
|
||||
async logs({ services, ...optionsInputs }) {
|
||||
const commandArgs = this.getDockerComposeCommandArgs("logs", {
|
||||
dockerFlags: optionsInputs.dockerFlags,
|
||||
composeFlags: optionsInputs.composeFlags,
|
||||
composeFiles: optionsInputs.composeFiles,
|
||||
commandArgs: services
|
||||
});
|
||||
const options = this.getCommonOptions(optionsInputs);
|
||||
const { executablePath, executableArgs } = this.getDockerComposeCommandExecution("logs", services, options);
|
||||
return new Promise((resolve2) => {
|
||||
let settled = false;
|
||||
const childProcess = spawn2("docker", commandArgs, {
|
||||
cwd: optionsInputs.cwd
|
||||
const childProcess = spawn2(executablePath, executableArgs, {
|
||||
cwd: options.cwd
|
||||
});
|
||||
childProcess.on("error", (error2) => {
|
||||
if (settled) {
|
||||
@ -31433,10 +31429,10 @@ var DockerComposeService = class {
|
||||
return;
|
||||
}
|
||||
childProcess.stdout.on("data", (chunk) => {
|
||||
optionsInputs.serviceLogger(chunk.toString());
|
||||
options.callback?.(Buffer.from(chunk), "stdout");
|
||||
});
|
||||
childProcess.stderr.on("data", (chunk) => {
|
||||
optionsInputs.serviceLogger(chunk.toString());
|
||||
options.callback?.(Buffer.from(chunk), "stderr");
|
||||
});
|
||||
childProcess.on("close", (exitCode, signal) => {
|
||||
if (settled) {
|
||||
@ -31468,30 +31464,55 @@ var DockerComposeService = class {
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Builds docker CLI arguments in the order expected by `docker compose`.
|
||||
*/
|
||||
getDockerComposeCommandArgs(command, {
|
||||
dockerFlags,
|
||||
composeFlags,
|
||||
composeFiles,
|
||||
commandArgs
|
||||
}) {
|
||||
return [
|
||||
...dockerFlags,
|
||||
"compose",
|
||||
...composeFlags,
|
||||
...composeFiles.flatMap((composeFile) => ["-f", composeFile]),
|
||||
command,
|
||||
...commandArgs
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Formats docker-compose errors into proper Error objects with readable messages
|
||||
*/
|
||||
formatDockerComposeError(error2) {
|
||||
return new Error(this.getDockerComposeErrorMessage(error2));
|
||||
}
|
||||
getDockerComposeCommandExecution(command, commandArgs, options) {
|
||||
const composeArgs = [
|
||||
...this.getComposeOptionArgs(options.composeOptions),
|
||||
...this.getConfigArgs(options.config),
|
||||
command,
|
||||
...this.getComposeOptionArgs(options.commandOptions),
|
||||
...commandArgs
|
||||
];
|
||||
if (options.executable?.standalone) {
|
||||
return {
|
||||
executablePath: options.executable.executablePath ?? "docker-compose",
|
||||
executableArgs: composeArgs
|
||||
};
|
||||
}
|
||||
return {
|
||||
executablePath: options.executable?.executablePath ?? "docker",
|
||||
executableArgs: [
|
||||
...this.getComposeOptionArgs(options.executable?.options),
|
||||
"compose",
|
||||
...composeArgs
|
||||
]
|
||||
};
|
||||
}
|
||||
getConfigArgs(config) {
|
||||
if (typeof config === "undefined") {
|
||||
return [];
|
||||
}
|
||||
if (typeof config === "string") {
|
||||
return ["-f", config];
|
||||
}
|
||||
return config.flatMap((item) => ["-f", item]);
|
||||
}
|
||||
getComposeOptionArgs(composeOptions) {
|
||||
if (!composeOptions) {
|
||||
return [];
|
||||
}
|
||||
return composeOptions.flatMap(
|
||||
(option) => Array.isArray(option) ? option : [option]
|
||||
);
|
||||
}
|
||||
getDockerComposeErrorMessage(error2) {
|
||||
if (error2 instanceof Error) {
|
||||
return error2;
|
||||
return error2.message;
|
||||
}
|
||||
if (this.isDockerComposeResult(error2)) {
|
||||
const parts = [];
|
||||
@ -31510,12 +31531,12 @@ var DockerComposeService = class {
|
||||
parts.push("\nStandard output:");
|
||||
parts.push(error2.out.trim());
|
||||
}
|
||||
return new Error(parts.join("\n"));
|
||||
return parts.join("\n");
|
||||
}
|
||||
if (typeof error2 === "string") {
|
||||
return new Error(error2);
|
||||
return error2;
|
||||
}
|
||||
return new Error(JSON.stringify(error2));
|
||||
return JSON.stringify(error2);
|
||||
}
|
||||
/**
|
||||
* Type guard to check if an object is a docker-compose result
|
||||
|
||||
85
dist/post.js
generated
vendored
85
dist/post.js
generated
vendored
@ -27140,16 +27140,12 @@ var DockerComposeService = class {
|
||||
}
|
||||
}
|
||||
async logs({ services, ...optionsInputs }) {
|
||||
const commandArgs = this.getDockerComposeCommandArgs("logs", {
|
||||
dockerFlags: optionsInputs.dockerFlags,
|
||||
composeFlags: optionsInputs.composeFlags,
|
||||
composeFiles: optionsInputs.composeFiles,
|
||||
commandArgs: services
|
||||
});
|
||||
const options = this.getCommonOptions(optionsInputs);
|
||||
const { executablePath, executableArgs } = this.getDockerComposeCommandExecution("logs", services, options);
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
const childProcess = spawn("docker", commandArgs, {
|
||||
cwd: optionsInputs.cwd
|
||||
const childProcess = spawn(executablePath, executableArgs, {
|
||||
cwd: options.cwd
|
||||
});
|
||||
childProcess.on("error", (error2) => {
|
||||
if (settled) {
|
||||
@ -27170,10 +27166,10 @@ var DockerComposeService = class {
|
||||
return;
|
||||
}
|
||||
childProcess.stdout.on("data", (chunk) => {
|
||||
optionsInputs.serviceLogger(chunk.toString());
|
||||
options.callback?.(Buffer.from(chunk), "stdout");
|
||||
});
|
||||
childProcess.stderr.on("data", (chunk) => {
|
||||
optionsInputs.serviceLogger(chunk.toString());
|
||||
options.callback?.(Buffer.from(chunk), "stderr");
|
||||
});
|
||||
childProcess.on("close", (exitCode, signal) => {
|
||||
if (settled) {
|
||||
@ -27205,30 +27201,55 @@ var DockerComposeService = class {
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Builds docker CLI arguments in the order expected by `docker compose`.
|
||||
*/
|
||||
getDockerComposeCommandArgs(command, {
|
||||
dockerFlags,
|
||||
composeFlags,
|
||||
composeFiles,
|
||||
commandArgs
|
||||
}) {
|
||||
return [
|
||||
...dockerFlags,
|
||||
"compose",
|
||||
...composeFlags,
|
||||
...composeFiles.flatMap((composeFile) => ["-f", composeFile]),
|
||||
command,
|
||||
...commandArgs
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Formats docker-compose errors into proper Error objects with readable messages
|
||||
*/
|
||||
formatDockerComposeError(error2) {
|
||||
return new Error(this.getDockerComposeErrorMessage(error2));
|
||||
}
|
||||
getDockerComposeCommandExecution(command, commandArgs, options) {
|
||||
const composeArgs = [
|
||||
...this.getComposeOptionArgs(options.composeOptions),
|
||||
...this.getConfigArgs(options.config),
|
||||
command,
|
||||
...this.getComposeOptionArgs(options.commandOptions),
|
||||
...commandArgs
|
||||
];
|
||||
if (options.executable?.standalone) {
|
||||
return {
|
||||
executablePath: options.executable.executablePath ?? "docker-compose",
|
||||
executableArgs: composeArgs
|
||||
};
|
||||
}
|
||||
return {
|
||||
executablePath: options.executable?.executablePath ?? "docker",
|
||||
executableArgs: [
|
||||
...this.getComposeOptionArgs(options.executable?.options),
|
||||
"compose",
|
||||
...composeArgs
|
||||
]
|
||||
};
|
||||
}
|
||||
getConfigArgs(config) {
|
||||
if (typeof config === "undefined") {
|
||||
return [];
|
||||
}
|
||||
if (typeof config === "string") {
|
||||
return ["-f", config];
|
||||
}
|
||||
return config.flatMap((item) => ["-f", item]);
|
||||
}
|
||||
getComposeOptionArgs(composeOptions) {
|
||||
if (!composeOptions) {
|
||||
return [];
|
||||
}
|
||||
return composeOptions.flatMap(
|
||||
(option) => Array.isArray(option) ? option : [option]
|
||||
);
|
||||
}
|
||||
getDockerComposeErrorMessage(error2) {
|
||||
if (error2 instanceof Error) {
|
||||
return error2;
|
||||
return error2.message;
|
||||
}
|
||||
if (this.isDockerComposeResult(error2)) {
|
||||
const parts = [];
|
||||
@ -27247,12 +27268,12 @@ var DockerComposeService = class {
|
||||
parts.push("\nStandard output:");
|
||||
parts.push(error2.out.trim());
|
||||
}
|
||||
return new Error(parts.join("\n"));
|
||||
return parts.join("\n");
|
||||
}
|
||||
if (typeof error2 === "string") {
|
||||
return new Error(error2);
|
||||
return error2;
|
||||
}
|
||||
return new Error(JSON.stringify(error2));
|
||||
return JSON.stringify(error2);
|
||||
}
|
||||
/**
|
||||
* Type guard to check if an object is a docker-compose result
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@ -3145,9 +3145,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.17",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz",
|
||||
"integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==",
|
||||
"version": "3.3.18",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
|
||||
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import type {
|
||||
IDockerComposeOptions,
|
||||
IDockerComposeResult,
|
||||
} from "docker-compose";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Mock docker-compose before importing the module under test
|
||||
@ -357,13 +357,13 @@ describe("DockerComposeService", () => {
|
||||
});
|
||||
|
||||
describe("logs", () => {
|
||||
it("should stream logs with correct command arguments", async () => {
|
||||
it("should stream logs with wrapper-compatible command arguments", async () => {
|
||||
const debugMock = vi.fn();
|
||||
const logsInputs = {
|
||||
dockerFlags: [] as string[],
|
||||
dockerFlags: ["--context", "dev"] as string[],
|
||||
composeFiles: ["docker-compose.yml"],
|
||||
services: ["helloworld2", "helloworld3"],
|
||||
composeFlags: [] as string[],
|
||||
composeFlags: ["--profile", "ci"] as string[],
|
||||
cwd: "/current/working/dir",
|
||||
serviceLogger: debugMock,
|
||||
};
|
||||
@ -380,22 +380,31 @@ describe("DockerComposeService", () => {
|
||||
|
||||
const logsPromise = service.logs(logsInputs);
|
||||
|
||||
expect(spawnMock).toHaveBeenCalledWith("docker", [
|
||||
"compose",
|
||||
"-f",
|
||||
"docker-compose.yml",
|
||||
"logs",
|
||||
"helloworld2",
|
||||
"helloworld3",
|
||||
], {
|
||||
cwd: "/current/working/dir",
|
||||
});
|
||||
expect(spawnMock).toHaveBeenCalledWith(
|
||||
"docker",
|
||||
[
|
||||
"--context",
|
||||
"dev",
|
||||
"compose",
|
||||
"--profile",
|
||||
"ci",
|
||||
"-f",
|
||||
"docker-compose.yml",
|
||||
"logs",
|
||||
"helloworld2",
|
||||
"helloworld3",
|
||||
],
|
||||
{
|
||||
cwd: "/current/working/dir",
|
||||
},
|
||||
);
|
||||
|
||||
stdout.emit("data", Buffer.from("logs"));
|
||||
stderr.emit("data", Buffer.from("error logs"));
|
||||
childProcess.emit("close", 0);
|
||||
|
||||
await expect(logsPromise).resolves.toEqual({ error: "", output: "" });
|
||||
|
||||
expect(debugMock).toHaveBeenNthCalledWith(1, "logs");
|
||||
expect(debugMock).toHaveBeenNthCalledWith(2, "error logs");
|
||||
});
|
||||
@ -420,19 +429,6 @@ describe("DockerComposeService", () => {
|
||||
|
||||
const logsPromise = service.logs(logsInputs);
|
||||
|
||||
expect(spawnMock).toHaveBeenCalledWith("docker", [
|
||||
"--context",
|
||||
"dev",
|
||||
"compose",
|
||||
"--profile",
|
||||
"ci",
|
||||
"-f",
|
||||
"docker-compose.yml",
|
||||
"logs",
|
||||
], {
|
||||
cwd: "/current/working/dir",
|
||||
});
|
||||
|
||||
childProcess.emit("close", 1);
|
||||
|
||||
await expect(logsPromise).resolves.toEqual({
|
||||
@ -516,7 +512,8 @@ describe("DockerComposeService", () => {
|
||||
spawnMock.mockReturnValue(childProcess);
|
||||
|
||||
await expect(service.logs(logsInputs)).resolves.toEqual({
|
||||
error: "Unable to collect docker compose logs: stdout/stderr unavailable",
|
||||
error:
|
||||
"Unable to collect docker compose logs: stdout/stderr unavailable",
|
||||
output: "",
|
||||
});
|
||||
});
|
||||
|
||||
@ -59,56 +59,58 @@ export class DockerComposeService {
|
||||
error: string;
|
||||
output: string;
|
||||
}> {
|
||||
const commandArgs = this.getDockerComposeCommandArgs("logs", {
|
||||
dockerFlags: optionsInputs.dockerFlags,
|
||||
composeFlags: optionsInputs.composeFlags,
|
||||
composeFiles: optionsInputs.composeFiles,
|
||||
commandArgs: services,
|
||||
});
|
||||
const options = this.getCommonOptions(optionsInputs);
|
||||
const { executablePath, executableArgs } =
|
||||
this.getDockerComposeCommandExecution("logs", services, options);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
const childProcess = spawn("docker", commandArgs, {
|
||||
cwd: optionsInputs.cwd,
|
||||
const childProcess = spawn(executablePath, executableArgs, {
|
||||
cwd: options.cwd,
|
||||
});
|
||||
|
||||
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",
|
||||
error:
|
||||
"Unable to collect docker compose logs: stdout/stderr unavailable",
|
||||
output: "",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
childProcess.stdout.on("data", (chunk: Buffer | string) => {
|
||||
optionsInputs.serviceLogger(chunk.toString());
|
||||
options.callback?.(Buffer.from(chunk), "stdout");
|
||||
});
|
||||
|
||||
childProcess.stderr.on("data", (chunk: Buffer | string) => {
|
||||
optionsInputs.serviceLogger(chunk.toString());
|
||||
options.callback?.(Buffer.from(chunk), "stderr");
|
||||
});
|
||||
|
||||
childProcess.on("close", (exitCode, signal) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
|
||||
settled = true;
|
||||
resolve({
|
||||
error:
|
||||
signal
|
||||
? `Docker Compose logs command failed with signal ${signal}`
|
||||
: exitCode !== null && 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: "",
|
||||
});
|
||||
});
|
||||
@ -134,47 +136,81 @@ export class DockerComposeService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds docker CLI arguments in the order expected by `docker compose`.
|
||||
*/
|
||||
private getDockerComposeCommandArgs(
|
||||
command: "logs",
|
||||
{
|
||||
dockerFlags,
|
||||
composeFlags,
|
||||
composeFiles,
|
||||
commandArgs,
|
||||
}: {
|
||||
dockerFlags: string[];
|
||||
composeFlags: string[];
|
||||
composeFiles: string[];
|
||||
commandArgs: string[];
|
||||
},
|
||||
): string[] {
|
||||
return [
|
||||
...dockerFlags,
|
||||
"compose",
|
||||
...composeFlags,
|
||||
...composeFiles.flatMap((composeFile) => ["-f", composeFile]),
|
||||
command,
|
||||
...commandArgs,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return new Error(this.getDockerComposeErrorMessage(error));
|
||||
}
|
||||
|
||||
private getDockerComposeCommandExecution(
|
||||
command: string,
|
||||
commandArgs: string[],
|
||||
options: IDockerComposeOptions,
|
||||
): {
|
||||
executablePath: string;
|
||||
executableArgs: string[];
|
||||
} {
|
||||
const composeArgs = [
|
||||
...this.getComposeOptionArgs(options.composeOptions),
|
||||
...this.getConfigArgs(options.config),
|
||||
command,
|
||||
...this.getComposeOptionArgs(options.commandOptions),
|
||||
...commandArgs,
|
||||
];
|
||||
|
||||
if (options.executable?.standalone) {
|
||||
return {
|
||||
executablePath: options.executable.executablePath ?? "docker-compose",
|
||||
executableArgs: composeArgs,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
executablePath: options.executable?.executablePath ?? "docker",
|
||||
executableArgs: [
|
||||
...this.getComposeOptionArgs(options.executable?.options),
|
||||
"compose",
|
||||
...composeArgs,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private getConfigArgs(config: IDockerComposeOptions["config"]): string[] {
|
||||
if (typeof config === "undefined") {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof config === "string") {
|
||||
return ["-f", config];
|
||||
}
|
||||
|
||||
return config.flatMap((item) => ["-f", item]);
|
||||
}
|
||||
|
||||
private getComposeOptionArgs(
|
||||
composeOptions:
|
||||
| IDockerComposeOptions["composeOptions"]
|
||||
| IDockerComposeOptions["commandOptions"]
|
||||
| NonNullable<IDockerComposeOptions["executable"]>["options"],
|
||||
): string[] {
|
||||
if (!composeOptions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return composeOptions.flatMap((option) =>
|
||||
Array.isArray(option) ? option : [option],
|
||||
);
|
||||
}
|
||||
|
||||
private getDockerComposeErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
// 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}`,
|
||||
@ -183,30 +219,25 @@ export class DockerComposeService {
|
||||
parts.push("Docker Compose command failed");
|
||||
}
|
||||
|
||||
// Add error stream output if available
|
||||
if (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?.trim() && error.out !== error.err) {
|
||||
parts.push("\nStandard output:");
|
||||
parts.push(error.out.trim());
|
||||
}
|
||||
|
||||
return new Error(parts.join("\n"));
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
// Handle string errors
|
||||
if (typeof error === "string") {
|
||||
return new Error(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
// Fallback for unknown error types
|
||||
return new Error(JSON.stringify(error));
|
||||
return JSON.stringify(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an object is a docker-compose result
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user