Implement cp, cpSync, and promises.cp APIs for memfs#1110
Conversation
Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
cp API
streamich
left a comment
There was a problem hiding this comment.
Throws this error when testing on Windows:
FAIL src/__tests__/volume/cpSync.test.ts (64.032 s)
● cpSync(src, dest[, options]) › throws error when trying to copy directory to subdirectory of itself
expect(received).toThrow(expected)
Expected pattern: /EINVAL/
Received message: "Maximum call stack size exceeded"
325 | const normalizePath = (str, stripTrailing): string => {
326 | if (typeof str !== 'string') throw new TypeError('expected a string');
> 327 | str = str.replace(/[\\\/]+/g, '/');
| ^
328 | if (stripTrailing !== false) str = removeTrailingSeparator(str);
329 | return str;
330 | };
streamich
left a comment
There was a problem hiding this comment.
Test does not pass on Windows:
FAIL src/__tests__/volume/cp-edge-cases.test.ts (44.799 s)
● cp edge cases › path validation › prevents copying directory to its own subdirectory
expect(received).toThrow(expected)
Expected pattern: /EINVAL/
Received message: "Maximum call stack size exceeded"
325 | const normalizePath = (str, stripTrailing): string => {
326 | if (typeof str !== 'string') throw new TypeError('expected a string');
> 327 | str = str.replace(/[\\\/]+/g, '/');
| ^
328 | if (stripTrailing !== false) str = removeTrailingSeparator(str);
329 | return str;
330 | };
at normalizePath (src/node/util.ts:327:13)
at unixify (src/node/util.ts:334:16)
at resolve (src/volume.ts:146:40)
at filenameToSteps (src/volume.ts:150:20)
at Volume.walk (src/volume.ts:448:15)
at Volume.getResolvedLinkOrThrow (src/volume.ts:511:17)
at Volume.openLink (src/volume.ts:710:42)
at Volume.openFile (src/volume.ts:778:27)
at Volume.openBase (src/volume.ts:783:23)
at Volume.openSync (src/volume.ts:793:17)
at Volume.readFileBase (src/volume.ts:975:17)
at Volume.readFileSync (src/volume.ts:992:17)
at Volume.copyFileBase (src/volume.ts:1196:[22](https://github.com/streamich/memfs/actions/runs/16528959537/job/46749340596?pr=1110#step:5:23))
at Volume.copyFileSync (src/volume.ts:1215:17)
at Volume.cpFileSync (src/volume.ts:1335:10)
at Volume.cpSyncBase (src/volume.ts:1291:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1[28](https://github.com/streamich/memfs/actions/runs/16528959537/job/46749340596?pr=1110#step:5:29)9:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1[36](https://github.com/streamich/memfs/actions/runs/16528959537/job/46749340596?pr=1110#step:5:37)9:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:13[69](https://github.com/streamich/memfs/actions/runs/16528959537/job/46749340596?pr=1110#step:5:70):12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:12[89](https://github.com/streamich/memfs/actions/runs/16528959537/job/46749340596?pr=1110#step:5:90):12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
at Volume.cpSyncBase (src/volume.ts:1289:12)
at Volume.cpDirSync (src/volume.ts:1369:12)
…rations Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
... Fixed the Windows infinite recursion issue in commit 0f26418. The problem was in the Replaced the complex path resolution with a simpler normalization approach that converts all paths to posix-style and avoids the recursion issue while maintaining all existing functionality. |
…nt Windows infinite recursion Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
... Fixed the Windows infinite recursion issue in commit 77535d0. The problem was in the Replaced the custom path normalization with Node.js built-in path utilities ( |
|
🎉 This PR is included in version 4.19.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This PR implements the complete
cpAPI family for memfs, providing Node.js-compatible file and directory copying functionality.Features Implemented
Core API Methods
cpSync(src, dest, options)- Synchronous copy operationcp(src, dest, options, callback)- Callback-based copy operationpromises.cp(src, dest, options)- Promise-based copy operation (auto-generated)Options Support
All standard Node.js cp options are fully supported:
recursive- Copy directories recursively (default: false)force- Overwrite existing files (default: true)errorOnExist- Throw error if destination exists when force is false (default: false)filter- Custom function to filter copied files/directoriespreserveTimestamps- Preserve file timestamps from source (default: false)dereference- Follow symlinks instead of copying them (default: false)verbatimSymlinks- Copy symlinks as-is without resolving relative paths (default: false)mode- Copy operation modifiers (default: 0)File Type Handling
Safety Features
Usage Examples
Testing
Added comprehensive test coverage with 39 new tests across 4 test suites:
All existing tests continue to pass, ensuring backward compatibility.
API Compatibility
The implementation follows the Node.js
fs.cpspecification exactly:Fixes #1088.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.