Repository: jprichardson/node-fs-extra (js)

Issue:
Allow passing undefined options to move()
<!-- First ensure you installed the latest version of fs-extra -->
<!-- If your bug still exists please fill out the following information if it applies to your issue: -->
<!-- Please check if you have installed a supported version of Node.js as written in "engines" in the package.json -->
- **Operating System:**macos
- **Node.js version:**14.18.3
- **`fs-extra` version:**10.0.1

The follow code:

```
const { move } = require("fs-extra");

move("/some/path/that/exists", "some/path/that/doesnt/exist", undefined)
  .then(() => console.log("success")
  .catch(console.error);
```

produces the following type error: `TypeError: Cannot read property 'overwrite' of undefined`

The error seems to be from the handling of `optional callback` and `optional options` on line 12-15 of `lib/move/move.js`

Pull request:
Allow passing undefined opts to move()
Fixes #947

Closes #948

Will merge in a few days if no objections.

Patch:
diff --git a/lib/fs/index.js b/lib/fs/index.js
index e460b74..7b025e2 100644
--- a/lib/fs/index.js
+++ b/lib/fs/index.js
@@ -54,7 +54,6 @@ Object.assign(exports, fs)
 api.forEach(method => {
   exports[method] = u(fs[method])
 })
-exports.realpath.native = u(fs.realpath.native)
 
 // We differ from mz/fs in that we still ship the old, broken, fs.exists()
 // since we are a drop-in replacement for the native module
@@ -117,3 +116,13 @@ if (typeof fs.writev === 'function') {
     })
   }
 }
+
+// fs.realpath.native sometimes not available if fs is monkey-patched
+if (typeof fs.realpath.native === 'function') {
+  exports.realpath.native = u(fs.realpath.native)
+} else {
+  process.emitWarning(
+    'fs.realpath.native is not a function. Is fs being monkey-patched?',
+    'Warning', 'fs-extra-WARN0003'
+  )
+}
diff --git a/lib/move/move.js b/lib/move/move.js
index e9523e6..7dc6ecd 100644
--- a/lib/move/move.js
+++ b/lib/move/move.js
@@ -14,6 +14,8 @@ function move (src, dest, opts, cb) {
     opts = {}
   }
 
+  opts = opts || {}
+
   const overwrite = opts.overwrite || opts.clobber || false
 
   stat.checkPaths(src, dest, 'move', opts, (err, stats) => {


Test patch:
diff --git a/lib/__tests__/promise.test.js b/lib/__tests__/promise.test.js
index 31228bd..92a7a27 100644
--- a/lib/__tests__/promise.test.js
+++ b/lib/__tests__/promise.test.js
@@ -10,7 +10,6 @@ const methods = [
   'ensureFile',
   'ensureDir',
   'mkdirs',
-  'move',
   'readJson',
   'readJSON',
   'remove'
diff --git a/lib/fs/__tests__/realpath.test.js b/lib/fs/__tests__/realpath.test.js
index 9306859..b03249b 100644
--- a/lib/fs/__tests__/realpath.test.js
+++ b/lib/fs/__tests__/realpath.test.js
@@ -1,11 +1,67 @@
 'use strict'
 
-const fse = require('../..')
+const fs = require('fs')
+const path = require('path')
 const assert = require('assert')
 
 /* eslint-env mocha */
 
+describe('realpath.native does not exist', () => {
+  let warning
+  const warningListener = error => {
+    if (error.name === 'Warning') {
+      if (error.code.startsWith('fs-extra-WARN0003')) {
+        warning = error
+      }
+    }
+  }
+
+  const realpathNativeBackup = fs.realpath.native
+  const clearFseCache = () => {
+    const fsePath = path.dirname(require.resolve('../../..'))
+    for (const entry in require.cache) {
+      if (entry.startsWith(fsePath)) {
+        delete require.cache[entry]
+      }
+    }
+  }
+
+  before(() => {
+    process.on('warning', warningListener)
+
+    // clear existing require.cache
+    clearFseCache()
+
+    // simulate fs monkey-patch
+    delete fs.realpath.native
+  })
+
+  after(() => {
+    process.off('warning', warningListener)
+
+    // clear stubbed require.cache
+    clearFseCache()
+
+    // reinstate fs.realpath.native
+    fs.realpath.native = realpathNativeBackup
+  })
+
+  it('fse should not export realpath.native', done => {
+    const fse = require('../..')
+
+    // next event loop to allow event emitter/listener to happen
+    setImmediate(() => {
+      assert(warning, 'fs-extra-WARN0003 should be emitted')
+      done()
+    })
+
+    assert(!fse.realpath.native)
+  })
+})
+
 describe('realpath.native', () => {
+  const fse = require('../..')
+
   it('works with callbacks', () => {
     fse.realpath.native(__dirname, (err, path) => {
       assert.ifError(err)
diff --git a/lib/move/__tests__/move.test.js b/lib/move/__tests__/move.test.js
index d20f530..e7841ca 100644
--- a/lib/move/__tests__/move.test.js
+++ b/lib/move/__tests__/move.test.js
@@ -140,6 +140,17 @@ describe('+ move()', () => {
       })
     })
 
+    it('should support promises', async () => {
+      const src = path.join(TEST_DIR, 'a-file')
+      const dest = path.join(TEST_DIR, 'a-file-dest')
+
+      await fse.move(src, dest)
+
+      const contents = fs.readFileSync(dest, 'utf8')
+      const expected = /^sonic the hedgehog\r?\n$/
+      assert.ok(contents.match(expected))
+    })
+
     it('should not move a file if source and destination are the same', done => {
       const src = path.join(TEST_DIR, 'a-file')
       const dest = src
@@ -273,6 +284,34 @@ describe('+ move()', () => {
     })
   })
 
+  describe('> when opts is explicit undefined', () => {
+    it('works with callbacks', done => {
+      const src = path.join(TEST_DIR, 'a-file')
+      const dest = path.join(TEST_DIR, 'a-file-dest')
+
+      fse.move(src, dest, undefined, err => {
+        assert.ifError(err)
+        fs.readFile(dest, 'utf8', (err, contents) => {
+          const expected = /^sonic the hedgehog\r?\n$/
+          assert.ifError(err)
+          assert.ok(contents.match(expected))
+          done()
+        })
+      })
+    })
+
+    it('works with promises', async () => {
+      const src = path.join(TEST_DIR, 'a-file')
+      const dest = path.join(TEST_DIR, 'a-file-dest')
+
+      await fse.move(src, dest, undefined)
+
+      const contents = fs.readFileSync(dest, 'utf8')
+      const expected = /^sonic the hedgehog\r?\n$/
+      assert.ok(contents.match(expected))
+    })
+  })
+
   describeIfWindows('> when dest parent is root', () => {
     let dest