mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-18 11:12:56 +00:00
- Removed redundant helper functions for retrieving git configurations for applications and composes, streamlining the codebase. - Updated the `ensurePatchRepo` function to directly handle repository cloning based on the application or compose type, improving clarity and maintainability. - Refactored patch creation logic to eliminate unnecessary checks and streamline the process of creating patches. - Enhanced the handling of output paths in repository cloning functions across different git providers, ensuring consistent behavior.
187 lines
4.3 KiB
TypeScript
187 lines
4.3 KiB
TypeScript
import { join } from "node:path";
|
|
import { paths } from "@dokploy/server/constants";
|
|
import { db } from "@dokploy/server/db";
|
|
import { type apiCreatePatch, patch } from "@dokploy/server/db/schema";
|
|
import {
|
|
execAsync,
|
|
execAsyncRemote,
|
|
} from "@dokploy/server/utils/process/execAsync";
|
|
import { TRPCError } from "@trpc/server";
|
|
import { and, eq, isNotNull } from "drizzle-orm";
|
|
|
|
export type Patch = typeof patch.$inferSelect;
|
|
|
|
export const createPatch = async (input: typeof apiCreatePatch._type) => {
|
|
if (!input.applicationId && !input.composeId) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Either applicationId or composeId must be provided",
|
|
});
|
|
}
|
|
|
|
const newPatch = await db
|
|
.insert(patch)
|
|
.values({
|
|
...input,
|
|
content: input.content,
|
|
enabled: true,
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newPatch) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error creating the patch",
|
|
});
|
|
}
|
|
|
|
return newPatch;
|
|
};
|
|
|
|
export const findPatchById = async (patchId: string) => {
|
|
const result = await db.query.patch.findFirst({
|
|
where: eq(patch.patchId, patchId),
|
|
});
|
|
|
|
if (!result) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Patch not found",
|
|
});
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
export const findPatchesByApplicationId = async (applicationId: string) => {
|
|
return await db.query.patch.findMany({
|
|
where: and(
|
|
eq(patch.applicationId, applicationId),
|
|
isNotNull(patch.applicationId),
|
|
),
|
|
orderBy: (patch, { asc }) => [asc(patch.filePath)],
|
|
});
|
|
};
|
|
|
|
export const findPatchesByComposeId = async (composeId: string) => {
|
|
return await db.query.patch.findMany({
|
|
where: and(eq(patch.composeId, composeId), isNotNull(patch.composeId)),
|
|
orderBy: (patch, { asc }) => [asc(patch.filePath)],
|
|
});
|
|
};
|
|
|
|
export const findPatchByFilePath = async (
|
|
filePath: string,
|
|
id: string,
|
|
type: "application" | "compose",
|
|
) => {
|
|
return await db.query.patch.findFirst({
|
|
where:
|
|
type === "application"
|
|
? and(eq(patch.filePath, filePath), eq(patch.applicationId, id))
|
|
: and(eq(patch.filePath, filePath), eq(patch.composeId, id)),
|
|
});
|
|
};
|
|
|
|
export const updatePatch = async (patchId: string, data: Partial<Patch>) => {
|
|
const result = await db
|
|
.update(patch)
|
|
.set({
|
|
...data,
|
|
...(data.content && {
|
|
content: data.content.endsWith("\n")
|
|
? data.content
|
|
: `${data.content}\n`,
|
|
}),
|
|
updatedAt: new Date().toISOString(),
|
|
})
|
|
.where(eq(patch.patchId, patchId))
|
|
.returning();
|
|
|
|
return result[0];
|
|
};
|
|
|
|
export const deletePatch = async (patchId: string) => {
|
|
const result = await db
|
|
.delete(patch)
|
|
.where(eq(patch.patchId, patchId))
|
|
.returning();
|
|
|
|
return result[0];
|
|
};
|
|
|
|
// Patch Application Functions
|
|
|
|
interface ApplyPatchesOptions {
|
|
appName: string;
|
|
type: "application" | "compose";
|
|
serverId: string | null;
|
|
patches: Patch[];
|
|
}
|
|
|
|
/**
|
|
* Generate shell commands to apply patches to cloned repository
|
|
* Uses git apply to apply unified diff patches
|
|
*/
|
|
export const generateApplyPatchesCommand = ({
|
|
appName,
|
|
type,
|
|
patches,
|
|
serverId,
|
|
}: ApplyPatchesOptions): string => {
|
|
if (patches.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId);
|
|
const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH;
|
|
const codePath = join(basePath, appName, "code");
|
|
|
|
let command = `echo "Applying ${patches.length} patch(es)...";`;
|
|
|
|
for (const p of patches) {
|
|
// Create a temporary patch file and apply it
|
|
const patchFileName = `/tmp/patch_${p.patchId}.patch`;
|
|
// Escape content for shell - use base64 encoding
|
|
const encodedContent = Buffer.from(p.content).toString("base64");
|
|
|
|
command += `
|
|
echo "${encodedContent}" | base64 -d > ${patchFileName};
|
|
cd ${codePath} && git apply --whitespace=fix ${patchFileName} && echo "✅ Applied patch for: ${p.filePath}" || echo "⚠️ Warning: Failed to apply patch for: ${p.filePath}";
|
|
rm -f ${patchFileName};
|
|
`;
|
|
}
|
|
|
|
return command;
|
|
};
|
|
|
|
/**
|
|
* Apply patches during build process
|
|
*/
|
|
export const applyPatches = async ({
|
|
appName,
|
|
type,
|
|
serverId,
|
|
patches,
|
|
}: ApplyPatchesOptions): Promise<void> => {
|
|
const enabledPatches = patches.filter((p) => p.enabled);
|
|
|
|
if (enabledPatches.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const command = generateApplyPatchesCommand({
|
|
appName,
|
|
type,
|
|
serverId,
|
|
patches: enabledPatches,
|
|
});
|
|
|
|
if (serverId) {
|
|
await execAsyncRemote(serverId, command);
|
|
} else {
|
|
await execAsync(command);
|
|
}
|
|
};
|