Files
dokploy/packages/server/src/services/patch.ts
T
Mauricio Siu 9818e3c3ba refactor(patch): simplify patch repository management and enhance git configuration handling
- 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.
2026-02-17 01:11:23 -06:00

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);
}
};