diff --git a/packages/core/src/node/__tests__/fs.test.ts b/packages/core/src/node/__tests__/fs.test.ts index 1aaed3d4..79b20bbd 100644 --- a/packages/core/src/node/__tests__/fs.test.ts +++ b/packages/core/src/node/__tests__/fs.test.ts @@ -16,6 +16,18 @@ afterEach(async () => { }); describe("writeFileWithMode", () => { + test("applies the requested mode when creating a new file", async () => { + const tempDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-write-file-with-mode-")); + tempDirectories.add(tempDirectory); + + const filePath = path.join(tempDirectory, "identity"); + + await writeFileWithMode(filePath, "content", FILE_MODES.ownerReadWrite); + + expect(await fs.readFile(filePath, "utf8")).toBe("content"); + expect((await fs.stat(filePath)).mode & 0o777).toBe(0o600); + }); + test("applies the requested mode even when rewriting an existing file", async () => { const tempDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-write-file-with-mode-")); tempDirectories.add(tempDirectory); diff --git a/packages/core/src/node/fs.ts b/packages/core/src/node/fs.ts index 1b5b4b06..de8b9d1b 100644 --- a/packages/core/src/node/fs.ts +++ b/packages/core/src/node/fs.ts @@ -7,6 +7,13 @@ export const FILE_MODES = { type FileMode = (typeof FILE_MODES)[keyof typeof FILE_MODES]; export const writeFileWithMode = async (filePath: string, data: string, mode: FileMode) => { + // Remove any existing file first so the mode option on writeFile is always applied + // on a fresh file creation (mode is ignored for existing files). This also avoids + // inheriting incorrect permissions from a previously-created file on filesystems + // where chmod may not behave as expected (e.g. Docker bind-mounts on some NAS systems). + await fs.unlink(filePath).catch((error: NodeJS.ErrnoException) => { + if (error.code !== "ENOENT") throw error; + }); await fs.writeFile(filePath, data, { mode }); await fs.chmod(filePath, mode); };