diff --git a/cli/tests/unit_node/fs_test.ts b/cli/tests/unit_node/fs_test.ts index 310113233e..d9fe3af76e 100644 --- a/cli/tests/unit_node/fs_test.ts +++ b/cli/tests/unit_node/fs_test.ts @@ -3,7 +3,8 @@ import { assert, assertThrows } from "../../../test_util/std/assert/mod.ts"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { pathToAbsoluteFileUrl } from "../unit/test_util.ts"; Deno.test( "[node/fs writeFileSync] write file without option", @@ -43,3 +44,39 @@ Deno.test( ); }, ); + +Deno.test( + "[node/fs existsSync] path", + { permissions: { read: true } }, + () => { + assert(existsSync("cli/tests/testdata/assets/fixture.json")); + }, +); + +Deno.test( + "[node/fs existsSync] url", + { permissions: { read: true } }, + () => { + assert(existsSync( + pathToAbsoluteFileUrl("cli/tests/testdata/assets/fixture.json"), + )); + }, +); + +Deno.test( + "[node/fs existsSync] no permission", + { permissions: { read: false } }, + () => { + assertThrows(() => { + existsSync("cli/tests/testdata/assets/fixture.json"); + }, Deno.errors.PermissionDenied); + }, +); + +Deno.test( + "[node/fs existsSync] not exists", + { permissions: { read: true } }, + () => { + assert(!existsSync("bad_filename")); + }, +); diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 7fa5b893b2..547f1d60a1 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -50,7 +50,15 @@ pub trait NodePermissions { url: &Url, api_name: &str, ) -> Result<(), AnyError>; - fn check_read(&self, path: &Path) -> Result<(), AnyError>; + #[inline(always)] + fn check_read(&self, path: &Path) -> Result<(), AnyError> { + self.check_read_with_api_name(path, None) + } + fn check_read_with_api_name( + &self, + path: &Path, + api_name: Option<&str>, + ) -> Result<(), AnyError>; fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError>; } @@ -64,7 +72,11 @@ impl NodePermissions for AllowAllNodePermissions { ) -> Result<(), AnyError> { Ok(()) } - fn check_read(&self, _path: &Path) -> Result<(), AnyError> { + fn check_read_with_api_name( + &self, + _path: &Path, + _api_name: Option<&str>, + ) -> Result<(), AnyError> { Ok(()) } fn check_sys(&self, _kind: &str, _api_name: &str) -> Result<(), AnyError> { @@ -227,6 +239,7 @@ deno_core::extension!(deno_node, ops::crypto::x509::op_node_x509_get_valid_to, ops::crypto::x509::op_node_x509_get_serial_number, ops::crypto::x509::op_node_x509_key_usage, + ops::fs::op_node_fs_exists_sync

, ops::winerror::op_node_sys_to_uv_error, ops::v8::op_v8_cached_data_version_tag, ops::v8::op_v8_get_heap_statistics, diff --git a/ext/node/ops/fs.rs b/ext/node/ops/fs.rs new file mode 100644 index 0000000000..b21652634d --- /dev/null +++ b/ext/node/ops/fs.rs @@ -0,0 +1,26 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::path::PathBuf; + +use deno_core::error::AnyError; +use deno_core::op2; +use deno_core::OpState; +use deno_fs::FileSystemRc; + +use crate::NodePermissions; + +#[op2(fast)] +pub fn op_node_fs_exists_sync

( + state: &mut OpState, + #[string] path: String, +) -> Result +where + P: NodePermissions + 'static, +{ + let path = PathBuf::from(path); + state + .borrow_mut::

() + .check_read_with_api_name(&path, Some("node:fs.existsSync()"))?; + let fs = state.borrow::(); + Ok(fs.lstat_sync(&path).is_ok()) +} diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index d1bb4b7f48..ec4324da3b 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -1,6 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. pub mod crypto; +pub mod fs; pub mod http; pub mod http2; pub mod idna; diff --git a/ext/node/polyfills/_fs/_fs_exists.ts b/ext/node/polyfills/_fs/_fs_exists.ts index 9c8458b586..db41aedec6 100644 --- a/ext/node/polyfills/_fs/_fs_exists.ts +++ b/ext/node/polyfills/_fs/_fs_exists.ts @@ -2,7 +2,7 @@ // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials - +const core = globalThis.__bootstrap.core; import { pathFromURL } from "ext:deno_web/00_infra.js"; type ExistsCallback = (exists: boolean) => void; @@ -35,10 +35,5 @@ Object.defineProperty(exists, kCustomPromisifiedSymbol, { */ export function existsSync(path: string | URL): boolean { path = path instanceof URL ? pathFromURL(path) : path; - try { - Deno.lstatSync(path); - return true; - } catch (_err) { - return false; - } + return core.ops.op_node_fs_exists_sync(path); } diff --git a/runtime/permissions/mod.rs b/runtime/permissions/mod.rs index 75d4af1c74..7677b5c36e 100644 --- a/runtime/permissions/mod.rs +++ b/runtime/permissions/mod.rs @@ -1376,8 +1376,12 @@ impl deno_node::NodePermissions for PermissionsContainer { } #[inline(always)] - fn check_read(&self, path: &Path) -> Result<(), AnyError> { - self.0.lock().read.check(path, None) + fn check_read_with_api_name( + &self, + path: &Path, + api_name: Option<&str>, + ) -> Result<(), AnyError> { + self.0.lock().read.check(path, api_name) } fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError> { diff --git a/runtime/snapshot.rs b/runtime/snapshot.rs index f7642573ed..a0f0665b45 100644 --- a/runtime/snapshot.rs +++ b/runtime/snapshot.rs @@ -76,7 +76,11 @@ impl deno_node::NodePermissions for Permissions { ) -> Result<(), deno_core::error::AnyError> { unreachable!("snapshotting!") } - fn check_read(&self, _p: &Path) -> Result<(), deno_core::error::AnyError> { + fn check_read_with_api_name( + &self, + _p: &Path, + _api_name: Option<&str>, + ) -> Result<(), deno_core::error::AnyError> { unreachable!("snapshotting!") } fn check_sys(