Skip to content

fix(ext/node): implement Module._stat - #34157

Merged
littledivy merged 2 commits into
mainfrom
orch/divybot-114
May 17, 2026
Merged

littledivy merged 2 commits into
mainfrom
orch/divybot-114

Conversation

@divybot

@divybot divybot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the internal-but-public Module._stat(path) accessor on the
node:module Module constructor, matching Node's
lib/internal/modules/cjs/loader.js:

  • Returns 0 if path is a file
  • Returns 1 if path is a directory
  • Returns a negative value if the path does not exist or stat fails
  • Synchronous

The default implementation is backed by the existing op_require_stat op
(which Deno's internal stat() helper already used). Assigning
Module._stat = fn emits an ExperimentalWarning (matching Node) and
swaps the underlying function so the module loader's internal stat lookups
also pick it up, which is what patch-package, certain bundlers, and the
Node test suite rely on.

Test plan

  • cargo test --test node_compat -- test-module-stat passes
  • Manual: Module._stat(<file>) === 0, Module._stat(<dir>) === 1,
    Module._stat(<nonexistent>) < 0
  • CI

parallel/test-vfs.js still fails after this change but at a later point:
it expects the CJS loader to use the (user-patched) fs.realpathSync and
fs.readFileSync. Deno's loader currently calls op_require_real_path /
op_require_read_file directly, which bypass user monkey-patching. Making
the loader patchable is a larger architectural change worth handling in a
follow-up.

Closes denoland/orchid#114

Adds the internal-but-public `Module._stat(path)` accessor on the
`node:module` Module constructor.

Per Node's `lib/internal/modules/cjs/loader.js`:
- Returns 0 if path is a file
- Returns 1 if path is a directory
- Returns a negative value if the path does not exist or stat fails

The default implementation is backed by op_require_stat. User code can
overwrite the property; the setter emits an `ExperimentalWarning` and
swaps in the new function so the loader's internal stat() picks it up
(used by patch-package, bundlers, and other tooling).

Fixes `parallel/test-module-stat.js` node compat test.

Closes denoland/orchid#114

Co-Authored-By: Divy Srivastava <me@littledivy.com>

@lunadogbot lunadogbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. const result = internalModuleStat(filename); is the load-bearing behavior change, but the enabled parallel/test-module-stat.js only calls the default Module._stat getter. It does not prove that assigning Module._stat = fn affects Deno's internal CJS resolution path, or that the setter warning path works. Please add a focused test that patches Module._stat and observes require() resolution changing, for example by returning < 0 for an existing temp module and asserting require(file) fails with MODULE_NOT_FOUND.

CI is not green yet, but this test gap is independent of the current CI state.

@lunadogbot

Copy link
Copy Markdown
Contributor

Build infra flake (actions/upload-artifact exited -1073740791 in test integration (1/2) debug windows-x86_64), unrelated to this PR. Re-running.

Adds a regression test in tests/unit_node/module_test.ts that:

1. Asserts the default Module._stat returns 0 for files, 1 for
   directories, and a negative value for missing paths.
2. Patches Module._stat to return -ENOENT for a real on-disk file and
   asserts require() fails with MODULE_NOT_FOUND, then restores the
   original stat and asserts the same require() succeeds. This proves
   the setter actually swaps the function used by Deno's internal CJS
   resolution path, not just the public accessor.

Co-Authored-By: Divy Srivastava <me@littledivy.com>
@littledivy
littledivy merged commit 565302b into main May 17, 2026
136 checks passed
@littledivy
littledivy deleted the orch/divybot-114 branch May 17, 2026 05:28
littledivy added a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
## Summary

Implements the internal-but-public `Module._stat(path)` accessor on the
`node:module` `Module` constructor, matching Node's
`lib/internal/modules/cjs/loader.js`:

- Returns `0` if `path` is a file
- Returns `1` if `path` is a directory
- Returns a negative value if the path does not exist or stat fails
- Synchronous

The default implementation is backed by the existing `op_require_stat`
op
(which Deno's internal `stat()` helper already used). Assigning
`Module._stat = fn` emits an `ExperimentalWarning` (matching Node) and
swaps the underlying function so the module loader's internal stat
lookups
also pick it up, which is what `patch-package`, certain bundlers, and
the
Node test suite rely on.

## Test plan

- [x] `cargo test --test node_compat -- test-module-stat` passes
- [ ] Manual: `Module._stat(<file>) === 0`, `Module._stat(<dir>) === 1`,
      `Module._stat(<nonexistent>) < 0`
- [ ] CI

`parallel/test-vfs.js` still fails after this change but at a later
point:
it expects the CJS loader to use the (user-patched) `fs.realpathSync`
and
`fs.readFileSync`. Deno's loader currently calls `op_require_real_path`
/
`op_require_read_file` directly, which bypass user monkey-patching.
Making
the loader patchable is a larger architectural change worth handling in
a
follow-up.

Closes denoland/orchid#114

---------

Co-authored-by: divybot <divybot@users.noreply.github.com>
Co-authored-by: Divy Srivastava <me@littledivy.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants