├── .github ├── issue_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── copy-sync.md ├── copy.md ├── emptyDir-sync.md ├── emptyDir.md ├── ensureDir-sync.md ├── ensureDir.md ├── ensureFile-sync.md ├── ensureFile.md ├── ensureLink-sync.md ├── ensureLink.md ├── ensureSymlink-sync.md ├── ensureSymlink.md ├── fs-read-write-writev.md ├── move-sync.md ├── move.md ├── outputFile-sync.md ├── outputFile.md ├── outputJson-sync.md ├── outputJson.md ├── pathExists-sync.md ├── pathExists.md ├── readJson-sync.md ├── readJson.md ├── remove-sync.md ├── remove.md ├── writeJson-sync.md └── writeJson.md ├── lib ├── __tests__ │ └── promise.test.js ├── copy │ ├── __tests__ │ │ ├── copy-broken-symlink.test.js │ │ ├── copy-case-insensitive-paths.test.js │ │ ├── copy-dev-null.test.js │ │ ├── copy-gh-89.test.js │ │ ├── copy-permissions.test.js │ │ ├── copy-preserve-timestamp.test.js │ │ ├── copy-prevent-copying-identical.test.js │ │ ├── copy-prevent-copying-into-itself.test.js │ │ ├── copy-readonly-dir.test.js │ │ ├── copy-sync-broken-symlink.test.js │ │ ├── copy-sync-case-insensitive-paths.test.js │ │ ├── copy-sync-dir.test.js │ │ ├── copy-sync-file.test.js │ │ ├── copy-sync-preserve-timestamp.test.js │ │ ├── copy-sync-prevent-copying-identical.test.js │ │ ├── copy-sync-prevent-copying-into-itself.test.js │ │ ├── copy-sync-readonly-dir.test.js │ │ ├── copy-sync-symlink.test.js │ │ ├── copy.test.js │ │ └── ncp │ │ │ ├── README.md │ │ │ ├── broken-symlink.test.js │ │ │ ├── fixtures │ │ │ ├── modified-files │ │ │ │ ├── out │ │ │ │ │ └── a │ │ │ │ └── src │ │ │ │ │ └── a │ │ │ └── regular-fixtures │ │ │ │ ├── out │ │ │ │ ├── a │ │ │ │ ├── b │ │ │ │ ├── c │ │ │ │ ├── d │ │ │ │ ├── e │ │ │ │ ├── f │ │ │ │ └── sub │ │ │ │ │ ├── a │ │ │ │ │ └── b │ │ │ │ └── src │ │ │ │ ├── a │ │ │ │ ├── b │ │ │ │ ├── c │ │ │ │ ├── d │ │ │ │ ├── e │ │ │ │ ├── f │ │ │ │ └── sub │ │ │ │ ├── a │ │ │ │ └── b │ │ │ ├── ncp-error-perm.test.js │ │ │ ├── ncp.test.js │ │ │ └── symlink.test.js │ ├── copy-sync.js │ ├── copy.js │ └── index.js ├── empty │ ├── __tests__ │ │ ├── empty-dir-sync.test.js │ │ └── empty-dir.test.js │ └── index.js ├── ensure │ ├── __tests__ │ │ ├── create.test.js │ │ ├── ensure.test.js │ │ ├── link.test.js │ │ ├── symlink-paths.test.js │ │ ├── symlink-type.test.js │ │ └── symlink.test.js │ ├── file.js │ ├── index.js │ ├── link.js │ ├── symlink-paths.js │ ├── symlink-type.js │ └── symlink.js ├── esm.mjs ├── fs │ ├── __tests__ │ │ ├── copyFile.test.js │ │ ├── fs-integration.test.js │ │ ├── multi-param.test.js │ │ ├── mz.test.js │ │ ├── realpath.test.js │ │ └── rm.test.js │ └── index.js ├── index.js ├── json │ ├── __tests__ │ │ ├── jsonfile-integration.test.js │ │ ├── output-json-sync.test.js │ │ ├── output-json.test.js │ │ ├── promise-support.test.js │ │ └── read.test.js │ ├── index.js │ ├── jsonfile.js │ ├── output-json-sync.js │ └── output-json.js ├── mkdirs │ ├── __tests__ │ │ ├── chmod.test.js │ │ ├── clobber.test.js │ │ ├── issue-209.test.js │ │ ├── issue-93.test.js │ │ ├── mkdir.test.js │ │ ├── mkdirp.test.js │ │ ├── opts-undef.test.js │ │ ├── perm.test.js │ │ ├── perm_sync.test.js │ │ ├── race.test.js │ │ ├── rel.test.js │ │ ├── root.test.js │ │ └── sync.test.js │ ├── index.js │ ├── make-dir.js │ └── utils.js ├── move │ ├── __tests__ │ │ ├── cross-device-utils.js │ │ ├── move-case-insensitive-paths.test.js │ │ ├── move-preserve-timestamp.test.js │ │ ├── move-prevent-moving-identical.test.js │ │ ├── move-prevent-moving-into-itself.test.js │ │ ├── move-sync-case-insensitive-paths.test.js │ │ ├── move-sync-preserve-timestamp.test.js │ │ ├── move-sync-prevent-moving-identical.test.js │ │ ├── move-sync-prevent-moving-into-itself.test.js │ │ ├── move-sync.test.js │ │ └── move.test.js │ ├── index.js │ ├── move-sync.js │ └── move.js ├── output-file │ ├── __tests__ │ │ └── output.test.js │ └── index.js ├── path-exists │ ├── __tests__ │ │ ├── path-exists-sync.test.js │ │ └── path-exists.test.js │ └── index.js ├── remove │ ├── __tests__ │ │ ├── remove-dir.test.js │ │ ├── remove-file.test.js │ │ ├── remove-sync-dir.test.js │ │ ├── remove-sync-file.test.js │ │ └── remove.test.js │ └── index.js └── util │ ├── __tests__ │ ├── stat.test.js │ └── utimes.test.js │ ├── stat.js │ └── utimes.js ├── package.json ├── test.js ├── test.mjs └── test └── readme.md /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - **Operating System:** 5 | - **Node.js version:** 6 | - **`fs-extra` version:** 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | on: 3 | push: 4 | branches: master 5 | pull_request: 6 | 7 | permissions: 8 | contents: read # to fetch code (actions/checkout) 9 | 10 | jobs: 11 | test: 12 | strategy: 13 | matrix: 14 | node: [14.x, 16.x, 18.x, 19.x] 15 | os: [ubuntu-latest, macos-13, windows-latest] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Use Node.js ${{ matrix.node }} 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node }} 23 | - run: npm install 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | node_modules/ 4 | 5 | .idea 6 | *.iml 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011-2024 JP Richardson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 6 | (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 7 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 14 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /docs/copy-sync.md: -------------------------------------------------------------------------------- 1 | # copySync(src, dest[, options]) 2 | 3 | Copy a file or directory. The directory can have contents. 4 | 5 | - `src` `` Note that if `src` is a directory it will copy everything inside of this directory, not the entire directory itself (see [issue #537](https://github.com/jprichardson/node-fs-extra/issues/537)). 6 | - `dest` `` Note that if `src` is a file, `dest` cannot be a directory (see [issue #323](https://github.com/jprichardson/node-fs-extra/issues/323)). 7 | - `options` `` 8 | - `overwrite` ``: overwrite existing file or directory, default is `true`. _Note that the copy operation will silently fail if you set this to `false` and the destination exists._ Use the `errorOnExist` option to change this behavior. 9 | - `errorOnExist` ``: when `overwrite` is `false` and the destination exists, throw an error. Default is `false`. 10 | - `dereference` ``: dereference symlinks, default is `false`. 11 | - `preserveTimestamps` ``: When true, will set last modification and access times to the ones of the original source files. When false, timestamp behavior is OS-dependent. Default is `false`. 12 | - `filter` ``: Function to filter copied files/directories. Return `true` to copy the item, `false` to ignore it. 13 | 14 | ## Example: 15 | 16 | ```js 17 | const fs = require('fs-extra') 18 | 19 | // copy file 20 | fs.copySync('/tmp/myfile', '/tmp/mynewfile') 21 | 22 | // copy directory, even if it has subdirectories or files 23 | fs.copySync('/tmp/mydir', '/tmp/mynewdir') 24 | ``` 25 | 26 | **Using filter function** 27 | 28 | ```js 29 | const fs = require('fs-extra') 30 | 31 | const filterFunc = (src, dest) => { 32 | // your logic here 33 | // it will be copied if return true 34 | } 35 | 36 | fs.copySync('/tmp/mydir', '/tmp/mynewdir', { filter: filterFunc }) 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/copy.md: -------------------------------------------------------------------------------- 1 | # copy(src, dest[, options][, callback]) 2 | 3 | Copy a file or directory. The directory can have contents. 4 | 5 | - `src` `` Note that if `src` is a directory it will copy everything inside of this directory, not the entire directory itself (see [issue #537](https://github.com/jprichardson/node-fs-extra/issues/537)). 6 | - `dest` `` Note that if `src` is a file, `dest` cannot be a directory (see [issue #323](https://github.com/jprichardson/node-fs-extra/issues/323)). 7 | - `options` `` 8 | - `overwrite` ``: overwrite existing file or directory, default is `true`. _Note that the copy operation will silently fail if you set this to `false` and the destination exists._ Use the `errorOnExist` option to change this behavior. 9 | - `errorOnExist` ``: when `overwrite` is `false` and the destination exists, throw an error. Default is `false`. 10 | - `dereference` ``: dereference symlinks, default is `false`. 11 | - `preserveTimestamps` ``: When true, will set last modification and access times to the ones of the original source files. When false, timestamp behavior is OS-dependent. Default is `false`. 12 | - `filter` ``: Function to filter copied files/directories. Return `true` to copy the item, `false` to ignore it. Can also return a `Promise` that resolves to `true` or `false` (or pass in an `async` function). 13 | - `callback` `` 14 | - `err` `` 15 | 16 | ## Example: 17 | 18 | ```js 19 | const fs = require('fs-extra') 20 | 21 | // With a callback: 22 | fs.copy('/tmp/myfile', '/tmp/mynewfile', err => { 23 | if (err) return console.error(err) 24 | console.log('success!') 25 | }) // copies file 26 | 27 | fs.copy('/tmp/mydir', '/tmp/mynewdir', err => { 28 | if (err) return console.error(err) 29 | console.log('success!') 30 | }) // copies directory, even if it has subdirectories or files 31 | 32 | // With Promises: 33 | fs.copy('/tmp/myfile', '/tmp/mynewfile') 34 | .then(() => { 35 | console.log('success!') 36 | }) 37 | .catch(err => { 38 | console.error(err) 39 | }) 40 | 41 | // With async/await: 42 | async function example () { 43 | try { 44 | await fs.copy('/tmp/myfile', '/tmp/mynewfile') 45 | console.log('success!') 46 | } catch (err) { 47 | console.error(err) 48 | } 49 | } 50 | 51 | example() 52 | ``` 53 | 54 | **Using filter function** 55 | 56 | ```js 57 | const fs = require('fs-extra') 58 | 59 | const filterFunc = (src, dest) => { 60 | // your logic here 61 | // it will be copied if return true 62 | } 63 | 64 | fs.copy('/tmp/mydir', '/tmp/mynewdir', { filter: filterFunc }, err => { 65 | if (err) return console.error(err) 66 | console.log('success!') 67 | }) 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/emptyDir-sync.md: -------------------------------------------------------------------------------- 1 | # emptyDirSync(dir) 2 | 3 | Ensures that a directory is empty. Deletes directory contents if the directory is not empty. If the directory does not exist, it is created. The directory itself is not deleted. 4 | 5 | **Alias:** `emptydirSync()` 6 | 7 | - `dir` `` 8 | 9 | ## Example: 10 | 11 | ```js 12 | const fs = require('fs-extra') 13 | 14 | // assume this directory has a lot of files and folders 15 | fs.emptyDirSync('/tmp/some/dir') 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/emptyDir.md: -------------------------------------------------------------------------------- 1 | # emptyDir(dir[, callback]) 2 | 3 | Ensures that a directory is empty. Deletes directory contents if the directory is not empty. If the directory does not exist, it is created. The directory itself is not deleted. 4 | 5 | **Alias:** `emptydir()` 6 | 7 | - `dir` `` 8 | - `callback` `` 9 | - `err` `` 10 | 11 | ## Example: 12 | 13 | ```js 14 | const fs = require('fs-extra') 15 | 16 | // assume this directory has a lot of files and folders 17 | // With a callback: 18 | fs.emptyDir('/tmp/some/dir', err => { 19 | if (err) return console.error(err) 20 | console.log('success!') 21 | }) 22 | 23 | // With Promises: 24 | fs.emptyDir('/tmp/some/dir') 25 | .then(() => { 26 | console.log('success!') 27 | }) 28 | .catch(err => { 29 | console.error(err) 30 | }) 31 | 32 | // With async/await: 33 | async function example () { 34 | try { 35 | await fs.emptyDir('/tmp/some/dir') 36 | console.log('success!') 37 | } catch (err) { 38 | console.error(err) 39 | } 40 | } 41 | 42 | example() 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/ensureDir-sync.md: -------------------------------------------------------------------------------- 1 | # ensureDirSync(dir[,options]) 2 | 3 | Ensures that the directory exists. If the directory structure does not exist, it is created. If provided, options may specify the desired mode for the directory. 4 | 5 | **Aliases:** `mkdirsSync()`, `mkdirpSync()` 6 | 7 | - `dir` `` 8 | - `options` ` | ` 9 | - If it is `Integer`, it will be `mode`. 10 | - If it is `Object`, it will be `{ mode: }`. 11 | 12 | ## Example: 13 | 14 | ```js 15 | const fs = require('fs-extra') 16 | 17 | const dir = '/tmp/this/path/does/not/exist' 18 | 19 | const desiredMode = 0o2775 20 | const options = { 21 | mode: 0o2775 22 | } 23 | 24 | fs.ensureDirSync(dir) 25 | // dir has now been created, including the directory it is to be placed in 26 | 27 | fs.ensureDirSync(dir, desiredMode) 28 | // dir has now been created, including the directory it is to be placed in with permission 0o2775 29 | 30 | fs.ensureDirSync(dir, options) 31 | // dir has now been created, including the directory it is to be placed in with permission 0o2775 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/ensureDir.md: -------------------------------------------------------------------------------- 1 | # ensureDir(dir[,options][,callback]) 2 | 3 | Ensures that the directory exists. If the directory structure does not exist, it is created. 4 | 5 | **Aliases:** `mkdirs()`, `mkdirp()` 6 | 7 | - `dir` `` 8 | - `options` ` | ` 9 | - If it is `Integer`, it will be `mode`. 10 | - If it is `Object`, it will be `{ mode: }`. 11 | - `callback` `` 12 | - `err` `` 13 | 14 | ## Example: 15 | 16 | ```js 17 | const fs = require('fs-extra') 18 | 19 | const dir = '/tmp/this/path/does/not/exist' 20 | const desiredMode = 0o2775 21 | const options = { 22 | mode: 0o2775 23 | } 24 | 25 | // With a callback: 26 | fs.ensureDir(dir, err => { 27 | console.log(err) // => null 28 | // dir has now been created, including the directory it is to be placed in 29 | }) 30 | 31 | // With a callback and a mode integer 32 | fs.ensureDir(dir, desiredMode, err => { 33 | console.log(err) // => null 34 | // dir has now been created with mode 0o2775, including the directory it is to be placed in 35 | }) 36 | 37 | // With Promises: 38 | fs.ensureDir(dir) 39 | .then(() => { 40 | console.log('success!') 41 | }) 42 | .catch(err => { 43 | console.error(err) 44 | }) 45 | 46 | // With Promises and a mode integer: 47 | fs.ensureDir(dir, desiredMode) 48 | .then(() => { 49 | console.log('success!') 50 | }) 51 | .catch(err => { 52 | console.error(err) 53 | }) 54 | 55 | // With async/await: 56 | async function example (directory) { 57 | try { 58 | await fs.ensureDir(directory) 59 | console.log('success!') 60 | } catch (err) { 61 | console.error(err) 62 | } 63 | } 64 | example(dir) 65 | 66 | // With async/await and an options object, containing mode: 67 | async function exampleMode (directory) { 68 | try { 69 | await fs.ensureDir(directory, options) 70 | console.log('success!') 71 | } catch (err) { 72 | console.error(err) 73 | } 74 | } 75 | exampleMode(dir) 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/ensureFile-sync.md: -------------------------------------------------------------------------------- 1 | # ensureFileSync(file) 2 | 3 | Ensures that the file exists. If the file that is requested to be created is in directories that do not exist, these directories are created. If the file already exists, it is **NOT MODIFIED**. 4 | 5 | **Alias:** `createFileSync()` 6 | 7 | - `file` `` 8 | 9 | ## Example: 10 | 11 | ```js 12 | const fs = require('fs-extra') 13 | 14 | const file = '/tmp/this/path/does/not/exist/file.txt' 15 | fs.ensureFileSync(file) 16 | // file has now been created, including the directory it is to be placed in 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/ensureFile.md: -------------------------------------------------------------------------------- 1 | # ensureFile(file[, callback]) 2 | 3 | Ensures that the file exists. If the file that is requested to be created is in directories that do not exist, these directories are created. If the file already exists, it is **NOT MODIFIED**. 4 | 5 | **Alias:** `createFile()` 6 | 7 | - `file` `` 8 | - `callback` `` 9 | - `err` `` 10 | 11 | ## Example: 12 | 13 | ```js 14 | const fs = require('fs-extra') 15 | 16 | const file = '/tmp/this/path/does/not/exist/file.txt' 17 | 18 | // With a callback: 19 | fs.ensureFile(file, err => { 20 | console.log(err) // => null 21 | // file has now been created, including the directory it is to be placed in 22 | }) 23 | 24 | // With Promises: 25 | fs.ensureFile(file) 26 | .then(() => { 27 | console.log('success!') 28 | }) 29 | .catch(err => { 30 | console.error(err) 31 | }) 32 | 33 | // With async/await: 34 | async function example (f) { 35 | try { 36 | await fs.ensureFile(f) 37 | console.log('success!') 38 | } catch (err) { 39 | console.error(err) 40 | } 41 | } 42 | 43 | example(file) 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/ensureLink-sync.md: -------------------------------------------------------------------------------- 1 | # ensureLinkSync(srcPath, destPath) 2 | 3 | Ensures that the link exists. If the directory structure does not exist, it is created. 4 | 5 | **Alias:** `createLinkSync()` 6 | 7 | - `srcPath` `` 8 | - `destPath` `` 9 | 10 | ## Example: 11 | 12 | ```js 13 | const fs = require('fs-extra') 14 | 15 | const srcPath = '/tmp/file.txt' 16 | const destPath = '/tmp/this/path/does/not/exist/file.txt' 17 | fs.ensureLinkSync(srcPath, destPath) 18 | // link has now been created, including the directory it is to be placed in 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/ensureLink.md: -------------------------------------------------------------------------------- 1 | # ensureLink(srcPath, destPath[, callback]) 2 | 3 | Ensures that the link exists. If the directory structure does not exist, it is created. 4 | 5 | **Alias:** `createLink()` 6 | 7 | - `srcPath` `` 8 | - `destPath` `` 9 | - `callback` `` 10 | - `err` `` 11 | 12 | ## Example: 13 | 14 | ```js 15 | const fs = require('fs-extra') 16 | 17 | const srcPath = '/tmp/file.txt' 18 | const destPath = '/tmp/this/path/does/not/exist/file.txt' 19 | 20 | // With a callback: 21 | fs.ensureLink(srcPath, destPath, err => { 22 | console.log(err) // => null 23 | // link has now been created, including the directory it is to be placed in 24 | }) 25 | 26 | // With Promises: 27 | fs.ensureLink(srcPath, destPath) 28 | .then(() => { 29 | console.log('success!') 30 | }) 31 | .catch(err => { 32 | console.error(err) 33 | }) 34 | 35 | // With async/await: 36 | async function example (src, dest) { 37 | try { 38 | await fs.ensureLink(src, dest) 39 | console.log('success!') 40 | } catch (err) { 41 | console.error(err) 42 | } 43 | } 44 | 45 | example(srcPath, destPath) 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/ensureSymlink-sync.md: -------------------------------------------------------------------------------- 1 | # ensureSymlinkSync(srcPath, destPath[, type]) 2 | 3 | Ensures that the symlink exists. If the directory structure does not exist, it is created. 4 | 5 | **Alias:** `createSymlinkSync()` 6 | 7 | - `srcPath` `` 8 | - `destPath` `` 9 | - `type` `` It is only available on Windows and ignored on other platforms. It can be set to `dir`, `file`, or `junction`. 10 | 11 | ## Example: 12 | 13 | ```js 14 | const fs = require('fs-extra') 15 | 16 | const srcPath = '/tmp/file.txt' 17 | const destPath = '/tmp/this/path/does/not/exist/file.txt' 18 | fs.ensureSymlinkSync(srcPath, destPath) 19 | // symlink has now been created, including the directory it is to be placed in 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/ensureSymlink.md: -------------------------------------------------------------------------------- 1 | # ensureSymlink(srcPath, destPath[, type][, callback]) 2 | 3 | Ensures that the symlink exists. If the directory structure does not exist, it is created. 4 | 5 | **Alias:** `createSymlink()` 6 | 7 | - `srcPath` `` 8 | - `destPath` `` 9 | - `type` `` It is only available on Windows and ignored on other platforms. It can be set to `dir`, `file`, or `junction`. 10 | - `callback` `` 11 | - `err` `` 12 | 13 | ## Example: 14 | 15 | ```js 16 | const fs = require('fs-extra') 17 | 18 | const srcPath = '/tmp/file.txt' 19 | const destPath = '/tmp/this/path/does/not/exist/file.txt' 20 | 21 | // With a callback: 22 | fs.ensureSymlink(srcPath, destPath, err => { 23 | console.log(err) // => null 24 | // symlink has now been created, including the directory it is to be placed in 25 | }) 26 | 27 | // With Promises: 28 | fs.ensureSymlink(srcPath, destPath) 29 | .then(() => { 30 | console.log('success!') 31 | }) 32 | .catch(err => { 33 | console.error(err) 34 | }) 35 | 36 | // With async/await: 37 | async function example (src, dest) { 38 | try { 39 | await fs.ensureSymlink(src, dest) 40 | console.log('success!') 41 | } catch (err) { 42 | console.error(err) 43 | } 44 | } 45 | 46 | example(srcPath, destPath) 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/fs-read-write-writev.md: -------------------------------------------------------------------------------- 1 | # About `fs.read()` & `fs.write()` 2 | 3 | [`fs.read()`](https://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback), [`fs.write()`](https://nodejs.org/api/fs.html#fs_fs_write_fd_buffer_offset_length_position_callback), & [`fs.writev()`](https://nodejs.org/api/fs.html#fs_fs_writev_fd_buffers_position_callback) are different from other `fs` methods in that their callbacks are called with 3 arguments instead of the usual 2 arguments. 4 | 5 | If you're using them with callbacks, they will behave as usual. However, their promise usage is a little different. `fs-extra` promisifies these methods like [`util.promisify()`](https://nodejs.org/api/util.html#util_util_promisify_original) (only available in Node 8+) does. 6 | 7 | Here's the example promise usage: 8 | 9 | ## `fs.read()` 10 | 11 | ```js 12 | // With Promises: 13 | fs.read(fd, buffer, offset, length, position) 14 | .then(results => { 15 | console.log(results) 16 | // { bytesRead: 20, buffer: } 17 | }) 18 | 19 | // With async/await: 20 | async function example () { 21 | const { bytesRead, buffer } = await fs.read(fd, Buffer.alloc(length), offset, length, position) 22 | } 23 | ``` 24 | 25 | ## `fs.write()` 26 | 27 | ```js 28 | // With Promises: 29 | fs.write(fd, buffer, offset, length, position) 30 | .then(results => { 31 | console.log(results) 32 | // { bytesWritten: 20, buffer: } 33 | }) 34 | 35 | // With async/await: 36 | async function example () { 37 | const { bytesWritten, buffer } = await fs.write(fd, Buffer.alloc(length), offset, length, position) 38 | } 39 | ``` 40 | 41 | ## `fs.writev()` 42 | 43 | ```js 44 | // With async/await: 45 | async function example () { 46 | const { bytesWritten, buffers } = await fs.writev(fd, buffers, position) 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/move-sync.md: -------------------------------------------------------------------------------- 1 | # moveSync(src, dest[, options]) 2 | 3 | Moves a file or directory, even across devices. 4 | 5 | - `src` `` 6 | - `dest` `` Note: When `src` is a file, `dest` must be a file and when `src` is a directory, `dest` must be a directory. 7 | - `options` `` 8 | - `overwrite` ``: overwrite existing file or directory, default is `false`. 9 | 10 | ## Example: 11 | 12 | ```js 13 | const fs = require('fs-extra') 14 | 15 | fs.moveSync('/tmp/somefile', '/tmp/does/not/exist/yet/somefile') 16 | ``` 17 | 18 | **Using `overwrite` option** 19 | 20 | ```js 21 | const fs = require('fs-extra') 22 | 23 | fs.moveSync('/tmp/somedir', '/tmp/may/already/exist/somedir', { overwrite: true }) 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/move.md: -------------------------------------------------------------------------------- 1 | # move(src, dest[, options][, callback]) 2 | 3 | Moves a file or directory, even across devices. 4 | 5 | - `src` `` 6 | - `dest` `` Note: When `src` is a file, `dest` must be a file and when `src` is a directory, `dest` must be a directory. 7 | - `options` `` 8 | - `overwrite` ``: overwrite existing file or directory, default is `false`. 9 | - `callback` `` 10 | - `err` `` 11 | 12 | ## Example: 13 | 14 | ```js 15 | const fs = require('fs-extra') 16 | 17 | const src = '/tmp/file.txt' 18 | const dest = '/tmp/this/path/does/not/exist/file.txt' 19 | 20 | // With a callback: 21 | fs.move(src, dest, err => { 22 | if (err) return console.error(err) 23 | console.log('success!') 24 | }) 25 | 26 | // With Promises: 27 | fs.move(src, dest) 28 | .then(() => { 29 | console.log('success!') 30 | }) 31 | .catch(err => { 32 | console.error(err) 33 | }) 34 | 35 | // With async/await: 36 | async function example (src, dest) { 37 | try { 38 | await fs.move(src, dest) 39 | console.log('success!') 40 | } catch (err) { 41 | console.error(err) 42 | } 43 | } 44 | 45 | example(src, dest) 46 | ``` 47 | 48 | **Using `overwrite` option** 49 | 50 | ```js 51 | const fs = require('fs-extra') 52 | 53 | fs.move('/tmp/somedir', '/tmp/may/already/exist/somedir', { overwrite: true }, err => { 54 | if (err) return console.error(err) 55 | console.log('success!') 56 | }) 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/outputFile-sync.md: -------------------------------------------------------------------------------- 1 | # outputFileSync(file, data[, options]) 2 | 3 | Almost the same as `writeFileSync` (i.e. it overwrites), except that if the parent directory does not exist, it's created. `file` must be a file path (a buffer or a file descriptor is not allowed). 4 | 5 | - `file` `` 6 | - `data` ` | | ` 7 | - `options` ` | ` (the same as [`fs.writeFileSync()` options](https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options)) 8 | 9 | ## Example: 10 | 11 | ```js 12 | const fs = require('fs-extra') 13 | 14 | const file = '/tmp/this/path/does/not/exist/file.txt' 15 | fs.outputFileSync(file, 'hello!') 16 | 17 | const data = fs.readFileSync(file, 'utf8') 18 | console.log(data) // => hello! 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/outputFile.md: -------------------------------------------------------------------------------- 1 | # outputFile(file, data[, options][, callback]) 2 | 3 | Almost the same as `writeFile` (i.e. it overwrites), except that if the parent directory does not exist, it's created. `file` must be a file path (a buffer or a file descriptor is not allowed). 4 | 5 | - `file` `` 6 | - `data` ` | | ` 7 | - `options` ` | ` (the same as [`fs.writeFile()` options](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback)) 8 | - `callback` `` 9 | - `err` `` 10 | 11 | ## Example: 12 | 13 | ```js 14 | const fs = require('fs-extra') 15 | 16 | const file = '/tmp/this/path/does/not/exist/file.txt' 17 | 18 | // With a callback: 19 | fs.outputFile(file, 'hello!', err => { 20 | console.log(err) // => null 21 | 22 | fs.readFile(file, 'utf8', (err, data) => { 23 | if (err) return console.error(err) 24 | console.log(data) // => hello! 25 | }) 26 | }) 27 | 28 | // With Promises: 29 | fs.outputFile(file, 'hello!') 30 | .then(() => fs.readFile(file, 'utf8')) 31 | .then(data => { 32 | console.log(data) // => hello! 33 | }) 34 | .catch(err => { 35 | console.error(err) 36 | }) 37 | 38 | // With async/await: 39 | async function example (f) { 40 | try { 41 | await fs.outputFile(f, 'hello!') 42 | 43 | const data = await fs.readFile(f, 'utf8') 44 | 45 | console.log(data) // => hello! 46 | } catch (err) { 47 | console.error(err) 48 | } 49 | } 50 | 51 | example(file) 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/outputJson-sync.md: -------------------------------------------------------------------------------- 1 | # outputJsonSync(file, object[, options]) 2 | 3 | Almost the same as [`writeJsonSync`](writeJson-sync.md), except that if the directory does not exist, it's created. 4 | 5 | **Alias:** `outputJSONSync()` 6 | 7 | - `file` `` 8 | - `object` `` 9 | - `options` `` 10 | - `spaces` ` | ` Number of spaces to indent; or a string to use for indentation (i.e. pass `'\t'` for tab indentation). See [the docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_space_argument) for more info. 11 | - `EOL` `` Set EOL character. Default is `\n`. 12 | - `replacer` [JSON replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) 13 | - Also accepts [`fs.writeFileSync()` options](https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options) 14 | 15 | ## Example: 16 | 17 | ```js 18 | const fs = require('fs-extra') 19 | 20 | const file = '/tmp/this/path/does/not/exist/file.json' 21 | fs.outputJsonSync(file, {name: 'JP'}) 22 | 23 | const data = fs.readJsonSync(file) 24 | console.log(data.name) // => JP 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/outputJson.md: -------------------------------------------------------------------------------- 1 | # outputJson(file, object[, options][, callback]) 2 | 3 | Almost the same as [`writeJson`](writeJson.md), except that if the directory does not exist, it's created. 4 | 5 | **Alias:** `outputJSON()` 6 | 7 | - `file` `` 8 | - `object` `` 9 | - `options` `` 10 | - `spaces` ` | ` Number of spaces to indent; or a string to use for indentation (i.e. pass `'\t'` for tab indentation). See [the docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_space_argument) for more info. 11 | - `EOL` `` Set EOL character. Default is `\n`. 12 | - `replacer` [JSON replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) 13 | - Also accepts [`fs.writeFile()` options](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback) 14 | - `callback` `` 15 | - `err` `` 16 | 17 | ## Example: 18 | 19 | ```js 20 | const fs = require('fs-extra') 21 | 22 | const file = '/tmp/this/path/does/not/exist/file.json' 23 | 24 | // With a callback: 25 | fs.outputJson(file, {name: 'JP'}, err => { 26 | console.log(err) // => null 27 | 28 | fs.readJson(file, (err, data) => { 29 | if (err) return console.error(err) 30 | console.log(data.name) // => JP 31 | }) 32 | }) 33 | 34 | // With Promises: 35 | fs.outputJson(file, {name: 'JP'}) 36 | .then(() => fs.readJson(file)) 37 | .then(data => { 38 | console.log(data.name) // => JP 39 | }) 40 | .catch(err => { 41 | console.error(err) 42 | }) 43 | 44 | // With async/await: 45 | async function example (f) { 46 | try { 47 | await fs.outputJson(f, {name: 'JP'}) 48 | 49 | const data = await fs.readJson(f) 50 | 51 | console.log(data.name) // => JP 52 | } catch (err) { 53 | console.error(err) 54 | } 55 | } 56 | 57 | example(file) 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/pathExists-sync.md: -------------------------------------------------------------------------------- 1 | # pathExistsSync(file) 2 | 3 | An alias for [`fs.existsSync()`](https://nodejs.org/api/fs.html#fs_fs_existssync_path), created for consistency with [`pathExists()`](pathExists.md). 4 | -------------------------------------------------------------------------------- /docs/pathExists.md: -------------------------------------------------------------------------------- 1 | # pathExists(file[, callback]) 2 | 3 | Test whether or not the given path exists by checking with the file system. Like [`fs.exists`](https://nodejs.org/api/fs.html#fs_fs_exists_path_callback), but with a normal callback signature (err, exists). Uses `fs.access` under the hood. 4 | 5 | - `file` `` 6 | - `callback` `` 7 | - `err` `` 8 | - `exists` `` 9 | 10 | ## Example: 11 | 12 | ```js 13 | const fs = require('fs-extra') 14 | 15 | const file = '/tmp/this/path/does/not/exist/file.txt' 16 | 17 | // With a callback: 18 | fs.pathExists(file, (err, exists) => { 19 | console.log(err) // => null 20 | console.log(exists) // => false 21 | }) 22 | 23 | // Promise usage: 24 | fs.pathExists(file) 25 | .then(exists => console.log(exists)) // => false 26 | 27 | // With async/await: 28 | async function example (f) { 29 | const exists = await fs.pathExists(f) 30 | 31 | console.log(exists) // => false 32 | } 33 | 34 | example(file) 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/readJson-sync.md: -------------------------------------------------------------------------------- 1 | # readJsonSync(file[, options]) 2 | 3 | Reads a JSON file and then parses it into an object. 4 | 5 | **Alias:** `readJSONSync()` 6 | 7 | - `file` `` 8 | - `options` `` (the same as [`jsonFile.readFileSync()` options](https://github.com/jprichardson/node-jsonfile#readfilesyncfilename-options)) 9 | 10 | ## Example: 11 | 12 | ```js 13 | const fs = require('fs-extra') 14 | 15 | const packageObj = fs.readJsonSync('./package.json') 16 | console.log(packageObj.version) // => 2.0.0 17 | ``` 18 | 19 | --- 20 | 21 | `readJsonSync()` can take a `throws` option set to `false` and it won't throw if the JSON is invalid. Example: 22 | 23 | ```js 24 | const fs = require('fs-extra') 25 | 26 | const file = '/tmp/some-invalid.json' 27 | const data = '{not valid JSON' 28 | fs.writeFileSync(file, data) 29 | 30 | const obj = fs.readJsonSync(file, { throws: false }) 31 | console.log(obj) // => null 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/readJson.md: -------------------------------------------------------------------------------- 1 | # readJson(file[, options][, callback]) 2 | 3 | Reads a JSON file and then parses it into an object. 4 | 5 | **Alias:** `readJSON()` 6 | 7 | - `file` `` 8 | - `options` `` (the same as [`jsonFile.readFile()` options](https://github.com/jprichardson/node-jsonfile#readfilefilename-options-callback)) 9 | - `callback` `` 10 | - `err` `` 11 | - `obj` `` 12 | 13 | ## Example: 14 | 15 | ```js 16 | const fs = require('fs-extra') 17 | 18 | // With a callback: 19 | fs.readJson('./package.json', (err, packageObj) => { 20 | if (err) console.error(err) 21 | console.log(packageObj.version) // => 0.1.3 22 | }) 23 | 24 | // With Promises: 25 | fs.readJson('./package.json') 26 | .then(packageObj => { 27 | console.log(packageObj.version) // => 0.1.3 28 | }) 29 | .catch(err => { 30 | console.error(err) 31 | }) 32 | 33 | // With async/await: 34 | async function example () { 35 | try { 36 | const packageObj = await fs.readJson('./package.json') 37 | console.log(packageObj.version) // => 0.1.3 38 | } catch (err) { 39 | console.error(err) 40 | } 41 | } 42 | 43 | example() 44 | ``` 45 | 46 | --- 47 | 48 | `readJson()` can take a `throws` option set to `false` and it won't throw if the JSON is invalid. Example: 49 | 50 | ```js 51 | const fs = require('fs-extra') 52 | 53 | const file = '/tmp/some-invalid.json' 54 | const data = '{not valid JSON' 55 | fs.writeFileSync(file, data) 56 | 57 | // With a callback: 58 | fs.readJson(file, { throws: false }, (err, obj) => { 59 | if (err) console.error(err) 60 | console.log(obj) // => null 61 | }) 62 | 63 | // With Promises: 64 | fs.readJson(file, { throws: false }) 65 | .then(obj => { 66 | console.log(obj) // => null 67 | }) 68 | .catch(err => { 69 | console.error(err) // Not called 70 | }) 71 | 72 | // With async/await: 73 | async function example (f) { 74 | const obj = await fs.readJson(f, { throws: false }) 75 | console.log(obj) // => null 76 | } 77 | 78 | example(file) 79 | ``` 80 | -------------------------------------------------------------------------------- /docs/remove-sync.md: -------------------------------------------------------------------------------- 1 | # removeSync(path) 2 | 3 | Removes a file or directory. The directory can have contents. If the path does not exist, silently does nothing. 4 | 5 | - `path` `` 6 | 7 | ## Example: 8 | 9 | ```js 10 | const fs = require('fs-extra') 11 | 12 | // remove file 13 | fs.removeSync('/tmp/myfile') 14 | 15 | fs.removeSync('/home/jprichardson') // I just deleted my entire HOME directory. 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/remove.md: -------------------------------------------------------------------------------- 1 | # remove(path[, callback]) 2 | 3 | Removes a file or directory. The directory can have contents. If the path does not exist, silently does nothing. 4 | 5 | - `path` `` 6 | - `callback` `` 7 | - `err` `` 8 | 9 | ## Example: 10 | 11 | ```js 12 | const fs = require('fs-extra') 13 | 14 | // remove file 15 | // With a callback: 16 | fs.remove('/tmp/myfile', err => { 17 | if (err) return console.error(err) 18 | console.log('success!') 19 | }) 20 | 21 | fs.remove('/home/jprichardson', err => { 22 | if (err) return console.error(err) 23 | console.log('success!') // I just deleted my entire HOME directory. 24 | }) 25 | 26 | // With Promises: 27 | fs.remove('/tmp/myfile') 28 | .then(() => { 29 | console.log('success!') 30 | }) 31 | .catch(err => { 32 | console.error(err) 33 | }) 34 | 35 | // With async/await: 36 | async function example (src, dest) { 37 | try { 38 | await fs.remove('/tmp/myfile') 39 | console.log('success!') 40 | } catch (err) { 41 | console.error(err) 42 | } 43 | } 44 | 45 | example() 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/writeJson-sync.md: -------------------------------------------------------------------------------- 1 | # writeJsonSync(file, object[, options]) 2 | 3 | Writes an object to a JSON file. 4 | 5 | **Alias:** `writeJSONSync()` 6 | 7 | - `file` `` 8 | - `object` `` 9 | - `options` `` 10 | - `spaces` ` | ` Number of spaces to indent; or a string to use for indentation (i.e. pass `'\t'` for tab indentation). See [the docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_space_argument) for more info. 11 | - `EOL` `` Set EOL character. Default is `\n`. 12 | - `replacer` [JSON replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) 13 | - Also accepts [`fs.writeFileSync()` options](https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options) 14 | 15 | ## Example: 16 | 17 | ```js 18 | const fs = require('fs-extra') 19 | 20 | fs.writeJsonSync('./package.json', {name: 'fs-extra'}) 21 | ``` 22 | --- 23 | 24 | **See also:** [`outputJsonSync()`](outputJson-sync.md) 25 | -------------------------------------------------------------------------------- /docs/writeJson.md: -------------------------------------------------------------------------------- 1 | # writeJson(file, object[, options][, callback]) 2 | 3 | Writes an object to a JSON file. 4 | 5 | **Alias:** `writeJSON()` 6 | 7 | - `file` `` 8 | - `object` `` 9 | - `options` `` 10 | - `spaces` ` | ` Number of spaces to indent; or a string to use for indentation (i.e. pass `'\t'` for tab indentation). See [the docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_space_argument) for more info. 11 | - `EOL` `` Set EOL character. Default is `\n`. 12 | - `replacer` [JSON replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) 13 | - Also accepts [`fs.writeFile()` options](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback) 14 | - `callback` `` 15 | - `err` `` 16 | 17 | ## Example: 18 | 19 | ```js 20 | const fs = require('fs-extra') 21 | 22 | // With a callback: 23 | fs.writeJson('./package.json', {name: 'fs-extra'}, err => { 24 | if (err) return console.error(err) 25 | console.log('success!') 26 | }) 27 | 28 | // With Promises: 29 | fs.writeJson('./package.json', {name: 'fs-extra'}) 30 | .then(() => { 31 | console.log('success!') 32 | }) 33 | .catch(err => { 34 | console.error(err) 35 | }) 36 | 37 | // With async/await: 38 | async function example () { 39 | try { 40 | await fs.writeJson('./package.json', {name: 'fs-extra'}) 41 | console.log('success!') 42 | } catch (err) { 43 | console.error(err) 44 | } 45 | } 46 | 47 | example() 48 | ``` 49 | 50 | --- 51 | 52 | **See also:** [`outputJson()`](outputJson.md) 53 | -------------------------------------------------------------------------------- /lib/__tests__/promise.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* eslint-env mocha */ 4 | 5 | const assert = require('assert') 6 | const fse = require('..') 7 | 8 | const methods = [ 9 | 'emptyDir', 10 | 'ensureFile', 11 | 'ensureDir', 12 | 'mkdirs', 13 | 'readJson', 14 | 'readJSON', 15 | 'remove' 16 | ] 17 | 18 | describe('promise support', () => { 19 | methods.forEach(method => { 20 | it(method, done => { 21 | fse[method]().catch(() => done()) 22 | }) 23 | }) 24 | 25 | it('provides fse.promises API', () => { 26 | assert.ok(fse.promises) 27 | assert.strictEqual(typeof fse.promises.writeFile, 'function') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-broken-symlink.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | const { copy } = require('../') 9 | 10 | /* global afterEach, beforeEach, describe, it */ 11 | 12 | describe('copy / broken symlink', () => { 13 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-broken-symlink') 14 | const src = path.join(TEST_DIR, 'src') 15 | const dest = path.join(TEST_DIR, 'dest') 16 | 17 | beforeEach(done => { 18 | fse.emptyDir(TEST_DIR, err => { 19 | assert.ifError(err) 20 | createFixtures(src, done) 21 | }) 22 | }) 23 | 24 | afterEach(done => fse.remove(TEST_DIR, done)) 25 | 26 | describe('when symlink is broken', () => { 27 | it('should not throw error if dereference is false', done => { 28 | copy(src, dest, err => { 29 | assert.strictEqual(err, null) 30 | done() 31 | }) 32 | }) 33 | 34 | it('should throw error if dereference is true', done => { 35 | copy(src, dest, { dereference: true }, err => { 36 | assert.strictEqual(err.code, 'ENOENT') 37 | done() 38 | }) 39 | }) 40 | }) 41 | }) 42 | 43 | function createFixtures (srcDir, callback) { 44 | fs.mkdir(srcDir, err => { 45 | let brokenFile 46 | let brokenFileLink 47 | 48 | if (err) return callback(err) 49 | 50 | try { 51 | brokenFile = path.join(srcDir, 'does-not-exist') 52 | brokenFileLink = path.join(srcDir, 'broken-symlink') 53 | fs.writeFileSync(brokenFile, 'does not matter') 54 | fs.symlinkSync(brokenFile, brokenFileLink, 'file') 55 | } catch (err) { 56 | callback(err) 57 | } 58 | 59 | // break the symlink now 60 | fse.remove(brokenFile, callback) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-case-insensitive-paths.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assert = require('assert') 4 | const os = require('os') 5 | const path = require('path') 6 | const fs = require('../../') 7 | const platform = os.platform() 8 | 9 | /* global beforeEach, afterEach, describe, it */ 10 | 11 | describe('+ copy() - case insensitive paths', () => { 12 | let TEST_DIR = '' 13 | let src = '' 14 | let dest = '' 15 | 16 | beforeEach(done => { 17 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-case-insensitive-paths') 18 | fs.emptyDir(TEST_DIR, done) 19 | }) 20 | 21 | afterEach(done => fs.remove(TEST_DIR, done)) 22 | 23 | describe('> when src is a directory', () => { 24 | it('should behave correctly based on the OS', done => { 25 | src = path.join(TEST_DIR, 'srcdir') 26 | fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data') 27 | dest = path.join(TEST_DIR, 'srcDir') 28 | 29 | fs.copy(src, dest, err => { 30 | if (platform === 'linux') { 31 | assert.ifError(err) 32 | assert(fs.existsSync(dest)) 33 | assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data') 34 | } 35 | if (platform === 'darwin' || platform === 'win32') { 36 | assert.strictEqual(err.message, 'Source and destination must not be the same.') 37 | } 38 | done() 39 | }) 40 | }) 41 | }) 42 | 43 | describe('> when src is a file', () => { 44 | it('should behave correctly based on the OS', done => { 45 | src = path.join(TEST_DIR, 'srcfile') 46 | fs.outputFileSync(src, 'some data') 47 | dest = path.join(TEST_DIR, 'srcFile') 48 | 49 | fs.copy(src, dest, err => { 50 | if (platform === 'linux') { 51 | assert.ifError(err) 52 | assert(fs.existsSync(dest)) 53 | assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data') 54 | } 55 | if (platform === 'darwin' || platform === 'win32') { 56 | assert.strictEqual(err.message, 'Source and destination must not be the same.') 57 | } 58 | done() 59 | }) 60 | }) 61 | }) 62 | 63 | describe('> when src is a symlink', () => { 64 | it('should behave correctly based on the OS, symlink dir', done => { 65 | src = path.join(TEST_DIR, 'srcdir') 66 | fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data') 67 | const srcLink = path.join(TEST_DIR, 'src-symlink') 68 | fs.symlinkSync(src, srcLink, 'dir') 69 | dest = path.join(TEST_DIR, 'src-Symlink') 70 | 71 | fs.copy(srcLink, dest, err => { 72 | if (platform === 'linux') { 73 | assert.ifError(err) 74 | assert(fs.existsSync(dest)) 75 | assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data') 76 | const destLink = fs.readlinkSync(dest) 77 | assert.strictEqual(destLink, src) 78 | } 79 | if (platform === 'darwin' || platform === 'win32') { 80 | assert.strictEqual(err.message, 'Source and destination must not be the same.') 81 | } 82 | done() 83 | }) 84 | }) 85 | 86 | it('should behave correctly based on the OS, symlink file', done => { 87 | src = path.join(TEST_DIR, 'srcfile') 88 | fs.outputFileSync(src, 'some data') 89 | const srcLink = path.join(TEST_DIR, 'src-symlink') 90 | fs.symlinkSync(src, srcLink, 'file') 91 | dest = path.join(TEST_DIR, 'src-Symlink') 92 | 93 | fs.copy(srcLink, dest, err => { 94 | if (platform === 'linux') { 95 | assert.ifError(err) 96 | assert(fs.existsSync(dest)) 97 | assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data') 98 | const destLink = fs.readlinkSync(dest) 99 | assert.strictEqual(destLink, src) 100 | } 101 | if (platform === 'darwin' || platform === 'win32') { 102 | assert.strictEqual(err.message, 'Source and destination must not be the same.') 103 | } 104 | done() 105 | }) 106 | }) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-dev-null.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../../') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | let TEST_DIR = '' 12 | 13 | describe('+ copy() - copy /dev/null', () => { 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'test', 'fs-extra', 'copy-dev-null') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | describe('> when src is /dev/null', () => { 22 | it('should copy successfully', done => { 23 | // no /dev/null on windows 24 | if (process.platform === 'win32') return done() 25 | 26 | const tmpFile = path.join(TEST_DIR, 'foo') 27 | 28 | fse.copy('/dev/null', tmpFile, err => { 29 | assert.ifError(err) 30 | const stats = fs.lstatSync(tmpFile) 31 | assert.strictEqual(stats.size, 0) 32 | done() 33 | }) 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-gh-89.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // relevant: https://github.com/jprichardson/node-fs-extra/issues/89 4 | // come up with better file name 5 | 6 | const fs = require('fs') 7 | const os = require('os') 8 | const fse = require('../../') 9 | const path = require('path') 10 | const assert = require('assert') 11 | 12 | /* global afterEach, beforeEach, describe, it */ 13 | 14 | describe('copy() - gh #89', () => { 15 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-gh-89') 16 | 17 | beforeEach(done => { 18 | fse.emptyDir(TEST_DIR, done) 19 | }) 20 | 21 | afterEach(done => { 22 | fse.remove(TEST_DIR, done) 23 | }) 24 | 25 | it('should copy successfully', done => { 26 | const A = path.join(TEST_DIR, 'A') 27 | const B = path.join(TEST_DIR, 'B') 28 | fs.mkdirSync(A) 29 | fs.mkdirSync(B) 30 | 31 | const one = path.join(A, 'one.txt') 32 | const two = path.join(A, 'two.txt') 33 | const three = path.join(B, 'three.txt') 34 | const four = path.join(B, 'four.txt') 35 | 36 | fs.writeFileSync(one, '1') 37 | fs.writeFileSync(two, '2') 38 | fs.writeFileSync(three, '3') 39 | fs.writeFileSync(four, '4') 40 | 41 | const C = path.join(TEST_DIR, 'C') 42 | fse.copy(A, C, err => { 43 | if (err) return done(err) 44 | 45 | fse.copy(B, C, err => { 46 | if (err) return done(err) 47 | 48 | assert(fs.existsSync(path.join(C, 'one.txt'))) 49 | assert(fs.existsSync(path.join(C, 'two.txt'))) 50 | assert(fs.existsSync(path.join(C, 'three.txt'))) 51 | assert(fs.existsSync(path.join(C, 'four.txt'))) 52 | done() 53 | }) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-permissions.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../../') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global beforeEach, describe, it */ 10 | 11 | describe('copy', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | // pretty UNIX specific, may not pass on windows... only tested on Mac OS X 10.9 20 | it('should maintain file permissions and ownership', done => { 21 | if (process.platform === 'win32') return done() 22 | 23 | // var userid = require('userid') 24 | 25 | // http://man7.org/linux/man-pages/man2/stat.2.html 26 | const S_IFREG = 0o100000 // regular file 27 | const S_IFDIR = 0o40000 // directory 28 | 29 | // these are Mac specific I think (at least staff), should find Linux equivalent 30 | let gidWheel 31 | let gidStaff 32 | 33 | try { 34 | gidWheel = process.getgid() // userid.gid('wheel') 35 | } catch { 36 | gidWheel = process.getgid() 37 | } 38 | 39 | try { 40 | gidStaff = process.getgid() // userid.gid('staff') 41 | } catch { 42 | gidStaff = process.getgid() 43 | } 44 | 45 | const permDir = path.join(TEST_DIR, 'perms') 46 | fs.mkdirSync(permDir) 47 | 48 | const srcDir = path.join(permDir, 'src') 49 | fs.mkdirSync(srcDir) 50 | 51 | const f1 = path.join(srcDir, 'f1.txt') 52 | fs.writeFileSync(f1, '') 53 | fs.chmodSync(f1, 0o666) 54 | fs.chownSync(f1, process.getuid(), gidWheel) 55 | const f1stats = fs.lstatSync(f1) 56 | assert.strictEqual(f1stats.mode - S_IFREG, 0o666) 57 | 58 | const d1 = path.join(srcDir, 'somedir') 59 | fs.mkdirSync(d1) 60 | fs.chmodSync(d1, 0o777) 61 | fs.chownSync(d1, process.getuid(), gidStaff) 62 | const d1stats = fs.lstatSync(d1) 63 | assert.strictEqual(d1stats.mode - S_IFDIR, 0o777) 64 | 65 | const f2 = path.join(d1, 'f2.bin') 66 | fs.writeFileSync(f2, '') 67 | fs.chmodSync(f2, 0o777) 68 | fs.chownSync(f2, process.getuid(), gidStaff) 69 | const f2stats = fs.lstatSync(f2) 70 | assert.strictEqual(f2stats.mode - S_IFREG, 0o777) 71 | 72 | const d2 = path.join(srcDir, 'crazydir') 73 | fs.mkdirSync(d2) 74 | fs.chmodSync(d2, 0o444) 75 | fs.chownSync(d2, process.getuid(), gidWheel) 76 | const d2stats = fs.lstatSync(d2) 77 | assert.strictEqual(d2stats.mode - S_IFDIR, 0o444) 78 | 79 | const destDir = path.join(permDir, 'dest') 80 | fse.copy(srcDir, destDir, err => { 81 | assert.ifError(err) 82 | 83 | const newf1stats = fs.lstatSync(path.join(permDir, 'dest/f1.txt')) 84 | const newd1stats = fs.lstatSync(path.join(permDir, 'dest/somedir')) 85 | const newf2stats = fs.lstatSync(path.join(permDir, 'dest/somedir/f2.bin')) 86 | const newd2stats = fs.lstatSync(path.join(permDir, 'dest/crazydir')) 87 | 88 | assert.strictEqual(newf1stats.mode, f1stats.mode) 89 | assert.strictEqual(newd1stats.mode, d1stats.mode) 90 | assert.strictEqual(newf2stats.mode, f2stats.mode) 91 | assert.strictEqual(newd2stats.mode, d2stats.mode) 92 | 93 | assert.strictEqual(newf1stats.gid, f1stats.gid) 94 | assert.strictEqual(newd1stats.gid, d1stats.gid) 95 | assert.strictEqual(newf2stats.gid, f2stats.gid) 96 | assert.strictEqual(newd2stats.gid, d2stats.gid) 97 | 98 | assert.strictEqual(newf1stats.uid, f1stats.uid) 99 | assert.strictEqual(newd1stats.uid, d1stats.uid) 100 | assert.strictEqual(newf2stats.uid, f2stats.uid) 101 | assert.strictEqual(newd2stats.uid, d2stats.uid) 102 | 103 | done() 104 | }) 105 | }) 106 | }) 107 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-preserve-timestamp.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../../') 4 | const os = require('os') 5 | const path = require('path') 6 | const { copy } = require('../') 7 | const utimesSync = require('../../util/utimes').utimesMillisSync 8 | const assert = require('assert') 9 | 10 | /* global beforeEach, afterEach, describe, it */ 11 | 12 | if (process.arch === 'ia32') console.warn('32 bit arch; skipping copy timestamp tests') 13 | 14 | const describeIfPractical = process.arch === 'ia32' ? describe.skip : describe 15 | 16 | describeIfPractical('copy() - preserve timestamp', () => { 17 | let TEST_DIR, SRC, DEST, FILES 18 | 19 | function setupFixture (readonly) { 20 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-preserve-timestamp') 21 | SRC = path.join(TEST_DIR, 'src') 22 | DEST = path.join(TEST_DIR, 'dest') 23 | FILES = ['a-file', path.join('a-folder', 'another-file'), path.join('a-folder', 'another-folder', 'file3')] 24 | const timestamp = Date.now() / 1000 - 5 25 | FILES.forEach(f => { 26 | const filePath = path.join(SRC, f) 27 | fs.ensureFileSync(filePath) 28 | // rewind timestamps to make sure that coarser OS timestamp resolution 29 | // does not alter results 30 | utimesSync(filePath, timestamp, timestamp) 31 | if (readonly) { 32 | fs.chmodSync(filePath, 0o444) 33 | } 34 | }) 35 | } 36 | 37 | afterEach(done => fs.remove(TEST_DIR, done)) 38 | 39 | describe('> when preserveTimestamps option is true', () => { 40 | ;[ 41 | { subcase: 'writable', readonly: false }, 42 | { subcase: 'readonly', readonly: true } 43 | ].forEach(params => { 44 | describe(`>> with ${params.subcase} source files`, () => { 45 | beforeEach(() => setupFixture(params.readonly)) 46 | 47 | it('should have the same timestamps on copy', done => { 48 | copy(SRC, DEST, { preserveTimestamps: true }, (err) => { 49 | if (err) return done(err) 50 | FILES.forEach(testFile({ preserveTimestamps: true })) 51 | done() 52 | }) 53 | }) 54 | }) 55 | }) 56 | }) 57 | 58 | function testFile (options) { 59 | return function (file) { 60 | const a = path.join(SRC, file) 61 | const b = path.join(DEST, file) 62 | const fromStat = fs.statSync(a) 63 | const toStat = fs.statSync(b) 64 | if (options.preserveTimestamps) { 65 | // Windows sub-second precision fixed: https://github.com/nodejs/io.js/issues/2069 66 | assert.strictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime(), 'different mtime values') 67 | assert.strictEqual(toStat.atime.getTime(), fromStat.atime.getTime(), 'different atime values') 68 | } else { 69 | // the access time might actually be the same, so check only modification time 70 | assert.notStrictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime(), 'same mtime values') 71 | } 72 | } 73 | } 74 | }) 75 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-readonly-dir.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // relevant: https://github.com/jprichardson/node-fs-extra/issues/599 4 | 5 | const fs = require('../../') 6 | const os = require('os') 7 | const fse = require('../../') 8 | const path = require('path') 9 | const assert = require('assert') 10 | const klawSync = require('klaw-sync') 11 | 12 | /* global afterEach, beforeEach, describe, it */ 13 | 14 | let TEST_DIR = '' 15 | 16 | const FILES = [ 17 | path.join('dir1', 'file1.txt'), 18 | path.join('dir1', 'dir2', 'file2.txt'), 19 | path.join('dir1', 'dir2', 'dir3', 'file3.txt') 20 | ] 21 | 22 | describe('+ copy() - copy a readonly directory with content', () => { 23 | beforeEach(done => { 24 | TEST_DIR = path.join(os.tmpdir(), 'test', 'fs-extra', 'copy-readonly-dir') 25 | fse.emptyDir(TEST_DIR, done) 26 | }) 27 | 28 | afterEach(done => { 29 | klawSync(TEST_DIR).forEach(data => fs.chmodSync(data.path, 0o777)) 30 | fse.remove(TEST_DIR, done) 31 | }) 32 | 33 | describe('> when src is readonly directory with content', () => { 34 | it('should copy successfully', done => { 35 | FILES.forEach(file => { 36 | fs.outputFileSync(path.join(TEST_DIR, file), file) 37 | }) 38 | 39 | const sourceDir = path.join(TEST_DIR, 'dir1') 40 | const sourceHierarchy = klawSync(sourceDir) 41 | sourceHierarchy.forEach(source => fs.chmodSync(source.path, source.stats.isDirectory() ? 0o555 : 0o444)) 42 | 43 | const targetDir = path.join(TEST_DIR, 'target') 44 | fse.copy(sourceDir, targetDir, err => { 45 | assert.ifError(err) 46 | 47 | // Make sure copy was made and mode was preserved 48 | assert(fs.existsSync(targetDir)) 49 | const targetHierarchy = klawSync(targetDir) 50 | assert(targetHierarchy.length === sourceHierarchy.length) 51 | targetHierarchy.forEach(target => assert(target.stats.mode === target.stats.isDirectory() ? 0o555 : 0o444)) 52 | done() 53 | }) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-sync-broken-symlink.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | const copySync = require('../copy-sync') 9 | 10 | /* global afterEach, beforeEach, describe, it */ 11 | 12 | describe('copy-sync / broken symlink', () => { 13 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-broken-symlink') 14 | const src = path.join(TEST_DIR, 'src') 15 | const dest = path.join(TEST_DIR, 'dest') 16 | 17 | beforeEach(done => { 18 | fse.emptyDir(TEST_DIR, err => { 19 | assert.ifError(err) 20 | createFixtures(src, done) 21 | }) 22 | }) 23 | 24 | afterEach(done => fse.remove(TEST_DIR, done)) 25 | 26 | describe('when symlink is broken', () => { 27 | it('should not throw error if dereference is false', () => { 28 | let err = null 29 | try { 30 | copySync(src, dest) 31 | } catch (e) { 32 | err = e 33 | } 34 | assert.strictEqual(err, null) 35 | }) 36 | 37 | it('should throw error if dereference is true', () => { 38 | assert.throws(() => copySync(src, dest, { dereference: true }), err => err.code === 'ENOENT') 39 | }) 40 | }) 41 | }) 42 | 43 | function createFixtures (srcDir, callback) { 44 | fs.mkdir(srcDir, err => { 45 | let brokenFile 46 | let brokenFileLink 47 | 48 | if (err) return callback(err) 49 | 50 | try { 51 | brokenFile = path.join(srcDir, 'does-not-exist') 52 | brokenFileLink = path.join(srcDir, 'broken-symlink') 53 | fs.writeFileSync(brokenFile, 'does not matter') 54 | fs.symlinkSync(brokenFile, brokenFileLink, 'file') 55 | } catch (err) { 56 | callback(err) 57 | } 58 | 59 | // break the symlink now 60 | fse.remove(brokenFile, callback) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-sync-case-insensitive-paths.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assert = require('assert') 4 | const os = require('os') 5 | const path = require('path') 6 | const fs = require('../../') 7 | const platform = os.platform() 8 | 9 | /* global beforeEach, afterEach, describe, it */ 10 | 11 | describe('+ copySync() - case insensitive paths', () => { 12 | let TEST_DIR = '' 13 | let src = '' 14 | let dest = '' 15 | 16 | beforeEach(done => { 17 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-case-insensitive-paths') 18 | fs.emptyDir(TEST_DIR, done) 19 | }) 20 | 21 | afterEach(() => fs.removeSync(TEST_DIR)) 22 | 23 | describe('> when src is a directory', () => { 24 | it('should behave correctly based on the OS', () => { 25 | src = path.join(TEST_DIR, 'srcdir') 26 | fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data') 27 | dest = path.join(TEST_DIR, 'srcDir') 28 | let errThrown = false 29 | 30 | try { 31 | fs.copySync(src, dest) 32 | } catch (err) { 33 | if (platform === 'darwin' || platform === 'win32') { 34 | assert.strictEqual(err.message, 'Source and destination must not be the same.') 35 | errThrown = true 36 | } 37 | } 38 | if (platform === 'darwin' || platform === 'win32') assert(errThrown) 39 | if (platform === 'linux') { 40 | assert(fs.existsSync(dest)) 41 | assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data') 42 | assert(!errThrown) 43 | } 44 | }) 45 | }) 46 | 47 | describe('> when src is a file', () => { 48 | it('should behave correctly based on the OS', () => { 49 | src = path.join(TEST_DIR, 'srcfile') 50 | fs.outputFileSync(src, 'some data') 51 | dest = path.join(TEST_DIR, 'srcFile') 52 | let errThrown = false 53 | 54 | try { 55 | fs.copySync(src, dest) 56 | } catch (err) { 57 | if (platform === 'darwin' || platform === 'win32') { 58 | assert.strictEqual(err.message, 'Source and destination must not be the same.') 59 | errThrown = true 60 | } 61 | } 62 | if (platform === 'darwin' || platform === 'win32') assert(errThrown) 63 | if (platform === 'linux') { 64 | assert(fs.existsSync(dest)) 65 | assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data') 66 | assert(!errThrown) 67 | } 68 | }) 69 | }) 70 | 71 | describe('> when src is a symlink', () => { 72 | it('should behave correctly based on the OS, symlink dir', () => { 73 | src = path.join(TEST_DIR, 'srcdir') 74 | fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data') 75 | const srcLink = path.join(TEST_DIR, 'src-symlink') 76 | fs.symlinkSync(src, srcLink, 'dir') 77 | dest = path.join(TEST_DIR, 'src-Symlink') 78 | let errThrown = false 79 | 80 | try { 81 | fs.copySync(srcLink, dest) 82 | } catch (err) { 83 | if (platform === 'darwin' || platform === 'win32') { 84 | assert.strictEqual(err.message, 'Source and destination must not be the same.') 85 | errThrown = true 86 | } 87 | } 88 | if (platform === 'darwin' || platform === 'win32') assert(errThrown) 89 | if (platform === 'linux') { 90 | assert(fs.existsSync(dest)) 91 | assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data') 92 | const destLink = fs.readlinkSync(dest) 93 | assert.strictEqual(destLink, src) 94 | assert(!errThrown) 95 | } 96 | }) 97 | 98 | it('should behave correctly based on the OS, symlink file', () => { 99 | src = path.join(TEST_DIR, 'srcfile') 100 | fs.outputFileSync(src, 'some data') 101 | const srcLink = path.join(TEST_DIR, 'src-symlink') 102 | fs.symlinkSync(src, srcLink, 'file') 103 | dest = path.join(TEST_DIR, 'src-Symlink') 104 | let errThrown = false 105 | 106 | try { 107 | fs.copySync(srcLink, dest) 108 | } catch (err) { 109 | if (platform === 'darwin' || platform === 'win32') { 110 | assert.strictEqual(err.message, 'Source and destination must not be the same.') 111 | errThrown = true 112 | } 113 | } 114 | if (platform === 'darwin' || platform === 'win32') assert(errThrown) 115 | if (platform === 'linux') { 116 | assert(fs.existsSync(dest)) 117 | assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data') 118 | const destLink = fs.readlinkSync(dest) 119 | assert.strictEqual(destLink, src) 120 | assert(!errThrown) 121 | } 122 | }) 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-sync-preserve-timestamp.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../../') 4 | const os = require('os') 5 | const path = require('path') 6 | const copySync = require('../copy-sync') 7 | const utimesSync = require('../../util/utimes').utimesMillisSync 8 | const assert = require('assert') 9 | 10 | /* global beforeEach, afterEach, describe, it */ 11 | 12 | if (process.arch === 'ia32') console.warn('32 bit arch; skipping copySync timestamp tests') 13 | 14 | const describeIfPractical = process.arch === 'ia32' ? describe.skip : describe 15 | 16 | describeIfPractical('copySync() - preserveTimestamps option', () => { 17 | let TEST_DIR, SRC, DEST, FILES 18 | 19 | function setupFixture (readonly) { 20 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-preserve-timestamp') 21 | SRC = path.join(TEST_DIR, 'src') 22 | DEST = path.join(TEST_DIR, 'dest') 23 | FILES = ['a-file', path.join('a-folder', 'another-file'), path.join('a-folder', 'another-folder', 'file3')] 24 | const timestamp = Date.now() / 1000 - 5 25 | FILES.forEach(f => { 26 | const filePath = path.join(SRC, f) 27 | fs.ensureFileSync(filePath) 28 | // rewind timestamps to make sure that coarser OS timestamp resolution 29 | // does not alter results 30 | utimesSync(filePath, timestamp, timestamp) 31 | if (readonly) { 32 | fs.chmodSync(filePath, 0o444) 33 | } 34 | }) 35 | } 36 | 37 | afterEach(done => fs.remove(TEST_DIR, done)) 38 | 39 | describe('> when preserveTimestamps option is true', () => { 40 | ;[ 41 | { subcase: 'writable', readonly: false }, 42 | { subcase: 'readonly', readonly: true } 43 | ].forEach(params => { 44 | describe(`>> with ${params.subcase} source files`, () => { 45 | beforeEach(() => setupFixture(params.readonly)) 46 | 47 | it('should have the same timestamps on copy', () => { 48 | copySync(SRC, DEST, { preserveTimestamps: true }) 49 | FILES.forEach(testFile({ preserveTimestamps: true })) 50 | }) 51 | }) 52 | }) 53 | }) 54 | 55 | function testFile (options) { 56 | return function (file) { 57 | const a = path.join(SRC, file) 58 | const b = path.join(DEST, file) 59 | const fromStat = fs.statSync(a) 60 | const toStat = fs.statSync(b) 61 | if (options.preserveTimestamps) { 62 | // Windows sub-second precision fixed: https://github.com/nodejs/io.js/issues/2069 63 | assert.strictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime(), 'different mtime values') 64 | assert.strictEqual(toStat.atime.getTime(), fromStat.atime.getTime(), 'different atime values') 65 | } else { 66 | // the access time might actually be the same, so check only modification time 67 | assert.notStrictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime(), 'same mtime values') 68 | } 69 | } 70 | } 71 | }) 72 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-sync-readonly-dir.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // relevant: https://github.com/jprichardson/node-fs-extra/issues/599 4 | 5 | const os = require('os') 6 | const fs = require('../../') 7 | const path = require('path') 8 | const assert = require('assert') 9 | const klawSync = require('klaw-sync') 10 | 11 | /* global afterEach, beforeEach, describe, it */ 12 | 13 | let TEST_DIR = '' 14 | 15 | const FILES = [ 16 | path.join('dir1', 'file1.txt'), 17 | path.join('dir1', 'dir2', 'file2.txt'), 18 | path.join('dir1', 'dir2', 'dir3', 'file3.txt') 19 | ] 20 | 21 | describe('+ copySync() - copy a readonly directory with content', () => { 22 | beforeEach(done => { 23 | TEST_DIR = path.join(os.tmpdir(), 'test', 'fs-extra', 'copy-readonly-dir') 24 | fs.emptyDir(TEST_DIR, done) 25 | }) 26 | 27 | afterEach(done => { 28 | klawSync(TEST_DIR).forEach(data => fs.chmodSync(data.path, 0o777)) 29 | fs.remove(TEST_DIR, done) 30 | }) 31 | 32 | describe('> when src is readonly directory with content', () => { 33 | it('should copy successfully', () => { 34 | FILES.forEach(file => { 35 | fs.outputFileSync(path.join(TEST_DIR, file), file) 36 | }) 37 | const sourceDir = path.join(TEST_DIR, 'dir1') 38 | const sourceHierarchy = klawSync(sourceDir) 39 | sourceHierarchy.forEach(source => fs.chmodSync(source.path, source.stats.isDirectory() ? 0o555 : 0o444)) 40 | 41 | const targetDir = path.join(TEST_DIR, 'target') 42 | fs.copySync(sourceDir, targetDir) 43 | 44 | // Make sure copy was made and mode was preserved 45 | assert(fs.existsSync(targetDir)) 46 | const targetHierarchy = klawSync(targetDir) 47 | assert(targetHierarchy.length === sourceHierarchy.length) 48 | targetHierarchy.forEach(target => assert(target.stats.mode === target.stats.isDirectory() ? 0o555 : 0o444)) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /lib/copy/__tests__/copy-sync-symlink.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const os = require('os') 4 | const fs = require('../..') 5 | const path = require('path') 6 | const assert = require('assert') 7 | const copySync = require('../copy-sync') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('copy-sync / symlink', () => { 12 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-symlinks') 13 | const src = path.join(TEST_DIR, 'src') 14 | const out = path.join(TEST_DIR, 'out') 15 | 16 | beforeEach(done => { 17 | fs.emptyDir(TEST_DIR, err => { 18 | assert.ifError(err) 19 | createFixtures(src, done) 20 | }) 21 | }) 22 | 23 | afterEach(done => { 24 | fs.remove(TEST_DIR, done) 25 | }) 26 | 27 | it('copies symlinks by default', () => { 28 | assert.doesNotThrow(() => { 29 | copySync(src, out) 30 | }) 31 | 32 | assert.strictEqual(fs.readlinkSync(path.join(out, 'file-symlink')), path.join(src, 'foo')) 33 | assert.strictEqual(fs.readlinkSync(path.join(out, 'dir-symlink')), path.join(src, 'dir')) 34 | }) 35 | 36 | it('copies file contents when dereference=true', () => { 37 | try { 38 | copySync(src, out, { dereference: true }) 39 | } catch (err) { 40 | assert.ifError(err) 41 | } 42 | 43 | const fileSymlinkPath = path.join(out, 'file-symlink') 44 | assert.ok(fs.lstatSync(fileSymlinkPath).isFile()) 45 | assert.strictEqual(fs.readFileSync(fileSymlinkPath, 'utf8'), 'foo contents') 46 | 47 | const dirSymlinkPath = path.join(out, 'dir-symlink') 48 | assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory()) 49 | assert.deepStrictEqual(fs.readdirSync(dirSymlinkPath), ['bar']) 50 | }) 51 | }) 52 | 53 | function createFixtures (srcDir, callback) { 54 | fs.mkdir(srcDir, err => { 55 | if (err) return callback(err) 56 | 57 | // note: third parameter in symlinkSync is type e.g. 'file' or 'dir' 58 | // https://nodejs.org/api/fs.html#fs_fs_symlink_srcpath_dstpath_type_callback 59 | try { 60 | const fooFile = path.join(srcDir, 'foo') 61 | const fooFileLink = path.join(srcDir, 'file-symlink') 62 | fs.writeFileSync(fooFile, 'foo contents') 63 | fs.symlinkSync(fooFile, fooFileLink, 'file') 64 | 65 | const dir = path.join(srcDir, 'dir') 66 | const dirFile = path.join(dir, 'bar') 67 | const dirLink = path.join(srcDir, 'dir-symlink') 68 | fs.mkdirSync(dir) 69 | fs.writeFileSync(dirFile, 'bar contents') 70 | fs.symlinkSync(dir, dirLink, 'dir') 71 | } catch (err) { 72 | callback(err) 73 | } 74 | 75 | callback() 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/README.md: -------------------------------------------------------------------------------- 1 | These tests came from: https://github.com/AvianFlu/ncp/tree/v1.0.1/test -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/broken-symlink.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../../..') 6 | const { copy: ncp } = require('../../') 7 | const path = require('path') 8 | const assert = require('assert') 9 | 10 | /* global afterEach, beforeEach, describe, it */ 11 | 12 | describe('ncp broken symlink', () => { 13 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-broken-symlinks') 14 | const src = path.join(TEST_DIR, 'src') 15 | const out = path.join(TEST_DIR, 'out') 16 | 17 | beforeEach(done => { 18 | fse.emptyDir(TEST_DIR, err => { 19 | assert.ifError(err) 20 | createFixtures(src, done) 21 | }) 22 | }) 23 | 24 | afterEach(done => fse.remove(TEST_DIR, done)) 25 | 26 | it('should not error if symlink is broken', done => { 27 | ncp(src, out, err => { 28 | assert.strictEqual(err, null) 29 | done() 30 | }) 31 | }) 32 | 33 | it('should return an error if symlink is broken and dereference=true', done => { 34 | ncp(src, out, { dereference: true }, err => { 35 | assert.strictEqual(err.code, 'ENOENT') 36 | done() 37 | }) 38 | }) 39 | }) 40 | 41 | function createFixtures (srcDir, callback) { 42 | fs.mkdir(srcDir, err => { 43 | let brokenFile 44 | let brokenFileLink 45 | 46 | if (err) return callback(err) 47 | 48 | try { 49 | brokenFile = path.join(srcDir, 'does-not-exist') 50 | brokenFileLink = path.join(srcDir, 'broken-symlink') 51 | fs.writeFileSync(brokenFile, 'does not matter') 52 | fs.symlinkSync(brokenFile, brokenFileLink, 'file') 53 | } catch (err) { 54 | callback(err) 55 | } 56 | 57 | // break the symlink now 58 | fse.remove(brokenFile, callback) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/modified-files/out/a: -------------------------------------------------------------------------------- 1 | test2 -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/modified-files/src/a: -------------------------------------------------------------------------------- 1 | test3 -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a: -------------------------------------------------------------------------------- 1 | Hello world 2 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b: -------------------------------------------------------------------------------- 1 | Hello ncp 2 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/c -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/d -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/e -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/f -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a: -------------------------------------------------------------------------------- 1 | Hello nodejitsu 2 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/b -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a: -------------------------------------------------------------------------------- 1 | Hello world 2 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b: -------------------------------------------------------------------------------- 1 | Hello ncp 2 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/c -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/d -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/e -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/f -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a: -------------------------------------------------------------------------------- 1 | Hello nodejitsu 2 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprichardson/node-fs-extra/176ad6a6dbdec503c2d79fa70d17df32dff49972/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/b -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/ncp-error-perm.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // file in reference: https://github.com/jprichardson/node-fs-extra/issues/56 4 | 5 | const fs = require('fs') 6 | const os = require('os') 7 | const fse = require('../../..') 8 | const { copy: ncp } = require('../../') 9 | const path = require('path') 10 | const assert = require('assert') 11 | 12 | /* global afterEach, beforeEach, describe, it */ 13 | 14 | // skip test for windows 15 | // eslint-disable globalReturn */ 16 | // if (os.platform().indexOf('win') === 0) return 17 | // eslint-enable globalReturn */ 18 | 19 | describe('ncp / error / dest-permission', () => { 20 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-error-dest-perm') 21 | const src = path.join(TEST_DIR, 'src') 22 | const dest = path.join(TEST_DIR, 'dest') 23 | 24 | // when we are root, then we will be able to create the subdirectory even if 25 | // we don't have the permissions to do so, so no point in running this test 26 | if (os.platform().indexOf('win') === 0 || os.userInfo().uid === 0) return 27 | 28 | beforeEach(done => { 29 | fse.emptyDir(TEST_DIR, err => { 30 | assert.ifError(err) 31 | done() 32 | }) 33 | }) 34 | 35 | afterEach(done => fse.remove(TEST_DIR, done)) 36 | 37 | it('should return an error', done => { 38 | const someFile = path.join(src, 'some-file') 39 | fse.outputFileSync(someFile, 'hello') 40 | 41 | fse.mkdirsSync(dest) 42 | fs.chmodSync(dest, 0o444) 43 | 44 | const subdest = path.join(dest, 'another-dir') 45 | 46 | ncp(src, subdest, err => { 47 | assert(err) 48 | assert.strictEqual(err.code, 'EACCES') 49 | done() 50 | }) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/ncp.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const { copy: ncp } = require('../../') 5 | const path = require('path') 6 | const rimraf = require('rimraf') 7 | const assert = require('assert') 8 | const readDirFiles = require('read-dir-files').read // temporary, will remove 9 | 10 | /* eslint-env mocha */ 11 | 12 | const fixturesDir = path.join(__dirname, 'fixtures') 13 | 14 | describe('ncp', () => { 15 | describe('regular files and directories', () => { 16 | const fixtures = path.join(fixturesDir, 'regular-fixtures') 17 | const src = path.join(fixtures, 'src') 18 | const out = path.join(fixtures, 'out') 19 | 20 | before(cb => rimraf(out, () => ncp(src, out, cb))) 21 | 22 | describe('when copying a directory of files', () => { 23 | it('files are copied correctly', cb => { 24 | readDirFiles(src, 'utf8', (srcErr, srcFiles) => { 25 | readDirFiles(out, 'utf8', (outErr, outFiles) => { 26 | assert.ifError(srcErr) 27 | assert.deepStrictEqual(srcFiles, outFiles) 28 | cb() 29 | }) 30 | }) 31 | }) 32 | }) 33 | 34 | describe('when copying files using filter', () => { 35 | before(cb => { 36 | const filter = name => name.slice(-1) !== 'a' 37 | 38 | rimraf(out, () => ncp(src, out, { filter }, cb)) 39 | }) 40 | 41 | it('files are copied correctly', cb => { 42 | readDirFiles(src, 'utf8', (srcErr, srcFiles) => { 43 | function filter (files) { 44 | for (const fileName in files) { 45 | const curFile = files[fileName] 46 | if (curFile instanceof Object) { 47 | filter(curFile) 48 | } else if (fileName.slice(-1) === 'a') { 49 | delete files[fileName] 50 | } 51 | } 52 | } 53 | filter(srcFiles) 54 | readDirFiles(out, 'utf8', (outErr, outFiles) => { 55 | assert.ifError(outErr) 56 | assert.deepStrictEqual(srcFiles, outFiles) 57 | cb() 58 | }) 59 | }) 60 | }) 61 | }) 62 | 63 | describe('when using overwrite=true', () => { 64 | before(function () { 65 | this.originalCreateReadStream = fs.createReadStream 66 | }) 67 | 68 | after(function () { 69 | fs.createReadStream = this.originalCreateReadStream 70 | }) 71 | 72 | it('the copy is complete after callback', done => { 73 | ncp(src, out, { overwrite: true }, err => { 74 | fs.createReadStream = () => done(new Error('createReadStream after callback')) 75 | 76 | assert.ifError(err) 77 | process.nextTick(done) 78 | }) 79 | }) 80 | }) 81 | 82 | describe('when using overwrite=false', () => { 83 | beforeEach(done => rimraf(out, done)) 84 | 85 | it('works', cb => { 86 | ncp(src, out, { overwrite: false }, err => { 87 | assert.ifError(err) 88 | cb() 89 | }) 90 | }) 91 | 92 | it('should not error if files exist', cb => { 93 | ncp(src, out, () => { 94 | ncp(src, out, { overwrite: false }, err => { 95 | assert.ifError(err) 96 | cb() 97 | }) 98 | }) 99 | }) 100 | 101 | it('should error if errorOnExist and file exists', cb => { 102 | ncp(src, out, () => { 103 | ncp(src, out, { 104 | overwrite: false, 105 | errorOnExist: true 106 | }, err => { 107 | assert(err) 108 | cb() 109 | }) 110 | }) 111 | }) 112 | }) 113 | 114 | describe('clobber', () => { 115 | beforeEach(done => rimraf(out, done)) 116 | 117 | it('is an alias for overwrite', cb => { 118 | ncp(src, out, () => { 119 | ncp(src, out, { 120 | clobber: false, 121 | errorOnExist: true 122 | }, err => { 123 | assert(err) 124 | cb() 125 | }) 126 | }) 127 | }) 128 | }) 129 | 130 | describe('when using transform', () => { 131 | it('file descriptors are passed correctly', cb => { 132 | ncp(src, out, { 133 | transform: (read, write, file) => { 134 | assert.notStrictEqual(file.name, undefined) 135 | assert.strictEqual(typeof file.mode, 'number') 136 | read.pipe(write) 137 | } 138 | }, cb) 139 | }) 140 | }) 141 | }) 142 | 143 | // see https://github.com/AvianFlu/ncp/issues/71 144 | describe('Issue 71: Odd Async Behaviors', () => { 145 | const fixtures = path.join(__dirname, 'fixtures', 'regular-fixtures') 146 | const src = path.join(fixtures, 'src') 147 | const out = path.join(fixtures, 'out') 148 | 149 | let totalCallbacks = 0 150 | 151 | function copyAssertAndCount (callback) { 152 | // rimraf(out, function() { 153 | ncp(src, out, err => { 154 | assert(!err) 155 | totalCallbacks += 1 156 | readDirFiles(src, 'utf8', (srcErr, srcFiles) => { 157 | readDirFiles(out, 'utf8', (outErr, outFiles) => { 158 | assert.ifError(srcErr) 159 | assert.deepStrictEqual(srcFiles, outFiles) 160 | callback() 161 | }) 162 | }) 163 | }) 164 | // }) 165 | } 166 | 167 | describe('when copying a directory of files without cleaning the destination', () => { 168 | it('callback fires once per run and directories are equal', done => { 169 | const expected = 10 170 | let count = 10 171 | 172 | function next () { 173 | if (count > 0) { 174 | setTimeout(() => { 175 | copyAssertAndCount(() => { 176 | count -= 1 177 | next() 178 | }) 179 | }, 100) 180 | } else { 181 | // console.log('Total callback count is', totalCallbacks) 182 | assert.strictEqual(totalCallbacks, expected) 183 | done() 184 | } 185 | } 186 | 187 | next() 188 | }) 189 | }) 190 | }) 191 | }) 192 | -------------------------------------------------------------------------------- /lib/copy/__tests__/ncp/symlink.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../../..') 6 | const { copy: ncp } = require('../../') 7 | const path = require('path') 8 | const assert = require('assert') 9 | 10 | /* global afterEach, beforeEach, describe, it */ 11 | 12 | describe('ncp / symlink', () => { 13 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-symlinks') 14 | const src = path.join(TEST_DIR, 'src') 15 | const out = path.join(TEST_DIR, 'out') 16 | 17 | beforeEach(done => { 18 | fse.emptyDir(TEST_DIR, err => { 19 | assert.ifError(err) 20 | createFixtures(src, done) 21 | }) 22 | }) 23 | 24 | afterEach(done => fse.remove(TEST_DIR, done)) 25 | 26 | it('copies symlinks by default', done => { 27 | ncp(src, out, err => { 28 | assert.ifError(err) 29 | 30 | assert.strictEqual(fs.readlinkSync(path.join(out, 'file-symlink')), path.join(src, 'foo')) 31 | assert.strictEqual(fs.readlinkSync(path.join(out, 'dir-symlink')), path.join(src, 'dir')) 32 | 33 | done() 34 | }) 35 | }) 36 | 37 | it('copies file contents when dereference=true', done => { 38 | ncp(src, out, { dereference: true }, err => { 39 | assert.ifError(err) 40 | 41 | const fileSymlinkPath = path.join(out, 'file-symlink') 42 | assert.ok(fs.lstatSync(fileSymlinkPath).isFile()) 43 | assert.strictEqual(fs.readFileSync(fileSymlinkPath, 'utf8'), 'foo contents') 44 | 45 | const dirSymlinkPath = path.join(out, 'dir-symlink') 46 | assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory()) 47 | assert.deepStrictEqual(fs.readdirSync(dirSymlinkPath), ['bar']) 48 | 49 | done() 50 | }) 51 | }) 52 | }) 53 | 54 | function createFixtures (srcDir, callback) { 55 | fs.mkdir(srcDir, err => { 56 | if (err) return callback(err) 57 | 58 | // note: third parameter in symlinkSync is type e.g. 'file' or 'dir' 59 | // https://nodejs.org/api/fs.html#fs_fs_symlink_srcpath_dstpath_type_callback 60 | try { 61 | const fooFile = path.join(srcDir, 'foo') 62 | const fooFileLink = path.join(srcDir, 'file-symlink') 63 | fs.writeFileSync(fooFile, 'foo contents') 64 | fs.symlinkSync(fooFile, fooFileLink, 'file') 65 | 66 | const dir = path.join(srcDir, 'dir') 67 | const dirFile = path.join(dir, 'bar') 68 | const dirLink = path.join(srcDir, 'dir-symlink') 69 | fs.mkdirSync(dir) 70 | fs.writeFileSync(dirFile, 'bar contents') 71 | fs.symlinkSync(dir, dirLink, 'dir') 72 | } catch (err) { 73 | callback(err) 74 | } 75 | 76 | callback() 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /lib/copy/copy-sync.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('graceful-fs') 4 | const path = require('path') 5 | const mkdirsSync = require('../mkdirs').mkdirsSync 6 | const utimesMillisSync = require('../util/utimes').utimesMillisSync 7 | const stat = require('../util/stat') 8 | 9 | function copySync (src, dest, opts) { 10 | if (typeof opts === 'function') { 11 | opts = { filter: opts } 12 | } 13 | 14 | opts = opts || {} 15 | opts.clobber = 'clobber' in opts ? !!opts.clobber : true // default to true for now 16 | opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber // overwrite falls back to clobber 17 | 18 | // Warn about using preserveTimestamps on 32-bit node 19 | if (opts.preserveTimestamps && process.arch === 'ia32') { 20 | process.emitWarning( 21 | 'Using the preserveTimestamps option in 32-bit node is not recommended;\n\n' + 22 | '\tsee https://github.com/jprichardson/node-fs-extra/issues/269', 23 | 'Warning', 'fs-extra-WARN0002' 24 | ) 25 | } 26 | 27 | const { srcStat, destStat } = stat.checkPathsSync(src, dest, 'copy', opts) 28 | stat.checkParentPathsSync(src, srcStat, dest, 'copy') 29 | if (opts.filter && !opts.filter(src, dest)) return 30 | const destParent = path.dirname(dest) 31 | if (!fs.existsSync(destParent)) mkdirsSync(destParent) 32 | return getStats(destStat, src, dest, opts) 33 | } 34 | 35 | function getStats (destStat, src, dest, opts) { 36 | const statSync = opts.dereference ? fs.statSync : fs.lstatSync 37 | const srcStat = statSync(src) 38 | 39 | if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts) 40 | else if (srcStat.isFile() || 41 | srcStat.isCharacterDevice() || 42 | srcStat.isBlockDevice()) return onFile(srcStat, destStat, src, dest, opts) 43 | else if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts) 44 | else if (srcStat.isSocket()) throw new Error(`Cannot copy a socket file: ${src}`) 45 | else if (srcStat.isFIFO()) throw new Error(`Cannot copy a FIFO pipe: ${src}`) 46 | throw new Error(`Unknown file: ${src}`) 47 | } 48 | 49 | function onFile (srcStat, destStat, src, dest, opts) { 50 | if (!destStat) return copyFile(srcStat, src, dest, opts) 51 | return mayCopyFile(srcStat, src, dest, opts) 52 | } 53 | 54 | function mayCopyFile (srcStat, src, dest, opts) { 55 | if (opts.overwrite) { 56 | fs.unlinkSync(dest) 57 | return copyFile(srcStat, src, dest, opts) 58 | } else if (opts.errorOnExist) { 59 | throw new Error(`'${dest}' already exists`) 60 | } 61 | } 62 | 63 | function copyFile (srcStat, src, dest, opts) { 64 | fs.copyFileSync(src, dest) 65 | if (opts.preserveTimestamps) handleTimestamps(srcStat.mode, src, dest) 66 | return setDestMode(dest, srcStat.mode) 67 | } 68 | 69 | function handleTimestamps (srcMode, src, dest) { 70 | // Make sure the file is writable before setting the timestamp 71 | // otherwise open fails with EPERM when invoked with 'r+' 72 | // (through utimes call) 73 | if (fileIsNotWritable(srcMode)) makeFileWritable(dest, srcMode) 74 | return setDestTimestamps(src, dest) 75 | } 76 | 77 | function fileIsNotWritable (srcMode) { 78 | return (srcMode & 0o200) === 0 79 | } 80 | 81 | function makeFileWritable (dest, srcMode) { 82 | return setDestMode(dest, srcMode | 0o200) 83 | } 84 | 85 | function setDestMode (dest, srcMode) { 86 | return fs.chmodSync(dest, srcMode) 87 | } 88 | 89 | function setDestTimestamps (src, dest) { 90 | // The initial srcStat.atime cannot be trusted 91 | // because it is modified by the read(2) system call 92 | // (See https://nodejs.org/api/fs.html#fs_stat_time_values) 93 | const updatedSrcStat = fs.statSync(src) 94 | return utimesMillisSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime) 95 | } 96 | 97 | function onDir (srcStat, destStat, src, dest, opts) { 98 | if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts) 99 | return copyDir(src, dest, opts) 100 | } 101 | 102 | function mkDirAndCopy (srcMode, src, dest, opts) { 103 | fs.mkdirSync(dest) 104 | copyDir(src, dest, opts) 105 | return setDestMode(dest, srcMode) 106 | } 107 | 108 | function copyDir (src, dest, opts) { 109 | const dir = fs.opendirSync(src) 110 | 111 | try { 112 | let dirent 113 | 114 | while ((dirent = dir.readSync()) !== null) { 115 | copyDirItem(dirent.name, src, dest, opts) 116 | } 117 | } finally { 118 | dir.closeSync() 119 | } 120 | } 121 | 122 | function copyDirItem (item, src, dest, opts) { 123 | const srcItem = path.join(src, item) 124 | const destItem = path.join(dest, item) 125 | if (opts.filter && !opts.filter(srcItem, destItem)) return 126 | const { destStat } = stat.checkPathsSync(srcItem, destItem, 'copy', opts) 127 | return getStats(destStat, srcItem, destItem, opts) 128 | } 129 | 130 | function onLink (destStat, src, dest, opts) { 131 | let resolvedSrc = fs.readlinkSync(src) 132 | if (opts.dereference) { 133 | resolvedSrc = path.resolve(process.cwd(), resolvedSrc) 134 | } 135 | 136 | if (!destStat) { 137 | return fs.symlinkSync(resolvedSrc, dest) 138 | } else { 139 | let resolvedDest 140 | try { 141 | resolvedDest = fs.readlinkSync(dest) 142 | } catch (err) { 143 | // dest exists and is a regular file or directory, 144 | // Windows may throw UNKNOWN error. If dest already exists, 145 | // fs throws error anyway, so no need to guard against it here. 146 | if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return fs.symlinkSync(resolvedSrc, dest) 147 | throw err 148 | } 149 | if (opts.dereference) { 150 | resolvedDest = path.resolve(process.cwd(), resolvedDest) 151 | } 152 | if (stat.isSrcSubdir(resolvedSrc, resolvedDest)) { 153 | throw new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`) 154 | } 155 | 156 | // prevent copy if src is a subdir of dest since unlinking 157 | // dest in this case would result in removing src contents 158 | // and therefore a broken symlink would be created. 159 | if (stat.isSrcSubdir(resolvedDest, resolvedSrc)) { 160 | throw new Error(`Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.`) 161 | } 162 | return copyLink(resolvedSrc, dest) 163 | } 164 | } 165 | 166 | function copyLink (resolvedSrc, dest) { 167 | fs.unlinkSync(dest) 168 | return fs.symlinkSync(resolvedSrc, dest) 169 | } 170 | 171 | module.exports = copySync 172 | -------------------------------------------------------------------------------- /lib/copy/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const u = require('universalify').fromPromise 4 | module.exports = { 5 | copy: u(require('./copy')), 6 | copySync: require('./copy-sync') 7 | } 8 | -------------------------------------------------------------------------------- /lib/empty/__tests__/empty-dir-sync.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('+ emptyDir()', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(() => { 15 | TEST_DIR = path.join(os.tmpdir(), 'test-fs-extra', 'empty-dir') 16 | if (fs.existsSync(TEST_DIR)) { 17 | fse.removeSync(TEST_DIR) 18 | } 19 | fse.ensureDirSync(TEST_DIR) 20 | }) 21 | 22 | afterEach(done => fse.remove(TEST_DIR, done)) 23 | 24 | describe('> when directory exists and contains items', () => { 25 | it('should delete all of the items', () => { 26 | // verify nothing 27 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 28 | fse.ensureFileSync(path.join(TEST_DIR, 'some-file')) 29 | fse.ensureFileSync(path.join(TEST_DIR, 'some-file-2')) 30 | fse.ensureDirSync(path.join(TEST_DIR, 'some-dir')) 31 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 3) 32 | 33 | fse.emptyDirSync(TEST_DIR) 34 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 35 | }) 36 | }) 37 | 38 | describe('> when directory exists and contains no items', () => { 39 | it('should do nothing', () => { 40 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 41 | fse.emptyDirSync(TEST_DIR) 42 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 43 | }) 44 | }) 45 | 46 | describe('> when directory does not exist', () => { 47 | it('should create it', () => { 48 | fse.removeSync(TEST_DIR) 49 | assert(!fs.existsSync(TEST_DIR)) 50 | fse.emptyDirSync(TEST_DIR) 51 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /lib/empty/__tests__/empty-dir.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('+ emptyDir()', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(() => { 15 | TEST_DIR = path.join(os.tmpdir(), 'test-fs-extra', 'empty-dir') 16 | if (fs.existsSync(TEST_DIR)) { 17 | fse.removeSync(TEST_DIR) 18 | } 19 | fse.ensureDirSync(TEST_DIR) 20 | }) 21 | 22 | afterEach(done => fse.remove(TEST_DIR, done)) 23 | 24 | describe('> when directory exists and contains items', () => { 25 | it('should delete all of the items', done => { 26 | // verify nothing 27 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 28 | fse.ensureFileSync(path.join(TEST_DIR, 'some-file')) 29 | fse.ensureFileSync(path.join(TEST_DIR, 'some-file-2')) 30 | fse.ensureDirSync(path.join(TEST_DIR, 'some-dir')) 31 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 3) 32 | 33 | fse.emptyDir(TEST_DIR, err => { 34 | assert.ifError(err) 35 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 36 | done() 37 | }) 38 | }) 39 | }) 40 | 41 | describe('> when directory exists and contains no items', () => { 42 | it('should do nothing', done => { 43 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 44 | fse.emptyDir(TEST_DIR, err => { 45 | assert.ifError(err) 46 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 47 | done() 48 | }) 49 | }) 50 | }) 51 | 52 | describe('> when directory does not exist', () => { 53 | it('should create it', done => { 54 | fse.removeSync(TEST_DIR) 55 | assert(!fs.existsSync(TEST_DIR)) 56 | fse.emptyDir(TEST_DIR, err => { 57 | assert.ifError(err) 58 | assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0) 59 | done() 60 | }) 61 | }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /lib/empty/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const u = require('universalify').fromPromise 4 | const fs = require('../fs') 5 | const path = require('path') 6 | const mkdir = require('../mkdirs') 7 | const remove = require('../remove') 8 | 9 | const emptyDir = u(async function emptyDir (dir) { 10 | let items 11 | try { 12 | items = await fs.readdir(dir) 13 | } catch { 14 | return mkdir.mkdirs(dir) 15 | } 16 | 17 | return Promise.all(items.map(item => remove.remove(path.join(dir, item)))) 18 | }) 19 | 20 | function emptyDirSync (dir) { 21 | let items 22 | try { 23 | items = fs.readdirSync(dir) 24 | } catch { 25 | return mkdir.mkdirsSync(dir) 26 | } 27 | 28 | items.forEach(item => { 29 | item = path.join(dir, item) 30 | remove.removeSync(item) 31 | }) 32 | } 33 | 34 | module.exports = { 35 | emptyDirSync, 36 | emptydirSync: emptyDirSync, 37 | emptyDir, 38 | emptydir: emptyDir 39 | } 40 | -------------------------------------------------------------------------------- /lib/ensure/__tests__/create.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('fs-extra', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'create') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | describe('+ createFile', () => { 22 | describe('> when the file and directory does not exist', () => { 23 | it('should create the file', done => { 24 | const file = path.join(TEST_DIR, Math.random() + 't-ne', Math.random() + '.txt') 25 | assert(!fs.existsSync(file)) 26 | fse.createFile(file, err => { 27 | assert.ifError(err) 28 | assert(fs.existsSync(file)) 29 | done() 30 | }) 31 | }) 32 | }) 33 | 34 | describe('> when the file does exist', () => { 35 | it('should not modify the file', done => { 36 | const file = path.join(TEST_DIR, Math.random() + 't-e', Math.random() + '.txt') 37 | fse.mkdirsSync(path.dirname(file)) 38 | fs.writeFileSync(file, 'hello world') 39 | fse.createFile(file, err => { 40 | assert.ifError(err) 41 | assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello world') 42 | done() 43 | }) 44 | }) 45 | 46 | it('should give clear error if node in directory tree is a file', done => { 47 | const existingFile = path.join(TEST_DIR, Math.random() + 'ts-e', Math.random() + '.txt') 48 | fse.mkdirsSync(path.dirname(existingFile)) 49 | fs.writeFileSync(existingFile, '') 50 | 51 | const file = path.join(existingFile, Math.random() + '.txt') 52 | fse.createFile(file, err => { 53 | assert.strictEqual(err.code, 'ENOTDIR') 54 | done() 55 | }) 56 | }) 57 | }) 58 | }) 59 | 60 | describe('+ createFileSync', () => { 61 | describe('> when the file and directory does not exist', () => { 62 | it('should create the file', () => { 63 | const file = path.join(TEST_DIR, Math.random() + 'ts-ne', Math.random() + '.txt') 64 | assert(!fs.existsSync(file)) 65 | fse.createFileSync(file) 66 | assert(fs.existsSync(file)) 67 | }) 68 | }) 69 | 70 | describe('> when the file does exist', () => { 71 | it('should not modify the file', () => { 72 | const file = path.join(TEST_DIR, Math.random() + 'ts-e', Math.random() + '.txt') 73 | fse.mkdirsSync(path.dirname(file)) 74 | fs.writeFileSync(file, 'hello world') 75 | fse.createFileSync(file) 76 | assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello world') 77 | }) 78 | 79 | it('should give clear error if node in directory tree is a file', () => { 80 | const existingFile = path.join(TEST_DIR, Math.random() + 'ts-e', Math.random() + '.txt') 81 | fse.mkdirsSync(path.dirname(existingFile)) 82 | fs.writeFileSync(existingFile, '') 83 | 84 | const file = path.join(existingFile, Math.random() + '.txt') 85 | try { 86 | fse.createFileSync(file) 87 | assert.fail() 88 | } catch (err) { 89 | assert.strictEqual(err.code, 'ENOTDIR') 90 | } 91 | }) 92 | }) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /lib/ensure/__tests__/ensure.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('fs-extra', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | describe('+ ensureFile()', () => { 22 | describe('> when file exists', () => { 23 | it('should not do anything', done => { 24 | const file = path.join(TEST_DIR, 'file.txt') 25 | fs.writeFileSync(file, 'blah') 26 | 27 | assert(fs.existsSync(file)) 28 | fse.ensureFile(file, err => { 29 | assert.ifError(err) 30 | assert(fs.existsSync(file)) 31 | done() 32 | }) 33 | }) 34 | }) 35 | 36 | describe('> when file does not exist', () => { 37 | it('should create the file', done => { 38 | const file = path.join(TEST_DIR, 'dir/that/does/not/exist', 'file.txt') 39 | 40 | assert(!fs.existsSync(file)) 41 | fse.ensureFile(file, err => { 42 | assert.ifError(err) 43 | assert(fs.existsSync(file)) 44 | done() 45 | }) 46 | }) 47 | }) 48 | 49 | describe('> when there is a directory at that path', () => { 50 | it('should error', done => { 51 | const p = path.join(TEST_DIR, 'somedir') 52 | fs.mkdirSync(p) 53 | 54 | fse.ensureFile(p, err => { 55 | assert(err) 56 | assert.strictEqual(err.code, 'EISDIR') 57 | done() 58 | }) 59 | }) 60 | }) 61 | }) 62 | 63 | describe('+ ensureFileSync()', () => { 64 | describe('> when file exists', () => { 65 | it('should not do anything', () => { 66 | const file = path.join(TEST_DIR, 'file.txt') 67 | fs.writeFileSync(file, 'blah') 68 | 69 | assert(fs.existsSync(file)) 70 | fse.ensureFileSync(file) 71 | assert(fs.existsSync(file)) 72 | }) 73 | }) 74 | 75 | describe('> when file does not exist', () => { 76 | it('should create the file', () => { 77 | const file = path.join(TEST_DIR, 'dir/that/does/not/exist', 'file.txt') 78 | 79 | assert(!fs.existsSync(file)) 80 | fse.ensureFileSync(file) 81 | assert(fs.existsSync(file)) 82 | }) 83 | }) 84 | 85 | describe('> when there is a directory at that path', () => { 86 | it('should error', () => { 87 | const p = path.join(TEST_DIR, 'somedir2') 88 | fs.mkdirSync(p) 89 | 90 | assert.throws(() => { 91 | try { 92 | fse.ensureFileSync(p) 93 | } catch (e) { 94 | assert.strictEqual(e.code, 'EISDIR') 95 | throw e 96 | } 97 | }) 98 | }) 99 | }) 100 | }) 101 | 102 | describe('+ ensureDir()', () => { 103 | describe('> when dir exists', () => { 104 | it('should not do anything', done => { 105 | const dir = path.join(TEST_DIR, 'dir/does/not/exist') 106 | fse.mkdirpSync(dir) 107 | 108 | assert(fs.existsSync(dir)) 109 | fse.ensureDir(dir, err => { 110 | assert.ifError(err) 111 | assert(fs.existsSync(dir)) 112 | done() 113 | }) 114 | }) 115 | }) 116 | 117 | describe('> when dir does not exist', () => { 118 | it('should create the dir', done => { 119 | const dir = path.join(TEST_DIR, 'dir/that/does/not/exist') 120 | 121 | assert(!fs.existsSync(dir)) 122 | fse.ensureDir(dir, err => { 123 | assert.ifError(err) 124 | assert(fs.existsSync(dir)) 125 | done() 126 | }) 127 | }) 128 | }) 129 | }) 130 | 131 | describe('+ ensureDirSync()', () => { 132 | describe('> when dir exists', () => { 133 | it('should not do anything', () => { 134 | const dir = path.join(TEST_DIR, 'dir/does/not/exist') 135 | fse.mkdirpSync(dir) 136 | 137 | assert(fs.existsSync(dir)) 138 | fse.ensureDirSync(dir) 139 | assert(fs.existsSync(dir)) 140 | }) 141 | }) 142 | 143 | describe('> when dir does not exist', () => { 144 | it('should create the dir', () => { 145 | const dir = path.join(TEST_DIR, 'dir/that/does/not/exist') 146 | 147 | assert(!fs.existsSync(dir)) 148 | fse.ensureDirSync(dir) 149 | assert(fs.existsSync(dir)) 150 | }) 151 | }) 152 | }) 153 | }) 154 | -------------------------------------------------------------------------------- /lib/ensure/__tests__/symlink-paths.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const CWD = process.cwd() 4 | 5 | const fs = require('graceful-fs') 6 | const os = require('os') 7 | const fse = require('../..') 8 | const path = require('path') 9 | const assert = require('assert') 10 | const _symlinkPaths = require('../symlink-paths') 11 | const symlinkPaths = _symlinkPaths.symlinkPaths 12 | const symlinkPathsSync = _symlinkPaths.symlinkPathsSync 13 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure-symlink') 14 | 15 | /* global afterEach, beforeEach, describe, it, after, before */ 16 | 17 | describe('symlink-type', () => { 18 | before(() => { 19 | fse.emptyDirSync(TEST_DIR) 20 | process.chdir(TEST_DIR) 21 | }) 22 | 23 | beforeEach(() => { 24 | fs.writeFileSync('./foo.txt', 'foo\n') 25 | fse.mkdirsSync('./empty-dir') 26 | fse.mkdirsSync('./dir-foo') 27 | fs.writeFileSync('./dir-foo/foo.txt', 'dir-foo\n') 28 | fse.mkdirsSync('./dir-bar') 29 | fs.writeFileSync('./dir-bar/bar.txt', 'dir-bar\n') 30 | fse.mkdirsSync('./real-alpha/real-beta/real-gamma') 31 | }) 32 | 33 | afterEach(done => fse.emptyDir(TEST_DIR, done)) 34 | 35 | after(() => { 36 | process.chdir(CWD) 37 | fse.removeSync(TEST_DIR) 38 | }) 39 | 40 | const tests = [ 41 | [['foo.txt', 'symlink.txt'], { toCwd: 'foo.txt', toDst: 'foo.txt' }], // smart && nodestyle 42 | [['foo.txt', 'empty-dir/symlink.txt'], { toCwd: 'foo.txt', toDst: '../foo.txt' }], // smart 43 | [['../foo.txt', 'empty-dir/symlink.txt'], { toCwd: 'foo.txt', toDst: '../foo.txt' }], // nodestyle 44 | [['foo.txt', 'dir-bar/symlink.txt'], { toCwd: 'foo.txt', toDst: '../foo.txt' }], // smart 45 | [['../foo.txt', 'dir-bar/symlink.txt'], { toCwd: 'foo.txt', toDst: '../foo.txt' }], // nodestyle 46 | // this is to preserve node's symlink capability these arguments say create 47 | // a link to 'dir-foo/foo.txt' this works because it exists this is unlike 48 | // the previous example with 'empty-dir' because 'empty-dir/foo.txt' does not exist. 49 | [['foo.txt', 'dir-foo/symlink.txt'], { toCwd: 'dir-foo/foo.txt', toDst: 'foo.txt' }], // nodestyle 50 | [['foo.txt', 'real-alpha/real-beta/real-gamma/symlink.txt'], { toCwd: 'foo.txt', toDst: '../../../foo.txt' }] 51 | ] 52 | 53 | // formats paths to pass on multiple operating systems 54 | tests.forEach(test => { 55 | test[0][0] = path.join(test[0][0]) 56 | test[0][1] = path.join(test[0][1]) 57 | test[1] = { 58 | toCwd: path.join(test[1].toCwd), 59 | toDst: path.join(test[1].toDst) 60 | } 61 | }) 62 | 63 | describe('symlinkPaths()', () => { 64 | tests.forEach(test => { 65 | const args = test[0].slice(0) 66 | const expectedRelativePaths = test[1] 67 | it(`should return '${JSON.stringify(expectedRelativePaths)}' when src '${args[0]}' and dst is '${args[1]}'`, done => { 68 | const callback = (err, relativePaths) => { 69 | if (err) done(err) 70 | assert.deepStrictEqual(relativePaths, expectedRelativePaths) 71 | done() 72 | } 73 | args.push(callback) 74 | return symlinkPaths(...args) 75 | }) 76 | }) 77 | }) 78 | 79 | describe('symlinkPathsSync()', () => { 80 | tests.forEach(test => { 81 | const args = test[0].slice(0) 82 | const expectedRelativePaths = test[1] 83 | it(`should return '${JSON.stringify(expectedRelativePaths)}' when src '${args[0]}' and dst is '${args[1]}'`, () => { 84 | const relativePaths = symlinkPathsSync(...args) 85 | assert.deepStrictEqual(relativePaths, expectedRelativePaths) 86 | }) 87 | }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /lib/ensure/__tests__/symlink-type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const CWD = process.cwd() 4 | 5 | const fs = require('graceful-fs') 6 | const os = require('os') 7 | const fse = require('../..') 8 | const path = require('path') 9 | const assert = require('assert') 10 | const _symlinkType = require('../symlink-type') 11 | const symlinkType = _symlinkType.symlinkType 12 | const symlinkTypeSync = _symlinkType.symlinkTypeSync 13 | const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure-symlink') 14 | 15 | /* global afterEach, beforeEach, describe, it, after, before */ 16 | 17 | describe('symlink-type', () => { 18 | before(() => { 19 | fse.emptyDirSync(TEST_DIR) 20 | process.chdir(TEST_DIR) 21 | }) 22 | 23 | beforeEach(() => { 24 | fs.writeFileSync('./foo.txt', 'foo\n') 25 | fse.mkdirsSync('./empty-dir') 26 | fse.mkdirsSync('./dir-foo') 27 | fs.writeFileSync('./dir-foo/foo.txt', 'dir-foo\n') 28 | fse.mkdirsSync('./dir-bar') 29 | fs.writeFileSync('./dir-bar/bar.txt', 'dir-bar\n') 30 | fse.mkdirsSync('./real-alpha/real-beta/real-gamma') 31 | }) 32 | 33 | afterEach(done => fse.emptyDir(TEST_DIR, done)) 34 | 35 | after(() => { 36 | process.chdir(CWD) 37 | fse.removeSync(TEST_DIR) 38 | }) 39 | 40 | const tests = { 41 | success: [ 42 | // [{arguments} [srcpath, dirpath, [type] , result] 43 | // smart file type checking 44 | [['./foo.txt'], 'file'], 45 | [['./empty-dir'], 'dir'], 46 | [['./dir-foo/foo.txt'], 'file'], 47 | [['./dir-bar'], 'dir'], 48 | [['./dir-bar/bar.txt'], 'file'], 49 | [['./real-alpha/real-beta/real-gamma'], 'dir'], 50 | // force dir 51 | [['./foo.txt', 'dir'], 'dir'], 52 | [['./empty-dir', 'dir'], 'dir'], 53 | [['./dir-foo/foo.txt', 'dir'], 'dir'], 54 | [['./dir-bar', 'dir'], 'dir'], 55 | [['./dir-bar/bar.txt', 'dir'], 'dir'], 56 | [['./real-alpha/real-beta/real-gamma', 'dir'], 'dir'], 57 | // force file 58 | [['./foo.txt', 'file'], 'file'], 59 | [['./empty-dir', 'file'], 'file'], 60 | [['./dir-foo/foo.txt', 'file'], 'file'], 61 | [['./dir-bar', 'file'], 'file'], 62 | [['./dir-bar/bar.txt', 'file'], 'file'], 63 | [['./real-alpha/real-beta/real-gamma', 'file'], 'file'], 64 | // default for files or dirs that don't exist is file 65 | [['./missing.txt'], 'file'], 66 | [['./missing'], 'file'], 67 | [['./missing.txt'], 'file'], 68 | [['./missing'], 'file'], 69 | [['./empty-dir/missing.txt'], 'file'], 70 | [['./empty-dir/missing'], 'file'], 71 | [['./empty-dir/missing.txt'], 'file'], 72 | [['./empty-dir/missing'], 'file'], 73 | // when src doesnt exist and provided type 'file' 74 | [['./missing.txt', 'file'], 'file'], 75 | [['./missing', 'file'], 'file'], 76 | [['./missing.txt', 'file'], 'file'], 77 | [['./missing', 'file'], 'file'], 78 | [['./empty-dir/missing.txt', 'file'], 'file'], 79 | [['./empty-dir/missing', 'file'], 'file'], 80 | [['./empty-dir/missing.txt', 'file'], 'file'], 81 | [['./empty-dir/missing', 'file'], 'file'], 82 | // when src doesnt exist and provided type 'dir' 83 | [['./missing.txt', 'dir'], 'dir'], 84 | [['./missing', 'dir'], 'dir'], 85 | [['./missing.txt', 'dir'], 'dir'], 86 | [['./missing', 'dir'], 'dir'], 87 | [['./empty-dir/missing.txt', 'dir'], 'dir'], 88 | [['./empty-dir/missing', 'dir'], 'dir'], 89 | [['./empty-dir/missing.txt', 'dir'], 'dir'], 90 | [['./empty-dir/missing', 'dir'], 'dir'] 91 | ] 92 | } 93 | 94 | describe('symlinkType()', () => { 95 | tests.success.forEach(test => { 96 | const args = test[0].slice(0) 97 | const expectedType = test[1] 98 | it(`should return '${expectedType}' when src '${args[0]}'`, done => { 99 | const callback = (err, type) => { 100 | if (err) done(err) 101 | assert.strictEqual(type, expectedType) 102 | done() 103 | } 104 | args.push(callback) 105 | return symlinkType(...args) 106 | }) 107 | }) 108 | }) 109 | 110 | describe('symlinkTypeSync()', () => { 111 | tests.success.forEach(test => { 112 | const args = test[0] 113 | const expectedType = test[1] 114 | it(`should return '${expectedType}' when src '${args[0]}'`, () => { 115 | const type = symlinkTypeSync(...args) 116 | assert.strictEqual(type, expectedType) 117 | }) 118 | }) 119 | }) 120 | }) 121 | -------------------------------------------------------------------------------- /lib/ensure/file.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const u = require('universalify').fromPromise 4 | const path = require('path') 5 | const fs = require('../fs') 6 | const mkdir = require('../mkdirs') 7 | 8 | async function createFile (file) { 9 | let stats 10 | try { 11 | stats = await fs.stat(file) 12 | } catch { } 13 | if (stats && stats.isFile()) return 14 | 15 | const dir = path.dirname(file) 16 | 17 | let dirStats = null 18 | try { 19 | dirStats = await fs.stat(dir) 20 | } catch (err) { 21 | // if the directory doesn't exist, make it 22 | if (err.code === 'ENOENT') { 23 | await mkdir.mkdirs(dir) 24 | await fs.writeFile(file, '') 25 | return 26 | } else { 27 | throw err 28 | } 29 | } 30 | 31 | if (dirStats.isDirectory()) { 32 | await fs.writeFile(file, '') 33 | } else { 34 | // parent is not a directory 35 | // This is just to cause an internal ENOTDIR error to be thrown 36 | await fs.readdir(dir) 37 | } 38 | } 39 | 40 | function createFileSync (file) { 41 | let stats 42 | try { 43 | stats = fs.statSync(file) 44 | } catch { } 45 | if (stats && stats.isFile()) return 46 | 47 | const dir = path.dirname(file) 48 | try { 49 | if (!fs.statSync(dir).isDirectory()) { 50 | // parent is not a directory 51 | // This is just to cause an internal ENOTDIR error to be thrown 52 | fs.readdirSync(dir) 53 | } 54 | } catch (err) { 55 | // If the stat call above failed because the directory doesn't exist, create it 56 | if (err && err.code === 'ENOENT') mkdir.mkdirsSync(dir) 57 | else throw err 58 | } 59 | 60 | fs.writeFileSync(file, '') 61 | } 62 | 63 | module.exports = { 64 | createFile: u(createFile), 65 | createFileSync 66 | } 67 | -------------------------------------------------------------------------------- /lib/ensure/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { createFile, createFileSync } = require('./file') 4 | const { createLink, createLinkSync } = require('./link') 5 | const { createSymlink, createSymlinkSync } = require('./symlink') 6 | 7 | module.exports = { 8 | // file 9 | createFile, 10 | createFileSync, 11 | ensureFile: createFile, 12 | ensureFileSync: createFileSync, 13 | // link 14 | createLink, 15 | createLinkSync, 16 | ensureLink: createLink, 17 | ensureLinkSync: createLinkSync, 18 | // symlink 19 | createSymlink, 20 | createSymlinkSync, 21 | ensureSymlink: createSymlink, 22 | ensureSymlinkSync: createSymlinkSync 23 | } 24 | -------------------------------------------------------------------------------- /lib/ensure/link.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const u = require('universalify').fromPromise 4 | const path = require('path') 5 | const fs = require('../fs') 6 | const mkdir = require('../mkdirs') 7 | const { pathExists } = require('../path-exists') 8 | const { areIdentical } = require('../util/stat') 9 | 10 | async function createLink (srcpath, dstpath) { 11 | let dstStat 12 | try { 13 | dstStat = await fs.lstat(dstpath) 14 | } catch { 15 | // ignore error 16 | } 17 | 18 | let srcStat 19 | try { 20 | srcStat = await fs.lstat(srcpath) 21 | } catch (err) { 22 | err.message = err.message.replace('lstat', 'ensureLink') 23 | throw err 24 | } 25 | 26 | if (dstStat && areIdentical(srcStat, dstStat)) return 27 | 28 | const dir = path.dirname(dstpath) 29 | 30 | const dirExists = await pathExists(dir) 31 | 32 | if (!dirExists) { 33 | await mkdir.mkdirs(dir) 34 | } 35 | 36 | await fs.link(srcpath, dstpath) 37 | } 38 | 39 | function createLinkSync (srcpath, dstpath) { 40 | let dstStat 41 | try { 42 | dstStat = fs.lstatSync(dstpath) 43 | } catch {} 44 | 45 | try { 46 | const srcStat = fs.lstatSync(srcpath) 47 | if (dstStat && areIdentical(srcStat, dstStat)) return 48 | } catch (err) { 49 | err.message = err.message.replace('lstat', 'ensureLink') 50 | throw err 51 | } 52 | 53 | const dir = path.dirname(dstpath) 54 | const dirExists = fs.existsSync(dir) 55 | if (dirExists) return fs.linkSync(srcpath, dstpath) 56 | mkdir.mkdirsSync(dir) 57 | 58 | return fs.linkSync(srcpath, dstpath) 59 | } 60 | 61 | module.exports = { 62 | createLink: u(createLink), 63 | createLinkSync 64 | } 65 | -------------------------------------------------------------------------------- /lib/ensure/symlink-paths.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const fs = require('../fs') 5 | const { pathExists } = require('../path-exists') 6 | 7 | const u = require('universalify').fromPromise 8 | 9 | /** 10 | * Function that returns two types of paths, one relative to symlink, and one 11 | * relative to the current working directory. Checks if path is absolute or 12 | * relative. If the path is relative, this function checks if the path is 13 | * relative to symlink or relative to current working directory. This is an 14 | * initiative to find a smarter `srcpath` to supply when building symlinks. 15 | * This allows you to determine which path to use out of one of three possible 16 | * types of source paths. The first is an absolute path. This is detected by 17 | * `path.isAbsolute()`. When an absolute path is provided, it is checked to 18 | * see if it exists. If it does it's used, if not an error is returned 19 | * (callback)/ thrown (sync). The other two options for `srcpath` are a 20 | * relative url. By default Node's `fs.symlink` works by creating a symlink 21 | * using `dstpath` and expects the `srcpath` to be relative to the newly 22 | * created symlink. If you provide a `srcpath` that does not exist on the file 23 | * system it results in a broken symlink. To minimize this, the function 24 | * checks to see if the 'relative to symlink' source file exists, and if it 25 | * does it will use it. If it does not, it checks if there's a file that 26 | * exists that is relative to the current working directory, if does its used. 27 | * This preserves the expectations of the original fs.symlink spec and adds 28 | * the ability to pass in `relative to current working direcotry` paths. 29 | */ 30 | 31 | async function symlinkPaths (srcpath, dstpath) { 32 | if (path.isAbsolute(srcpath)) { 33 | try { 34 | await fs.lstat(srcpath) 35 | } catch (err) { 36 | err.message = err.message.replace('lstat', 'ensureSymlink') 37 | throw err 38 | } 39 | 40 | return { 41 | toCwd: srcpath, 42 | toDst: srcpath 43 | } 44 | } 45 | 46 | const dstdir = path.dirname(dstpath) 47 | const relativeToDst = path.join(dstdir, srcpath) 48 | 49 | const exists = await pathExists(relativeToDst) 50 | if (exists) { 51 | return { 52 | toCwd: relativeToDst, 53 | toDst: srcpath 54 | } 55 | } 56 | 57 | try { 58 | await fs.lstat(srcpath) 59 | } catch (err) { 60 | err.message = err.message.replace('lstat', 'ensureSymlink') 61 | throw err 62 | } 63 | 64 | return { 65 | toCwd: srcpath, 66 | toDst: path.relative(dstdir, srcpath) 67 | } 68 | } 69 | 70 | function symlinkPathsSync (srcpath, dstpath) { 71 | if (path.isAbsolute(srcpath)) { 72 | const exists = fs.existsSync(srcpath) 73 | if (!exists) throw new Error('absolute srcpath does not exist') 74 | return { 75 | toCwd: srcpath, 76 | toDst: srcpath 77 | } 78 | } 79 | 80 | const dstdir = path.dirname(dstpath) 81 | const relativeToDst = path.join(dstdir, srcpath) 82 | const exists = fs.existsSync(relativeToDst) 83 | if (exists) { 84 | return { 85 | toCwd: relativeToDst, 86 | toDst: srcpath 87 | } 88 | } 89 | 90 | const srcExists = fs.existsSync(srcpath) 91 | if (!srcExists) throw new Error('relative srcpath does not exist') 92 | return { 93 | toCwd: srcpath, 94 | toDst: path.relative(dstdir, srcpath) 95 | } 96 | } 97 | 98 | module.exports = { 99 | symlinkPaths: u(symlinkPaths), 100 | symlinkPathsSync 101 | } 102 | -------------------------------------------------------------------------------- /lib/ensure/symlink-type.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../fs') 4 | const u = require('universalify').fromPromise 5 | 6 | async function symlinkType (srcpath, type) { 7 | if (type) return type 8 | 9 | let stats 10 | try { 11 | stats = await fs.lstat(srcpath) 12 | } catch { 13 | return 'file' 14 | } 15 | 16 | return (stats && stats.isDirectory()) ? 'dir' : 'file' 17 | } 18 | 19 | function symlinkTypeSync (srcpath, type) { 20 | if (type) return type 21 | 22 | let stats 23 | try { 24 | stats = fs.lstatSync(srcpath) 25 | } catch { 26 | return 'file' 27 | } 28 | return (stats && stats.isDirectory()) ? 'dir' : 'file' 29 | } 30 | 31 | module.exports = { 32 | symlinkType: u(symlinkType), 33 | symlinkTypeSync 34 | } 35 | -------------------------------------------------------------------------------- /lib/ensure/symlink.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const u = require('universalify').fromPromise 4 | const path = require('path') 5 | const fs = require('../fs') 6 | 7 | const { mkdirs, mkdirsSync } = require('../mkdirs') 8 | 9 | const { symlinkPaths, symlinkPathsSync } = require('./symlink-paths') 10 | const { symlinkType, symlinkTypeSync } = require('./symlink-type') 11 | 12 | const { pathExists } = require('../path-exists') 13 | 14 | const { areIdentical } = require('../util/stat') 15 | 16 | async function createSymlink (srcpath, dstpath, type) { 17 | let stats 18 | try { 19 | stats = await fs.lstat(dstpath) 20 | } catch { } 21 | 22 | if (stats && stats.isSymbolicLink()) { 23 | const [srcStat, dstStat] = await Promise.all([ 24 | fs.stat(srcpath), 25 | fs.stat(dstpath) 26 | ]) 27 | 28 | if (areIdentical(srcStat, dstStat)) return 29 | } 30 | 31 | const relative = await symlinkPaths(srcpath, dstpath) 32 | srcpath = relative.toDst 33 | const toType = await symlinkType(relative.toCwd, type) 34 | const dir = path.dirname(dstpath) 35 | 36 | if (!(await pathExists(dir))) { 37 | await mkdirs(dir) 38 | } 39 | 40 | return fs.symlink(srcpath, dstpath, toType) 41 | } 42 | 43 | function createSymlinkSync (srcpath, dstpath, type) { 44 | let stats 45 | try { 46 | stats = fs.lstatSync(dstpath) 47 | } catch { } 48 | if (stats && stats.isSymbolicLink()) { 49 | const srcStat = fs.statSync(srcpath) 50 | const dstStat = fs.statSync(dstpath) 51 | if (areIdentical(srcStat, dstStat)) return 52 | } 53 | 54 | const relative = symlinkPathsSync(srcpath, dstpath) 55 | srcpath = relative.toDst 56 | type = symlinkTypeSync(relative.toCwd, type) 57 | const dir = path.dirname(dstpath) 58 | const exists = fs.existsSync(dir) 59 | if (exists) return fs.symlinkSync(srcpath, dstpath, type) 60 | mkdirsSync(dir) 61 | return fs.symlinkSync(srcpath, dstpath, type) 62 | } 63 | 64 | module.exports = { 65 | createSymlink: u(createSymlink), 66 | createSymlinkSync 67 | } 68 | -------------------------------------------------------------------------------- /lib/esm.mjs: -------------------------------------------------------------------------------- 1 | import _copy from './copy/index.js' 2 | import _empty from './empty/index.js' 3 | import _ensure from './ensure/index.js' 4 | import _json from './json/index.js' 5 | import _mkdirs from './mkdirs/index.js' 6 | import _move from './move/index.js' 7 | import _outputFile from './output-file/index.js' 8 | import _pathExists from './path-exists/index.js' 9 | import _remove from './remove/index.js' 10 | 11 | // NOTE: Only exports fs-extra's functions; fs functions must be imported from "node:fs" or "node:fs/promises" 12 | 13 | export const copy = _copy.copy 14 | export const copySync = _copy.copySync 15 | export const emptyDirSync = _empty.emptyDirSync 16 | export const emptydirSync = _empty.emptydirSync 17 | export const emptyDir = _empty.emptyDir 18 | export const emptydir = _empty.emptydir 19 | export const createFile = _ensure.createFile 20 | export const createFileSync = _ensure.createFileSync 21 | export const ensureFile = _ensure.ensureFile 22 | export const ensureFileSync = _ensure.ensureFileSync 23 | export const createLink = _ensure.createLink 24 | export const createLinkSync = _ensure.createLinkSync 25 | export const ensureLink = _ensure.ensureLink 26 | export const ensureLinkSync = _ensure.ensureLinkSync 27 | export const createSymlink = _ensure.createSymlink 28 | export const createSymlinkSync = _ensure.createSymlinkSync 29 | export const ensureSymlink = _ensure.ensureSymlink 30 | export const ensureSymlinkSync = _ensure.ensureSymlinkSync 31 | export const readJson = _json.readJson 32 | export const readJSON = _json.readJSON 33 | export const readJsonSync = _json.readJsonSync 34 | export const readJSONSync = _json.readJSONSync 35 | export const writeJson = _json.writeJson 36 | export const writeJSON = _json.writeJSON 37 | export const writeJsonSync = _json.writeJsonSync 38 | export const writeJSONSync = _json.writeJSONSync 39 | export const outputJson = _json.outputJson 40 | export const outputJSON = _json.outputJSON 41 | export const outputJsonSync = _json.outputJsonSync 42 | export const outputJSONSync = _json.outputJSONSync 43 | export const mkdirs = _mkdirs.mkdirs 44 | export const mkdirsSync = _mkdirs.mkdirsSync 45 | export const mkdirp = _mkdirs.mkdirp 46 | export const mkdirpSync = _mkdirs.mkdirpSync 47 | export const ensureDir = _mkdirs.ensureDir 48 | export const ensureDirSync = _mkdirs.ensureDirSync 49 | export const move = _move.move 50 | export const moveSync = _move.moveSync 51 | export const outputFile = _outputFile.outputFile 52 | export const outputFileSync = _outputFile.outputFileSync 53 | export const pathExists = _pathExists.pathExists 54 | export const pathExistsSync = _pathExists.pathExistsSync 55 | export const remove = _remove.remove 56 | export const removeSync = _remove.removeSync 57 | 58 | export default { 59 | ..._copy, 60 | ..._empty, 61 | ..._ensure, 62 | ..._json, 63 | ..._mkdirs, 64 | ..._move, 65 | ..._outputFile, 66 | ..._pathExists, 67 | ..._remove 68 | } 69 | -------------------------------------------------------------------------------- /lib/fs/__tests__/copyFile.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const os = require('os') 4 | const fse = require('../..') 5 | const path = require('path') 6 | const assert = require('assert') 7 | 8 | /* eslint-env mocha */ 9 | 10 | describe('fs.copyFile', () => { 11 | let TEST_DIR 12 | 13 | beforeEach(done => { 14 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'fs-copyfile') 15 | fse.emptyDir(TEST_DIR, done) 16 | }) 17 | 18 | afterEach(done => fse.remove(TEST_DIR, done)) 19 | 20 | it('supports promises', () => { 21 | const src = path.join(TEST_DIR, 'init.txt') 22 | const dest = path.join(TEST_DIR, 'copy.txt') 23 | fse.writeFileSync(src, 'hello') 24 | return fse.copyFile(src, dest).then(() => { 25 | const data = fse.readFileSync(dest, 'utf8') 26 | assert.strictEqual(data, 'hello') 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /lib/fs/__tests__/fs-integration.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const os = require('os') 4 | const fs = require('fs') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('native fs', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'native-fs') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | it('should use native fs methods', () => { 22 | const file = path.join(TEST_DIR, 'write.txt') 23 | fse.writeFileSync(file, 'hello') 24 | const data = fse.readFileSync(file, 'utf8') 25 | assert.strictEqual(data, 'hello') 26 | }) 27 | 28 | it('should have native fs constants', () => { 29 | assert.strictEqual(fse.constants.F_OK, fs.constants.F_OK) 30 | assert.strictEqual(fse.F_OK, fs.F_OK) // soft deprecated usage, but still available 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /lib/fs/__tests__/mz.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // This is adapted from https://github.com/normalize/mz 3 | // Copyright (c) 2014-2016 Jonathan Ong me@jongleberry.com and Contributors 4 | 5 | /* eslint-env mocha */ 6 | const assert = require('assert') 7 | const fs = require('../..') 8 | 9 | describe('fs', () => { 10 | it('.stat()', done => { 11 | fs.stat(__filename).then(stats => { 12 | assert.strictEqual(typeof stats.size, 'number') 13 | done() 14 | }).catch(done) 15 | }) 16 | 17 | it('.statSync()', () => { 18 | const stats = fs.statSync(__filename) 19 | assert.strictEqual(typeof stats.size, 'number') 20 | }) 21 | 22 | it('.exists()', done => { 23 | fs.exists(__filename).then(exists => { 24 | assert(exists) 25 | done() 26 | }).catch(done) 27 | }) 28 | 29 | it('.existsSync()', () => { 30 | const exists = fs.existsSync(__filename) 31 | assert(exists) 32 | }) 33 | 34 | describe('callback support', () => { 35 | it('.stat()', done => { 36 | fs.stat(__filename, (err, stats) => { 37 | assert(!err) 38 | assert.strictEqual(typeof stats.size, 'number') 39 | done() 40 | }) 41 | }) 42 | 43 | // This test is different from mz/fs, since we are a drop-in replacement for native fs 44 | it('.exists()', done => { 45 | fs.exists(__filename, exists => { 46 | assert(exists) 47 | done() 48 | }) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /lib/fs/__tests__/realpath.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const assert = require('assert') 6 | 7 | /* eslint-env mocha */ 8 | 9 | describe('realpath.native does not exist', () => { 10 | let warning 11 | const warningListener = error => { 12 | if (error.name === 'Warning') { 13 | if (error.code.startsWith('fs-extra-WARN0003')) { 14 | warning = error 15 | } 16 | } 17 | } 18 | 19 | const realpathNativeBackup = fs.realpath.native 20 | const clearFseCache = () => { 21 | const fsePath = path.dirname(require.resolve('../..')) 22 | for (const entry in require.cache) { 23 | if (entry.startsWith(fsePath)) { 24 | delete require.cache[entry] 25 | } 26 | } 27 | } 28 | 29 | before(() => { 30 | process.on('warning', warningListener) 31 | 32 | // clear existing require.cache 33 | clearFseCache() 34 | 35 | // simulate fs monkey-patch 36 | delete fs.realpath.native 37 | }) 38 | 39 | after(() => { 40 | process.off('warning', warningListener) 41 | 42 | // clear stubbed require.cache 43 | clearFseCache() 44 | 45 | // reinstate fs.realpath.native 46 | fs.realpath.native = realpathNativeBackup 47 | }) 48 | 49 | it('fse should not export realpath.native', done => { 50 | const fse = require('../..') 51 | 52 | // next event loop to allow event emitter/listener to happen 53 | setImmediate(() => { 54 | assert(warning, 'fs-extra-WARN0003 should be emitted') 55 | done() 56 | }) 57 | 58 | assert(!fse.realpath.native) 59 | }) 60 | }) 61 | 62 | describe('realpath.native', () => { 63 | const fse = require('../..') 64 | 65 | it('works with callbacks', () => { 66 | fse.realpath.native(__dirname, (err, path) => { 67 | assert.ifError(err) 68 | assert.strictEqual(path, __dirname) 69 | }) 70 | }) 71 | 72 | it('works with promises', (done) => { 73 | fse.realpath.native(__dirname) 74 | .then(path => { 75 | assert.strictEqual(path, __dirname) 76 | done() 77 | }) 78 | .catch(done) 79 | }) 80 | 81 | it('works with sync version', () => { 82 | const path = fse.realpathSync.native(__dirname) 83 | assert.strictEqual(path, __dirname) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /lib/fs/__tests__/rm.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fse = require('../..') 4 | const os = require('os') 5 | const path = require('path') 6 | const assert = require('assert') 7 | 8 | /* eslint-env mocha */ 9 | 10 | describe('fs.rm', () => { 11 | let TEST_FILE 12 | 13 | beforeEach(done => { 14 | TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'fs-rm') 15 | fse.remove(TEST_FILE, done) 16 | }) 17 | 18 | afterEach(done => fse.remove(TEST_FILE, done)) 19 | 20 | it('supports promises', () => { 21 | fse.writeFileSync(TEST_FILE, 'hello') 22 | return fse.rm(TEST_FILE).then(() => { 23 | assert(!fse.pathExistsSync(TEST_FILE)) 24 | }) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /lib/fs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // This is adapted from https://github.com/normalize/mz 3 | // Copyright (c) 2014-2016 Jonathan Ong me@jongleberry.com and Contributors 4 | const u = require('universalify').fromCallback 5 | const fs = require('graceful-fs') 6 | 7 | const api = [ 8 | 'access', 9 | 'appendFile', 10 | 'chmod', 11 | 'chown', 12 | 'close', 13 | 'copyFile', 14 | 'cp', 15 | 'fchmod', 16 | 'fchown', 17 | 'fdatasync', 18 | 'fstat', 19 | 'fsync', 20 | 'ftruncate', 21 | 'futimes', 22 | 'glob', 23 | 'lchmod', 24 | 'lchown', 25 | 'lutimes', 26 | 'link', 27 | 'lstat', 28 | 'mkdir', 29 | 'mkdtemp', 30 | 'open', 31 | 'opendir', 32 | 'readdir', 33 | 'readFile', 34 | 'readlink', 35 | 'realpath', 36 | 'rename', 37 | 'rm', 38 | 'rmdir', 39 | 'stat', 40 | 'statfs', 41 | 'symlink', 42 | 'truncate', 43 | 'unlink', 44 | 'utimes', 45 | 'writeFile' 46 | ].filter(key => { 47 | // Some commands are not available on some systems. Ex: 48 | // fs.cp was added in Node.js v16.7.0 49 | // fs.statfs was added in Node v19.6.0, v18.15.0 50 | // fs.glob was added in Node.js v22.0.0 51 | // fs.lchown is not available on at least some Linux 52 | return typeof fs[key] === 'function' 53 | }) 54 | 55 | // Export cloned fs: 56 | Object.assign(exports, fs) 57 | 58 | // Universalify async methods: 59 | api.forEach(method => { 60 | exports[method] = u(fs[method]) 61 | }) 62 | 63 | // We differ from mz/fs in that we still ship the old, broken, fs.exists() 64 | // since we are a drop-in replacement for the native module 65 | exports.exists = function (filename, callback) { 66 | if (typeof callback === 'function') { 67 | return fs.exists(filename, callback) 68 | } 69 | return new Promise(resolve => { 70 | return fs.exists(filename, resolve) 71 | }) 72 | } 73 | 74 | // fs.read(), fs.write(), fs.readv(), & fs.writev() need special treatment due to multiple callback args 75 | 76 | exports.read = function (fd, buffer, offset, length, position, callback) { 77 | if (typeof callback === 'function') { 78 | return fs.read(fd, buffer, offset, length, position, callback) 79 | } 80 | return new Promise((resolve, reject) => { 81 | fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => { 82 | if (err) return reject(err) 83 | resolve({ bytesRead, buffer }) 84 | }) 85 | }) 86 | } 87 | 88 | // Function signature can be 89 | // fs.write(fd, buffer[, offset[, length[, position]]], callback) 90 | // OR 91 | // fs.write(fd, string[, position[, encoding]], callback) 92 | // We need to handle both cases, so we use ...args 93 | exports.write = function (fd, buffer, ...args) { 94 | if (typeof args[args.length - 1] === 'function') { 95 | return fs.write(fd, buffer, ...args) 96 | } 97 | 98 | return new Promise((resolve, reject) => { 99 | fs.write(fd, buffer, ...args, (err, bytesWritten, buffer) => { 100 | if (err) return reject(err) 101 | resolve({ bytesWritten, buffer }) 102 | }) 103 | }) 104 | } 105 | 106 | // Function signature is 107 | // s.readv(fd, buffers[, position], callback) 108 | // We need to handle the optional arg, so we use ...args 109 | exports.readv = function (fd, buffers, ...args) { 110 | if (typeof args[args.length - 1] === 'function') { 111 | return fs.readv(fd, buffers, ...args) 112 | } 113 | 114 | return new Promise((resolve, reject) => { 115 | fs.readv(fd, buffers, ...args, (err, bytesRead, buffers) => { 116 | if (err) return reject(err) 117 | resolve({ bytesRead, buffers }) 118 | }) 119 | }) 120 | } 121 | 122 | // Function signature is 123 | // s.writev(fd, buffers[, position], callback) 124 | // We need to handle the optional arg, so we use ...args 125 | exports.writev = function (fd, buffers, ...args) { 126 | if (typeof args[args.length - 1] === 'function') { 127 | return fs.writev(fd, buffers, ...args) 128 | } 129 | 130 | return new Promise((resolve, reject) => { 131 | fs.writev(fd, buffers, ...args, (err, bytesWritten, buffers) => { 132 | if (err) return reject(err) 133 | resolve({ bytesWritten, buffers }) 134 | }) 135 | }) 136 | } 137 | 138 | // fs.realpath.native sometimes not available if fs is monkey-patched 139 | if (typeof fs.realpath.native === 'function') { 140 | exports.realpath.native = u(fs.realpath.native) 141 | } else { 142 | process.emitWarning( 143 | 'fs.realpath.native is not a function. Is fs being monkey-patched?', 144 | 'Warning', 'fs-extra-WARN0003' 145 | ) 146 | } 147 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | // Export promiseified graceful-fs: 5 | ...require('./fs'), 6 | // Export extra methods: 7 | ...require('./copy'), 8 | ...require('./empty'), 9 | ...require('./ensure'), 10 | ...require('./json'), 11 | ...require('./mkdirs'), 12 | ...require('./move'), 13 | ...require('./output-file'), 14 | ...require('./path-exists'), 15 | ...require('./remove') 16 | } 17 | -------------------------------------------------------------------------------- /lib/json/__tests__/jsonfile-integration.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('jsonfile-integration', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'json') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | describe('+ writeJsonSync / spaces', () => { 22 | it('should read a file and parse the json', () => { 23 | const obj1 = { 24 | firstName: 'JP', 25 | lastName: 'Richardson' 26 | } 27 | 28 | const file = path.join(TEST_DIR, 'file.json') 29 | fse.writeJsonSync(file, obj1) 30 | const data = fs.readFileSync(file, 'utf8') 31 | assert.strictEqual(data, JSON.stringify(obj1) + '\n') 32 | }) 33 | }) 34 | 35 | describe('+ writeJsonSync / EOL', () => { 36 | it('should read a file and parse the json', () => { 37 | const obj1 = { 38 | firstName: 'JP', 39 | lastName: 'Richardson' 40 | } 41 | 42 | const file = path.join(TEST_DIR, 'file.json') 43 | fse.writeJsonSync(file, obj1, { spaces: 2, EOL: '\r\n' }) 44 | const data = fs.readFileSync(file, 'utf8') 45 | assert.strictEqual( 46 | data, 47 | JSON.stringify(obj1, null, 2).replace(/\n/g, '\r\n') + '\r\n' 48 | ) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /lib/json/__tests__/output-json-sync.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global beforeEach, describe, it */ 10 | 11 | describe('json', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra-output-json-sync') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | describe('+ outputJsonSync(file, data)', () => { 20 | it('should write the file regardless of whether the directory exists or not', () => { 21 | const file = path.join(TEST_DIR, 'this-dir', 'does-not', 'exist', 'file.json') 22 | assert(!fs.existsSync(file)) 23 | 24 | const data = { name: 'JP' } 25 | fse.outputJsonSync(file, data) 26 | 27 | assert(fs.existsSync(file)) 28 | const newData = JSON.parse(fs.readFileSync(file, 'utf8')) 29 | 30 | assert.strictEqual(data.name, newData.name) 31 | }) 32 | 33 | describe('> when an option is passed, like JSON replacer', () => { 34 | it('should pass the option along to jsonfile module', () => { 35 | const file = path.join(TEST_DIR, 'this-dir', 'does-not', 'exist', 'really', 'file.json') 36 | assert(!fs.existsSync(file)) 37 | 38 | const replacer = (k, v) => v === 'JP' ? 'Jon Paul' : v 39 | const data = { name: 'JP' } 40 | 41 | fse.outputJsonSync(file, data, { replacer }) 42 | const newData = JSON.parse(fs.readFileSync(file, 'utf8')) 43 | 44 | assert.strictEqual(newData.name, 'Jon Paul') 45 | }) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /lib/json/__tests__/output-json.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global beforeEach, describe, it */ 10 | 11 | describe('json', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra-output-json') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | describe('+ outputJson(file, data)', () => { 20 | it('should write the file regardless of whether the directory exists or not', done => { 21 | const file = path.join(TEST_DIR, 'this-dir', 'prob-does-not', 'exist', 'file.json') 22 | assert(!fs.existsSync(file)) 23 | 24 | const data = { name: 'JP' } 25 | fse.outputJson(file, data, err => { 26 | if (err) return done(err) 27 | 28 | assert(fs.existsSync(file)) 29 | const newData = JSON.parse(fs.readFileSync(file, 'utf8')) 30 | 31 | assert.strictEqual(data.name, newData.name) 32 | done() 33 | }) 34 | }) 35 | 36 | it('should be mutation-proof', async () => { 37 | const dir = path.join(TEST_DIR, 'this-dir', 'certanly-does-not', 'exist') 38 | const file = path.join(dir, 'file.json') 39 | assert(!fs.existsSync(dir), 'directory cannot exist') 40 | 41 | const name = 'JP' 42 | const data = { name } 43 | const promise = fse.outputJson(file, data) 44 | // Mutate data right after call 45 | data.name = 'Ryan' 46 | // now await for the call to finish 47 | await promise 48 | 49 | assert(fs.existsSync(file)) 50 | const newData = JSON.parse(fs.readFileSync(file, 'utf8')) 51 | 52 | // mutation did not change data 53 | assert.strictEqual(newData.name, name) 54 | }) 55 | 56 | it('should support Promises', () => { 57 | const file = path.join(TEST_DIR, 'this-dir', 'prob-does-not', 'exist', 'file.json') 58 | assert(!fs.existsSync(file)) 59 | 60 | const data = { name: 'JP' } 61 | return fse.outputJson(file, data) 62 | }) 63 | 64 | describe('> when an option is passed, like JSON replacer', () => { 65 | it('should pass the option along to jsonfile module', done => { 66 | const file = path.join(TEST_DIR, 'this-dir', 'does-not', 'exist', 'really', 'file.json') 67 | assert(!fs.existsSync(file)) 68 | 69 | const replacer = (k, v) => v === 'JP' ? 'Jon Paul' : v 70 | const data = { name: 'JP' } 71 | 72 | fse.outputJson(file, data, { replacer }, err => { 73 | assert.ifError(err) 74 | const newData = JSON.parse(fs.readFileSync(file, 'utf8')) 75 | assert.strictEqual(newData.name, 'Jon Paul') 76 | done() 77 | }) 78 | }) 79 | }) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /lib/json/__tests__/promise-support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('json promise support', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'json') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | ;['writeJson', 'writeJSON'].forEach(method => { 22 | describe(method, () => { 23 | it('should support promises', () => { 24 | const obj1 = { 25 | firstName: 'JP', 26 | lastName: 'Richardson' 27 | } 28 | 29 | const file = path.join(TEST_DIR, 'promise.json') 30 | return fse[method](file, obj1) 31 | .then(() => { 32 | const data = fs.readFileSync(file, 'utf8') 33 | assert.strictEqual(data, JSON.stringify(obj1) + '\n') 34 | }) 35 | }) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /lib/json/__tests__/read.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('read', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'read-json') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | describe('+ readJSON', () => { 22 | it('should read a file and parse the json', done => { 23 | const obj1 = { 24 | firstName: 'JP', 25 | lastName: 'Richardson' 26 | } 27 | 28 | const file = path.join(TEST_DIR, 'file.json') 29 | fs.writeFileSync(file, JSON.stringify(obj1)) 30 | fse.readJSON(file, (err, obj2) => { 31 | assert.ifError(err) 32 | assert.strictEqual(obj1.firstName, obj2.firstName) 33 | assert.strictEqual(obj1.lastName, obj2.lastName) 34 | done() 35 | }) 36 | }) 37 | 38 | it('should error if it cant parse the json', done => { 39 | const file = path.join(TEST_DIR, 'file2.json') 40 | fs.writeFileSync(file, '%asdfasdff444') 41 | fse.readJSON(file, (err, obj) => { 42 | assert(err) 43 | assert(!obj) 44 | done() 45 | }) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /lib/json/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const u = require('universalify').fromPromise 4 | const jsonFile = require('./jsonfile') 5 | 6 | jsonFile.outputJson = u(require('./output-json')) 7 | jsonFile.outputJsonSync = require('./output-json-sync') 8 | // aliases 9 | jsonFile.outputJSON = jsonFile.outputJson 10 | jsonFile.outputJSONSync = jsonFile.outputJsonSync 11 | jsonFile.writeJSON = jsonFile.writeJson 12 | jsonFile.writeJSONSync = jsonFile.writeJsonSync 13 | jsonFile.readJSON = jsonFile.readJson 14 | jsonFile.readJSONSync = jsonFile.readJsonSync 15 | 16 | module.exports = jsonFile 17 | -------------------------------------------------------------------------------- /lib/json/jsonfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const jsonFile = require('jsonfile') 4 | 5 | module.exports = { 6 | // jsonfile exports 7 | readJson: jsonFile.readFile, 8 | readJsonSync: jsonFile.readFileSync, 9 | writeJson: jsonFile.writeFile, 10 | writeJsonSync: jsonFile.writeFileSync 11 | } 12 | -------------------------------------------------------------------------------- /lib/json/output-json-sync.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { stringify } = require('jsonfile/utils') 4 | const { outputFileSync } = require('../output-file') 5 | 6 | function outputJsonSync (file, data, options) { 7 | const str = stringify(data, options) 8 | 9 | outputFileSync(file, str, options) 10 | } 11 | 12 | module.exports = outputJsonSync 13 | -------------------------------------------------------------------------------- /lib/json/output-json.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { stringify } = require('jsonfile/utils') 4 | const { outputFile } = require('../output-file') 5 | 6 | async function outputJson (file, data, options = {}) { 7 | const str = stringify(data, options) 8 | 9 | await outputFile(file, str, options) 10 | } 11 | 12 | module.exports = outputJson 13 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/chmod.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../../') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('mkdirp / chmod', () => { 12 | let TEST_DIR 13 | let TEST_SUBDIR 14 | 15 | beforeEach(done => { 16 | const ps = [] 17 | for (let i = 0; i < 15; i++) { 18 | const dir = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 19 | ps.push(dir) 20 | } 21 | 22 | TEST_SUBDIR = ps.join(path.sep) 23 | 24 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-chmod') 25 | TEST_SUBDIR = path.join(TEST_DIR, TEST_SUBDIR) 26 | 27 | fse.emptyDir(TEST_DIR, done) 28 | }) 29 | 30 | afterEach(done => fse.remove(TEST_DIR, done)) 31 | 32 | it('chmod-pre', done => { 33 | const mode = 0o744 34 | fse.mkdirp(TEST_SUBDIR, mode, err => { 35 | assert.ifError(err, 'should not error') 36 | fs.stat(TEST_SUBDIR, (err, stat) => { 37 | assert.ifError(err, 'should exist') 38 | assert.ok(stat && stat.isDirectory(), 'should be directory') 39 | 40 | if (os.platform().indexOf('win') === 0) { 41 | assert.strictEqual(stat && stat.mode & 0o777, 0o666, 'windows shit') 42 | } else { 43 | assert.strictEqual(stat && stat.mode & 0o777, mode, 'should be 0744') 44 | } 45 | 46 | done() 47 | }) 48 | }) 49 | }) 50 | 51 | it('chmod', done => { 52 | const mode = 0o755 53 | fse.mkdirp(TEST_SUBDIR, mode, err => { 54 | assert.ifError(err, 'should not error') 55 | fs.stat(TEST_SUBDIR, (err, stat) => { 56 | assert.ifError(err, 'should exist') 57 | assert.ok(stat && stat.isDirectory(), 'should be directory') 58 | done() 59 | }) 60 | }) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/clobber.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global before, describe, it */ 10 | 11 | describe('mkdirp / clobber', () => { 12 | let TEST_DIR 13 | let file 14 | 15 | before(done => { 16 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-clobber') 17 | fse.emptyDir(TEST_DIR, err => { 18 | assert.ifError(err) 19 | 20 | const ps = [TEST_DIR] 21 | 22 | for (let i = 0; i < 15; i++) { 23 | const dir = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 24 | ps.push(dir) 25 | } 26 | 27 | file = ps.join(path.sep) 28 | 29 | // a file in the way 30 | const itw = ps.slice(0, 2).join(path.sep) 31 | 32 | fs.writeFileSync(itw, 'I AM IN THE WAY, THE TRUTH, AND THE LIGHT.') 33 | 34 | fs.stat(itw, (err, stat) => { 35 | assert.ifError(err) 36 | assert.ok(stat && stat.isFile(), 'should be file') 37 | done() 38 | }) 39 | }) 40 | }) 41 | 42 | it('should clobber', done => { 43 | fse.mkdirp(file, 0o755, err => { 44 | assert.ok(err) 45 | assert.strictEqual(err.code, 'ENOTDIR') 46 | done() 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/issue-209.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assert = require('assert') 4 | const fse = require('../..') 5 | 6 | /* global describe, it */ 7 | 8 | describe('mkdirp: issue-209, win32, when bad path, should return a cleaner error', () => { 9 | // only seems to be an issue on Windows. 10 | if (process.platform !== 'win32') return 11 | 12 | it('should return a callback', done => { 13 | const file = './bad?dir' 14 | fse.mkdirp(file, err => { 15 | assert(err, 'error is present') 16 | assert.strictEqual(err.code, 'EINVAL') 17 | 18 | const file2 = 'c:\\tmp\foo:moo' 19 | fse.mkdirp(file2, err => { 20 | assert(err, 'error is present') 21 | assert.strictEqual(err.code, 'EINVAL') 22 | done() 23 | }) 24 | }) 25 | }) 26 | 27 | describe('> sync', () => { 28 | it('should throw an error', () => { 29 | let didErr 30 | try { 31 | const file = 'c:\\tmp\foo:moo' 32 | fse.mkdirpSync(file) 33 | } catch { 34 | didErr = true 35 | } 36 | assert(didErr) 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/issue-93.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const os = require('os') 4 | const fse = require('../..') 5 | const path = require('path') 6 | const assert = require('assert') 7 | const util = require('util') 8 | 9 | /* global before, describe, it */ 10 | 11 | describe('mkdirp: issue-93, win32, when drive does not exist, it should return a cleaner error', () => { 12 | let TEST_DIR 13 | 14 | // only seems to be an issue on Windows. 15 | if (process.platform !== 'win32') return 16 | 17 | before(done => { 18 | TEST_DIR = path.join(os.tmpdir(), 'tests', 'fs-extra', 'mkdirp-issue-93') 19 | fse.emptyDir(TEST_DIR, err => { 20 | assert.ifError(err) 21 | done() 22 | }) 23 | }) 24 | 25 | it('should return a cleaner error than inifinite loop, stack crash', done => { 26 | const file = 'R:\\afasd\\afaff\\fdfd' // hopefully drive 'r' does not exist on appveyor 27 | // Different error codes on different Node versions (matches native mkdir behavior) 28 | const assertErr = (err) => assert( 29 | ['EPERM', 'ENOENT'].includes(err.code), 30 | `expected 'EPERM' or 'ENOENT', got ${util.inspect(err.code)}` 31 | ) 32 | 33 | fse.mkdirp(file, err => { 34 | assertErr(err) 35 | 36 | try { 37 | fse.mkdirsSync(file) 38 | } catch (err) { 39 | assertErr(err) 40 | } 41 | 42 | done() 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/mkdir.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('fs-extra', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdir') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | describe('+ mkdirs()', () => { 22 | it('should make the directory', done => { 23 | const dir = path.join(TEST_DIR, 'tmp-' + Date.now() + Math.random()) 24 | 25 | assert(!fs.existsSync(dir)) 26 | 27 | fse.mkdirs(dir, err => { 28 | assert.ifError(err) 29 | assert(fs.existsSync(dir)) 30 | done() 31 | }) 32 | }) 33 | 34 | it('should make the entire directory path', done => { 35 | const dir = path.join(TEST_DIR, 'tmp-' + Date.now() + Math.random()) 36 | const newDir = path.join(TEST_DIR, 'dfdf', 'ffff', 'aaa') 37 | 38 | assert(!fs.existsSync(dir)) 39 | 40 | fse.mkdirs(newDir, err => { 41 | assert.ifError(err) 42 | assert(fs.existsSync(newDir)) 43 | done() 44 | }) 45 | }) 46 | }) 47 | 48 | describe('+ mkdirsSync()', () => { 49 | it('should make the directory', done => { 50 | const dir = path.join(TEST_DIR, 'tmp-' + Date.now() + Math.random()) 51 | 52 | assert(!fs.existsSync(dir)) 53 | fse.mkdirsSync(dir) 54 | assert(fs.existsSync(dir)) 55 | 56 | done() 57 | }) 58 | 59 | it('should make the entire directory path', done => { 60 | const dir = path.join(TEST_DIR, 'tmp-' + Date.now() + Math.random()) 61 | const newDir = path.join(dir, 'dfdf', 'ffff', 'aaa') 62 | 63 | assert(!fs.existsSync(newDir)) 64 | fse.mkdirsSync(newDir) 65 | assert(fs.existsSync(newDir)) 66 | 67 | done() 68 | }) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/mkdirp.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('mkdirp / mkdirp', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | it('should make the dir', done => { 22 | const x = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 23 | const y = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 24 | const z = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 25 | 26 | const file = path.join(TEST_DIR, x, y, z) 27 | 28 | fse.mkdirp(file, 0o755, err => { 29 | assert.ifError(err) 30 | fse.pathExists(file, (err, ex) => { 31 | assert.ifError(err) 32 | assert.ok(ex, 'file created') 33 | fs.stat(file, (err, stat) => { 34 | assert.ifError(err) 35 | 36 | if (os.platform().indexOf('win') === 0) { 37 | assert.strictEqual(stat.mode & 0o777, 0o666) 38 | } else { 39 | assert.strictEqual(stat.mode & 0o777, 0o755) 40 | } 41 | 42 | assert.ok(stat.isDirectory(), 'target not a directory') 43 | done() 44 | }) 45 | }) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/opts-undef.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global beforeEach, describe, it */ 10 | 11 | describe('mkdirs / opts-undef', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirs') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | // https://github.com/substack/node-mkdirp/issues/45 20 | it('should not hang', done => { 21 | const newDir = path.join(TEST_DIR, 'doest', 'not', 'exist') 22 | assert(!fs.existsSync(newDir)) 23 | 24 | fse.mkdirs(newDir, undefined, err => { 25 | assert.ifError(err) 26 | assert(fs.existsSync(newDir)) 27 | done() 28 | }) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/perm.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../../') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('mkdirp / perm', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-perm') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | it('async perm', done => { 22 | const file = path.join(TEST_DIR, (Math.random() * (1 << 30)).toString(16)) 23 | 24 | fse.mkdirp(file, 0o755, err => { 25 | assert.ifError(err) 26 | fse.pathExists(file, (err, ex) => { 27 | assert.ifError(err) 28 | assert.ok(ex, 'file created') 29 | fs.stat(file, (err, stat) => { 30 | assert.ifError(err) 31 | 32 | if (os.platform().indexOf('win') === 0) { 33 | assert.strictEqual(stat.mode & 0o777, 0o666) 34 | } else { 35 | assert.strictEqual(stat.mode & 0o777, 0o755) 36 | } 37 | 38 | assert.ok(stat.isDirectory(), 'target not a directory') 39 | done() 40 | }) 41 | }) 42 | }) 43 | }) 44 | 45 | it('async root perm', done => { 46 | fse.mkdirp(path.join(os.tmpdir(), 'tmp'), 0o755, err => { 47 | assert.ifError(err) 48 | done() 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/perm_sync.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('mkdirp / perm_sync', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-perm-sync') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | it('sync perm', done => { 22 | const file = path.join(TEST_DIR, (Math.random() * (1 << 30)).toString(16) + '.json') 23 | 24 | fse.mkdirpSync(file, 0o755) 25 | fse.pathExists(file, (err, ex) => { 26 | assert.ifError(err) 27 | assert.ok(ex, 'file created') 28 | fs.stat(file, (err, stat) => { 29 | assert.ifError(err) 30 | 31 | if (os.platform().indexOf('win') === 0) { 32 | assert.strictEqual(stat.mode & 0o777, 0o666) 33 | } else { 34 | assert.strictEqual(stat.mode & 0o777, 0o755) 35 | } 36 | 37 | assert.ok(stat.isDirectory(), 'target not a directory') 38 | done() 39 | }) 40 | }) 41 | }) 42 | 43 | it('sync root perm', done => { 44 | const file = TEST_DIR 45 | fse.mkdirpSync(file, 0o755) 46 | fse.pathExists(file, (err, ex) => { 47 | assert.ifError(err) 48 | assert.ok(ex, 'file created') 49 | fs.stat(file, (err, stat) => { 50 | assert.ifError(err) 51 | assert.ok(stat.isDirectory(), 'target not a directory') 52 | done() 53 | }) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/race.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('mkdirp / race', () => { 12 | let TEST_DIR 13 | let file 14 | 15 | beforeEach(done => { 16 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-race') 17 | fse.emptyDir(TEST_DIR, err => { 18 | assert.ifError(err) 19 | 20 | const ps = [TEST_DIR] 21 | 22 | for (let i = 0; i < 15; i++) { 23 | const dir = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 24 | ps.push(dir) 25 | } 26 | 27 | file = path.join(...ps) 28 | done() 29 | }) 30 | }) 31 | 32 | afterEach(done => fse.remove(TEST_DIR, done)) 33 | 34 | it('race', done => { 35 | let res = 2 36 | 37 | mk(file, () => --res === 0 ? done() : undefined) 38 | mk(file, () => --res === 0 ? done() : undefined) 39 | 40 | function mk (file, callback) { 41 | fse.mkdirp(file, 0o755, err => { 42 | assert.ifError(err) 43 | fse.pathExists(file, (err, ex) => { 44 | assert.ifError(err) 45 | assert.ok(ex, 'file created') 46 | fs.stat(file, (err, stat) => { 47 | assert.ifError(err) 48 | 49 | if (os.platform().indexOf('win') === 0) { 50 | assert.strictEqual(stat.mode & 0o777, 0o666) 51 | } else { 52 | assert.strictEqual(stat.mode & 0o777, 0o755) 53 | } 54 | 55 | assert.ok(stat.isDirectory(), 'target not a directory') 56 | if (callback) callback() 57 | }) 58 | }) 59 | }) 60 | } 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/rel.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const CWD = process.cwd() 4 | 5 | const fs = require('fs') 6 | const os = require('os') 7 | const fse = require('../..') 8 | const path = require('path') 9 | const assert = require('assert') 10 | 11 | /* global afterEach, beforeEach, describe, it */ 12 | 13 | describe('mkdirp / relative', () => { 14 | let TEST_DIR 15 | let file 16 | 17 | beforeEach(done => { 18 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-relative') 19 | fse.emptyDir(TEST_DIR, err => { 20 | assert.ifError(err) 21 | 22 | const x = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 23 | const y = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 24 | const z = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 25 | 26 | // relative path 27 | file = path.join(x, y, z) 28 | 29 | done() 30 | }) 31 | }) 32 | 33 | afterEach(done => fse.remove(TEST_DIR, done)) 34 | 35 | it('should make the directory with relative path', done => { 36 | process.chdir(TEST_DIR) 37 | 38 | fse.mkdirp(file, 0o755, err => { 39 | assert.ifError(err) 40 | fse.pathExists(file, (err, ex) => { 41 | assert.ifError(err) 42 | assert.ok(ex, 'file created') 43 | fs.stat(file, (err, stat) => { 44 | assert.ifError(err) 45 | // restore 46 | process.chdir(CWD) 47 | 48 | if (os.platform().indexOf('win') === 0) { 49 | assert.strictEqual(stat.mode & 0o777, 0o666) 50 | } else { 51 | assert.strictEqual(stat.mode & 0o777, 0o755) 52 | } 53 | 54 | assert.ok(stat.isDirectory(), 'target not a directory') 55 | done() 56 | }) 57 | }) 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const fse = require('../../') 5 | const path = require('path') 6 | const assert = require('assert') 7 | 8 | /* global describe, it */ 9 | 10 | describe('mkdirp / root', () => { 11 | // '/' on unix 12 | const dir = path.normalize(path.resolve(path.sep)).toLowerCase() 13 | 14 | // Windows does not have permission to mkdir on root 15 | if (process.platform === 'win32') return 16 | 17 | it('should', done => { 18 | fse.mkdirp(dir, 0o755, err => { 19 | if (err) return done(err) 20 | fs.stat(dir, (er, stat) => { 21 | if (er) return done(er) 22 | assert.ok(stat.isDirectory(), 'target is a directory') 23 | done() 24 | }) 25 | }) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /lib/mkdirs/__tests__/sync.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('mkdirp / sync', () => { 12 | let TEST_DIR 13 | let file 14 | 15 | beforeEach(done => { 16 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-sync') 17 | fse.emptyDir(TEST_DIR, err => { 18 | assert.ifError(err) 19 | 20 | const x = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 21 | const y = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 22 | const z = Math.floor(Math.random() * Math.pow(16, 4)).toString(16) 23 | 24 | file = path.join(TEST_DIR, x, y, z) 25 | 26 | done() 27 | }) 28 | }) 29 | 30 | afterEach(done => fse.remove(TEST_DIR, done)) 31 | 32 | it('should', done => { 33 | try { 34 | fse.mkdirpSync(file, 0o755) 35 | } catch (err) { 36 | assert.fail(err) 37 | } 38 | 39 | fse.pathExists(file, (err, ex) => { 40 | assert.ifError(err) 41 | assert.ok(ex, 'file created') 42 | fs.stat(file, (err, stat) => { 43 | assert.ifError(err) 44 | // http://stackoverflow.com/questions/592448/c-how-to-set-file-permissions-cross-platform 45 | if (os.platform().indexOf('win') === 0) { 46 | assert.strictEqual(stat.mode & 0o777, 0o666) 47 | } else { 48 | assert.strictEqual(stat.mode & 0o777, 0o755) 49 | } 50 | 51 | assert.ok(stat.isDirectory(), 'target not a directory') 52 | done() 53 | }) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /lib/mkdirs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const u = require('universalify').fromPromise 3 | const { makeDir: _makeDir, makeDirSync } = require('./make-dir') 4 | const makeDir = u(_makeDir) 5 | 6 | module.exports = { 7 | mkdirs: makeDir, 8 | mkdirsSync: makeDirSync, 9 | // alias 10 | mkdirp: makeDir, 11 | mkdirpSync: makeDirSync, 12 | ensureDir: makeDir, 13 | ensureDirSync: makeDirSync 14 | } 15 | -------------------------------------------------------------------------------- /lib/mkdirs/make-dir.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const fs = require('../fs') 3 | const { checkPath } = require('./utils') 4 | 5 | const getMode = options => { 6 | const defaults = { mode: 0o777 } 7 | if (typeof options === 'number') return options 8 | return ({ ...defaults, ...options }).mode 9 | } 10 | 11 | module.exports.makeDir = async (dir, options) => { 12 | checkPath(dir) 13 | 14 | return fs.mkdir(dir, { 15 | mode: getMode(options), 16 | recursive: true 17 | }) 18 | } 19 | 20 | module.exports.makeDirSync = (dir, options) => { 21 | checkPath(dir) 22 | 23 | return fs.mkdirSync(dir, { 24 | mode: getMode(options), 25 | recursive: true 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /lib/mkdirs/utils.js: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/sindresorhus/make-dir 2 | // Copyright (c) Sindre Sorhus (sindresorhus.com) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 5 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | 'use strict' 7 | const path = require('path') 8 | 9 | // https://github.com/nodejs/node/issues/8987 10 | // https://github.com/libuv/libuv/pull/1088 11 | module.exports.checkPath = function checkPath (pth) { 12 | if (process.platform === 'win32') { 13 | const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path.parse(pth).root, '')) 14 | 15 | if (pathHasInvalidWinCharacters) { 16 | const error = new Error(`Path contains invalid characters: ${pth}`) 17 | error.code = 'EINVAL' 18 | throw error 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/move/__tests__/cross-device-utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('graceful-fs') 2 | const path = require('path') 3 | 4 | const { CROSS_DEVICE_PATH } = process.env 5 | let runCrossDeviceTests = !!CROSS_DEVICE_PATH 6 | 7 | if (runCrossDeviceTests) { 8 | // make sure we have permission on device 9 | try { 10 | fs.writeFileSync(path.join(CROSS_DEVICE_PATH, 'file'), 'hi') 11 | } catch { 12 | runCrossDeviceTests = false 13 | throw new Error(`Can't write to device ${CROSS_DEVICE_PATH}`) 14 | } 15 | } else console.log('Skipping cross-device move tests') 16 | 17 | module.exports = { 18 | differentDevice: CROSS_DEVICE_PATH, 19 | ifCrossDeviceEnabled: (fn) => runCrossDeviceTests ? fn : fn.skip 20 | } 21 | -------------------------------------------------------------------------------- /lib/move/__tests__/move-case-insensitive-paths.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assert = require('assert') 4 | const os = require('os') 5 | const path = require('path') 6 | const fs = require('../../') 7 | 8 | /* global beforeEach, afterEach, describe, it */ 9 | 10 | describe('+ move() - case insensitive paths', () => { 11 | let TEST_DIR = '' 12 | let src = '' 13 | let dest = '' 14 | 15 | beforeEach(done => { 16 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-case-insensitive-paths') 17 | fs.emptyDir(TEST_DIR, done) 18 | }) 19 | 20 | afterEach(done => fs.remove(TEST_DIR, done)) 21 | 22 | describe('> when src is a directory', () => { 23 | it('should move successfully', done => { 24 | src = path.join(TEST_DIR, 'srcdir') 25 | fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data') 26 | dest = path.join(TEST_DIR, 'srcDir') 27 | 28 | fs.move(src, dest, err => { 29 | assert.ifError(err) 30 | assert(fs.existsSync(dest)) 31 | assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data') 32 | done() 33 | }) 34 | }) 35 | }) 36 | 37 | describe('> when src is a file', () => { 38 | it('should move successfully', done => { 39 | src = path.join(TEST_DIR, 'srcfile') 40 | fs.outputFileSync(src, 'some data') 41 | dest = path.join(TEST_DIR, 'srcFile') 42 | 43 | fs.move(src, dest, err => { 44 | assert.ifError(err) 45 | assert(fs.existsSync(dest)) 46 | assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data') 47 | done() 48 | }) 49 | }) 50 | }) 51 | 52 | describe('> when src is a symlink', () => { 53 | it('should move successfully, symlink dir', done => { 54 | src = path.join(TEST_DIR, 'srcdir') 55 | fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data') 56 | const srcLink = path.join(TEST_DIR, 'src-symlink') 57 | fs.symlinkSync(src, srcLink, 'dir') 58 | dest = path.join(TEST_DIR, 'src-Symlink') 59 | 60 | fs.move(srcLink, dest, err => { 61 | assert.ifError(err) 62 | assert(fs.existsSync(dest)) 63 | assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data') 64 | const destLink = fs.readlinkSync(dest) 65 | assert.strictEqual(destLink, src) 66 | done() 67 | }) 68 | }) 69 | 70 | it('should move successfully, symlink file', done => { 71 | src = path.join(TEST_DIR, 'srcfile') 72 | fs.outputFileSync(src, 'some data') 73 | const srcLink = path.join(TEST_DIR, 'src-symlink') 74 | fs.symlinkSync(src, srcLink, 'file') 75 | dest = path.join(TEST_DIR, 'src-Symlink') 76 | 77 | fs.move(srcLink, dest, err => { 78 | assert.ifError(err) 79 | assert(fs.existsSync(dest)) 80 | assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data') 81 | const destLink = fs.readlinkSync(dest) 82 | assert.strictEqual(destLink, src) 83 | done() 84 | }) 85 | }) 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /lib/move/__tests__/move-preserve-timestamp.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../../') 4 | const os = require('os') 5 | const path = require('path') 6 | const utimesSync = require('../../util/utimes').utimesMillisSync 7 | const assert = require('assert') 8 | const fse = require('../../index') 9 | const { differentDevice, ifCrossDeviceEnabled } = require('./cross-device-utils') 10 | 11 | /* global beforeEach, afterEach, describe, it */ 12 | 13 | if (process.arch === 'ia32') console.warn('32 bit arch; skipping move timestamp tests') 14 | 15 | const describeIfPractical = process.arch === 'ia32' ? describe.skip : describe 16 | 17 | describeIfPractical('move() - across different devices', () => { 18 | let TEST_DIR, SRC, DEST, FILES 19 | 20 | function setupFixture (readonly) { 21 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync-preserve-timestamp') 22 | SRC = path.join(differentDevice, 'some/weird/dir-really-weird') 23 | DEST = path.join(TEST_DIR, 'dest') 24 | FILES = ['a-file', path.join('a-folder', 'another-file'), path.join('a-folder', 'another-folder', 'file3')] 25 | const timestamp = Date.now() / 1000 - 5 26 | FILES.forEach(f => { 27 | const filePath = path.join(SRC, f) 28 | fs.ensureFileSync(filePath) 29 | // rewind timestamps to make sure that coarser OS timestamp resolution 30 | // does not alter results 31 | utimesSync(filePath, timestamp, timestamp) 32 | if (readonly) { 33 | fs.chmodSync(filePath, 0o444) 34 | } 35 | }) 36 | } 37 | 38 | afterEach(() => { 39 | fse.removeSync(TEST_DIR) 40 | fse.removeSync(SRC) 41 | }) 42 | 43 | ifCrossDeviceEnabled(describe)('> default behaviour', () => { 44 | ;[ 45 | { subcase: 'writable', readonly: false }, 46 | { subcase: 'readonly', readonly: true } 47 | ].forEach(params => { 48 | describe(`>> with ${params.subcase} source files`, () => { 49 | beforeEach(() => setupFixture(params.readonly)) 50 | 51 | it('should have the same timestamps after move', done => { 52 | const originalTimestamps = FILES.map(file => { 53 | const originalPath = path.join(SRC, file) 54 | const originalStat = fs.statSync(originalPath) 55 | return { 56 | mtime: originalStat.mtime.getTime(), 57 | atime: originalStat.atime.getTime() 58 | } 59 | }) 60 | fse.move(SRC, DEST, {}, (err) => { 61 | if (err) return done(err) 62 | FILES.forEach(testFile({}, originalTimestamps)) 63 | done() 64 | }) 65 | }) 66 | }) 67 | }) 68 | }) 69 | 70 | function testFile (options, originalTimestamps) { 71 | return function (file, idx) { 72 | const originalTimestamp = originalTimestamps[idx] 73 | const currentPath = path.join(DEST, file) 74 | const currentStats = fs.statSync(currentPath) 75 | assert.strictEqual(currentStats.mtime.getTime(), originalTimestamp.mtime, 'different mtime values') 76 | assert.strictEqual(currentStats.atime.getTime(), originalTimestamp.atime, 'different atime values') 77 | } 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /lib/move/__tests__/move-sync-case-insensitive-paths.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assert = require('assert') 4 | const os = require('os') 5 | const path = require('path') 6 | const fs = require('../../') 7 | 8 | /* global beforeEach, afterEach, describe, it */ 9 | 10 | describe('+ moveSync() - case insensitive paths', () => { 11 | let TEST_DIR = '' 12 | let src = '' 13 | let dest = '' 14 | 15 | beforeEach(done => { 16 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync-case-insensitive-paths') 17 | fs.emptyDir(TEST_DIR, done) 18 | }) 19 | 20 | afterEach(() => fs.removeSync(TEST_DIR)) 21 | 22 | describe('> when src is a directory', () => { 23 | it('should move successfully', () => { 24 | src = path.join(TEST_DIR, 'srcdir') 25 | fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data') 26 | dest = path.join(TEST_DIR, 'srcDir') 27 | 28 | fs.moveSync(src, dest) 29 | assert(fs.existsSync(dest)) 30 | assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data') 31 | }) 32 | }) 33 | 34 | describe('> when src is a file', () => { 35 | it('should move successfully', () => { 36 | src = path.join(TEST_DIR, 'srcfile') 37 | fs.outputFileSync(src, 'some data') 38 | dest = path.join(TEST_DIR, 'srcFile') 39 | 40 | fs.moveSync(src, dest) 41 | assert(fs.existsSync(dest)) 42 | assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data') 43 | }) 44 | }) 45 | 46 | describe('> when src is a symlink', () => { 47 | it('should move successfully, symlink dir', () => { 48 | src = path.join(TEST_DIR, 'srcdir') 49 | fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data') 50 | const srcLink = path.join(TEST_DIR, 'src-symlink') 51 | fs.symlinkSync(src, srcLink, 'dir') 52 | dest = path.join(TEST_DIR, 'src-Symlink') 53 | 54 | fs.moveSync(srcLink, dest) 55 | assert(fs.existsSync(dest)) 56 | assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data') 57 | const destLink = fs.readlinkSync(dest) 58 | assert.strictEqual(destLink, src) 59 | }) 60 | 61 | it('should move successfully, symlink file', () => { 62 | src = path.join(TEST_DIR, 'srcfile') 63 | fs.outputFileSync(src, 'some data') 64 | const srcLink = path.join(TEST_DIR, 'src-symlink') 65 | fs.symlinkSync(src, srcLink, 'file') 66 | dest = path.join(TEST_DIR, 'src-Symlink') 67 | 68 | fs.moveSync(srcLink, dest) 69 | assert(fs.existsSync(dest)) 70 | assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data') 71 | const destLink = fs.readlinkSync(dest) 72 | assert.strictEqual(destLink, src) 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /lib/move/__tests__/move-sync-preserve-timestamp.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../../') 4 | const os = require('os') 5 | const path = require('path') 6 | const utimesSync = require('../../util/utimes').utimesMillisSync 7 | const assert = require('assert') 8 | const fse = require('../../index') 9 | const { differentDevice, ifCrossDeviceEnabled } = require('./cross-device-utils') 10 | 11 | /* global beforeEach, afterEach, describe, it */ 12 | 13 | if (process.arch === 'ia32') console.warn('32 bit arch; skipping move timestamp tests') 14 | 15 | const describeIfPractical = process.arch === 'ia32' ? describe.skip : describe 16 | 17 | describeIfPractical('moveSync() - across different devices', () => { 18 | let TEST_DIR, SRC, DEST, FILES 19 | 20 | function setupFixture (readonly) { 21 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync-preserve-timestamp') 22 | SRC = path.join(differentDevice, 'some/weird/dir-really-weird') 23 | DEST = path.join(TEST_DIR, 'dest') 24 | FILES = ['a-file', path.join('a-folder', 'another-file'), path.join('a-folder', 'another-folder', 'file3')] 25 | const timestamp = Date.now() / 1000 - 5 26 | FILES.forEach(f => { 27 | const filePath = path.join(SRC, f) 28 | fs.ensureFileSync(filePath) 29 | // rewind timestamps to make sure that coarser OS timestamp resolution 30 | // does not alter results 31 | utimesSync(filePath, timestamp, timestamp) 32 | if (readonly) { 33 | fs.chmodSync(filePath, 0o444) 34 | } 35 | }) 36 | } 37 | 38 | afterEach(() => { 39 | fse.removeSync(TEST_DIR) 40 | fse.removeSync(SRC) 41 | }) 42 | 43 | ifCrossDeviceEnabled(describe)('> default behaviour', () => { 44 | ;[ 45 | { subcase: 'writable', readonly: false }, 46 | { subcase: 'readonly', readonly: true } 47 | ].forEach(params => { 48 | describe(`>> with ${params.subcase} source files`, () => { 49 | beforeEach(() => setupFixture(params.readonly)) 50 | 51 | it('should have the same timestamps after move', () => { 52 | const originalTimestamps = FILES.map(file => { 53 | const originalPath = path.join(SRC, file) 54 | const originalStat = fs.statSync(originalPath) 55 | return { 56 | mtime: originalStat.mtime.getTime(), 57 | atime: originalStat.atime.getTime() 58 | } 59 | }) 60 | fse.moveSync(SRC, DEST, {}) 61 | FILES.forEach(testFile({}, originalTimestamps)) 62 | }) 63 | }) 64 | }) 65 | }) 66 | 67 | function testFile (options, originalTimestamps) { 68 | return function (file, idx) { 69 | const originalTimestamp = originalTimestamps[idx] 70 | const currentPath = path.join(DEST, file) 71 | const currentStats = fs.statSync(currentPath) 72 | // Windows sub-second precision fixed: https://github.com/nodejs/io.js/issues/2069 73 | assert.strictEqual(currentStats.mtime.getTime(), originalTimestamp.mtime, 'different mtime values') 74 | assert.strictEqual(currentStats.atime.getTime(), originalTimestamp.atime, 'different atime values') 75 | } 76 | } 77 | }) 78 | -------------------------------------------------------------------------------- /lib/move/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const u = require('universalify').fromPromise 4 | module.exports = { 5 | move: u(require('./move')), 6 | moveSync: require('./move-sync') 7 | } 8 | -------------------------------------------------------------------------------- /lib/move/move-sync.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('graceful-fs') 4 | const path = require('path') 5 | const copySync = require('../copy').copySync 6 | const removeSync = require('../remove').removeSync 7 | const mkdirpSync = require('../mkdirs').mkdirpSync 8 | const stat = require('../util/stat') 9 | 10 | function moveSync (src, dest, opts) { 11 | opts = opts || {} 12 | const overwrite = opts.overwrite || opts.clobber || false 13 | 14 | const { srcStat, isChangingCase = false } = stat.checkPathsSync(src, dest, 'move', opts) 15 | stat.checkParentPathsSync(src, srcStat, dest, 'move') 16 | if (!isParentRoot(dest)) mkdirpSync(path.dirname(dest)) 17 | return doRename(src, dest, overwrite, isChangingCase) 18 | } 19 | 20 | function isParentRoot (dest) { 21 | const parent = path.dirname(dest) 22 | const parsedPath = path.parse(parent) 23 | return parsedPath.root === parent 24 | } 25 | 26 | function doRename (src, dest, overwrite, isChangingCase) { 27 | if (isChangingCase) return rename(src, dest, overwrite) 28 | if (overwrite) { 29 | removeSync(dest) 30 | return rename(src, dest, overwrite) 31 | } 32 | if (fs.existsSync(dest)) throw new Error('dest already exists.') 33 | return rename(src, dest, overwrite) 34 | } 35 | 36 | function rename (src, dest, overwrite) { 37 | try { 38 | fs.renameSync(src, dest) 39 | } catch (err) { 40 | if (err.code !== 'EXDEV') throw err 41 | return moveAcrossDevice(src, dest, overwrite) 42 | } 43 | } 44 | 45 | function moveAcrossDevice (src, dest, overwrite) { 46 | const opts = { 47 | overwrite, 48 | errorOnExist: true, 49 | preserveTimestamps: true 50 | } 51 | copySync(src, dest, opts) 52 | return removeSync(src) 53 | } 54 | 55 | module.exports = moveSync 56 | -------------------------------------------------------------------------------- /lib/move/move.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../fs') 4 | const path = require('path') 5 | const { copy } = require('../copy') 6 | const { remove } = require('../remove') 7 | const { mkdirp } = require('../mkdirs') 8 | const { pathExists } = require('../path-exists') 9 | const stat = require('../util/stat') 10 | 11 | async function move (src, dest, opts = {}) { 12 | const overwrite = opts.overwrite || opts.clobber || false 13 | 14 | const { srcStat, isChangingCase = false } = await stat.checkPaths(src, dest, 'move', opts) 15 | 16 | await stat.checkParentPaths(src, srcStat, dest, 'move') 17 | 18 | // If the parent of dest is not root, make sure it exists before proceeding 19 | const destParent = path.dirname(dest) 20 | const parsedParentPath = path.parse(destParent) 21 | if (parsedParentPath.root !== destParent) { 22 | await mkdirp(destParent) 23 | } 24 | 25 | return doRename(src, dest, overwrite, isChangingCase) 26 | } 27 | 28 | async function doRename (src, dest, overwrite, isChangingCase) { 29 | if (!isChangingCase) { 30 | if (overwrite) { 31 | await remove(dest) 32 | } else if (await pathExists(dest)) { 33 | throw new Error('dest already exists.') 34 | } 35 | } 36 | 37 | try { 38 | // Try w/ rename first, and try copy + remove if EXDEV 39 | await fs.rename(src, dest) 40 | } catch (err) { 41 | if (err.code !== 'EXDEV') { 42 | throw err 43 | } 44 | await moveAcrossDevice(src, dest, overwrite) 45 | } 46 | } 47 | 48 | async function moveAcrossDevice (src, dest, overwrite) { 49 | const opts = { 50 | overwrite, 51 | errorOnExist: true, 52 | preserveTimestamps: true 53 | } 54 | 55 | await copy(src, dest, opts) 56 | return remove(src) 57 | } 58 | 59 | module.exports = move 60 | -------------------------------------------------------------------------------- /lib/output-file/__tests__/output.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global afterEach, beforeEach, describe, it */ 10 | 11 | describe('output', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'output') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fse.remove(TEST_DIR, done)) 20 | 21 | describe('+ outputFile', () => { 22 | describe('> when the file and directory does not exist', () => { 23 | it('should create the file', done => { 24 | const file = path.join(TEST_DIR, Math.random() + 't-ne', Math.random() + '.txt') 25 | assert(!fs.existsSync(file)) 26 | fse.outputFile(file, 'hi jp', err => { 27 | assert.ifError(err) 28 | assert(fs.existsSync(file)) 29 | assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hi jp') 30 | done() 31 | }) 32 | }) 33 | it('should support promises', () => { 34 | const file = path.join(TEST_DIR, Math.random() + 't-ne', Math.random() + '.txt') 35 | assert(!fs.existsSync(file)) 36 | return fse.outputFile(file, 'hi jp') 37 | }) 38 | }) 39 | 40 | describe('> when the file does exist', () => { 41 | it('should still modify the file', done => { 42 | const file = path.join(TEST_DIR, Math.random() + 't-e', Math.random() + '.txt') 43 | fse.mkdirsSync(path.dirname(file)) 44 | fs.writeFileSync(file, 'hello world') 45 | fse.outputFile(file, 'hello jp', err => { 46 | if (err) return done(err) 47 | assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello jp') 48 | done() 49 | }) 50 | }) 51 | }) 52 | }) 53 | 54 | describe('+ outputFileSync', () => { 55 | describe('> when the file and directory does not exist', () => { 56 | it('should create the file', () => { 57 | const file = path.join(TEST_DIR, Math.random() + 'ts-ne', Math.random() + '.txt') 58 | assert(!fs.existsSync(file)) 59 | fse.outputFileSync(file, 'hello man') 60 | assert(fs.existsSync(file)) 61 | assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello man') 62 | }) 63 | }) 64 | 65 | describe('> when the file does exist', () => { 66 | it('should still modify the file', () => { 67 | const file = path.join(TEST_DIR, Math.random() + 'ts-e', Math.random() + '.txt') 68 | fse.mkdirsSync(path.dirname(file)) 69 | fs.writeFileSync(file, 'hello world') 70 | fse.outputFileSync(file, 'hello man') 71 | assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello man') 72 | }) 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /lib/output-file/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const u = require('universalify').fromPromise 4 | const fs = require('../fs') 5 | const path = require('path') 6 | const mkdir = require('../mkdirs') 7 | const pathExists = require('../path-exists').pathExists 8 | 9 | async function outputFile (file, data, encoding = 'utf-8') { 10 | const dir = path.dirname(file) 11 | 12 | if (!(await pathExists(dir))) { 13 | await mkdir.mkdirs(dir) 14 | } 15 | 16 | return fs.writeFile(file, data, encoding) 17 | } 18 | 19 | function outputFileSync (file, ...args) { 20 | const dir = path.dirname(file) 21 | if (!fs.existsSync(dir)) { 22 | mkdir.mkdirsSync(dir) 23 | } 24 | 25 | fs.writeFileSync(file, ...args) 26 | } 27 | 28 | module.exports = { 29 | outputFile: u(outputFile), 30 | outputFileSync 31 | } 32 | -------------------------------------------------------------------------------- /lib/path-exists/__tests__/path-exists-sync.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* eslint-env mocha */ 3 | 4 | const fs = require('../..') 5 | const path = require('path') 6 | const os = require('os') 7 | const assert = require('assert') 8 | 9 | describe('pathExists()', () => { 10 | let TEST_DIR 11 | 12 | beforeEach(done => { 13 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'path-exists') 14 | fs.emptyDir(TEST_DIR, done) 15 | }) 16 | 17 | afterEach(done => fs.remove(TEST_DIR, done)) 18 | 19 | it('should return false if file does not exist', () => { 20 | assert(!fs.pathExistsSync(path.join(TEST_DIR, 'somefile'))) 21 | }) 22 | 23 | it('should return true if file does exist', () => { 24 | const file = path.join(TEST_DIR, 'exists') 25 | fs.ensureFileSync(file) 26 | assert(fs.pathExistsSync(file)) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /lib/path-exists/__tests__/path-exists.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* eslint-env mocha */ 3 | 4 | const fs = require('../..') 5 | const path = require('path') 6 | const os = require('os') 7 | const assert = require('assert') 8 | 9 | describe('pathExists()', () => { 10 | let TEST_DIR 11 | 12 | beforeEach(done => { 13 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'path-exists') 14 | fs.emptyDir(TEST_DIR, done) 15 | }) 16 | 17 | afterEach(done => fs.remove(TEST_DIR, done)) 18 | 19 | it('should return false if file does not exist', () => { 20 | return fs.pathExists(path.join(TEST_DIR, 'somefile')) 21 | .then(exists => assert(!exists)) 22 | }) 23 | 24 | it('should return true if file does exist', () => { 25 | const file = path.join(TEST_DIR, 'exists') 26 | fs.ensureFileSync(file) 27 | return fs.pathExists(file) 28 | .then(exists => assert(exists)) 29 | }) 30 | 31 | it('should pass an empty error parameter to the callback', done => { 32 | const file = path.join(TEST_DIR, 'exists') 33 | fs.ensureFileSync(file) 34 | fs.pathExists(file, (err, exists) => { 35 | assert.ifError(err) 36 | assert(exists) 37 | done() 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /lib/path-exists/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const u = require('universalify').fromPromise 3 | const fs = require('../fs') 4 | 5 | function pathExists (path) { 6 | return fs.access(path).then(() => true).catch(() => false) 7 | } 8 | 9 | module.exports = { 10 | pathExists: u(pathExists), 11 | pathExistsSync: fs.existsSync 12 | } 13 | -------------------------------------------------------------------------------- /lib/remove/__tests__/remove-dir.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global beforeEach, describe, it */ 10 | 11 | describe('remove / async / dir', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'remove-async-dir') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | describe('> when dir does not exist', () => { 20 | it('should not throw an error', done => { 21 | const someDir = path.join(TEST_DIR, 'some-dir/') 22 | assert.strictEqual(fs.existsSync(someDir), false) 23 | fse.remove(someDir, err => { 24 | assert.ifError(err) 25 | done() 26 | }) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /lib/remove/__tests__/remove-file.test.js: -------------------------------------------------------------------------------- 1 | // todo 2 | -------------------------------------------------------------------------------- /lib/remove/__tests__/remove-sync-dir.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global beforeEach, describe, it */ 10 | 11 | describe('remove/sync', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'remove-sync') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | describe('+ removeSync()', () => { 20 | it('should delete directories and files synchronously', () => { 21 | assert(fs.existsSync(TEST_DIR)) 22 | fs.writeFileSync(path.join(TEST_DIR, 'somefile'), 'somedata') 23 | fse.removeSync(TEST_DIR) 24 | assert(!fs.existsSync(TEST_DIR)) 25 | }) 26 | 27 | it('should delete an empty directory synchronously', () => { 28 | assert(fs.existsSync(TEST_DIR)) 29 | fse.removeSync(TEST_DIR) 30 | assert(!fs.existsSync(TEST_DIR)) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /lib/remove/__tests__/remove-sync-file.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | 9 | /* global beforeEach, describe, it */ 10 | 11 | describe('remove/sync', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'remove-sync') 16 | fse.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | describe('+ removeSync()', () => { 20 | it('should delete a file synchronously', () => { 21 | const file = path.join(TEST_DIR, 'file') 22 | fs.writeFileSync(file, 'hello') 23 | assert(fs.existsSync(file)) 24 | fse.removeSync(file) 25 | assert(!fs.existsSync(file)) 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /lib/remove/__tests__/remove.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assert = require('assert') 4 | const fs = require('fs') 5 | const os = require('os') 6 | const path = require('path') 7 | const randomBytes = require('crypto').randomBytes 8 | const fse = require('../..') 9 | 10 | /* global afterEach, beforeEach, describe, it */ 11 | 12 | let TEST_DIR 13 | 14 | function buildFixtureDir () { 15 | const buf = randomBytes(5) 16 | const baseDir = path.join(TEST_DIR, `TEST_fs-extra_remove-${Date.now()}`) 17 | 18 | fs.mkdirSync(baseDir) 19 | fs.writeFileSync(path.join(baseDir, Math.random() + ''), buf) 20 | fs.writeFileSync(path.join(baseDir, Math.random() + ''), buf) 21 | 22 | const subDir = path.join(TEST_DIR, Math.random() + '') 23 | fs.mkdirSync(subDir) 24 | fs.writeFileSync(path.join(subDir, Math.random() + ''), buf) 25 | return baseDir 26 | } 27 | 28 | describe('remove', () => { 29 | beforeEach(done => { 30 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'remove') 31 | fse.emptyDir(TEST_DIR, done) 32 | }) 33 | 34 | afterEach(done => fse.remove(TEST_DIR, done)) 35 | 36 | describe('+ remove()', () => { 37 | it('should delete an empty directory', done => { 38 | assert(fs.existsSync(TEST_DIR)) 39 | fse.remove(TEST_DIR, err => { 40 | assert.ifError(err) 41 | assert(!fs.existsSync(TEST_DIR)) 42 | done() 43 | }) 44 | }) 45 | 46 | it('should delete a directory full of directories and files', done => { 47 | buildFixtureDir() 48 | assert(fs.existsSync(TEST_DIR)) 49 | fse.remove(TEST_DIR, err => { 50 | assert.ifError(err) 51 | assert(!fs.existsSync(TEST_DIR)) 52 | done() 53 | }) 54 | }) 55 | 56 | it('should delete a file', done => { 57 | const file = path.join(TEST_DIR, 'file') 58 | fs.writeFileSync(file, 'hello') 59 | 60 | assert(fs.existsSync(file)) 61 | fse.remove(file, err => { 62 | assert.ifError(err) 63 | assert(!fs.existsSync(file)) 64 | done() 65 | }) 66 | }) 67 | 68 | it('should delete without a callback', done => { 69 | const file = path.join(TEST_DIR, 'file') 70 | fs.writeFileSync(file, 'hello') 71 | 72 | assert(fs.existsSync(file)) 73 | let existsChecker = setInterval(() => { 74 | fse.pathExists(file, (err, itDoes) => { 75 | assert.ifError(err) 76 | if (!itDoes && existsChecker) { 77 | clearInterval(existsChecker) 78 | existsChecker = null 79 | done() 80 | } 81 | }) 82 | }, 25) 83 | fse.remove(file) 84 | }) 85 | 86 | it('shouldn’t delete glob matches', function (done) { 87 | const file = path.join(TEST_DIR, 'file?') 88 | try { 89 | fs.writeFileSync(file, 'hello') 90 | } catch (ex) { 91 | if (ex.code === 'ENOENT') return this.skip('Windows does not support filenames with ‘?’ or ‘*’ in them.') 92 | throw ex 93 | } 94 | 95 | const wrongFile = path.join(TEST_DIR, 'file1') 96 | fs.writeFileSync(wrongFile, 'yo') 97 | 98 | assert(fs.existsSync(file)) 99 | assert(fs.existsSync(wrongFile)) 100 | fse.remove(file, err => { 101 | assert.ifError(err) 102 | assert(!fs.existsSync(file)) 103 | assert(fs.existsSync(wrongFile)) 104 | done() 105 | }) 106 | }) 107 | 108 | it('shouldn’t delete glob matches when file doesn’t exist', done => { 109 | const nonexistentFile = path.join(TEST_DIR, 'file?') 110 | 111 | const wrongFile = path.join(TEST_DIR, 'file1') 112 | fs.writeFileSync(wrongFile, 'yo') 113 | 114 | assert(!fs.existsSync(nonexistentFile)) 115 | assert(fs.existsSync(wrongFile)) 116 | fse.remove(nonexistentFile, err => { 117 | assert.ifError(err) 118 | assert(!fs.existsSync(nonexistentFile)) 119 | assert(fs.existsSync(wrongFile)) 120 | done() 121 | }) 122 | }) 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /lib/remove/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('graceful-fs') 4 | const u = require('universalify').fromCallback 5 | 6 | function remove (path, callback) { 7 | fs.rm(path, { recursive: true, force: true }, callback) 8 | } 9 | 10 | function removeSync (path) { 11 | fs.rmSync(path, { recursive: true, force: true }) 12 | } 13 | 14 | module.exports = { 15 | remove: u(remove), 16 | removeSync 17 | } 18 | -------------------------------------------------------------------------------- /lib/util/__tests__/stat.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../..') 4 | const os = require('os') 5 | const path = require('path') 6 | const assert = require('assert') 7 | const stat = require('../stat.js') 8 | 9 | /* global beforeEach, afterEach, describe, it */ 10 | 11 | describe('util/stat', () => { 12 | let TEST_DIR 13 | 14 | beforeEach(done => { 15 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'util-stat') 16 | fs.emptyDir(TEST_DIR, done) 17 | }) 18 | 19 | afterEach(done => fs.remove(TEST_DIR, done)) 20 | 21 | describe('should use stats with bigint type', () => { 22 | it('stat.checkPaths()', () => { 23 | const src = path.join(TEST_DIR, 'src') 24 | const dest = path.join(TEST_DIR, 'dest') 25 | fs.ensureFileSync(src) 26 | fs.ensureFileSync(dest) 27 | stat.checkPaths(src, dest, 'copy', {}, (err, stats) => { 28 | assert.ifError(err) 29 | assert.strictEqual(typeof stats.srcStat.ino, 'bigint') 30 | }) 31 | }) 32 | 33 | it('stat.checkPathsSync()', () => { 34 | const src = path.join(TEST_DIR, 'src') 35 | const dest = path.join(TEST_DIR, 'dest') 36 | fs.ensureFileSync(src) 37 | fs.ensureFileSync(dest) 38 | const { srcStat } = stat.checkPathsSync(src, dest, 'copy', {}) 39 | assert.strictEqual(typeof srcStat.ino, 'bigint') 40 | }) 41 | }) 42 | 43 | describe('should stop at src or root path and not throw max call stack size error', () => { 44 | it('stat.checkParentPaths()', () => { 45 | const src = path.join(TEST_DIR, 'src') 46 | let dest = path.join(TEST_DIR, 'dest') 47 | fs.ensureFileSync(src) 48 | fs.ensureFileSync(dest) 49 | dest = path.basename(dest) 50 | const srcStat = fs.statSync(src) 51 | stat.checkParentPaths(src, srcStat, dest, 'copy', err => { 52 | assert.ifError(err) 53 | }) 54 | }) 55 | 56 | it('stat.checkParentPathsSync()', () => { 57 | const src = path.join(TEST_DIR, 'src') 58 | let dest = path.join(TEST_DIR, 'dest') 59 | fs.ensureFileSync(src) 60 | fs.ensureFileSync(dest) 61 | dest = path.basename(dest) 62 | const srcStat = fs.statSync(src) 63 | stat.checkParentPathsSync(src, srcStat, dest, 'copy') 64 | }) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /lib/util/__tests__/utimes.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const os = require('os') 5 | const fse = require('../..') 6 | const path = require('path') 7 | const assert = require('assert') 8 | const proxyquire = require('proxyquire') 9 | const u = require('universalify').fromCallback 10 | 11 | let gracefulFsStub 12 | let utimes 13 | 14 | /* global beforeEach, describe, it */ 15 | 16 | // HFS, ext{2,3}, FAT do not 17 | function hasMillisResSync () { 18 | let tmpfile = path.join('millis-test-sync' + Date.now().toString() + Math.random().toString().slice(2)) 19 | tmpfile = path.join(os.tmpdir(), tmpfile) 20 | 21 | // 550 millis past UNIX epoch 22 | const d = new Date(1435410243862) 23 | fs.writeFileSync(tmpfile, 'https://github.com/jprichardson/node-fs-extra/pull/141') 24 | const fd = fs.openSync(tmpfile, 'r+') 25 | fs.futimesSync(fd, d, d) 26 | fs.closeSync(fd) 27 | return fs.statSync(tmpfile).mtime > 1435410243000 28 | } 29 | 30 | describe('utimes', () => { 31 | let TEST_DIR 32 | 33 | beforeEach(done => { 34 | TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'utimes') 35 | fse.emptyDir(TEST_DIR, done) 36 | // reset stubs 37 | gracefulFsStub = {} 38 | utimes = proxyquire('../utimes', { '../fs': gracefulFsStub }) 39 | }) 40 | 41 | describe('utimesMillis()', () => { 42 | // see discussion https://github.com/jprichardson/node-fs-extra/pull/141 43 | it('should set the utimes w/ millisecond precision', done => { 44 | const tmpFile = path.join(TEST_DIR, 'someFile') 45 | fs.writeFileSync(tmpFile, 'hello') 46 | 47 | let stats = fs.lstatSync(tmpFile) 48 | 49 | // Apr 21st, 2012 50 | const awhileAgo = new Date(1334990868773) 51 | const awhileAgoNoMillis = new Date(1334990868000) 52 | 53 | assert.notDeepStrictEqual(stats.mtime, awhileAgo) 54 | assert.notDeepStrictEqual(stats.atime, awhileAgo) 55 | 56 | utimes.utimesMillis(tmpFile, awhileAgo, awhileAgo, err => { 57 | assert.ifError(err) 58 | stats = fs.statSync(tmpFile) 59 | if (hasMillisResSync()) { 60 | assert.deepStrictEqual(stats.mtime, awhileAgo) 61 | assert.deepStrictEqual(stats.atime, awhileAgo) 62 | } else { 63 | assert.deepStrictEqual(stats.mtime, awhileAgoNoMillis) 64 | assert.deepStrictEqual(stats.atime, awhileAgoNoMillis) 65 | } 66 | done() 67 | }) 68 | }) 69 | 70 | it('should close open file desciptors after encountering an error', done => { 71 | const fakeFd = Math.random() 72 | 73 | gracefulFsStub.open = u((pathIgnored, flagsIgnored, modeIgnored, callback) => { 74 | if (typeof modeIgnored === 'function') callback = modeIgnored 75 | process.nextTick(() => callback(null, fakeFd)) 76 | }) 77 | 78 | let closeCalled = false 79 | gracefulFsStub.close = u((fd, callback) => { 80 | assert.strictEqual(fd, fakeFd) 81 | closeCalled = true 82 | if (callback) process.nextTick(callback) 83 | }) 84 | 85 | let testError 86 | gracefulFsStub.futimes = u((fd, atimeIgnored, mtimeIgnored, callback) => { 87 | process.nextTick(() => { 88 | testError = new Error('A test error') 89 | callback(testError) 90 | }) 91 | }) 92 | 93 | utimes.utimesMillis('ignored', 'ignored', 'ignored', err => { 94 | assert.strictEqual(err, testError) 95 | assert(closeCalled) 96 | done() 97 | }) 98 | }) 99 | }) 100 | }) 101 | -------------------------------------------------------------------------------- /lib/util/stat.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../fs') 4 | const path = require('path') 5 | const u = require('universalify').fromPromise 6 | 7 | function getStats (src, dest, opts) { 8 | const statFunc = opts.dereference 9 | ? (file) => fs.stat(file, { bigint: true }) 10 | : (file) => fs.lstat(file, { bigint: true }) 11 | return Promise.all([ 12 | statFunc(src), 13 | statFunc(dest).catch(err => { 14 | if (err.code === 'ENOENT') return null 15 | throw err 16 | }) 17 | ]).then(([srcStat, destStat]) => ({ srcStat, destStat })) 18 | } 19 | 20 | function getStatsSync (src, dest, opts) { 21 | let destStat 22 | const statFunc = opts.dereference 23 | ? (file) => fs.statSync(file, { bigint: true }) 24 | : (file) => fs.lstatSync(file, { bigint: true }) 25 | const srcStat = statFunc(src) 26 | try { 27 | destStat = statFunc(dest) 28 | } catch (err) { 29 | if (err.code === 'ENOENT') return { srcStat, destStat: null } 30 | throw err 31 | } 32 | return { srcStat, destStat } 33 | } 34 | 35 | async function checkPaths (src, dest, funcName, opts) { 36 | const { srcStat, destStat } = await getStats(src, dest, opts) 37 | if (destStat) { 38 | if (areIdentical(srcStat, destStat)) { 39 | const srcBaseName = path.basename(src) 40 | const destBaseName = path.basename(dest) 41 | if (funcName === 'move' && 42 | srcBaseName !== destBaseName && 43 | srcBaseName.toLowerCase() === destBaseName.toLowerCase()) { 44 | return { srcStat, destStat, isChangingCase: true } 45 | } 46 | throw new Error('Source and destination must not be the same.') 47 | } 48 | if (srcStat.isDirectory() && !destStat.isDirectory()) { 49 | throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`) 50 | } 51 | if (!srcStat.isDirectory() && destStat.isDirectory()) { 52 | throw new Error(`Cannot overwrite directory '${dest}' with non-directory '${src}'.`) 53 | } 54 | } 55 | 56 | if (srcStat.isDirectory() && isSrcSubdir(src, dest)) { 57 | throw new Error(errMsg(src, dest, funcName)) 58 | } 59 | 60 | return { srcStat, destStat } 61 | } 62 | 63 | function checkPathsSync (src, dest, funcName, opts) { 64 | const { srcStat, destStat } = getStatsSync(src, dest, opts) 65 | 66 | if (destStat) { 67 | if (areIdentical(srcStat, destStat)) { 68 | const srcBaseName = path.basename(src) 69 | const destBaseName = path.basename(dest) 70 | if (funcName === 'move' && 71 | srcBaseName !== destBaseName && 72 | srcBaseName.toLowerCase() === destBaseName.toLowerCase()) { 73 | return { srcStat, destStat, isChangingCase: true } 74 | } 75 | throw new Error('Source and destination must not be the same.') 76 | } 77 | if (srcStat.isDirectory() && !destStat.isDirectory()) { 78 | throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`) 79 | } 80 | if (!srcStat.isDirectory() && destStat.isDirectory()) { 81 | throw new Error(`Cannot overwrite directory '${dest}' with non-directory '${src}'.`) 82 | } 83 | } 84 | 85 | if (srcStat.isDirectory() && isSrcSubdir(src, dest)) { 86 | throw new Error(errMsg(src, dest, funcName)) 87 | } 88 | return { srcStat, destStat } 89 | } 90 | 91 | // recursively check if dest parent is a subdirectory of src. 92 | // It works for all file types including symlinks since it 93 | // checks the src and dest inodes. It starts from the deepest 94 | // parent and stops once it reaches the src parent or the root path. 95 | async function checkParentPaths (src, srcStat, dest, funcName) { 96 | const srcParent = path.resolve(path.dirname(src)) 97 | const destParent = path.resolve(path.dirname(dest)) 98 | if (destParent === srcParent || destParent === path.parse(destParent).root) return 99 | 100 | let destStat 101 | try { 102 | destStat = await fs.stat(destParent, { bigint: true }) 103 | } catch (err) { 104 | if (err.code === 'ENOENT') return 105 | throw err 106 | } 107 | 108 | if (areIdentical(srcStat, destStat)) { 109 | throw new Error(errMsg(src, dest, funcName)) 110 | } 111 | 112 | return checkParentPaths(src, srcStat, destParent, funcName) 113 | } 114 | 115 | function checkParentPathsSync (src, srcStat, dest, funcName) { 116 | const srcParent = path.resolve(path.dirname(src)) 117 | const destParent = path.resolve(path.dirname(dest)) 118 | if (destParent === srcParent || destParent === path.parse(destParent).root) return 119 | let destStat 120 | try { 121 | destStat = fs.statSync(destParent, { bigint: true }) 122 | } catch (err) { 123 | if (err.code === 'ENOENT') return 124 | throw err 125 | } 126 | if (areIdentical(srcStat, destStat)) { 127 | throw new Error(errMsg(src, dest, funcName)) 128 | } 129 | return checkParentPathsSync(src, srcStat, destParent, funcName) 130 | } 131 | 132 | function areIdentical (srcStat, destStat) { 133 | return destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev 134 | } 135 | 136 | // return true if dest is a subdir of src, otherwise false. 137 | // It only checks the path strings. 138 | function isSrcSubdir (src, dest) { 139 | const srcArr = path.resolve(src).split(path.sep).filter(i => i) 140 | const destArr = path.resolve(dest).split(path.sep).filter(i => i) 141 | return srcArr.every((cur, i) => destArr[i] === cur) 142 | } 143 | 144 | function errMsg (src, dest, funcName) { 145 | return `Cannot ${funcName} '${src}' to a subdirectory of itself, '${dest}'.` 146 | } 147 | 148 | module.exports = { 149 | // checkPaths 150 | checkPaths: u(checkPaths), 151 | checkPathsSync, 152 | // checkParent 153 | checkParentPaths: u(checkParentPaths), 154 | checkParentPathsSync, 155 | // Misc 156 | isSrcSubdir, 157 | areIdentical 158 | } 159 | -------------------------------------------------------------------------------- /lib/util/utimes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../fs') 4 | const u = require('universalify').fromPromise 5 | 6 | async function utimesMillis (path, atime, mtime) { 7 | // if (!HAS_MILLIS_RES) return fs.utimes(path, atime, mtime, callback) 8 | const fd = await fs.open(path, 'r+') 9 | 10 | let closeErr = null 11 | 12 | try { 13 | await fs.futimes(fd, atime, mtime) 14 | } finally { 15 | try { 16 | await fs.close(fd) 17 | } catch (e) { 18 | closeErr = e 19 | } 20 | } 21 | 22 | if (closeErr) { 23 | throw closeErr 24 | } 25 | } 26 | 27 | function utimesMillisSync (path, atime, mtime) { 28 | const fd = fs.openSync(path, 'r+') 29 | fs.futimesSync(fd, atime, mtime) 30 | return fs.closeSync(fd) 31 | } 32 | 33 | module.exports = { 34 | utimesMillis: u(utimesMillis), 35 | utimesMillisSync 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fs-extra", 3 | "version": "11.3.0", 4 | "description": "fs-extra contains methods that aren't included in the vanilla Node.js fs package. Such as recursive mkdir, copy, and remove.", 5 | "engines": { 6 | "node": ">=14.14" 7 | }, 8 | "homepage": "https://github.com/jprichardson/node-fs-extra", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jprichardson/node-fs-extra" 12 | }, 13 | "keywords": [ 14 | "fs", 15 | "file", 16 | "file system", 17 | "copy", 18 | "directory", 19 | "extra", 20 | "mkdirp", 21 | "mkdir", 22 | "mkdirs", 23 | "recursive", 24 | "json", 25 | "read", 26 | "write", 27 | "extra", 28 | "delete", 29 | "remove", 30 | "touch", 31 | "create", 32 | "text", 33 | "output", 34 | "move", 35 | "promise" 36 | ], 37 | "author": "JP Richardson ", 38 | "license": "MIT", 39 | "dependencies": { 40 | "graceful-fs": "^4.2.0", 41 | "jsonfile": "^6.0.1", 42 | "universalify": "^2.0.0" 43 | }, 44 | "devDependencies": { 45 | "klaw": "^2.1.1", 46 | "klaw-sync": "^3.0.2", 47 | "minimist": "^1.1.1", 48 | "mocha": "^10.1.0", 49 | "nyc": "^15.0.0", 50 | "proxyquire": "^2.0.1", 51 | "read-dir-files": "^0.1.1", 52 | "standard": "^17.0.0" 53 | }, 54 | "main": "./lib/index.js", 55 | "exports": { 56 | ".": "./lib/index.js", 57 | "./esm": "./lib/esm.mjs" 58 | }, 59 | "files": [ 60 | "lib/", 61 | "!lib/**/__tests__/" 62 | ], 63 | "scripts": { 64 | "lint": "standard", 65 | "test-find": "find ./lib/**/__tests__ -name *.test.js | xargs mocha", 66 | "test": "npm run lint && npm run unit && npm run unit-esm", 67 | "unit": "nyc node test.js", 68 | "unit-esm": "node test.mjs" 69 | }, 70 | "sideEffects": false 71 | } 72 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const os = require('os') 4 | const path = require('path') 5 | const klaw = require('klaw') 6 | const Mocha = require('mocha') 7 | 8 | const argv = require('minimist')(process.argv.slice(2)) 9 | 10 | const mochaOpts = { 11 | ui: 'bdd', 12 | reporter: 'dot', 13 | timeout: 30000, 14 | ...argv 15 | } 16 | 17 | const mocha = new Mocha(mochaOpts) 18 | const testExt = '.test.js' 19 | 20 | klaw('./lib').on('readable', function () { 21 | let item 22 | while ((item = this.read())) { 23 | if (!item.stats.isFile()) return 24 | if (item.path.lastIndexOf(testExt) !== (item.path.length - testExt.length)) return 25 | mocha.addFile(item.path) 26 | } 27 | }).on('end', () => { 28 | mocha.run(failures => { 29 | require('./lib').remove(path.join(os.tmpdir(), 'fs-extra'), () => process.exit(failures)) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import fsLegacy from './lib/index.js' 3 | // NOTE: eslint comments needed because we're importing the same file multiple times 4 | import fsDefault from './lib/esm.mjs' // eslint-disable-line 5 | import * as fsStar from './lib/esm.mjs' 6 | import { 7 | copy, 8 | copySync, 9 | emptyDirSync, 10 | emptydirSync, 11 | emptyDir, 12 | emptydir, 13 | createFile, 14 | createFileSync, 15 | ensureFile, 16 | ensureFileSync, 17 | createLink, 18 | createLinkSync, 19 | ensureLink, 20 | ensureLinkSync, 21 | createSymlink, 22 | createSymlinkSync, 23 | ensureSymlink, 24 | ensureSymlinkSync, 25 | readJson, 26 | readJsonSync, 27 | writeJson, 28 | writeJsonSync, 29 | outputJson, 30 | outputJsonSync, 31 | outputJSON, 32 | outputJSONSync, 33 | writeJSON, 34 | writeJSONSync, 35 | readJSON, 36 | readJSONSync, 37 | mkdirs, 38 | mkdirsSync, 39 | mkdirp, 40 | mkdirpSync, 41 | ensureDir, 42 | ensureDirSync, 43 | move, 44 | moveSync, 45 | outputFile, 46 | outputFileSync, 47 | pathExists, 48 | pathExistsSync, 49 | remove, 50 | removeSync 51 | } from './lib/esm.mjs' // eslint-disable-line 52 | const fsNamed = [ 53 | copy, 54 | copySync, 55 | emptyDirSync, 56 | emptydirSync, 57 | emptyDir, 58 | emptydir, 59 | createFile, 60 | createFileSync, 61 | ensureFile, 62 | ensureFileSync, 63 | createLink, 64 | createLinkSync, 65 | ensureLink, 66 | ensureLinkSync, 67 | createSymlink, 68 | createSymlinkSync, 69 | ensureSymlink, 70 | ensureSymlinkSync, 71 | readJson, 72 | readJsonSync, 73 | writeJson, 74 | writeJsonSync, 75 | outputJson, 76 | outputJsonSync, 77 | outputJSON, 78 | outputJSONSync, 79 | writeJSON, 80 | writeJSONSync, 81 | readJSON, 82 | readJSONSync, 83 | mkdirs, 84 | mkdirsSync, 85 | mkdirp, 86 | mkdirpSync, 87 | ensureDir, 88 | ensureDirSync, 89 | move, 90 | moveSync, 91 | outputFile, 92 | outputFileSync, 93 | pathExists, 94 | pathExistsSync, 95 | remove, 96 | removeSync 97 | ] 98 | 99 | const keys = Object.keys(fsDefault) 100 | 101 | assert.deepStrictEqual(Object.values(fsDefault), fsNamed, 'named and default exports should match') 102 | assert.deepStrictEqual( 103 | Object.entries(fsStar) 104 | .filter(([name]) => name !== 'default') // remove "default" property here 105 | .sort(([nameA], [nameB]) => keys.indexOf(nameA) - keys.indexOf(nameB)) // sort for exact match 106 | .map(([name, fn]) => fn), 107 | Object.values(fsDefault), 108 | 'star and default exports should match' 109 | ) 110 | 111 | // default exports a subset of the legacy implementation, but functions are the same 112 | Object.entries(fsDefault).forEach(([name, fn]) => { 113 | assert.strictEqual(fn, fsLegacy[name], `${name}() should match legacy implementation`) 114 | }) 115 | 116 | console.warn('ESM tests pass!') 117 | -------------------------------------------------------------------------------- /test/readme.md: -------------------------------------------------------------------------------- 1 | Looking for the test files? You can find all of the test files in `lib/**/__tests__`. --------------------------------------------------------------------------------