refactor(patch-editor): remove repoPath dependency and streamline patch handling

- Eliminated the repoPath parameter from the PatchEditor component and related API calls, simplifying the patch management logic.
- Updated the patch retrieval and saving processes to focus on filePath and content, enhancing clarity and maintainability.
- Adjusted the handling of file content in the CodeEditor to ensure it retrieves the correct data, improving user experience.
This commit is contained in:
Mauricio Siu
2026-02-17 01:57:57 -06:00
parent 1c25ab4303
commit c89f2e302b
3 changed files with 48 additions and 121 deletions

View File

@@ -56,16 +56,10 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
{
id,
type,
repoPath,
filePath: selectedFile || "",
},
{
enabled: !!selectedFile,
onSuccess: (data) => {
if (data.patchError) {
toast.error(data.patchErrorMessage || "Failed to apply patch");
}
},
},
);
@@ -90,7 +84,6 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
saveAsPatch({
id,
type,
repoPath,
filePath: selectedFile,
content: fileContent,
})
@@ -213,7 +206,7 @@ export const PatchEditor = ({ id, type, repoPath, onClose }: Props) => {
</div>
) : selectedFile ? (
<CodeEditor
value={fileData?.content || ""}
value={fileData || ""}
onChange={(value) => setFileContent(value || "")}
className="h-full w-full"
wrapperClassName="h-full"

View File

@@ -186,7 +186,6 @@ export const patchRouter = createTRPCRouter({
z.object({
id: z.string(),
type: z.enum(["application", "compose"]),
repoPath: z.string(),
filePath: z.string(),
}),
)
@@ -229,12 +228,10 @@ export const patchRouter = createTRPCRouter({
input.type,
);
return await readPatchRepoFile(
input.repoPath,
input.filePath,
existingPatch?.enabled ? existingPatch?.content : undefined,
serverId,
);
if (existingPatch) {
return existingPatch.content;
}
return await readPatchRepoFile(input.id, input.type, input.filePath);
}),
saveFileAsPatch: protectedProcedure
@@ -242,14 +239,11 @@ export const patchRouter = createTRPCRouter({
z.object({
id: z.string(),
type: z.enum(["application", "compose"]),
repoPath: z.string(),
filePath: z.string(),
content: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
let serverId: string | null = null;
if (input.type === "application") {
const app = await findApplicationById(input.id);
if (
@@ -261,7 +255,6 @@ export const patchRouter = createTRPCRouter({
message: "You are not authorized to access this application",
});
}
serverId = app.serverId;
} else if (input.type === "compose") {
const compose = await findComposeById(input.id);
if (
@@ -273,7 +266,6 @@ export const patchRouter = createTRPCRouter({
message: "You are not authorized to access this compose",
});
}
serverId = compose.serverId;
} else {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -281,14 +273,24 @@ export const patchRouter = createTRPCRouter({
});
}
const newPatch = await createPatch({
filePath: input.filePath,
content: input.content,
applicationId: input.type === "application" ? input.id : undefined,
composeId: input.type === "compose" ? input.id : undefined,
});
const existingPatch = await findPatchByFilePath(
input.filePath,
input.id,
input.type,
);
return newPatch;
if (!existingPatch) {
const newPatch = await createPatch({
filePath: input.filePath,
content: input.content,
applicationId: input.type === "application" ? input.id : undefined,
composeId: input.type === "compose" ? input.id : undefined,
});
} else {
return await updatePatch(existingPatch.patchId, {
content: input.content,
});
}
}),
// Cleanup

View File

@@ -144,107 +144,39 @@ export const readPatchRepoDirectory = async (
return root;
};
interface ReadFileResult {
content: string;
patchError?: boolean;
patchErrorMessage?: string;
}
/**
* Read file content from patch repo, optionally with patch applied
*/
export const readPatchRepoFile = async (
repoPath: string,
id: string,
type: "application" | "compose",
filePath: string,
patchContent?: string,
serverId?: string | null,
): Promise<ReadFileResult> => {
) => {
let serverId: string | null = null;
if (type === "application") {
const application = await findApplicationById(id);
serverId = application.buildServerId || application.serverId;
} else {
const compose = await findComposeById(id);
serverId = compose.serverId;
}
const { PATCH_REPOS_PATH } = paths(!!serverId);
const application =
type === "application"
? await findApplicationById(id)
: await findComposeById(id);
const repoPath = join(PATCH_REPOS_PATH, type, application.appName);
const fullPath = join(repoPath, filePath);
// Read original file
const command = `cat "${fullPath}" 2>/dev/null || echo "__FILE_NOT_FOUND__"`;
const command = `cat "${fullPath}"`;
let content: string;
try {
if (serverId) {
const result = await execAsyncRemote(serverId, command);
content = result.stdout;
} else {
const result = await execAsync(command);
content = result.stdout;
}
} catch (error) {
throw new TRPCError({
code: "NOT_FOUND",
message: `File not found: ${filePath}`,
});
if (serverId) {
const result = await execAsyncRemote(serverId, command);
return result.stdout;
}
if (content.trim() === "__FILE_NOT_FOUND__") {
throw new TRPCError({
code: "NOT_FOUND",
message: `File not found: ${filePath}`,
});
}
// If no patch, return original content
if (!patchContent) {
return { content };
}
// Try to apply patch
const tempDir = `/tmp/patch_apply_${Date.now()}`;
const encodedContent = Buffer.from(content).toString("base64");
const encodedPatch = Buffer.from(patchContent).toString("base64");
// We need to recreate the file structure for git apply to work
// git diff usually uses paths relative to repo root
const applyCommand = `
set -e;
mkdir -p "${tempDir}";
cd "${tempDir}";
git init -q;
# Create file with correct path
mkdir -p "$(dirname "${filePath}")";
echo "${encodedContent}" | base64 -d > "${filePath}";
# Save patch
echo "${encodedPatch}" | base64 -d > "patch.diff";
# Apply patch
git apply --ignore-space-change --ignore-whitespace patch.diff;
# Read result
cat "${filePath}";
rm -rf "${tempDir}";
`;
try {
let patchedContent: string;
if (serverId) {
const result = await execAsyncRemote(serverId, applyCommand);
patchedContent = result.stdout;
} else {
const result = await execAsync(applyCommand);
patchedContent = result.stdout;
}
return { content: patchedContent };
} catch (error) {
// Patch failed - return original content with error
const cleanupCommand = `rm -rf "${tempDir}" 2>/dev/null || true`;
try {
if (serverId) {
await execAsyncRemote(serverId, cleanupCommand);
} else {
await execAsync(cleanupCommand);
}
} catch {
// Ignore cleanup errors
}
return {
content,
patchError: true,
patchErrorMessage: `Failed to apply patch: ${error}`,
};
}
const result = await execAsync(command);
return result.stdout;
};
/**