feat(core,cli): add generic atomicWriteFile, wire into Write/Edit tools, upgrade @types/node#4096
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a generic atomic file-write utility in core and wires core file-writing paths (notably StandardFileSystemService.writeTextFile, which underpins Write/Edit tools) to use atomic write semantics (temp file + rename), with additional durability via fsync and permission preservation. It also refactors existing atomic JSON writing to reuse the new helper, deduplicates rename-retry logic, and aligns the repo’s Node type definitions with the Node >=22 engine requirement.
Changes:
- Add
atomicWriteFile()(string/Buffer, fsync, permission handling, symlink-aware targeting, EXDEV fallback) and refactoratomicWriteJSON()to delegate to it. - Route
StandardFileSystemService.writeTextFile()throughatomicWriteFile()to make Write/Edit operations atomic. - Add
flush: trueto synchronous settings writes and upgrade@types/nodeto^22.0.0across packages.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/utils/atomicFileWrite.ts | Adds generic atomic write helper; refactors atomic JSON writes; exports renameWithRetry. |
| packages/core/src/utils/atomicFileWrite.test.ts | Adds tests for atomicWriteFile behavior (string/buffer, perms, temp cleanup, symlink case). |
| packages/core/src/services/fileSystemService.ts | Switches text writes to atomicWriteFile for atomic Write/Edit tool behavior. |
| packages/core/src/services/fileSystemService.test.ts | Mocks atomicWriteFile to keep file system service tests working. |
| packages/core/src/utils/runtimeStatus.ts | Uses atomicWriteJSON for runtime status writes and removes local rename retry implementation. |
| packages/cli/src/utils/writeWithBackup.ts | Adds flush: true to temp writes for better durability. |
| packages/cli/package.json | Upgrades @types/node to ^22.0.0. |
| packages/sdk-typescript/package.json | Upgrades @types/node to ^22.0.0. |
| packages/vscode-ide-companion/package.json | Upgrades @types/node to ^22.0.0. |
| packages/vscode-ide-companion/NOTICES.txt | Updates third-party notice entry for scheduler version. |
| package-lock.json | Lockfile updates reflecting dependency/type upgrades. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
atomicWriteFile()topackages/core/src/utils/atomicFileWrite.ts— a generic atomic write function supporting string/Buffer with flush (fsync), permission preservation, symlink chain resolution, EXDEV cross-device fallback, and FAT/exFAT chmod toleranceStandardFileSystemService.writeTextFile()throughatomicWriteFile, making all Write and Edit tool file operations atomicatomicWriteJSONto delegate toatomicWriteFile(adds previously missing fsync)renameWithRetryfromruntimeStatus.ts(was copy-pasted fromatomicFileWrite.ts)flush: truetowriteWithBackupSyncfor settings writes@types/nodeto^22.0.0across all packages (matches engine requirement, enablesflushoption types)edit.test.tsFILE_WRITE_FAILURE test: use mock instead ofchmod 444(atomic write bypasses readonly file check since tmp file creation succeeds)This directly closes the TODOs in
write-file.ts:371-385andedit.ts:487-497:Ref: #4095 (Phase 1)
Test plan
npx tsc --noEmit -p packages/core— typecheck cleanatomicFileWrite.test.ts— 18 tests pass (5 existing + 13 new)runtimeStatus.test.ts— 19 tests pass (no regression from dedup)fileSystemService.test.ts— 41 tests pass (mock updated for atomicWriteFile)edit.test.ts— 75 tests pass (FILE_WRITE_FAILURE test updated for atomic write)write-file.test.ts— 45 tests passManual E2E verification
Save the following script as
atomic-e2e-test.tsand run withnpx tsx atomic-e2e-test.tsfrom the repo root:atomic-e2e-test.ts (click to expand)
Expected output:
atomicWriteFile(f1, 'hello atomic')hello atomic; no.tmpfiles in directorychmod 600, thenatomicWriteFile(f2, 'new content')new content;stat.mode & 0o777==0o600real.txt→ symlinklink.txt, write vialink.txtreadlink(link.txt)still points toreal.txt;real.txtcontainsupdated dataB → A → real, write viaBreal.txtupdated tochain updated🤖 Generated with Qwen Code