diff --git a/apps/dokploy/__test__/deploy/builders-commit-info.test.ts b/apps/dokploy/__test__/deploy/builders-commit-info.test.ts new file mode 100644 index 0000000000..d02d335d5c --- /dev/null +++ b/apps/dokploy/__test__/deploy/builders-commit-info.test.ts @@ -0,0 +1,222 @@ +import { describe, expect, it } from "vitest"; + +import { getDockerCommand } from "@dokploy/server/utils/builders/docker-file"; +import { getHerokuCommand } from "@dokploy/server/utils/builders/heroku"; +import { getNixpacksCommand } from "@dokploy/server/utils/builders/nixpacks"; +import { getPaketoCommand } from "@dokploy/server/utils/builders/paketo"; +import { getRailpackCommand } from "@dokploy/server/utils/builders/railpack"; +import { + getCommitInfoBuildArgs, + getCommitInfoEnvArgs, + getGitCommitInfoCommands, +} from "@dokploy/server/utils/builders/utils"; +import type { ApplicationNested } from "@dokploy/server/utils/builders"; + +const createMockApplication = (overrides = {}): ApplicationNested => + ({ + appName: "test-app", + buildType: "dockerfile" as const, + env: null, + buildArgs: null, + buildSecrets: null, + cleanCache: false, + environment: { + env: "", + project: { env: "" }, + }, + ...overrides, + }) as unknown as ApplicationNested; + +describe("Git Commit Info Helpers", () => { + it("getGitCommitInfoCommands should return shell commands to extract commit info", () => { + const commands = getGitCommitInfoCommands(); + expect(commands).toContain( + "DOKPLOY_COMMIT_SHORT=$(git rev-parse --short HEAD", + ); + expect(commands).toContain("DOKPLOY_COMMIT_HASH=$(git rev-parse HEAD"); + expect(commands).toContain( + "DOKPLOY_COMMIT_MESSAGE=$(git log -1 --pretty=%s", + ); + expect(commands).toContain('|| echo "unknown"'); + }); + + it("getCommitInfoBuildArgs should return --build-arg flags", () => { + const args = getCommitInfoBuildArgs(); + expect(args).toContain( + '--build-arg DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH"', + ); + expect(args).toContain( + '--build-arg DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT"', + ); + expect(args).toContain( + '--build-arg DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"', + ); + }); + + it("getCommitInfoEnvArgs should return --env flags", () => { + const args = getCommitInfoEnvArgs(); + expect(args).toContain('--env DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH"'); + expect(args).toContain( + '--env DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT"', + ); + expect(args).toContain( + '--env DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"', + ); + }); +}); + +describe("Dockerfile builder - commit info injection", () => { + it("should include git commit info extraction commands", () => { + const command = getDockerCommand(createMockApplication()); + expect(command).toContain( + "DOKPLOY_COMMIT_SHORT=$(git rev-parse --short HEAD", + ); + expect(command).toContain("DOKPLOY_COMMIT_HASH=$(git rev-parse HEAD"); + expect(command).toContain( + "DOKPLOY_COMMIT_MESSAGE=$(git log -1 --pretty=%s", + ); + }); + + it("should pass commit info as --build-arg to docker build", () => { + const command = getDockerCommand(createMockApplication()); + expect(command).toContain( + '--build-arg DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH"', + ); + expect(command).toContain( + '--build-arg DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT"', + ); + expect(command).toContain( + '--build-arg DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"', + ); + }); + + it("should include commit info alongside user-defined build args", () => { + const command = getDockerCommand( + createMockApplication({ buildArgs: "MY_VAR=hello" }), + ); + expect(command).toContain("--build-arg MY_VAR"); + expect(command).toContain( + '--build-arg DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH"', + ); + }); +}); + +describe("Nixpacks builder - commit info injection", () => { + it("should include git commit info extraction commands", () => { + const command = getNixpacksCommand( + createMockApplication({ buildType: "nixpacks" }), + ); + expect(command).toContain( + "DOKPLOY_COMMIT_SHORT=$(git rev-parse --short HEAD", + ); + expect(command).toContain("DOKPLOY_COMMIT_HASH=$(git rev-parse HEAD"); + expect(command).toContain( + "DOKPLOY_COMMIT_MESSAGE=$(git log -1 --pretty=%s", + ); + }); + + it("should pass commit info as --env to nixpacks build", () => { + const command = getNixpacksCommand( + createMockApplication({ buildType: "nixpacks" }), + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH"', + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT"', + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"', + ); + }); +}); + +describe("Heroku builder - commit info injection", () => { + it("should include git commit info extraction commands", () => { + const command = getHerokuCommand( + createMockApplication({ buildType: "heroku_buildpacks" }), + ); + expect(command).toContain( + "DOKPLOY_COMMIT_SHORT=$(git rev-parse --short HEAD", + ); + expect(command).toContain("DOKPLOY_COMMIT_HASH=$(git rev-parse HEAD"); + expect(command).toContain( + "DOKPLOY_COMMIT_MESSAGE=$(git log -1 --pretty=%s", + ); + }); + + it("should pass commit info as --env to pack build", () => { + const command = getHerokuCommand( + createMockApplication({ buildType: "heroku_buildpacks" }), + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH"', + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT"', + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"', + ); + }); +}); + +describe("Paketo builder - commit info injection", () => { + it("should include git commit info extraction commands", () => { + const command = getPaketoCommand( + createMockApplication({ buildType: "paketo_buildpacks" }), + ); + expect(command).toContain( + "DOKPLOY_COMMIT_SHORT=$(git rev-parse --short HEAD", + ); + expect(command).toContain("DOKPLOY_COMMIT_HASH=$(git rev-parse HEAD"); + expect(command).toContain( + "DOKPLOY_COMMIT_MESSAGE=$(git log -1 --pretty=%s", + ); + }); + + it("should pass commit info as --env to pack build", () => { + const command = getPaketoCommand( + createMockApplication({ buildType: "paketo_buildpacks" }), + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH"', + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT"', + ); + expect(command).toContain( + '--env DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"', + ); + }); +}); + +describe("Railpack builder - commit info injection", () => { + it("should include git commit info extraction commands", () => { + const command = getRailpackCommand( + createMockApplication({ buildType: "railpack" }), + ); + expect(command).toContain( + "DOKPLOY_COMMIT_SHORT=$(git rev-parse --short HEAD", + ); + expect(command).toContain("DOKPLOY_COMMIT_HASH=$(git rev-parse HEAD"); + expect(command).toContain( + "DOKPLOY_COMMIT_MESSAGE=$(git log -1 --pretty=%s", + ); + }); + + it("should pass commit info as --build-arg to docker buildx", () => { + const command = getRailpackCommand( + createMockApplication({ buildType: "railpack" }), + ); + expect(command).toContain( + '--build-arg DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH"', + ); + expect(command).toContain( + '--build-arg DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT"', + ); + expect(command).toContain( + '--build-arg DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"', + ); + }); +}); diff --git a/packages/server/src/utils/builders/docker-file.ts b/packages/server/src/utils/builders/docker-file.ts index ea53a5bffe..9916679c1a 100644 --- a/packages/server/src/utils/builders/docker-file.ts +++ b/packages/server/src/utils/builders/docker-file.ts @@ -8,7 +8,11 @@ import { getDockerContextPath, } from "../filesystem/directory"; import type { ApplicationNested } from "."; -import { createEnvFileCommand } from "./utils"; +import { + createEnvFileCommand, + getCommitInfoBuildArgs, + getGitCommitInfoCommands, +} from "./utils"; export const getDockerCommand = (application: ApplicationNested) => { const { @@ -86,12 +90,14 @@ export const getDockerCommand = (application: ApplicationNested) => { command += ` echo "Building ${appName}" ; -cd ${dockerContextPath} || { +cd ${dockerContextPath} || { echo "❌ The path ${dockerContextPath} does not exist" ; exit 1; } -${joinedSecrets} docker ${commandArgs.join(" ")} || { +${getGitCommitInfoCommands()} + +${joinedSecrets} docker ${commandArgs.join(" ")} ${getCommitInfoBuildArgs()} || { echo "❌ Docker build failed" ; exit 1; } diff --git a/packages/server/src/utils/builders/heroku.ts b/packages/server/src/utils/builders/heroku.ts index 8b38c694d5..4daf6e8e37 100644 --- a/packages/server/src/utils/builders/heroku.ts +++ b/packages/server/src/utils/builders/heroku.ts @@ -1,6 +1,7 @@ import { prepareEnvironmentVariablesForShell } from "../docker/utils"; import { getBuildAppDirectory } from "../filesystem/directory"; import type { ApplicationNested } from "."; +import { getCommitInfoEnvArgs, getGitCommitInfoCommands } from "./utils"; export const getHerokuCommand = (application: ApplicationNested) => { const { env, appName, cleanCache } = application; @@ -31,8 +32,9 @@ export const getHerokuCommand = (application: ApplicationNested) => { const command = `pack ${args.join(" ")}`; const bashCommand = ` +${getGitCommitInfoCommands()} echo "Starting heroku build..." ; -${command} || { +${command} ${getCommitInfoEnvArgs()} || { echo "❌ Heroku build failed" ; exit 1; } diff --git a/packages/server/src/utils/builders/nixpacks.ts b/packages/server/src/utils/builders/nixpacks.ts index b7134ea658..6df77a8fd2 100644 --- a/packages/server/src/utils/builders/nixpacks.ts +++ b/packages/server/src/utils/builders/nixpacks.ts @@ -4,6 +4,7 @@ import { nanoid } from "nanoid"; import { prepareEnvironmentVariablesForShell } from "../docker/utils"; import { getBuildAppDirectory } from "../filesystem/directory"; import type { ApplicationNested } from "."; +import { getCommitInfoEnvArgs, getGitCommitInfoCommands } from "./utils"; export const getNixpacksCommand = (application: ApplicationNested) => { const { env, appName, publishDirectory, cleanCache } = application; @@ -32,8 +33,9 @@ export const getNixpacksCommand = (application: ApplicationNested) => { } const command = `nixpacks ${args.join(" ")}`; let bashCommand = ` + ${getGitCommitInfoCommands()} echo "Starting nixpacks build..." ; - ${command} || { + ${command} ${getCommitInfoEnvArgs()} || { echo "❌ Nixpacks build failed" ; exit 1; } diff --git a/packages/server/src/utils/builders/paketo.ts b/packages/server/src/utils/builders/paketo.ts index bb4f8c8a48..58c4417f99 100644 --- a/packages/server/src/utils/builders/paketo.ts +++ b/packages/server/src/utils/builders/paketo.ts @@ -1,6 +1,7 @@ import { prepareEnvironmentVariablesForShell } from "../docker/utils"; import { getBuildAppDirectory } from "../filesystem/directory"; import type { ApplicationNested } from "."; +import { getCommitInfoEnvArgs, getGitCommitInfoCommands } from "./utils"; export const getPaketoCommand = (application: ApplicationNested) => { const { env, appName, cleanCache } = application; @@ -31,8 +32,9 @@ export const getPaketoCommand = (application: ApplicationNested) => { const command = `pack ${args.join(" ")}`; const bashCommand = ` +${getGitCommitInfoCommands()} echo "Starting Paketo build..." ; -${command} || { +${command} ${getCommitInfoEnvArgs()} || { echo "❌ Paketo build failed" ; exit 1; } diff --git a/packages/server/src/utils/builders/railpack.ts b/packages/server/src/utils/builders/railpack.ts index 62fa9f9756..dafbb106ed 100644 --- a/packages/server/src/utils/builders/railpack.ts +++ b/packages/server/src/utils/builders/railpack.ts @@ -8,6 +8,7 @@ import { } from "../docker/utils"; import { getBuildAppDirectory } from "../filesystem/directory"; import type { ApplicationNested } from "."; +import { getCommitInfoBuildArgs, getGitCommitInfoCommands } from "./utils"; const calculateSecretsHash = (envVariables: string[]): string => { const hash = createHash("sha256"); @@ -102,7 +103,8 @@ echo "✅ Railpack prepare completed." ; echo "Building with Railpack frontend..." ; # Export environment variables for secrets ${exportEnvs.join("\n")} -docker ${buildArgs.join(" ")} || { +${getGitCommitInfoCommands()} +docker ${buildArgs.join(" ")} ${getCommitInfoBuildArgs()} || { echo "❌ Railpack build failed" ; docker buildx rm builder-containerd || true exit 1; diff --git a/packages/server/src/utils/builders/utils.ts b/packages/server/src/utils/builders/utils.ts index 97ea69ef8c..a7d15b3217 100644 --- a/packages/server/src/utils/builders/utils.ts +++ b/packages/server/src/utils/builders/utils.ts @@ -18,3 +18,27 @@ export const createEnvFileCommand = ( return `echo "${encodedContent}" | base64 -d > "${envFilePath}";`; }; + +/** + * Returns shell commands to extract git commit hash and message + * into DOKPLOY_COMMIT_HASH, DOKPLOY_COMMIT_SHORT, and DOKPLOY_COMMIT_MESSAGE shell variables. + */ +export const getGitCommitInfoCommands = () => { + return `DOKPLOY_COMMIT_HASH=$(git rev-parse HEAD 2>/dev/null || echo "unknown") +DOKPLOY_COMMIT_SHORT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") +DOKPLOY_COMMIT_MESSAGE=$(git log -1 --pretty=%s 2>/dev/null || echo "unknown")`; +}; + +/** + * Returns docker --build-arg flags for commit info. + */ +export const getCommitInfoBuildArgs = () => { + return '--build-arg DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH" --build-arg DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT" --build-arg DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"'; +}; + +/** + * Returns --env flags for commit info (for pack/nixpacks builders). + */ +export const getCommitInfoEnvArgs = () => { + return '--env DOKPLOY_COMMIT_HASH="$DOKPLOY_COMMIT_HASH" --env DOKPLOY_COMMIT_SHORT="$DOKPLOY_COMMIT_SHORT" --env DOKPLOY_COMMIT_MESSAGE="$DOKPLOY_COMMIT_MESSAGE"'; +};