fix(export): harden post-merge file handling

This commit is contained in:
wiiiii123
2026-05-08 13:07:25 +07:00
parent a079d669b6
commit 81bfd2dbcc
2 changed files with 36 additions and 1 deletions
+26
View File
@@ -40,6 +40,7 @@ async function makeTempDir() {
}
afterEach(async () => {
vi.restoreAllMocks();
await Promise.allSettled(
tempDirs.splice(0).map((dir) => fs.rm(dir, { force: true, recursive: true })),
);
@@ -59,4 +60,29 @@ describe("moveExportedTempFile", () => {
);
await expect(fs.access(tempPath)).rejects.toThrow();
});
it("falls back when Windows reports the destination already exists during initial rename", async () => {
const dir = await makeTempDir();
const tempPath = path.join(dir, "export-temp.mp4");
const destinationPath = path.join(dir, "export-final.mp4");
await fs.writeFile(tempPath, "new-export");
await fs.writeFile(destinationPath, "previous-export");
const originalRename = fs.rename.bind(fs);
const renameSpy = vi.spyOn(fs, "rename");
renameSpy.mockImplementation(async (from, to) => {
if (from === tempPath && to === destinationPath) {
const error = new Error("destination exists") as NodeJS.ErrnoException;
error.code = "EEXIST";
throw error;
}
return originalRename(from, to);
});
await moveExportedTempFile(tempPath, destinationPath);
await expect(fs.readFile(destinationPath, "utf8")).resolves.toBe("new-export");
await expect(fs.access(tempPath)).rejects.toThrow();
});
});
+10 -1
View File
@@ -58,7 +58,12 @@ export async function moveExportedTempFile(tempPath: string, destinationPath: st
return;
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code !== "EXDEV" && code !== "EPERM" && code !== "ENOTEMPTY") {
if (
code !== "EXDEV" &&
code !== "EPERM" &&
code !== "ENOTEMPTY" &&
code !== "EEXIST"
) {
throw error;
}
// Cross-device or Windows permission quirks — fall back to copy + unlink so
@@ -143,6 +148,10 @@ async function resolveAllowedReadableFilePath(
}
const resolvedPath = path.resolve(filePath);
if (!isAllowedLocalReadPath(resolvedPath)) {
throw new Error(`${label} is not approved for local reads`);
}
const realPath = await fs.realpath(resolvedPath).catch(() => null);
if (!realPath) {
throw new Error(`${label} does not exist`);