From 62954f028fdf61a896cc3d2e9d9c2e8fe4b087da Mon Sep 17 00:00:00 2001 From: wiiiii123 Date: Sat, 11 Jul 2026 10:46:33 +0700 Subject: [PATCH] fix(project): address atomic save review feedback --- electron/ipc/project/atomicSave.test.ts | 17 +++++++++++++---- electron/ipc/project/atomicSave.ts | 3 +++ electron/ipc/register/project.ts | 3 ++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/electron/ipc/project/atomicSave.test.ts b/electron/ipc/project/atomicSave.test.ts index 07c9534f..34646af3 100644 --- a/electron/ipc/project/atomicSave.test.ts +++ b/electron/ipc/project/atomicSave.test.ts @@ -31,6 +31,7 @@ describe("writeProjectFileAtomically", () => { code: "ENOENT", }); await expectNoTemporaryArtifacts(); + await expectNoTemporaryArtifacts(); }); it("removes a stale backup when the target has no previous generation", async () => { @@ -63,6 +64,7 @@ describe("writeProjectFileAtomically", () => { ).rejects.toBeDefined(); await expect(fs.readFile(projectPath, "utf-8")).resolves.toBe('{"version":1,"name":"old"}'); + await expectNoTemporaryArtifacts(); await fs.rm(getProjectBackupPath(projectPath), { recursive: true }); await writeProjectFileAtomically(projectPath, '{"version":1,"name":"retry"}'); @@ -78,6 +80,7 @@ describe("writeProjectFileAtomically", () => { it("serializes overlapping writes to the same project", async () => { await writeProjectFileAtomically(projectPath, '{"revision":1}'); + // Each call enters the queue synchronously before its first await, preserving invocation order. await Promise.all([ writeProjectFileAtomically(projectPath, '{"revision":2}'), writeProjectFileAtomically(projectPath, '{"revision":3}'), @@ -91,13 +94,19 @@ describe("writeProjectFileAtomically", () => { }); it.skipIf(process.platform === "win32")( - "preserves existing project permission bits on replacement", + "preserves exact project permission bits despite the process umask", async () => { - await fs.writeFile(projectPath, '{"revision":1}', { mode: 0o600 }); + await fs.writeFile(projectPath, '{"revision":1}', { mode: 0o666 }); + await fs.chmod(projectPath, 0o666); + const previousUmask = process.umask(0o077); - await writeProjectFileAtomically(projectPath, '{"revision":2}'); + try { + await writeProjectFileAtomically(projectPath, '{"revision":2}'); + } finally { + process.umask(previousUmask); + } - expect((await fs.stat(projectPath)).mode & 0o777).toBe(0o600); + expect((await fs.stat(projectPath)).mode & 0o777).toBe(0o666); }, ); }); diff --git a/electron/ipc/project/atomicSave.ts b/electron/ipc/project/atomicSave.ts index d4942e10..d2440ffe 100644 --- a/electron/ipc/project/atomicSave.ts +++ b/electron/ipc/project/atomicSave.ts @@ -47,6 +47,9 @@ async function writeSyncedTemporaryFile( const handle = await fs.open(filePath, "wx", mode); try { await handle.writeFile(contents, "utf-8"); + if (mode !== undefined) { + await handle.chmod(mode); + } await handle.sync(); } finally { await handle.close(); diff --git a/electron/ipc/register/project.ts b/electron/ipc/register/project.ts index 59b992e8..f1fa43e2 100644 --- a/electron/ipc/register/project.ts +++ b/electron/ipc/register/project.ts @@ -9,7 +9,7 @@ import { LEGACY_PROJECT_FILE_EXTENSIONS, PROJECT_FILE_EXTENSION, } from "../constants"; -import { writeProjectFileAtomically } from "../project/atomicSave"; +import { getProjectBackupPath, writeProjectFileAtomically } from "../project/atomicSave"; import { getProjectsDir, getProjectThumbnailPath, @@ -432,6 +432,7 @@ export function registerProjectHandlers() { } }) await fs.rm(getProjectThumbnailPath(activeProjectPath), { force: true }).catch(() => undefined) + await fs.rm(getProjectBackupPath(activeProjectPath), { force: true }).catch(() => undefined) const recentProjectPaths = await loadRecentProjectPaths() const filteredRecentProjectPaths: string[] = []