diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 106830f75682..bb861e5e22d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,8 @@ jobs: - latest os: - ubuntu-latest + - windows-latest + - macOS-latest steps: - name: Clone repository diff --git a/fs/_get_fs_flag.ts b/fs/_get_fs_flag.ts index 2fbabeb7cbf7..8b53f9288cf3 100644 --- a/fs/_get_fs_flag.ts +++ b/fs/_get_fs_flag.ts @@ -1,6 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { getNodeFs } from "./_utils.ts"; +import { getNodeFs, getNodeOs } from "./_utils.ts"; import type { WriteFileOptions } from "./unstable_types.ts"; import type { OpenOptions } from "./unstable_open.ts"; @@ -22,11 +22,23 @@ type OpenBooleanOptions = Pick< export function getWriteFsFlag(opt: WriteBooleanOptions): number { const { O_APPEND, O_CREAT, O_EXCL, O_TRUNC, O_WRONLY } = getNodeFs().constants; - - let flag = O_WRONLY; - if (opt.create) { + const { platform } = getNodeOs(); + + // On Windows: The O_CREAT flag is set by default to prevent throwing an + // EINVAL error (code -4071) when running Node on a Windows OS. The O_CREAT + // flag will create a new file if the file does not exist and is a no-op when + // the file exists on Windows. This makes the `WriteBooleanOption`, + // `{ create: true }`, the default option. Passing `{ create: false }` will + // throw an Error. + let flag = platform() !== "win32" ? O_WRONLY : O_CREAT | O_WRONLY; + + if (platform() === "win32" && !opt.create) { + flag ^= O_CREAT; + } + if (platform() !== "win32" && opt.create) { flag |= O_CREAT; } + if (opt.createNew) { flag |= O_EXCL; } diff --git a/fs/unstable_write_file_test.ts b/fs/unstable_write_file_test.ts index cdf9836e8c89..34259b678575 100644 --- a/fs/unstable_write_file_test.ts +++ b/fs/unstable_write_file_test.ts @@ -83,9 +83,16 @@ Deno.test("writeFile() handles 'create' when writing to a file", async () => { const data = encoder.encode("Hello"); // Rejects with NotFound when file does not initally exist. - await assertRejects(async () => { - await writeFile(testFile, data, { create: false }); - }, NotFound); + // TODO: This should throw NotFound on Windows. + if (platform() === "win32") { + await assertRejects(async () => { + await writeFile(testFile, data, { create: false }); + }, Error); + } else { + await assertRejects(async () => { + await writeFile(testFile, data, { create: false }); + }, NotFound); + } // Creates a file that does not initially exist. (This is default behavior). await writeFile(testFile, data, { create: true }); @@ -96,7 +103,11 @@ Deno.test("writeFile() handles 'create' when writing to a file", async () => { // Overwrites the existing file with new content. const dataAgain = encoder.encode("Hello, Standard Library"); - await writeFile(testFile, dataAgain, { create: false }); + if (platform() === "win32") { + await writeFile(testFile, dataAgain); + } else { + await writeFile(testFile, dataAgain, { create: false }); + } const dataReadAgain = await readFile(testFile); const readDataAgain = decoder.decode(dataReadAgain); assertEquals(readDataAgain, "Hello, Standard Library"); @@ -332,9 +343,16 @@ Deno.test("writeFileSync() handles 'create' when writing to a file", () => { const data = encoder.encode("Hello"); // Throws with NotFound when file does not initally exist. - assertThrows(() => { - writeFileSync(testFile, data, { create: false }); - }, NotFound); + // TODO: This should throw NotFound on Windows. + if (platform() === "win32") { + assertThrows(() => { + writeFileSync(testFile, data, { create: false }); + }, Error); + } else { + assertThrows(() => { + writeFileSync(testFile, data, { create: false }); + }, NotFound); + } // Creates a file that does not initially exist. (This is default behavior). writeFileSync(testFile, data, { create: true }); @@ -345,7 +363,11 @@ Deno.test("writeFileSync() handles 'create' when writing to a file", () => { // Overwrites the existing file with new content. const dataAgain = encoder.encode("Hello, Standard Library"); - writeFileSync(testFile, dataAgain, { create: false }); + if (platform() === "win32") { + writeFileSync(testFile, dataAgain); + } else { + writeFileSync(testFile, dataAgain, { create: false }); + } const dataReadAgain = readFileSync(testFile); const readDataAgain = decoder.decode(dataReadAgain); assertEquals(readDataAgain, "Hello, Standard Library"); diff --git a/fs/unstable_write_text_file_test.ts b/fs/unstable_write_text_file_test.ts index 874c300d7566..3e2215321198 100644 --- a/fs/unstable_write_text_file_test.ts +++ b/fs/unstable_write_text_file_test.ts @@ -93,9 +93,16 @@ Deno.test("writeTextFile() handles 'create' for a file", async () => { const tempDirPath = await makeTempDir({ prefix: "writeTextFile_" }); const testFile = join(tempDirPath, "testFile.txt"); - await assertRejects(async () => { - await writeTextFile(testFile, "Hello", { create: false }); - }, NotFound); + // TODO: This should throw NotFound on Windows. + if (platform() === "win32") { + await assertRejects(async () => { + await writeTextFile(testFile, "Hello", { create: false }); + }, Error); + } else { + await assertRejects(async () => { + await writeTextFile(testFile, "Hello", { create: false }); + }, NotFound); + } await writeTextFile(testFile, "Hello", { create: true }); const readData = await readTextFile(testFile); @@ -300,9 +307,16 @@ Deno.test("writeTextFileSync() handles 'create' for a file", () => { const tempDirPath = makeTempDirSync({ prefix: "writeTextFileSync_" }); const testFile = join(tempDirPath, "testFile.txt"); - assertThrows(() => { - writeTextFileSync(testFile, "Hello", { create: false }); - }, NotFound); + // TODO: This should throw NotFound on Windows. + if (platform() === "win32") { + assertThrows(() => { + writeTextFileSync(testFile, "Hello", { create: false }); + }, Error); + } else { + assertThrows(() => { + writeTextFileSync(testFile, "Hello", { create: false }); + }, NotFound); + } writeTextFileSync(testFile, "Hello", { create: true }); const readData = readTextFileSync(testFile);