node:fs: implement mkdtempDisposable / mkdtempDisposableSync - #31019
node:fs: implement mkdtempDisposable / mkdtempDisposableSync#31019sam-shridhar1950f wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughAdded ChangesTemporary Directory Disposable Helpers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
31028ec to
ddaa2e2
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/node/fs/fs.test.ts`:
- Line 4038: Replace uses of tmpdirSync() with the harness helper tempDir(...)
in these tests so the created temp roots are tracked and auto-cleaned;
specifically update calls like mkdtempDisposableSync(join(tmpdirSync(),
"disposable-")) to instead call tempDir("disposable-") (or wrap tempDir().path
as needed) and ensure you import tempDir from the test harness if not present.
Apply the same replacement for all similar occurrences (references to tmpdirSync
in the block around mkdtempDisposableSync and other mkdtemp* usages) so temp
directories are created via tempDir and the disposable cleanup semantics are
preserved.
- Around line 4094-4125: The tests only exercise the internal
_promises.mkdtempDisposable export and miss exercising the public entry points;
add at least one equivalent test case that calls mkdtempDisposable via the
public promises API (e.g., fs.promises.mkdtempDisposable or the imported
promises object) to ensure export wiring is correct; locate the existing tests
around mkdtempDisposable (symbols: _promises.mkdtempDisposable, result.remove,
Symbol.asyncDispose) and duplicate one of the existing cases (for example the
"remove() returns a Promise that resolves" or "creates a directory and removes
it via Symbol.asyncDispose") but invoke it through fs.promises.mkdtempDisposable
(or promises.mkdtempDisposable) and assert the same behaviors (path is string,
directory exists, removal results in non-existence).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 16918969-5ad6-4eab-9c55-2d10a1a9d5fc
📒 Files selected for processing (3)
src/js/node/fs.promises.tssrc/js/node/fs.tstest/js/node/fs/fs.test.ts
|
|
||
| describe("mkdtempDisposableSync", () => { | ||
| it("creates a directory and removes it via Symbol.dispose", () => { | ||
| const result = mkdtempDisposableSync(join(tmpdirSync(), "disposable-")); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Replace tmpdirSync() with tempDir(...) in these new tests.
These new cases create temp roots with tmpdirSync(), which conflicts with the test harness rule and loses disposable cleanup ergonomics.
Suggested pattern
- const result = mkdtempDisposableSync(join(tmpdirSync(), "disposable-"));
+ using base = tempDir("mkdtemp-disposable-sync", {});
+ const result = mkdtempDisposableSync(join(String(base), "disposable-"));- const result = await _promises.mkdtempDisposable(join(tmpdirSync(), "disposable-"));
+ using base = tempDir("mkdtemp-disposable-async", {});
+ const result = await _promises.mkdtempDisposable(join(String(base), "disposable-"));As per coding guidelines: "Use tempDir from 'harness' to create temporary directories - do not use tmpdirSync or fs.mkdtempSync."
Also applies to: 4046-4046, 4053-4053, 4061-4061, 4069-4069, 4079-4079, 4096-4096, 4106-4106, 4114-4114, 4121-4121
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/js/node/fs/fs.test.ts` at line 4038, Replace uses of tmpdirSync() with
the harness helper tempDir(...) in these tests so the created temp roots are
tracked and auto-cleaned; specifically update calls like
mkdtempDisposableSync(join(tmpdirSync(), "disposable-")) to instead call
tempDir("disposable-") (or wrap tempDir().path as needed) and ensure you import
tempDir from the test harness if not present. Apply the same replacement for all
similar occurrences (references to tmpdirSync in the block around
mkdtempDisposableSync and other mkdtemp* usages) so temp directories are created
via tempDir and the disposable cleanup semantics are preserved.
ddaa2e2 to
92d4c6f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/node/fs/fs.test.ts`:
- Around line 4094-4132: Add an async regression test to ensure
mkdtempDisposable does not suffer cwd drift by using process.chdir around the
async disposable - inside the "fs.promises.mkdtempDisposable" describe add a
test similar to the sync case that calls process.chdir(...) to a different
directory, then await _promises.mkdtempDisposable(...) (or
promises.mkdtempDisposable(...)), capture the returned result.path, change the
cwd back, call await result[Symbol.asyncDispose]() (or result.remove()), and
assert that the process.cwd() is unchanged and the temporary directory has been
removed (existsSync(result.path) is false); reference
_promises.mkdtempDisposable, promises.mkdtempDisposable, Symbol.asyncDispose and
remove() to locate the code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 197ced2f-088c-42af-9aa4-fa7862c39a38
📒 Files selected for processing (3)
src/js/node/fs.promises.tssrc/js/node/fs.tstest/js/node/fs/fs.test.ts
| describe("fs.promises.mkdtempDisposable", () => { | ||
| it("creates a directory and removes it via Symbol.asyncDispose", async () => { | ||
| const result = await _promises.mkdtempDisposable(join(tmpdirSync(), "disposable-")); | ||
| expect(typeof result.path).toBe("string"); | ||
| expect(existsSync(result.path)).toBe(true); | ||
| await result[Symbol.asyncDispose](); | ||
| expect(existsSync(result.path)).toBe(false); | ||
| }); | ||
|
|
||
| it("works with `await using` syntax", async () => { | ||
| let savedPath: string; | ||
| { | ||
| await using temp = await _promises.mkdtempDisposable(join(tmpdirSync(), "disposable-")); | ||
| savedPath = temp.path; | ||
| expect(existsSync(temp.path)).toBe(true); | ||
| } | ||
| expect(existsSync(savedPath!)).toBe(false); | ||
| }); | ||
|
|
||
| it("calling asyncDispose twice is safe", async () => { | ||
| const result = await _promises.mkdtempDisposable(join(tmpdirSync(), "disposable-")); | ||
| await result[Symbol.asyncDispose](); | ||
| await result[Symbol.asyncDispose](); | ||
| expect(existsSync(result.path)).toBe(false); | ||
| }); | ||
|
|
||
| it("remove() returns a Promise that resolves", async () => { | ||
| const result = await _promises.mkdtempDisposable(join(tmpdirSync(), "disposable-")); | ||
| await result.remove(); | ||
| expect(existsSync(result.path)).toBe(false); | ||
| }); | ||
|
|
||
| it("is also reachable via the node:fs `promises` namespace", async () => { | ||
| const result = await promises.mkdtempDisposable(join(tmpdirSync(), "disposable-")); | ||
| expect(existsSync(result.path)).toBe(true); | ||
| await result[Symbol.asyncDispose](); | ||
| expect(existsSync(result.path)).toBe(false); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Add a process.chdir() regression test for the async variant too.
The sync suite verifies cwd drift safety, but the async suite doesn’t. Add one equivalent async case to lock in the captured-cwd cleanup behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/js/node/fs/fs.test.ts` around lines 4094 - 4132, Add an async regression
test to ensure mkdtempDisposable does not suffer cwd drift by using
process.chdir around the async disposable - inside the
"fs.promises.mkdtempDisposable" describe add a test similar to the sync case
that calls process.chdir(...) to a different directory, then await
_promises.mkdtempDisposable(...) (or promises.mkdtempDisposable(...)), capture
the returned result.path, change the cwd back, call await
result[Symbol.asyncDispose]() (or result.remove()), and assert that the
process.cwd() is unchanged and the temporary directory has been removed
(existsSync(result.path) is false); reference _promises.mkdtempDisposable,
promises.mkdtempDisposable, Symbol.asyncDispose and remove() to locate the code.
Adds the Node 24 disposable mkdtemp APIs that pair with the explicit
resource management proposal:
- fs.mkdtempDisposableSync(prefix[, options]) returns { path, remove(),
[Symbol.dispose]() } for use with `using`.
- fsPromises.mkdtempDisposable(prefix[, options]) returns a Promise
resolving to { path, remove(), [Symbol.asyncDispose]() } for use with
`await using`.
Both capture process.cwd() at creation so a process.chdir() between
creation and disposal cannot misdirect the recursive rm. Disposing
twice is safe (force: true).
Closes oven-sh#24499
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92d4c6f to
8fad946
Compare
|
I independently reimplemented this while looking at #31400 (a fresh report of the same gap as #24499) and arrived at the same design, so I checked this PR against Node's upstream implementation — it matches their semantics exactly:
One optional nit for even tighter Node parity: Node's async variant returns the object with LGTM from my read. Deferring to this PR rather than opening a duplicate — it just needs a maintainer review/merge. |
Closes #24499
What does this PR do?
Adds the Node 24 disposable
mkdtempAPIs that pair with TC39's explicit resource management proposal:fs.mkdtempDisposableSync(prefix[, options])— returns{ path, remove(), [Symbol.dispose]() }for use withusing.fsPromises.mkdtempDisposable(prefix[, options])— returns aPromise<{ path, remove(), [Symbol.asyncDispose]() }>for use withawait using.The callback form
fs.mkdtempDisposable(prefix, options, callback)is intentionally not added, matching Node's documented surface (nodejs/node docs).Was wrong
Per the issue, the API doesn't exist:
Bun's docs already reference
mkdtempDisposableSync, so this also resolves an existing docs/runtime gap.Fix
Implementations live in the existing JS shims:
src/js/node/fs.ts— addsmkdtempDisposableSync, exports it, andsetNames it.src/js/node/fs.promises.ts— addsmkdtempDisposableto the promises exports.Both wrap the existing
fs.mkdtemp/fs.mkdtempSyncand dispose via the existingrm/rmSyncwith{ recursive: true, force: true }. They captureprocess.cwd()at creation so aprocess.chdir()between creation and disposal cannot misdirect the recursive removal.force: truemakes repeat disposal a no-op (matches Node).How did you verify your code works?
Added 10 tests in
test/js/node/fs/fs.test.tscovering:mkdtempDisposableSyncSymbol.disposeresult.remove()as a manual alternativeusingsyntaxprocess.chdir()between create and disposefs.promises.mkdtempDisposableSymbol.asyncDisposeawait usingsyntaxresult.remove()returns aPromiseLocal results on macOS arm64 (
1.3.14-debug+80a06a8c0):No new failures in the broader
test/js/node/fs/suite. Lint and format are clean (bun run lint,bun run fmt).This PR effectively replaces the stalled #22068, with a tighter diff (3 files / +125 lines), Node-spec-faithful API surface (no speculative callback variant), and focused tests added to the existing module's test file rather than a separate Node-port file.