From 2dbc3b3f7fbca2914090f2ad1229499c41267321 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Sat, 25 Feb 2017 23:04:06 -0800 Subject: [PATCH 01/14] Rewrite copy, add more tests to copy, activate skipped move test --- ...nk.test.js => copy-broken-symlink.test.js} | 16 +- .../__tests__/{async => }/copy-gh-89.test.js | 2 +- lib/copy/__tests__/copy-permissions.test.js | 10 +- lib/copy/__tests__/copy-preserve-time.test.js | 6 +- .../copy-prevent-copying-identical.test.js | 192 ++++++++ .../copy-prevent-copying-into-itself.test.js | 431 ++++++++++++++++++ .../symlink.test.js => copy-symlink.test.js} | 20 +- lib/copy/__tests__/copy.test.js | 8 +- lib/copy/__tests__/ncp/README.md | 1 - .../ncp/fixtures/modified-files/out/a | 1 - .../ncp/fixtures/modified-files/src/a | 1 - .../ncp/fixtures/regular-fixtures/out/a | 1 - .../ncp/fixtures/regular-fixtures/out/b | 1 - .../ncp/fixtures/regular-fixtures/out/c | 0 .../ncp/fixtures/regular-fixtures/out/d | 0 .../ncp/fixtures/regular-fixtures/out/e | 0 .../ncp/fixtures/regular-fixtures/out/f | 0 .../ncp/fixtures/regular-fixtures/out/sub/a | 1 - .../ncp/fixtures/regular-fixtures/out/sub/b | 0 .../ncp/fixtures/regular-fixtures/src/a | 1 - .../ncp/fixtures/regular-fixtures/src/b | 1 - .../ncp/fixtures/regular-fixtures/src/c | 0 .../ncp/fixtures/regular-fixtures/src/d | 0 .../ncp/fixtures/regular-fixtures/src/e | 0 .../ncp/fixtures/regular-fixtures/src/f | 0 .../ncp/fixtures/regular-fixtures/src/sub/a | 1 - .../ncp/fixtures/regular-fixtures/src/sub/b | 0 lib/copy/__tests__/ncp/ncp-error-perm.test.js | 50 -- lib/copy/__tests__/ncp/ncp.test.js | 193 -------- lib/copy/copy.js | 214 +++++++-- lib/copy/ncp.js | 234 ---------- lib/move/__tests__/move.test.js | 208 ++++----- lib/move/index.js | 10 +- 33 files changed, 952 insertions(+), 651 deletions(-) rename lib/copy/__tests__/{ncp/broken-symlink.test.js => copy-broken-symlink.test.js} (76%) rename lib/copy/__tests__/{async => }/copy-gh-89.test.js (96%) create mode 100644 lib/copy/__tests__/copy-prevent-copying-identical.test.js create mode 100644 lib/copy/__tests__/copy-prevent-copying-into-itself.test.js rename lib/copy/__tests__/{ncp/symlink.test.js => copy-symlink.test.js} (82%) delete mode 100644 lib/copy/__tests__/ncp/README.md delete mode 100644 lib/copy/__tests__/ncp/fixtures/modified-files/out/a delete mode 100644 lib/copy/__tests__/ncp/fixtures/modified-files/src/a delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/c delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/d delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/e delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/f delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/b delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/c delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/d delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/e delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/f delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a delete mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/b delete mode 100644 lib/copy/__tests__/ncp/ncp-error-perm.test.js delete mode 100644 lib/copy/__tests__/ncp/ncp.test.js delete mode 100644 lib/copy/ncp.js diff --git a/lib/copy/__tests__/ncp/broken-symlink.test.js b/lib/copy/__tests__/copy-broken-symlink.test.js similarity index 76% rename from lib/copy/__tests__/ncp/broken-symlink.test.js rename to lib/copy/__tests__/copy-broken-symlink.test.js index 781ac6d9..b598acfc 100644 --- a/lib/copy/__tests__/ncp/broken-symlink.test.js +++ b/lib/copy/__tests__/copy-broken-symlink.test.js @@ -3,14 +3,14 @@ const fs = require('fs') const os = require('os') const fse = require(process.cwd()) -const ncp = require('../../ncp') const path = require('path') const assert = require('assert') +const copy = require('../copy') /* global afterEach, beforeEach, describe, it */ -describe('ncp broken symlink', function () { - const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-broken-symlinks') +describe('copy() - broken symlink', () => { + const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-broken-symlinks') const src = path.join(TEST_DIR, 'src') const out = path.join(TEST_DIR, 'out') @@ -24,16 +24,16 @@ describe('ncp broken symlink', function () { afterEach(done => fse.remove(TEST_DIR, done)) it('should copy broken symlinks by default', done => { - ncp(src, out, err => { - if (err) return done(err) + copy(src, out, err => { + assert.ifError(err) assert.equal(fs.readlinkSync(path.join(out, 'broken-symlink')), path.join(src, 'does-not-exist')) done() }) }) - it('should return an error when dereference=true', done => { - ncp(src, out, {dereference: true}, err => { - assert.equal(err.code, 'ENOENT') + it('should throw an error when dereference=true', done => { + copy(src, out, {dereference: true}, err => { + assert.strictEqual(err.code, 'ENOENT') done() }) }) diff --git a/lib/copy/__tests__/async/copy-gh-89.test.js b/lib/copy/__tests__/copy-gh-89.test.js similarity index 96% rename from lib/copy/__tests__/async/copy-gh-89.test.js rename to lib/copy/__tests__/copy-gh-89.test.js index 4cfe191c..fb55c2e6 100644 --- a/lib/copy/__tests__/async/copy-gh-89.test.js +++ b/lib/copy/__tests__/copy-gh-89.test.js @@ -22,7 +22,7 @@ describe('copy / gh #89', () => { fse.remove(TEST_DIR, done) }) - it('should...', done => { + it('should copy successfully', done => { const A = path.join(TEST_DIR, 'A') const B = path.join(TEST_DIR, 'B') fs.mkdirSync(A) diff --git a/lib/copy/__tests__/copy-permissions.test.js b/lib/copy/__tests__/copy-permissions.test.js index ccc89cd2..199766af 100644 --- a/lib/copy/__tests__/copy-permissions.test.js +++ b/lib/copy/__tests__/copy-permissions.test.js @@ -12,7 +12,7 @@ const o777 = parseInt('777', 8) const o666 = parseInt('666', 8) const o444 = parseInt('444', 8) -describe('copy', () => { +describe('+ copy() - permissions', () => { let TEST_DIR beforeEach(done => { @@ -89,10 +89,10 @@ describe('copy', () => { const newf2stats = fs.lstatSync(path.join(permDir, 'dest/somedir/f2.bin')) const newd2stats = fs.lstatSync(path.join(permDir, 'dest/crazydir')) - assert.strictEqual(newf1stats.mode, f1stats.mode) - assert.strictEqual(newd1stats.mode, d1stats.mode) - assert.strictEqual(newf2stats.mode, f2stats.mode) - assert.strictEqual(newd2stats.mode, d2stats.mode) + assert.strictEqual(newf1stats.mode, f1stats.mode, 'f1 mode') + assert.strictEqual(newd1stats.mode, d1stats.mode, 'd1 mode') + assert.strictEqual(newf2stats.mode, f2stats.mode, 'f2 mode') + assert.strictEqual(newd2stats.mode, d2stats.mode, 'd2 mode') assert.strictEqual(newf1stats.gid, f1stats.gid) assert.strictEqual(newd1stats.gid, d1stats.gid) diff --git a/lib/copy/__tests__/copy-preserve-time.test.js b/lib/copy/__tests__/copy-preserve-time.test.js index 1c9fcb0d..6bbf627e 100644 --- a/lib/copy/__tests__/copy-preserve-time.test.js +++ b/lib/copy/__tests__/copy-preserve-time.test.js @@ -9,7 +9,7 @@ const assert = require('assert') /* global beforeEach, describe, it */ -describe('copy', () => { +describe('+ copy() - preserve time', () => { let TEST_DIR beforeEach(done => { @@ -21,7 +21,7 @@ describe('copy', () => { const SRC_FIXTURES_DIR = path.join(__dirname, '/fixtures') const FILES = ['a-file', path.join('a-folder', 'another-file'), path.join('a-folder', 'another-folder', 'file3')] - describe('> when modified option is turned off', () => { + describe('>> when modified option is turned off', () => { it('should have different timestamps on copy', done => { const from = path.join(SRC_FIXTURES_DIR) const to = path.join(TEST_DIR) @@ -33,7 +33,7 @@ describe('copy', () => { }) }) - describe('> when modified option is turned on', () => { + describe('>> when modified option is turned on', () => { it('should have the same timestamps on copy', done => { const from = path.join(SRC_FIXTURES_DIR) const to = path.join(TEST_DIR) diff --git a/lib/copy/__tests__/copy-prevent-copying-identical.test.js b/lib/copy/__tests__/copy-prevent-copying-identical.test.js new file mode 100644 index 00000000..9b2586c0 --- /dev/null +++ b/lib/copy/__tests__/copy-prevent-copying-identical.test.js @@ -0,0 +1,192 @@ +'use strict' + +const assert = require('assert') +const os = require('os') +const path = require('path') +const fs = require(process.cwd()) +const klawSync = require('klaw-sync') + +/* global beforeEach, afterEach, describe, it */ + +describe('+ copySync() - prevent copying identical files and dirs', () => { + let TEST_DIR = '' + let src = '' + let dest = '' + + beforeEach(done => { + TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-prevent-copying-identical') + fs.emptyDir(TEST_DIR, done) + }) + + afterEach(done => fs.remove(TEST_DIR, done)) + + it('should return an error if src and dest are the same', done => { + const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_copy_sync') + const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy_sync') + + fs.copy(fileSrc, fileDest, err => { + assert.equal(err.message, 'Source and destination must not be the same.') + done() + }) + }) + + // src is directory: + // src is regular, dest is symlink + // src is symlink, dest is regular + // src is symlink, dest is symlink + + describe('> when the source is a directory', () => { + describe(`>> when src is regular and dest is a symlink that points to src`, () => { + it('should not copy and return', done => { + src = path.join(TEST_DIR, 'src') + fs.mkdirsSync(src) + const subdir = path.join(TEST_DIR, 'src', 'subdir') + fs.mkdirsSync(subdir) + fs.writeFileSync(path.join(subdir, 'file.txt'), 'some data') + + const destLink = path.join(TEST_DIR, 'dest-symlink') + fs.symlinkSync(src, destLink, 'dir') + + const oldlen = klawSync(src).length + + fs.copy(src, destLink, err => { + assert.ifError(err) + + const newlen = klawSync(src).length + assert.strictEqual(newlen, oldlen) + const link = fs.readlinkSync(destLink) + assert.strictEqual(link, src) + done() + }) + }) + }) + + describe(`>> when src is a symlink that points to a regular dest`, () => { + it('should not copy and return', done => { + dest = path.join(TEST_DIR, 'dest') + fs.mkdirsSync(dest) + const subdir = path.join(TEST_DIR, 'dest', 'subdir') + fs.mkdirsSync(subdir) + fs.writeFileSync(path.join(subdir, 'file.txt'), 'some data') + + const srcLink = path.join(TEST_DIR, 'src-symlink') + fs.symlinkSync(dest, srcLink, 'dir') + + const oldlen = klawSync(dest).length + + fs.copy(srcLink, dest, err => { + assert.ifError(err) + + // assert nothing copied + const newlen = klawSync(dest).length + assert.strictEqual(newlen, oldlen) + const link = fs.readlinkSync(srcLink) + assert.strictEqual(link, dest) + done() + }) + }) + }) + + describe('>> when src and dest are symlinks that point to the exact same path', () => { + it('should not copy and return', done => { + src = path.join(TEST_DIR, 'src') + fs.mkdirsSync(src) + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + const destLink = path.join(TEST_DIR, 'dest_symlink') + fs.symlinkSync(src, destLink, 'dir') + + const srclenBefore = klawSync(srcLink).length + const destlenBefore = klawSync(destLink).length + + fs.copy(srcLink, destLink, err => { + assert.ifError(err) + + const srclenAfter = klawSync(srcLink).length + assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change') + const destlenAfter = klawSync(destLink).length + assert.strictEqual(destlenAfter, destlenBefore, 'dest length should not change') + + const srcln = fs.readlinkSync(srcLink) + assert.strictEqual(srcln, src) + const destln = fs.readlinkSync(destLink) + assert.strictEqual(destln, src) + done() + }) + }) + }) + }) + + // src is file: + // src is regular, dest is symlink + // src is symlink, dest is regular + // src is symlink, dest is symlink + + describe('> when the source is a file', () => { + describe(`>> when src is regular and dest is a symlink that points to src`, () => { + it('should not copy and return', done => { + src = path.join(TEST_DIR, 'src', 'somefile.txt') + fs.ensureFileSync(src) + fs.writeFileSync(src, 'some data') + + const destLink = path.join(TEST_DIR, 'dest-symlink') + fs.symlinkSync(src, destLink, 'file') + + fs.copy(src, destLink, err => { + assert.ifError(err) + + const link = fs.readlinkSync(destLink) + assert.strictEqual(link, src) + assert(fs.readFileSync(link, 'utf8'), 'some data') + done() + }) + }) + }) + + describe(`>> when src is a symlink that points to a regular dest`, () => { + it('should not copy and return', done => { + dest = path.join(TEST_DIR, 'dest', 'somefile.txt') + fs.ensureFileSync(dest) + fs.writeFileSync(dest, 'some data') + + const srcLink = path.join(TEST_DIR, 'src-symlink') + fs.symlinkSync(dest, srcLink, 'file') + + fs.copy(srcLink, dest, err => { + assert.ifError(err) + + const link = fs.readlinkSync(srcLink) + assert.strictEqual(link, dest) + assert(fs.readFileSync(link, 'utf8'), 'some data') + done() + }) + }) + }) + + describe('>> when src and dest are symlinks that point to the exact same path', () => { + it('should not copy and return', done => { + src = path.join(TEST_DIR, 'src', 'srcfile.txt') + fs.ensureFileSync(src) + fs.writeFileSync(src, 'src data') + + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'file') + + const destLink = path.join(TEST_DIR, 'dest_symlink') + fs.symlinkSync(src, destLink, 'file') + + fs.copy(srcLink, destLink, err => { + assert.ifError(err) + + const srcln = fs.readlinkSync(srcLink) + assert.strictEqual(srcln, src) + const destln = fs.readlinkSync(destLink) + assert.strictEqual(destln, src) + assert(fs.readFileSync(srcln, 'utf8'), 'src data') + assert(fs.readFileSync(destln, 'utf8'), 'src data') + done() + }) + }) + }) + }) +}) diff --git a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js new file mode 100644 index 00000000..6fc138a4 --- /dev/null +++ b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js @@ -0,0 +1,431 @@ +'use strict' + +const assert = require('assert') +const os = require('os') +const path = require('path') +const fs = require(process.cwd()) +const klawSync = require('klaw-sync') + +/* global beforeEach, afterEach, describe, it */ + +// these files are used for all tests +const FILES = [ + 'file0.txt', + path.join('dir1', 'file1.txt'), + path.join('dir1', 'dir2', 'file2.txt'), + path.join('dir1', 'dir2', 'dir3', 'file3.txt') +] + +const dat0 = 'file0' +const dat1 = 'file1' +const dat2 = 'file2' +const dat3 = 'file3' + +function testSuccess (src, dest, done) { + const srclen = klawSync(src).length + // assert src has contents + assert(srclen > 2) + fs.copy(src, dest, err => { + assert.ifError(err) + + const destlen = klawSync(dest).length + + // assert src and dest length are the same + assert.strictEqual(destlen, srclen, 'src and dest length should be equal') + + FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied')) + + const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8') + const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8') + const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8') + const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8') + + assert.strictEqual(o0, dat0, 'file contents matched') + assert.strictEqual(o1, dat1, 'file contents matched') + assert.strictEqual(o2, dat2, 'file contents matched') + assert.strictEqual(o3, dat3, 'file contents matched') + done() + }) +} + +function testError (src, dest, done) { + fs.copy(src, dest, err => { + assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) + done() + }) +} + +describe('+ copySync() - prevent copying into itself', () => { + let TEST_DIR, src, dest + + beforeEach(done => { + TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-prevent-infinite-recursion') + src = path.join(TEST_DIR, 'src') + fs.mkdirsSync(src) + + // ensureFileSync creates required parent dirs for us:) + FILES.forEach(f => fs.ensureFileSync(path.join(src, f))) + + fs.writeFileSync(path.join(src, FILES[0]), dat0) + fs.writeFileSync(path.join(src, FILES[1]), dat1) + fs.writeFileSync(path.join(src, FILES[2]), dat2) + fs.writeFileSync(path.join(src, FILES[3]), dat3) + done() + }) + + afterEach(done => fs.remove(TEST_DIR, done)) + + describe('> when source is a file', () => { + it(`should copy the file successfully even when dest is a subdir of src`, done => { + const srcFile = path.join(TEST_DIR, 'src', 'srcfile.txt') + const destFile = path.join(TEST_DIR, 'src', 'dest', 'destfile.txt') + fs.writeFileSync(srcFile, dat0) + + fs.copy(srcFile, destFile, err => { + assert.ifError(err) + + assert(fs.existsSync(destFile, 'file copied')) + const out = fs.readFileSync(destFile, 'utf8') + assert.strictEqual(out, dat0, 'file contents matched') + done() + }) + }) + }) + + // test for these cases: + // - src is directory, dest is directory + // - src is directory, dest is symlink + // - src is symlink, dest is directory + // - src is symlink, dest is symlink + + describe('> when source is a directory', () => { + describe('>> when dest is a directory', () => { + it(`should copy the directory successfully when dest is 'src_dest'`, done => { + dest = path.join(TEST_DIR, 'src_dest') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'src-dest'`, done => { + dest = path.join(TEST_DIR, 'src-dest') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'dest_src'`, done => { + dest = path.join(TEST_DIR, 'dest_src') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'src_dest/src'`, done => { + dest = path.join(TEST_DIR, 'src_dest', 'src') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'src-dest/src'`, done => { + dest = path.join(TEST_DIR, 'src-dest', 'src') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'dest_src/src'`, done => { + dest = path.join(TEST_DIR, 'dest_src', 'src') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'src_src/dest'`, done => { + dest = path.join(TEST_DIR, 'src_src', 'dest') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'src-src/dest'`, done => { + dest = path.join(TEST_DIR, 'src-src', 'dest') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'srcsrc/dest'`, done => { + dest = path.join(TEST_DIR, 'srcsrc', 'dest') + return testSuccess(src, dest, done) + }) + + it(`should copy the directory successfully when dest is 'dest/src'`, done => { + dest = path.join(TEST_DIR, 'dest', 'src') + return testSuccess(src, dest, done) + }) + + it('should copy the directory successfully when dest is very nested that all its parents need to be created', done => { + dest = path.join(TEST_DIR, 'dest', 'src', 'foo', 'bar', 'baz', 'qux', 'quux', 'waldo', + 'grault', 'garply', 'fred', 'plugh', 'thud', 'some', 'highly', 'deeply', + 'badly', 'nasty', 'crazy', 'mad', 'nested', 'dest') + return testSuccess(src, dest, done) + }) + + it(`should throw error when dest is 'src/dest'`, done => { + dest = path.join(TEST_DIR, 'src', 'dest') + return testError(src, dest, done) + }) + + it(`should throw error when dest is 'src/src_dest'`, done => { + dest = path.join(TEST_DIR, 'src', 'src_dest') + return testError(src, dest, done) + }) + + it(`should throw error when dest is 'src/dest_src'`, done => { + dest = path.join(TEST_DIR, 'src', 'dest_src') + return testError(src, dest, done) + }) + + it(`should throw error when dest is 'src/dest/src'`, done => { + dest = path.join(TEST_DIR, 'src', 'dest', 'src') + return testError(src, dest, done) + }) + }) + + describe('>> when dest is a symlink', () => { + it('should not copy and return when dest points exactly to src', done => { + const destLink = path.join(TEST_DIR, 'dest_symlink') + fs.symlinkSync(src, destLink, 'dir') + + const srclenBefore = klawSync(src).length + assert(srclenBefore > 2) + + fs.copy(src, destLink, err => { + assert.ifError(err) + + const srclenAfter = klawSync(src).length + assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change') + + const link = fs.readlinkSync(destLink) + assert.strictEqual(link, src) + done() + }) + }) + + it('should throw an error when resolved dest path is a subdir of src', done => { + const destLink = path.join(TEST_DIR, 'dest_symlink') + const resolvedDestPath = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest') + fs.ensureFileSync(path.join(resolvedDestPath, 'subdir', 'somefile.txt')) + + // make symlink that points to a subdir in src + fs.symlinkSync(resolvedDestPath, destLink, 'dir') + + fs.copy(src, destLink, err => { + assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${resolvedDestPath}'`) + done() + }) + }) + + it('should copy the directory successfully when src is a subdir of resolved dest path', done => { + const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src') + const destLink = path.join(TEST_DIR, 'dest_symlink') + fs.copySync(src, srcInDest) // put some stuff in srcInDest + + dest = path.join(TEST_DIR, 'dest') + fs.symlinkSync(dest, destLink, 'dir') + + const srclen = klawSync(srcInDest).length + const destlenBefore = klawSync(dest).length + + assert(srclen > 2) + fs.copy(srcInDest, destLink, err => { + assert.ifError(err) + + const destlenAfter = klawSync(dest).length + + // assert dest length is oldlen + length of stuff copied from src + assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth') + + FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied')) + + const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8') + const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8') + const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8') + const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8') + + assert.strictEqual(o0, dat0, 'files contents matched') + assert.strictEqual(o1, dat1, 'files contents matched') + assert.strictEqual(o2, dat2, 'files contents matched') + assert.strictEqual(o3, dat3, 'files contents matched') + done() + }) + }) + }) + }) + + describe('> when source is a symlink', () => { + describe('>> when dest is a directory', () => { + it('should not copy and return when resolved src path points to dest', done => { + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + + dest = path.join(TEST_DIR, 'src') + + fs.copy(srcLink, dest, err => { + assert.ifError(err) + // assert source not affected + const link = fs.readlinkSync(srcLink) + assert.strictEqual(link, src) + done() + }) + }) + + it('should throw an error when dest is a subdir of resolved src path', done => { + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + + dest = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest') + + fs.copy(srcLink, dest, err => { + assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) + // assert source not affecte + const link = fs.readlinkSync(srcLink) + assert.strictEqual(link, src) + done() + }) + }) + + it('should copy the directory successfully when resolved src path is a subdir of dest', done => { + const srcLink = path.join(TEST_DIR, 'src_symlink') + const resolvedSrcPath = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src') + fs.copySync(src, resolvedSrcPath) + + // make symlink that points to a subdir in dest + fs.symlinkSync(resolvedSrcPath, srcLink, 'dir') + + dest = path.join(TEST_DIR, 'dest') + + const srclen = klawSync(resolvedSrcPath).length + assert(srclen > 2) + const destlenBefore = klawSync(dest).length + + fs.copy(srcLink, dest, err => { + assert.ifError(err) + + const destlenAfter = klawSync(dest).length + + // assert dest length is oldlen + length of stuff copied from src + assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth') + + FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied')) + + const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8') + const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8') + const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8') + const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8') + + assert.strictEqual(o0, dat0, 'files contents matched') + assert.strictEqual(o1, dat1, 'files contents matched') + assert.strictEqual(o2, dat2, 'files contents matched') + assert.strictEqual(o3, dat3, 'files contents matched') + done() + }) + }) + + it(`should copy the directory successfully when dest is 'src_src/dest'`, done => { + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + + dest = path.join(TEST_DIR, 'src_src', 'dest') + testSuccess(srcLink, dest, () => { + const link = fs.readlinkSync(dest) + assert.strictEqual(link, src) + done() + }) + }) + + it(`should copy the directory successfully when dest is 'srcsrc/dest'`, done => { + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + + dest = path.join(TEST_DIR, 'src_src', 'dest') + testSuccess(srcLink, dest, () => { + const link = fs.readlinkSync(dest) + assert.strictEqual(link, src) + done() + }) + }) + }) + + describe('>> when dest is a symlink', () => { + it('should not copy and return when resolved dest path is exactly the same as resolved src path', done => { + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + const destLink = path.join(TEST_DIR, 'dest_symlink') + fs.symlinkSync(src, destLink, 'dir') + + const srclenBefore = klawSync(srcLink).length + const destlenBefore = klawSync(destLink).length + assert(srclenBefore > 2) + assert(destlenBefore > 2) + + fs.copy(srcLink, destLink, err => { + assert.ifError(err) + + const srclenAfter = klawSync(srcLink).length + assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change') + const destlenAfter = klawSync(destLink).length + assert.strictEqual(destlenAfter, destlenBefore, 'dest length should not change') + + const srcln = fs.readlinkSync(srcLink) + assert.strictEqual(srcln, src) + const destln = fs.readlinkSync(destLink) + assert.strictEqual(destln, src) + done() + }) + }) + + it('should not copy and throw error when resolved dest path is a subdir of resolved src path', done => { + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + + const destLink = path.join(TEST_DIR, 'dest_symlink') + const resolvedDestPath = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest') + fs.ensureFileSync(path.join(resolvedDestPath, 'subdir', 'somefile.txt')) + + fs.symlinkSync(resolvedDestPath, destLink, 'dir') + + fs.copy(srcLink, destLink, err => { + assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${resolvedDestPath}'`) + done() + }) + }) + + it('should copy the directory correctly when resolved src path is a subdir of resolved dest path', done => { + const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src') + const srcLink = path.join(TEST_DIR, 'src_symlink') + // put some stuff in resolved src path + fs.copySync(src, srcInDest) + + fs.symlinkSync(srcInDest, srcLink, 'dir') + + dest = path.join(TEST_DIR, 'dest') + + const destLink = path.join(TEST_DIR, 'dest_symlink') + fs.symlinkSync(dest, destLink, 'dir') + + const srclen = klawSync(srcInDest).length + assert(srclen > 2) + const destlenBefore = klawSync(dest).length + + fs.copy(srcLink, destLink, err => { + assert.ifError(err) + + const destlenAfter = klawSync(dest).length + + // assert dest length is oldlen + length of stuff copied from src + assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth') + + FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied')) + + const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8') + const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8') + const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8') + const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8') + + assert.strictEqual(o0, dat0, 'files contents matched') + assert.strictEqual(o1, dat1, 'files contents matched') + assert.strictEqual(o2, dat2, 'files contents matched') + assert.strictEqual(o3, dat3, 'files contents matched') + done() + }) + }) + }) + }) +}) diff --git a/lib/copy/__tests__/ncp/symlink.test.js b/lib/copy/__tests__/copy-symlink.test.js similarity index 82% rename from lib/copy/__tests__/ncp/symlink.test.js rename to lib/copy/__tests__/copy-symlink.test.js index 1b8816b6..cf148dbf 100644 --- a/lib/copy/__tests__/ncp/symlink.test.js +++ b/lib/copy/__tests__/copy-symlink.test.js @@ -3,14 +3,14 @@ const fs = require('fs') const os = require('os') const fse = require(process.cwd()) -const ncp = require('../../ncp') const path = require('path') const assert = require('assert') +const copy = require('../copy') /* global afterEach, beforeEach, describe, it */ -describe('ncp / symlink', () => { - const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-symlinks') +describe('copy() - symlink', () => { + const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-symlinks') const src = path.join(TEST_DIR, 'src') const out = path.join(TEST_DIR, 'out') @@ -21,21 +21,22 @@ describe('ncp / symlink', () => { }) }) - afterEach(done => fse.remove(TEST_DIR, done)) + afterEach(done => { + fse.remove(TEST_DIR, done) + }) - it('copies symlinks by default', done => { - ncp(src, out, err => { + it('should copy symlinks by default', done => { + copy(src, out, err => { assert.ifError(err) assert.equal(fs.readlinkSync(path.join(out, 'file-symlink')), path.join(src, 'foo')) assert.equal(fs.readlinkSync(path.join(out, 'dir-symlink')), path.join(src, 'dir')) - done() }) }) - it('copies file contents when dereference=true', done => { - ncp(src, out, {dereference: true}, err => { + it('should copy file contents when dereference=true', done => { + copy(src, out, {dereference: true}, err => { assert.ifError(err) const fileSymlinkPath = path.join(out, 'file-symlink') @@ -45,7 +46,6 @@ describe('ncp / symlink', () => { const dirSymlinkPath = path.join(out, 'dir-symlink') assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory()) assert.deepEqual(fs.readdirSync(dirSymlinkPath), ['bar']) - done() }) }) diff --git a/lib/copy/__tests__/copy.test.js b/lib/copy/__tests__/copy.test.js index 6ed64ff8..fa4dfce7 100644 --- a/lib/copy/__tests__/copy.test.js +++ b/lib/copy/__tests__/copy.test.js @@ -69,7 +69,7 @@ describe('fs-extra', () => { }) }) - describe('> when the destination dir does not exist', () => { + describe('>> when the destination dir does not exist', () => { it('should create the destination directory and copy the file', done => { const src = path.join(TEST_DIR, 'file.txt') const dest = path.join(TEST_DIR, 'this/path/does/not/exist/copied.txt') @@ -87,7 +87,7 @@ describe('fs-extra', () => { }) describe('> when the source is a directory', () => { - describe('> when the source directory does not exist', () => { + describe('>> when the source directory does not exist', () => { it('should return an error', done => { const ts = path.join(TEST_DIR, 'this_dir_does_not_exist') const td = path.join(TEST_DIR, 'this_dir_really_does_not_matter') @@ -135,7 +135,7 @@ describe('fs-extra', () => { }) }) - describe('> when the destination dir does not exist', () => { + describe('>> when the destination dir does not exist', () => { it('should create the destination directory and copy the file', done => { const src = path.join(TEST_DIR, 'data/') fse.mkdirsSync(src) @@ -159,6 +159,7 @@ describe('fs-extra', () => { }) }) + /* It is redundant. See above, we already tested for this case. describe('> when src dir does not exist', () => { it('should return an error', done => { fse.copy('/does/not/exist', '/something/else', err => { @@ -167,6 +168,7 @@ describe('fs-extra', () => { }) }) }) + */ }) describe('> when filter is used', () => { diff --git a/lib/copy/__tests__/ncp/README.md b/lib/copy/__tests__/ncp/README.md deleted file mode 100644 index 7b6c04e6..00000000 --- a/lib/copy/__tests__/ncp/README.md +++ /dev/null @@ -1 +0,0 @@ -These tests came from: https://github.com/AvianFlu/ncp/tree/v1.0.1/test \ No newline at end of file diff --git a/lib/copy/__tests__/ncp/fixtures/modified-files/out/a b/lib/copy/__tests__/ncp/fixtures/modified-files/out/a deleted file mode 100644 index d606037c..00000000 --- a/lib/copy/__tests__/ncp/fixtures/modified-files/out/a +++ /dev/null @@ -1 +0,0 @@ -test2 \ No newline at end of file diff --git a/lib/copy/__tests__/ncp/fixtures/modified-files/src/a b/lib/copy/__tests__/ncp/fixtures/modified-files/src/a deleted file mode 100644 index 29f446af..00000000 --- a/lib/copy/__tests__/ncp/fixtures/modified-files/src/a +++ /dev/null @@ -1 +0,0 @@ -test3 \ No newline at end of file diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a deleted file mode 100644 index 802992c4..00000000 --- a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a +++ /dev/null @@ -1 +0,0 @@ -Hello world diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b deleted file mode 100644 index 9f6bb185..00000000 --- a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b +++ /dev/null @@ -1 +0,0 @@ -Hello ncp diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/c b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/c deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/d b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/d deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/e b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/e deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/f b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/f deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a deleted file mode 100644 index cf291b5e..00000000 --- a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a +++ /dev/null @@ -1 +0,0 @@ -Hello nodejitsu diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/b b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/b deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a deleted file mode 100644 index 802992c4..00000000 --- a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a +++ /dev/null @@ -1 +0,0 @@ -Hello world diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b deleted file mode 100644 index 9f6bb185..00000000 --- a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b +++ /dev/null @@ -1 +0,0 @@ -Hello ncp diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/c b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/c deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/d b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/d deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/e b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/e deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/f b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/f deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a deleted file mode 100644 index cf291b5e..00000000 --- a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a +++ /dev/null @@ -1 +0,0 @@ -Hello nodejitsu diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/b b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/b deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/copy/__tests__/ncp/ncp-error-perm.test.js b/lib/copy/__tests__/ncp/ncp-error-perm.test.js deleted file mode 100644 index 451bbdf7..00000000 --- a/lib/copy/__tests__/ncp/ncp-error-perm.test.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict' - -// file in reference: https://github.com/jprichardson/node-fs-extra/issues/56 - -const fs = require('fs') -const os = require('os') -const fse = require(process.cwd()) -const ncp = require('../../ncp') -const path = require('path') -const assert = require('assert') - -/* global afterEach, beforeEach, describe, it */ - -// skip test for windows -// eslint-disable globalReturn */ -// if (os.platform().indexOf('win') === 0) return -// eslint-enable globalReturn */ - -describe('ncp / error / dest-permission', () => { - const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-error-dest-perm') - const src = path.join(TEST_DIR, 'src') - const dest = path.join(TEST_DIR, 'dest') - - if (os.platform().indexOf('win') === 0) return - - beforeEach(done => { - fse.emptyDir(TEST_DIR, err => { - assert.ifError(err) - done() - }) - }) - - afterEach(done => fse.remove(TEST_DIR, done)) - - it('should return an error', done => { - const someFile = path.join(src, 'some-file') - fse.outputFileSync(someFile, 'hello') - - fse.mkdirsSync(dest) - fs.chmodSync(dest, parseInt('444', 8)) - - const subdest = path.join(dest, 'another-dir') - - ncp(src, subdest, err => { - assert(err) - assert.equal(err.code, 'EACCES') - done() - }) - }) -}) diff --git a/lib/copy/__tests__/ncp/ncp.test.js b/lib/copy/__tests__/ncp/ncp.test.js deleted file mode 100644 index 23ad3fcc..00000000 --- a/lib/copy/__tests__/ncp/ncp.test.js +++ /dev/null @@ -1,193 +0,0 @@ -'use strict' - -const fs = require('fs') -const ncp = require('../../ncp') -const path = require('path') -const rimraf = require('rimraf') -const assert = require('assert') -const readDirFiles = require('read-dir-files').read // temporary, will remove - -/* eslint-env mocha */ - -const fixturesDir = path.join(__dirname, 'fixtures') - -describe('ncp', () => { - describe('regular files and directories', () => { - const fixtures = path.join(fixturesDir, 'regular-fixtures') - const src = path.join(fixtures, 'src') - const out = path.join(fixtures, 'out') - - before(cb => rimraf(out, () => ncp(src, out, cb))) - - describe('when copying a directory of files', () => { - it('files are copied correctly', cb => { - readDirFiles(src, 'utf8', (srcErr, srcFiles) => { - readDirFiles(out, 'utf8', (outErr, outFiles) => { - assert.ifError(srcErr) - assert.deepEqual(srcFiles, outFiles) - cb() - }) - }) - }) - }) - - describe('when copying files using filter', () => { - before(cb => { - const filter = name => name.substr(name.length - 1) !== 'a' - - rimraf(out, () => ncp(src, out, { filter }, cb)) - }) - - it('files are copied correctly', cb => { - readDirFiles(src, 'utf8', (srcErr, srcFiles) => { - function filter (files) { - for (let fileName in files) { - const curFile = files[fileName] - if (curFile instanceof Object) { - return filter(curFile) - } - - if (fileName.substr(fileName.length - 1) === 'a') { - delete files[fileName] - } - } - } - filter(srcFiles) - readDirFiles(out, 'utf8', (outErr, outFiles) => { - assert.ifError(outErr) - assert.deepEqual(srcFiles, outFiles) - cb() - }) - }) - }) - }) - - describe('when using overwrite=true', () => { - before(function () { - this.originalCreateReadStream = fs.createReadStream - }) - - after(function () { - fs.createReadStream = this.originalCreateReadStream - }) - - it('the copy is complete after callback', done => { - ncp(src, out, {overwrite: true}, err => { - fs.createReadStream = () => done(new Error('createReadStream after callback')) - - assert.ifError(err) - process.nextTick(done) - }) - }) - }) - - describe('when using overwrite=false', () => { - beforeEach(done => rimraf(out, done)) - - it('works', cb => { - ncp(src, out, {overwrite: false}, err => { - assert.ifError(err) - cb() - }) - }) - - it('should not error if files exist', cb => { - ncp(src, out, () => { - ncp(src, out, {overwrite: false}, err => { - assert.ifError(err) - cb() - }) - }) - }) - - it('should error if errorOnExist and file exists', cb => { - ncp(src, out, () => { - ncp(src, out, { - overwrite: false, - errorOnExist: true - }, err => { - assert(err) - cb() - }) - }) - }) - }) - - describe('clobber', () => { - beforeEach(done => rimraf(out, done)) - - it('is an alias for overwrite', cb => { - ncp(src, out, () => { - ncp(src, out, { - clobber: false, - errorOnExist: true - }, err => { - assert(err) - cb() - }) - }) - }) - }) - - describe('when using transform', () => { - it('file descriptors are passed correctly', cb => { - ncp(src, out, { - transform: (read, write, file) => { - assert.notEqual(file.name, undefined) - assert.strictEqual(typeof file.mode, 'number') - read.pipe(write) - } - }, cb) - }) - }) - }) - - // see https://github.com/AvianFlu/ncp/issues/71 - describe('Issue 71: Odd Async Behaviors', cb => { - const fixtures = path.join(__dirname, 'fixtures', 'regular-fixtures') - const src = path.join(fixtures, 'src') - const out = path.join(fixtures, 'out') - - let totalCallbacks = 0 - - function copyAssertAndCount (callback) { - // rimraf(out, function() { - ncp(src, out, err => { - assert(!err) - totalCallbacks += 1 - readDirFiles(src, 'utf8', (srcErr, srcFiles) => { - readDirFiles(out, 'utf8', (outErr, outFiles) => { - assert.ifError(srcErr) - assert.deepEqual(srcFiles, outFiles) - callback() - }) - }) - }) - // }) - } - - describe('when copying a directory of files without cleaning the destination', () => { - it('callback fires once per run and directories are equal', done => { - const expected = 10 - let count = 10 - - function next () { - if (count > 0) { - setTimeout(() => { - copyAssertAndCount(() => { - count -= 1 - next() - }) - }, 100) - } else { - // console.log('Total callback count is', totalCallbacks) - assert.equal(totalCallbacks, expected) - done() - } - } - - next() - }) - }) - }) -}) diff --git a/lib/copy/copy.js b/lib/copy/copy.js index d66c8981..7f561e29 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -2,20 +2,33 @@ const fs = require('graceful-fs') const path = require('path') -const ncp = require('./ncp') -const mkdir = require('../mkdirs') +const mkdirs = require('../mkdirs').mkdirs +const utimes = require('../util/utimes').utimesMillis -function copy (src, dest, options, callback) { - if (typeof options === 'function' && !callback) { - callback = options +const DEST_NOENT = -1 +const DEST_EXISTS = 1 + +function copy (src, dest, options, cb) { + if (typeof options === 'function' && !cb) { + cb = options options = {} } else if (typeof options === 'function' || options instanceof RegExp) { options = {filter: options} } - callback = callback || function () {} + + cb = cb || function () {} options = options || {} - // Warn about using preserveTimestamps on 32-bit node: + // default to true for now + options.clobber = 'clobber' in options ? !!options.clobber : true + // overwrite falls back to clobber + options.overwrite = 'overwrite' in options ? !!options.overwrite : options.clobber + options.dereference = 'dereference' in options ? !!options.dereference : false + options.preserveTimestamps = 'preserveTimestamps' in options ? !!options.preserveTimestamps : false + + options.filter = options.filter || function () { return true } + + // Warn about using preserveTimestamps on 32-bit node if (options.preserveTimestamps && process.arch === 'ia32') { console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n see https://github.com/jprichardson/node-fs-extra/issues/269`) @@ -23,30 +36,179 @@ function copy (src, dest, options, callback) { // don't allow src and dest to be the same const basePath = process.cwd() - const currentPath = path.resolve(basePath, src) - const targetPath = path.resolve(basePath, dest) - if (currentPath === targetPath) return callback(new Error('Source and destination must not be the same.')) - - fs.lstat(src, (err, stats) => { - if (err) return callback(err) - - let dir = null - if (stats.isDirectory()) { - const parts = dest.split(path.sep) - parts.pop() - dir = parts.join(path.sep) - } else { - dir = path.dirname(dest) + src = path.resolve(basePath, src) + dest = path.resolve(basePath, dest) + if (src === dest) return cb(new Error('Source and destination must not be the same.')) + + if (options.filter) { + if (options.filter instanceof RegExp) { + console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function') + if (!options.filter.test(src)) return cb() + } else if (typeof options.filter === 'function') { + if (!options.filter(src, dest)) return cb() + } + } + + const destParent = path.dirname(dest) + mkdirs(destParent, err => { + if (err && err.code !== 'EEXIST') return cb(err) + + let stat = options.dereference ? fs.stat : fs.lstat + stat(src, (err, srcStat) => { + if (err) return cb(err) + + if (srcStat.isDirectory()) { + return onDir(src, srcStat, dest, options, cb) + } else if (srcStat.isFile() || srcStat.isCharacterDevice() || srcStat.isBlockDevice()) { + return onFile(src, srcStat, dest, options, cb) + } else if (srcStat.isSymbolicLink() && !options.dereference) { + return onLink(src, dest, options, cb) + } + }) + }) +} + +function checkDest (dest, cb) { + fs.readlink(dest, (err, resolvedDestPath) => { + if (err) { + if (err.code === 'ENOENT') return cb(null, DEST_NOENT) + // dest exists but is not a link + else if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, DEST_EXISTS) + else return cb(err) + } else { // dest exists and is a link + return cb(null, resolvedDestPath) } + }) +} + +function onFile (src, srcStat, dest, options, cb) { + checkDest(dest, (err, res) => { + if (err) return cb(err) + if (res === DEST_NOENT) { + return cpFile(src, srcStat, dest, options, cb) + } else if (res === DEST_EXISTS) { + if (options.overwrite) { + fs.unlink(dest, err => { + if (err) return cb(err) + return cpFile(src, srcStat, dest, options, cb) + }) + } else if (options.errorOnExist) { + return cb(new Error(dest + ' already exists')) + } else return cb() + } else return cb() + }) +} + +function cpFile (src, srcStat, dest, options, cb) { + const rs = fs.createReadStream(src) + const ws = fs.createWriteStream(dest, { mode: srcStat.mode }) - fs.exists(dir, dirExists => { - if (dirExists) return ncp(src, dest, options, callback) - mkdir.mkdirs(dir, err => { - if (err) return callback(err) - ncp(src, dest, options, callback) + rs.on('error', err => cb(err)) + ws.on('error', err => cb(err)) + + ws.on('open', () => { + rs.pipe(ws) + }).once('close', () => { + fs.chmod(dest, srcStat.mode, err => { + if (err) return cb(err) + if (options.preserveTimestamps) { + return utimes(dest, srcStat.atime, srcStat.mtime, cb) + } else return cb() + }) + }) +} + +function onDir (src, srcStat, dest, options, cb) { + checkDest(dest, (err, res) => { + if (err) return cb(err) + if (res === DEST_NOENT) { + // if dest is a subdir of src, prevent copying into itself + if (isSrcSubdir(src, dest)) return cb(new Error(`Cannot copy directory '${src}' into itself '${dest}'`)) + fs.mkdir(dest, srcStat.mode, err => { + if (err) return cb(err) + fs.chmod(dest, srcStat.mode, err => { + if (err) return cb(err) + return cpDir(src, dest, options, cb) + }) }) + } else if (res === DEST_EXISTS) { + if (isSrcSubdir(src, dest)) return cb(new Error(`Cannot copy directory '${src}' into itself '${dest}'`)) + return cpDir(src, dest, options, cb) + } else if (res && typeof res !== 'number') { // dest exists and is a link + if (isSrcSubdir(src, res)) return cb(new Error(`Cannot copy directory '${src}' into itself '${res}'`)) + if (src === res) return cb() + return cpDir(src, dest, options, cb) + } else return cb() + }) +} + +function cpDir (src, dest, options, cb) { + fs.readdir(src, (err, items) => { + if (err) return cb(err) + Promise.all(items.map(item => { + return new Promise((resolve, reject) => { + copy(path.join(src, item), path.join(dest, item), options, err => { + if (err) reject(err) + else resolve() + }) + }) + })).then(() => { + return cb() + }).catch(err => { + return cb(err) + }) + }) +} + +function onLink (src, dest, options, cb) { + fs.readlink(src, (err, resolvedSrcPath) => { + if (err) return cb(err) + + if (options.dereference) { + resolvedSrcPath = path.resolve(process.cwd(), resolvedSrcPath) + } + + checkDest(dest, (err, resolvedDestPath) => { + if (err) return cb(err) + + if (resolvedDestPath === DEST_NOENT) { + // if dest is a subdir of resolved src path, prevent copying into itself + if (isSrcSubdir(resolvedSrcPath, dest)) { + return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${dest}'`)) + } + return fs.symlink(resolvedSrcPath, dest, cb) + } else if (resolvedDestPath === DEST_EXISTS) { // dest exists but is not a link + if (isSrcSubdir(resolvedSrcPath, dest)) { + return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${dest}'`)) + } + // if src points to dest + if (resolvedSrcPath === dest) return cb() + return copy(resolvedSrcPath, dest, options, cb) + } else if (resolvedDestPath && typeof resolvedDestPath !== 'number') { // dest is a link + if (options.dereference) { + resolvedDestPath = path.resolve(process.cwd(), resolvedDestPath) + } + // if resolved dest path is a subdir of resolved src path, prevent copying into itself + if (isSrcSubdir(resolvedSrcPath, resolvedDestPath)) { + return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${resolvedDestPath}'`)) + } + if (resolvedSrcPath === resolvedDestPath) return cb() + return copy(resolvedSrcPath, dest, options, cb) + } else return cb() }) }) } +// return true if dest is a subdir of src, otherwise false. +// extract dest base dir and check if that is the same as src basename +function isSrcSubdir (src, dest) { + try { + return src !== dest && + dest.indexOf(src) > -1 && + dest.split(path.dirname(src) + path.sep)[1].split(path.sep)[0] === path.basename(src) + } catch (e) { + return false + } +} + module.exports = copy diff --git a/lib/copy/ncp.js b/lib/copy/ncp.js deleted file mode 100644 index 9670ee02..00000000 --- a/lib/copy/ncp.js +++ /dev/null @@ -1,234 +0,0 @@ -// imported from ncp (this is temporary, will rewrite) - -var fs = require('graceful-fs') -var path = require('path') -var utimes = require('../util/utimes') - -function ncp (source, dest, options, callback) { - if (!callback) { - callback = options - options = {} - } - - var basePath = process.cwd() - var currentPath = path.resolve(basePath, source) - var targetPath = path.resolve(basePath, dest) - - var filter = options.filter - var transform = options.transform - var overwrite = options.overwrite - // If overwrite is undefined, use clobber, otherwise default to true: - if (overwrite === undefined) overwrite = options.clobber - if (overwrite === undefined) overwrite = true - var errorOnExist = options.errorOnExist - var dereference = options.dereference - var preserveTimestamps = options.preserveTimestamps === true - - var started = 0 - var finished = 0 - var running = 0 - - var errored = false - - startCopy(currentPath) - - function startCopy (source) { - started++ - if (filter) { - if (filter instanceof RegExp) { - console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function') - if (!filter.test(source)) { - return doneOne(true) - } - } else if (typeof filter === 'function') { - if (!filter(source, dest)) { - return doneOne(true) - } - } - } - return getStats(source) - } - - function getStats (source) { - var stat = dereference ? fs.stat : fs.lstat - running++ - stat(source, function (err, stats) { - if (err) return onError(err) - - // We need to get the mode from the stats object and preserve it. - var item = { - name: source, - mode: stats.mode, - mtime: stats.mtime, // modified time - atime: stats.atime, // access time - stats: stats // temporary - } - - if (stats.isDirectory()) { - return onDir(item) - } else if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) { - return onFile(item) - } else if (stats.isSymbolicLink()) { - // Symlinks don't really need to know about the mode. - return onLink(source) - } - }) - } - - function onFile (file) { - var target = file.name.replace(currentPath, targetPath.replace('$', '$$$$')) // escapes '$' with '$$' - isWritable(target, function (writable) { - if (writable) { - copyFile(file, target) - } else { - if (overwrite) { - rmFile(target, function () { - copyFile(file, target) - }) - } else if (errorOnExist) { - onError(new Error(target + ' already exists')) - } else { - doneOne() - } - } - }) - } - - function copyFile (file, target) { - var readStream = fs.createReadStream(file.name) - var writeStream = fs.createWriteStream(target, { mode: file.mode }) - - readStream.on('error', onError) - writeStream.on('error', onError) - - if (transform) { - transform(readStream, writeStream, file) - } else { - writeStream.on('open', function () { - readStream.pipe(writeStream) - }) - } - - writeStream.once('close', function () { - fs.chmod(target, file.mode, function (err) { - if (err) return onError(err) - if (preserveTimestamps) { - utimes.utimesMillis(target, file.atime, file.mtime, function (err) { - if (err) return onError(err) - return doneOne() - }) - } else { - doneOne() - } - }) - }) - } - - function rmFile (file, done) { - fs.unlink(file, function (err) { - if (err) return onError(err) - return done() - }) - } - - function onDir (dir) { - var target = dir.name.replace(currentPath, targetPath.replace('$', '$$$$')) // escapes '$' with '$$' - isWritable(target, function (writable) { - if (writable) { - return mkDir(dir, target) - } - copyDir(dir.name) - }) - } - - function mkDir (dir, target) { - fs.mkdir(target, dir.mode, function (err) { - if (err) return onError(err) - // despite setting mode in fs.mkdir, doesn't seem to work - // so we set it here. - fs.chmod(target, dir.mode, function (err) { - if (err) return onError(err) - copyDir(dir.name) - }) - }) - } - - function copyDir (dir) { - fs.readdir(dir, function (err, items) { - if (err) return onError(err) - items.forEach(function (item) { - startCopy(path.join(dir, item)) - }) - return doneOne() - }) - } - - function onLink (link) { - var target = link.replace(currentPath, targetPath) - fs.readlink(link, function (err, resolvedPath) { - if (err) return onError(err) - checkLink(resolvedPath, target) - }) - } - - function checkLink (resolvedPath, target) { - if (dereference) { - resolvedPath = path.resolve(basePath, resolvedPath) - } - isWritable(target, function (writable) { - if (writable) { - return makeLink(resolvedPath, target) - } - fs.readlink(target, function (err, targetDest) { - if (err) return onError(err) - - if (dereference) { - targetDest = path.resolve(basePath, targetDest) - } - if (targetDest === resolvedPath) { - return doneOne() - } - return rmFile(target, function () { - makeLink(resolvedPath, target) - }) - }) - }) - } - - function makeLink (linkPath, target) { - fs.symlink(linkPath, target, function (err) { - if (err) return onError(err) - return doneOne() - }) - } - - function isWritable (path, done) { - fs.lstat(path, function (err) { - if (err) { - if (err.code === 'ENOENT') return done(true) - return done(false) - } - return done(false) - }) - } - - function onError (err) { - // ensure callback is defined & called only once: - if (!errored && callback !== undefined) { - errored = true - return callback(err) - } - } - - function doneOne (skipped) { - if (!skipped) running-- - finished++ - if ((started === finished) && (running === 0)) { - if (callback !== undefined) { - return callback(null) - } - } - } -} - -module.exports = ncp diff --git a/lib/move/__tests__/move.test.js b/lib/move/__tests__/move.test.js index fc467b96..1d0e51d0 100644 --- a/lib/move/__tests__/move.test.js +++ b/lib/move/__tests__/move.test.js @@ -1,20 +1,18 @@ -'use strict' - -const fs = require('graceful-fs') -const os = require('os') -const fse = require(process.cwd()) -const path = require('path') -const assert = require('assert') -const rimraf = require('rimraf') +var assert = require('assert') +var os = require('os') +var path = require('path') +var rimraf = require('rimraf') +var fs = require('graceful-fs') +var fse = require(process.cwd()) /* global afterEach, beforeEach, describe, it */ function createAsyncErrFn (errCode) { - const fn = function () { + var fn = function () { fn.callCount++ - const callback = arguments[arguments.length - 1] - setTimeout(() => { - const err = new Error() + var callback = arguments[arguments.length - 1] + setTimeout(function () { + var err = new Error() err.code = errCode callback(err) }, 10) @@ -23,8 +21,8 @@ function createAsyncErrFn (errCode) { return fn } -const originalRename = fs.rename -const originalLink = fs.link +var originalRename = fs.rename +var originalLink = fs.link function setUpMockFs (errCode) { fs.rename = createAsyncErrFn(errCode) @@ -36,10 +34,10 @@ function tearDownMockFs () { fs.link = originalLink } -describe('move', () => { - let TEST_DIR +describe('move', function () { + var TEST_DIR - beforeEach(() => { + beforeEach(function () { TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move') fse.emptyDirSync(TEST_DIR) @@ -52,16 +50,18 @@ describe('move', () => { fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-folder/file3'), 'knuckles\n') }) - afterEach(done => rimraf(TEST_DIR, done)) + afterEach(function (done) { + rimraf(TEST_DIR, done) + }) - it('should rename a file on the same device', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/a-file-dest` + it('should rename a file on the same device', function (done) { + var src = TEST_DIR + '/a-file' + var dest = TEST_DIR + '/a-file-dest' - fse.move(src, dest, err => { + fse.move(src, dest, function (err) { assert.ifError(err) - fs.readFile(dest, 'utf8', (err, contents) => { - const expected = /^sonic the hedgehog\r?\n$/ + fs.readFile(dest, 'utf8', function (err, contents) { + var expected = /^sonic the hedgehog\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) done() @@ -69,43 +69,43 @@ describe('move', () => { }) }) - it('should not overwrite the destination by default', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/a-folder/another-file` + it('should not overwrite the destination by default', function (done) { + var src = TEST_DIR + '/a-file' + var dest = TEST_DIR + '/a-folder/another-file' // verify file exists already assert(fs.existsSync(dest)) - fse.move(src, dest, err => { + fse.move(src, dest, function (err) { assert.ok(err && err.code === 'EEXIST', 'throw EEXIST') done() }) }) - it('should not overwrite if overwrite = false', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/a-folder/another-file` + it('should not overwrite if overwrite = false', function (done) { + var src = TEST_DIR + '/a-file' + var dest = TEST_DIR + '/a-folder/another-file' // verify file exists already assert(fs.existsSync(dest)) - fse.move(src, dest, {overwrite: false}, err => { + fse.move(src, dest, {overwrite: false}, function (err) { assert.ok(err && err.code === 'EEXIST', 'throw EEXIST') done() }) }) - it('should overwrite file if overwrite = true', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/a-folder/another-file` + it('should overwrite file if overwrite = true', function (done) { + var src = TEST_DIR + '/a-file' + var dest = TEST_DIR + '/a-folder/another-file' // verify file exists already assert(fs.existsSync(dest)) - fse.move(src, dest, {overwrite: true}, err => { + fse.move(src, dest, {overwrite: true}, function (err) { assert.ifError(err) - fs.readFile(dest, 'utf8', (err, contents) => { - const expected = /^sonic the hedgehog\r?\n$/ + fs.readFile(dest, 'utf8', function (err, contents) { + var expected = /^sonic the hedgehog\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) done() @@ -121,23 +121,23 @@ describe('move', () => { this.timeout(90000) // Create src - const src = path.join(TEST_DIR, 'src') + var src = path.join(TEST_DIR, 'src') fse.ensureDirSync(src) fse.mkdirsSync(path.join(src, 'some-folder')) fs.writeFileSync(path.join(src, 'some-file'), 'hi') - const dest = path.join(TEST_DIR, 'a-folder') + var dest = path.join(TEST_DIR, 'a-folder') // verify dest has stuff in it - const paths = fs.readdirSync(dest) + var paths = fs.readdirSync(dest) assert(paths.indexOf('another-file') >= 0) assert(paths.indexOf('another-folder') >= 0) - fse.move(src, dest, {overwrite: true}, err => { + fse.move(src, dest, {overwrite: true}, function (err) { assert.ifError(err) // verify dest does not have old stuff - const paths = fs.readdirSync(dest) + var paths = fs.readdirSync(dest) assert.strictEqual(paths.indexOf('another-file'), -1) assert.strictEqual(paths.indexOf('another-folder'), -1) @@ -149,30 +149,30 @@ describe('move', () => { }) }) - it('should not create directory structure if mkdirp is false', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/does/not/exist/a-file-dest` + it('should not create directory structure if mkdirp is false', function (done) { + var src = TEST_DIR + '/a-file' + var dest = TEST_DIR + '/does/not/exist/a-file-dest' // verify dest directory does not exist assert(!fs.existsSync(path.dirname(dest))) - fse.move(src, dest, {mkdirp: false}, err => { + fse.move(src, dest, {mkdirp: false}, function (err) { assert.strictEqual(err.code, 'ENOENT') done() }) }) - it('should create directory structure by default', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/does/not/exist/a-file-dest` + it('should create directory structure by default', function (done) { + var src = TEST_DIR + '/a-file' + var dest = TEST_DIR + '/does/not/exist/a-file-dest' // verify dest directory does not exist assert(!fs.existsSync(path.dirname(dest))) - fse.move(src, dest, err => { + fse.move(src, dest, function (err) { assert.ifError(err) - fs.readFile(dest, 'utf8', (err, contents) => { - const expected = /^sonic the hedgehog\r?\n$/ + fs.readFile(dest, 'utf8', function (err, contents) { + var expected = /^sonic the hedgehog\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) done() @@ -180,18 +180,18 @@ describe('move', () => { }) }) - it('should work across devices', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/a-file-dest` + it('should work across devices', function (done) { + var src = TEST_DIR + '/a-file' + var dest = TEST_DIR + '/a-file-dest' setUpMockFs('EXDEV') - fse.move(src, dest, err => { + fse.move(src, dest, function (err) { assert.ifError(err) assert.strictEqual(fs.link.callCount, 1) - fs.readFile(dest, 'utf8', (err, contents) => { - const expected = /^sonic the hedgehog\r?\n$/ + fs.readFile(dest, 'utf8', function (err, contents) { + var expected = /^sonic the hedgehog\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) @@ -201,17 +201,17 @@ describe('move', () => { }) }) - it('should move folders', done => { - const src = `${TEST_DIR}/a-folder` - const dest = `${TEST_DIR}/a-folder-dest` + it('should move folders', function (done) { + var src = TEST_DIR + '/a-folder' + var dest = TEST_DIR + '/a-folder-dest' // verify it doesn't exist assert(!fs.existsSync(dest)) - fse.move(src, dest, err => { + fse.move(src, dest, function (err) { assert.ifError(err) - fs.readFile(dest + '/another-file', 'utf8', (err, contents) => { - const expected = /^tails\r?\n$/ + fs.readFile(dest + '/another-file', 'utf8', function (err, contents) { + var expected = /^tails\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) done() @@ -219,18 +219,18 @@ describe('move', () => { }) }) - it('should move folders across devices with EISDIR error', done => { - const src = `${TEST_DIR}/a-folder` - const dest = `${TEST_DIR}/a-folder-dest` + it('should move folders across devices with EISDIR error', function (done) { + var src = TEST_DIR + '/a-folder' + var dest = TEST_DIR + '/a-folder-dest' setUpMockFs('EISDIR') - fse.move(src, dest, err => { + fse.move(src, dest, function (err) { assert.ifError(err) assert.strictEqual(fs.link.callCount, 1) - fs.readFile(dest + '/another-folder/file3', 'utf8', (err, contents) => { - const expected = /^knuckles\r?\n$/ + fs.readFile(dest + '/another-folder/file3', 'utf8', function (err, contents) { + var expected = /^knuckles\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) @@ -241,20 +241,20 @@ describe('move', () => { }) }) - it('should overwrite folders across devices', done => { - const src = `${TEST_DIR}/a-folder` - const dest = `${TEST_DIR}/a-folder-dest` + it('should overwrite folders across devices', function (done) { + var src = TEST_DIR + '/a-folder' + var dest = TEST_DIR + '/a-folder-dest' fs.mkdirSync(dest) setUpMockFs('EXDEV') - fse.move(src, dest, {overwrite: true}, err => { + fse.move(src, dest, {overwrite: true}, function (err) { assert.ifError(err) assert.strictEqual(fs.rename.callCount, 1) - fs.readFile(dest + '/another-folder/file3', 'utf8', (err, contents) => { - const expected = /^knuckles\r?\n$/ + fs.readFile(dest + '/another-folder/file3', 'utf8', function (err, contents) { + var expected = /^knuckles\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) @@ -265,18 +265,18 @@ describe('move', () => { }) }) - it('should move folders across devices with EXDEV error', done => { - const src = `${TEST_DIR}/a-folder` - const dest = `${TEST_DIR}/a-folder-dest` + it('should move folders across devices with EXDEV error', function (done) { + var src = TEST_DIR + '/a-folder' + var dest = TEST_DIR + '/a-folder-dest' setUpMockFs('EXDEV') - fse.move(src, dest, err => { + fse.move(src, dest, function (err) { assert.ifError(err) assert.strictEqual(fs.link.callCount, 1) - fs.readFile(dest + '/another-folder/file3', 'utf8', (err, contents) => { - const expected = /^knuckles\r?\n$/ + fs.readFile(dest + '/another-folder/file3', 'utf8', function (err, contents) { + var expected = /^knuckles\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) @@ -287,18 +287,18 @@ describe('move', () => { }) }) - describe('clobber', () => { - it('should be an alias for overwrite', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/a-folder/another-file` + describe('clobber', function () { + it('should be an alias for overwrite', function (done) { + var src = TEST_DIR + '/a-file' + var dest = TEST_DIR + '/a-folder/another-file' // verify file exists already assert(fs.existsSync(dest)) - fse.move(src, dest, {overwrite: true}, err => { + fse.move(src, dest, {overwrite: true}, function (err) { assert.ifError(err) - fs.readFile(dest, 'utf8', (err, contents) => { - const expected = /^sonic the hedgehog\r?\n$/ + fs.readFile(dest, 'utf8', function (err, contents) { + var expected = /^sonic the hedgehog\r?\n$/ assert.ifError(err) assert.ok(contents.match(expected), `${contents} match ${expected}`) done() @@ -307,16 +307,16 @@ describe('move', () => { }) }) - describe.skip('> when trying to a move a folder into itself', () => { - it('should produce an error', done => { - const SRC_DIR = path.join(TEST_DIR, 'test') - const DEST_DIR = path.join(TEST_DIR, 'test', 'test') + describe('> when trying to a move a folder into itself', function () { + it('should produce an error', function (done) { + var SRC_DIR = path.join(TEST_DIR, 'test') + var DEST_DIR = path.join(TEST_DIR, 'test', 'test') assert(!fs.existsSync(SRC_DIR)) fs.mkdirSync(SRC_DIR) assert(fs.existsSync(SRC_DIR)) - fse.move(SRC_DIR, DEST_DIR, err => { + fse.move(SRC_DIR, DEST_DIR, function (err) { assert(fs.existsSync(SRC_DIR)) assert(err) done() @@ -327,9 +327,9 @@ describe('move', () => { // tested on Linux ubuntu 3.13.0-32-generic #57-Ubuntu SMP i686 i686 GNU/Linux // this won't trigger a bug on Mac OS X Yosimite with a USB drive (/Volumes) // see issue #108 - describe('> when actually trying to a move a folder across devices', () => { - const differentDevice = '/mnt' - let __skipTests = false + describe('> when actually trying to a move a folder across devices', function () { + var differentDevice = '/mnt' + var __skipTests = false // must set this up, if not, exit silently if (!fs.existsSync(differentDevice)) { @@ -345,12 +345,12 @@ describe('move', () => { __skipTests = true } - const _it = __skipTests ? it.skip : it + var _it = __skipTests ? it.skip : it - describe('> just the folder', () => { - _it('should move the folder', done => { - const src = '/mnt/some/weird/dir-really-weird' - const dest = path.join(TEST_DIR, 'device-weird') + describe('> just the folder', function () { + _it('should move the folder', function (done) { + var src = '/mnt/some/weird/dir-really-weird' + var dest = path.join(TEST_DIR, 'device-weird') if (!fs.existsSync(src)) { fse.mkdirpSync(src) @@ -360,7 +360,7 @@ describe('move', () => { assert(fs.lstatSync(src).isDirectory()) - fse.move(src, dest, err => { + fse.move(src, dest, function (err) { assert.ifError(err) assert(fs.existsSync(dest)) // console.log(path.normalize(dest)) diff --git a/lib/move/index.js b/lib/move/index.js index 195ab9f7..f5f8d1d6 100644 --- a/lib/move/index.js +++ b/lib/move/index.js @@ -7,8 +7,8 @@ // this needs a cleanup const fs = require('graceful-fs') -const ncp = require('../copy/ncp') const path = require('path') +const copy = require('../copy').copy const remove = require('../remove').remove const mkdirp = require('../mkdirs').mkdirs @@ -140,14 +140,14 @@ function moveDirAcrossDevice (source, dest, overwrite, callback) { if (overwrite) { remove(dest, err => { if (err) return callback(err) - startNcp() + startCopy() }) } else { - startNcp() + startCopy() } - function startNcp () { - ncp(source, dest, options, err => { + function startCopy () { + copy(source, dest, options, err => { if (err) return callback(err) remove(source, callback) }) From 84681ede6de9fa72540d4e0fd6abf8fa8592f3bd Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Sun, 26 Feb 2017 03:34:18 -0800 Subject: [PATCH 02/14] lib/copy/__tests__: Add ncp tests --- lib/copy/__tests__/ncp/README.md | 1 + lib/copy/__tests__/ncp/broken-symlink.test.js | 58 +++++ .../ncp/fixtures/modified-files/out/a | 1 + .../ncp/fixtures/modified-files/src/a | 1 + .../ncp/fixtures/regular-fixtures/out/a | 1 + .../ncp/fixtures/regular-fixtures/out/b | 1 + .../ncp/fixtures/regular-fixtures/out/c | 0 .../ncp/fixtures/regular-fixtures/out/d | 0 .../ncp/fixtures/regular-fixtures/out/e | 0 .../ncp/fixtures/regular-fixtures/out/f | 0 .../ncp/fixtures/regular-fixtures/out/sub/a | 1 + .../ncp/fixtures/regular-fixtures/out/sub/b | 0 .../ncp/fixtures/regular-fixtures/src/a | 1 + .../ncp/fixtures/regular-fixtures/src/b | 1 + .../ncp/fixtures/regular-fixtures/src/c | 0 .../ncp/fixtures/regular-fixtures/src/d | 0 .../ncp/fixtures/regular-fixtures/src/e | 0 .../ncp/fixtures/regular-fixtures/src/f | 0 .../ncp/fixtures/regular-fixtures/src/sub/a | 1 + .../ncp/fixtures/regular-fixtures/src/sub/b | 0 lib/copy/__tests__/ncp/ncp-error-perm.test.js | 50 +++++ lib/copy/__tests__/ncp/ncp.test.js | 202 ++++++++++++++++++ lib/copy/__tests__/ncp/symlink.test.js | 78 +++++++ 23 files changed, 397 insertions(+) create mode 100644 lib/copy/__tests__/ncp/README.md create mode 100644 lib/copy/__tests__/ncp/broken-symlink.test.js create mode 100644 lib/copy/__tests__/ncp/fixtures/modified-files/out/a create mode 100644 lib/copy/__tests__/ncp/fixtures/modified-files/src/a create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/c create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/d create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/e create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/f create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/b create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/c create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/d create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/e create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/f create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a create mode 100644 lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/b create mode 100644 lib/copy/__tests__/ncp/ncp-error-perm.test.js create mode 100644 lib/copy/__tests__/ncp/ncp.test.js create mode 100644 lib/copy/__tests__/ncp/symlink.test.js diff --git a/lib/copy/__tests__/ncp/README.md b/lib/copy/__tests__/ncp/README.md new file mode 100644 index 00000000..7b6c04e6 --- /dev/null +++ b/lib/copy/__tests__/ncp/README.md @@ -0,0 +1 @@ +These tests came from: https://github.com/AvianFlu/ncp/tree/v1.0.1/test \ No newline at end of file diff --git a/lib/copy/__tests__/ncp/broken-symlink.test.js b/lib/copy/__tests__/ncp/broken-symlink.test.js new file mode 100644 index 00000000..f3f8f76f --- /dev/null +++ b/lib/copy/__tests__/ncp/broken-symlink.test.js @@ -0,0 +1,58 @@ +var assert = require('assert') +var fs = require('fs') +var path = require('path') +var os = require('os') +var fse = require(process.cwd()) +var copy = require('../../copy') + +/* global afterEach, beforeEach, describe, it */ + +describe('ncp broken symlink', function () { + var TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-broken-symlinks') + var src = path.join(TEST_DIR, 'src') + var out = path.join(TEST_DIR, 'out') + + beforeEach(function (done) { + fse.emptyDir(TEST_DIR, function (err) { + assert.ifError(err) + createFixtures(src, done) + }) + }) + + afterEach(function (done) { + fse.remove(TEST_DIR, done) + }) + + it('should copy broken symlinks by default', function (done) { + copy(src, out, function (err) { + if (err) return done(err) + assert.equal(fs.readlinkSync(path.join(out, 'broken-symlink')), path.join(src, 'does-not-exist')) + done() + }) + }) + + it('should return an error when dereference=true', function (done) { + copy(src, out, {dereference: true}, function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) +}) + +function createFixtures (srcDir, callback) { + fs.mkdir(srcDir, function (err) { + if (err) return callback(err) + + try { + var brokenFile = path.join(srcDir, 'does-not-exist') + var brokenFileLink = path.join(srcDir, 'broken-symlink') + fs.writeFileSync(brokenFile, 'does not matter') + fs.symlinkSync(brokenFile, brokenFileLink, 'file') + } catch (err) { + callback(err) + } + + // break the symlink now + fse.remove(brokenFile, callback) + }) +} diff --git a/lib/copy/__tests__/ncp/fixtures/modified-files/out/a b/lib/copy/__tests__/ncp/fixtures/modified-files/out/a new file mode 100644 index 00000000..d606037c --- /dev/null +++ b/lib/copy/__tests__/ncp/fixtures/modified-files/out/a @@ -0,0 +1 @@ +test2 \ No newline at end of file diff --git a/lib/copy/__tests__/ncp/fixtures/modified-files/src/a b/lib/copy/__tests__/ncp/fixtures/modified-files/src/a new file mode 100644 index 00000000..29f446af --- /dev/null +++ b/lib/copy/__tests__/ncp/fixtures/modified-files/src/a @@ -0,0 +1 @@ +test3 \ No newline at end of file diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a new file mode 100644 index 00000000..802992c4 --- /dev/null +++ b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/a @@ -0,0 +1 @@ +Hello world diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b new file mode 100644 index 00000000..9f6bb185 --- /dev/null +++ b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/b @@ -0,0 +1 @@ +Hello ncp diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/c b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/c new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/d b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/d new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/e b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/e new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/f b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/f new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a new file mode 100644 index 00000000..cf291b5e --- /dev/null +++ b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/a @@ -0,0 +1 @@ +Hello nodejitsu diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/b b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/out/sub/b new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a new file mode 100644 index 00000000..802992c4 --- /dev/null +++ b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/a @@ -0,0 +1 @@ +Hello world diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b new file mode 100644 index 00000000..9f6bb185 --- /dev/null +++ b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/b @@ -0,0 +1 @@ +Hello ncp diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/c b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/c new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/d b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/d new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/e b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/e new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/f b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/f new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a new file mode 100644 index 00000000..cf291b5e --- /dev/null +++ b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/a @@ -0,0 +1 @@ +Hello nodejitsu diff --git a/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/b b/lib/copy/__tests__/ncp/fixtures/regular-fixtures/src/sub/b new file mode 100644 index 00000000..e69de29b diff --git a/lib/copy/__tests__/ncp/ncp-error-perm.test.js b/lib/copy/__tests__/ncp/ncp-error-perm.test.js new file mode 100644 index 00000000..2df093ae --- /dev/null +++ b/lib/copy/__tests__/ncp/ncp-error-perm.test.js @@ -0,0 +1,50 @@ +// file in reference: https://github.com/jprichardson/node-fs-extra/issues/56 + +var assert = require('assert') +var fs = require('fs') +var path = require('path') +var os = require('os') +var fse = require(process.cwd()) +var ncp = require('../../copy') + +/* global afterEach, beforeEach, describe, it */ + +// skip test for windows +// eslint-disable globalReturn */ +// if (os.platform().indexOf('win') === 0) return +// eslint-enable globalReturn */ + +describe('ncp / error / dest-permission', function () { + var TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-error-dest-perm') + var src = path.join(TEST_DIR, 'src') + var dest = path.join(TEST_DIR, 'dest') + + if (os.platform().indexOf('win') === 0) return + + beforeEach(function (done) { + fse.emptyDir(TEST_DIR, function (err) { + assert.ifError(err) + done() + }) + }) + + afterEach(function (done) { + fse.remove(TEST_DIR, done) + }) + + it('should return an error', function (done) { + var someFile = path.join(src, 'some-file') + fse.outputFileSync(someFile, 'hello') + + fse.mkdirsSync(dest) + fs.chmodSync(dest, parseInt('444', 8)) + + var subdest = path.join(dest, 'another-dir') + + ncp(src, subdest, function (err) { + assert(err) + assert.equal(err.code, 'EACCES') + done() + }) + }) +}) diff --git a/lib/copy/__tests__/ncp/ncp.test.js b/lib/copy/__tests__/ncp/ncp.test.js new file mode 100644 index 00000000..8d387fbc --- /dev/null +++ b/lib/copy/__tests__/ncp/ncp.test.js @@ -0,0 +1,202 @@ +'use strict' + +var assert = require('assert') +var fs = require('fs') +var path = require('path') +var rimraf = require('rimraf') +var readDirFiles = require('read-dir-files').read // temporary, will remove +var ncp = require('../../copy') + +/* eslint-env mocha */ + +var fixturesDir = path.join(__dirname, 'fixtures') + +describe('ncp', function () { + describe('regular files and directories', function () { + var fixtures = path.join(fixturesDir, 'regular-fixtures') + var src = path.join(fixtures, 'src') + var out = path.join(fixtures, 'out') + + before(function (cb) { + rimraf(out, function () { + ncp(src, out, cb) + }) + }) + + describe('when copying a directory of files', function () { + it('files are copied correctly', function (cb) { + readDirFiles(src, 'utf8', function (srcErr, srcFiles) { + readDirFiles(out, 'utf8', function (outErr, outFiles) { + assert.ifError(srcErr) + assert.deepEqual(srcFiles, outFiles) + cb() + }) + }) + }) + }) + + describe('when copying files using filter', function () { + before(function (cb) { + var filter = function (name) { + return name.substr(name.length - 1) !== 'a' + } + rimraf(out, function () { + ncp(src, out, {filter: filter}, cb) + }) + }) + + it('files are copied correctly', function (cb) { + readDirFiles(src, 'utf8', function (srcErr, srcFiles) { + function filter (files) { + for (var fileName in files) { + var curFile = files[fileName] + if (curFile instanceof Object) { + return filter(curFile) + } + + if (fileName.substr(fileName.length - 1) === 'a') { + delete files[fileName] + } + } + } + filter(srcFiles) + readDirFiles(out, 'utf8', function (outErr, outFiles) { + assert.ifError(outErr) + assert.deepEqual(srcFiles, outFiles) + cb() + }) + }) + }) + }) + + describe('when using overwrite=true', function () { + before(function () { + this.originalCreateReadStream = fs.createReadStream + }) + + after(function () { + fs.createReadStream = this.originalCreateReadStream + }) + + it('the copy is complete after callback', function (done) { + ncp(src, out, {overwrite: true}, function (err) { + fs.createReadStream = function () { + done(new Error('createReadStream after callback')) + } + assert.ifError(err) + process.nextTick(done) + }) + }) + }) + + describe('when using overwrite=false', function () { + beforeEach(function (done) { + rimraf(out, done) + }) + it('works', function (cb) { + ncp(src, out, {overwrite: false}, function (err) { + assert.ifError(err) + cb() + }) + }) + it('should not error if files exist', function (cb) { + ncp(src, out, function () { + ncp(src, out, {overwrite: false}, function (err) { + assert.ifError(err) + cb() + }) + }) + }) + it('should error if errorOnExist and file exists', function (cb) { + ncp(src, out, function () { + ncp(src, out, { + overwrite: false, + errorOnExist: true + }, function (err) { + assert(err) + cb() + }) + }) + }) + }) + + describe('clobber', function () { + beforeEach(function (done) { + rimraf(out, done) + }) + + it('is an alias for overwrite', function (cb) { + ncp(src, out, function () { + ncp(src, out, { + clobber: false, + errorOnExist: true + }, function (err) { + assert(err) + cb() + }) + }) + }) + }) + + describe('when using transform', function () { + it('file descriptors are passed correctly', function (cb) { + ncp(src, out, { + transform: function (read, write, file) { + assert.notEqual(file.name, undefined) + assert.strictEqual(typeof file.mode, 'number') + read.pipe(write) + } + }, cb) + }) + }) + }) + + // see https://github.com/AvianFlu/ncp/issues/71 + describe('Issue 71: Odd Async Behaviors', function (cb) { + var fixtures = path.join(__dirname, 'fixtures', 'regular-fixtures') + var src = path.join(fixtures, 'src') + var out = path.join(fixtures, 'out') + + var totalCallbacks = 0 + + function copyAssertAndCount (callback) { + // rimraf(out, function() { + ncp(src, out, function (err) { + assert(!err) + totalCallbacks += 1 + readDirFiles(src, 'utf8', function (srcErr, srcFiles) { + readDirFiles(out, 'utf8', function (outErr, outFiles) { + assert.ifError(srcErr) + assert.deepEqual(srcFiles, outFiles) + callback() + }) + }) + }) + // }) + } + + describe('when copying a directory of files without cleaning the destination', function () { + it('callback fires once per run and directories are equal', function (done) { + var expected = 10 + var count = 10 + + function next () { + if (count > 0) { + setTimeout(function () { + copyAssertAndCount(function () { + count -= 1 + next() + }) + }, 100) + } else { + // console.log('Total callback count is', totalCallbacks) + assert.equal(totalCallbacks, expected) + done() + } + } + + next() + }) + }) + }) +}) diff --git a/lib/copy/__tests__/ncp/symlink.test.js b/lib/copy/__tests__/ncp/symlink.test.js new file mode 100644 index 00000000..16102b5a --- /dev/null +++ b/lib/copy/__tests__/ncp/symlink.test.js @@ -0,0 +1,78 @@ +var assert = require('assert') +var fs = require('fs') +var path = require('path') +var os = require('os') +var fse = require(process.cwd()) +var ncp = require('../../copy') + +/* global afterEach, beforeEach, describe, it */ + +describe('ncp / symlink', function () { + var TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-symlinks') + var src = path.join(TEST_DIR, 'src') + var out = path.join(TEST_DIR, 'out') + + beforeEach(function (done) { + fse.emptyDir(TEST_DIR, function (err) { + assert.ifError(err) + createFixtures(src, done) + }) + }) + + afterEach(function (done) { + fse.remove(TEST_DIR, done) + }) + + it('copies symlinks by default', function (done) { + ncp(src, out, function (err) { + assert.ifError(err) + + assert.equal(fs.readlinkSync(path.join(out, 'file-symlink')), path.join(src, 'foo')) + assert.equal(fs.readlinkSync(path.join(out, 'dir-symlink')), path.join(src, 'dir')) + + done() + }) + }) + + it('copies file contents when dereference=true', function (done) { + ncp(src, out, {dereference: true}, function (err) { + assert.ifError(err) + + var fileSymlinkPath = path.join(out, 'file-symlink') + assert.ok(fs.lstatSync(fileSymlinkPath).isFile()) + assert.equal(fs.readFileSync(fileSymlinkPath), 'foo contents') + + var dirSymlinkPath = path.join(out, 'dir-symlink') + assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory()) + assert.deepEqual(fs.readdirSync(dirSymlinkPath), ['bar']) + + done() + }) + }) +}) + +function createFixtures (srcDir, callback) { + fs.mkdir(srcDir, function (err) { + if (err) return callback(err) + + // note: third parameter in symlinkSync is type e.g. 'file' or 'dir' + // https://nodejs.org/api/fs.html#fs_fs_symlink_srcpath_dstpath_type_callback + try { + var fooFile = path.join(srcDir, 'foo') + var fooFileLink = path.join(srcDir, 'file-symlink') + fs.writeFileSync(fooFile, 'foo contents') + fs.symlinkSync(fooFile, fooFileLink, 'file') + + var dir = path.join(srcDir, 'dir') + var dirFile = path.join(dir, 'bar') + var dirLink = path.join(srcDir, 'dir-symlink') + fs.mkdirSync(dir) + fs.writeFileSync(dirFile, 'bar contents') + fs.symlinkSync(dir, dirLink, 'dir') + } catch (err) { + callback(err) + } + + callback() + }) +} From 8175d0d612642595a0b0084fbdb0bd34242764be Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Sun, 26 Feb 2017 03:57:05 -0800 Subject: [PATCH 03/14] lib/copy/__tests__: Add more unit test --- .../copy-prevent-copying-into-itself.test.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js index 6fc138a4..37db411e 100644 --- a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js +++ b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js @@ -152,8 +152,8 @@ describe('+ copySync() - prevent copying into itself', () => { it('should copy the directory successfully when dest is very nested that all its parents need to be created', done => { dest = path.join(TEST_DIR, 'dest', 'src', 'foo', 'bar', 'baz', 'qux', 'quux', 'waldo', - 'grault', 'garply', 'fred', 'plugh', 'thud', 'some', 'highly', 'deeply', - 'badly', 'nasty', 'crazy', 'mad', 'nested', 'dest') + 'grault', 'garply', 'fred', 'plugh', 'thud', 'some', 'highly', 'deeply', + 'badly', 'nasty', 'crazy', 'mad', 'nested', 'dest') return testSuccess(src, dest, done) }) @@ -266,7 +266,7 @@ describe('+ copySync() - prevent copying into itself', () => { }) }) - it('should throw an error when dest is a subdir of resolved src path', done => { + it('should throw an error when dest not exist and is a subdir of resolved src path', done => { const srcLink = path.join(TEST_DIR, 'src_symlink') fs.symlinkSync(src, srcLink, 'dir') @@ -274,7 +274,21 @@ describe('+ copySync() - prevent copying into itself', () => { fs.copy(srcLink, dest, err => { assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) - // assert source not affecte + const link = fs.readlinkSync(srcLink) + assert.strictEqual(link, src) + done() + }) + }) + + it('should throw an error when dest exists and is a subdir of resolved src path', done => { + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + + dest = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest') + fs.mkdirsSync(dest) + + fs.copy(srcLink, dest, err => { + assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) const link = fs.readlinkSync(srcLink) assert.strictEqual(link, src) done() From 364a114f1dfd0528716ee401f17c4223b128f2e7 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Sun, 26 Feb 2017 04:41:22 -0800 Subject: [PATCH 04/14] lib/copy: Remove options.dereference from first check on link, add more tests --- lib/copy/__tests__/copy-symlink.test.js | 22 +++++++++++++++++++++- lib/copy/copy.js | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/copy/__tests__/copy-symlink.test.js b/lib/copy/__tests__/copy-symlink.test.js index cf148dbf..db60a29f 100644 --- a/lib/copy/__tests__/copy-symlink.test.js +++ b/lib/copy/__tests__/copy-symlink.test.js @@ -35,7 +35,7 @@ describe('copy() - symlink', () => { }) }) - it('should copy file contents when dereference=true', done => { + it('should copy file contents when dereference=true and dest not exist', done => { copy(src, out, {dereference: true}, err => { assert.ifError(err) @@ -49,6 +49,26 @@ describe('copy() - symlink', () => { done() }) }) + + it('should copy file contents when dereference=true and dest exists', done => { + const dest = path.join(TEST_DIR, 'dest') + fse.mkdirsSync(dest) + const destLink = path.join(TEST_DIR, 'dest_symlink') + fs.symlinkSync(dest, destLink, 'dir') + + copy(src, destLink, {dereference: true}, err => { + assert.ifError(err) + + const fileSymlinkPath = path.join(destLink, 'file-symlink') + assert.ok(fs.lstatSync(fileSymlinkPath).isFile()) + assert.equal(fs.readFileSync(fileSymlinkPath), 'foo contents') + + const dirSymlinkPath = path.join(destLink, 'dir-symlink') + assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory()) + assert.deepEqual(fs.readdirSync(dirSymlinkPath), ['bar']) + done() + }) + }) }) function createFixtures (srcDir, callback) { diff --git a/lib/copy/copy.js b/lib/copy/copy.js index 7f561e29..c6cb8a0a 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -61,7 +61,7 @@ function copy (src, dest, options, cb) { return onDir(src, srcStat, dest, options, cb) } else if (srcStat.isFile() || srcStat.isCharacterDevice() || srcStat.isBlockDevice()) { return onFile(src, srcStat, dest, options, cb) - } else if (srcStat.isSymbolicLink() && !options.dereference) { + } else if (srcStat.isSymbolicLink()) { return onLink(src, dest, options, cb) } }) From 6e0cc4024e8f421fe3d011d59356b7797dd94370 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Sun, 26 Feb 2017 04:59:36 -0800 Subject: [PATCH 05/14] lib/copy/__tests__: Add more unit tests for symlinks --- lib/copy/__tests__/copy-symlink.test.js | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/copy/__tests__/copy-symlink.test.js b/lib/copy/__tests__/copy-symlink.test.js index db60a29f..107a2dea 100644 --- a/lib/copy/__tests__/copy-symlink.test.js +++ b/lib/copy/__tests__/copy-symlink.test.js @@ -69,6 +69,48 @@ describe('copy() - symlink', () => { done() }) }) + + it('should copy file contents when dereference=true and src is a link and dest is regular', done => { + const dest = path.join(TEST_DIR, 'dest') + fse.mkdirsSync(dest) + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + + copy(srcLink, dest, {dereference: true}, err => { + assert.ifError(err) + + const fileSymlinkPath = path.join(dest, 'file-symlink') + assert.ok(fs.lstatSync(fileSymlinkPath).isFile()) + assert.equal(fs.readFileSync(fileSymlinkPath), 'foo contents') + + const dirSymlinkPath = path.join(dest, 'dir-symlink') + assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory()) + assert.deepEqual(fs.readdirSync(dirSymlinkPath), ['bar']) + done() + }) + }) + + it('should copy file contents when dereference=true and src and dest are links', done => { + const dest = path.join(TEST_DIR, 'dest') + fse.mkdirsSync(dest) + const srcLink = path.join(TEST_DIR, 'src_symlink') + fs.symlinkSync(src, srcLink, 'dir') + const destLink = path.join(TEST_DIR, 'dest_symlink') + fs.symlinkSync(dest, destLink, 'dir') + + copy(srcLink, destLink, {dereference: true}, err => { + assert.ifError(err) + + const fileSymlinkPath = path.join(destLink, 'file-symlink') + assert.ok(fs.lstatSync(fileSymlinkPath).isFile()) + assert.equal(fs.readFileSync(fileSymlinkPath), 'foo contents') + + const dirSymlinkPath = path.join(destLink, 'dir-symlink') + assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory()) + assert.deepEqual(fs.readdirSync(dirSymlinkPath), ['bar']) + done() + }) + }) }) function createFixtures (srcDir, callback) { From 10a5d1135358fefaf31760d05b2e7106e55ccdc7 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Sun, 26 Feb 2017 05:13:58 -0800 Subject: [PATCH 06/14] lib/copy/__tests__: Add test for regex filter --- lib/copy/__tests__/copy.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/copy/__tests__/copy.test.js b/lib/copy/__tests__/copy.test.js index fa4dfce7..9e5e9550 100644 --- a/lib/copy/__tests__/copy.test.js +++ b/lib/copy/__tests__/copy.test.js @@ -198,6 +198,19 @@ describe('fs-extra', () => { }) }) + it('should not copy and return when nothing matched', done => { + const srcFile1 = path.join(TEST_DIR, '1.jade') + fs.writeFileSync(srcFile1, '') + const destFile1 = path.join(TEST_DIR, 'dest1.jade') + const filter = /.html$|.css$/i + + fse.copy(srcFile1, destFile1, filter, (err) => { + assert(!err) + assert(!fs.existsSync(destFile1)) + done() + }) + }) + it('should should apply filter recursively', done => { const FILES = 2 // Don't match anything that ends with a digit higher than 0: From d139e4fdd5995e8d672aebd18dc5f2c21005d3ee Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Sun, 26 Feb 2017 05:43:56 -0800 Subject: [PATCH 07/14] lib/copy/__tests__/ncp: Revert ncp tests to previously es6 refactored style --- lib/copy/__tests__/ncp/broken-symlink.test.js | 47 +++--- lib/copy/__tests__/ncp/ncp-error-perm.test.js | 38 ++--- lib/copy/__tests__/ncp/ncp.test.js | 143 ++++++++---------- lib/copy/__tests__/ncp/symlink.test.js | 54 +++---- 4 files changed, 138 insertions(+), 144 deletions(-) diff --git a/lib/copy/__tests__/ncp/broken-symlink.test.js b/lib/copy/__tests__/ncp/broken-symlink.test.js index f3f8f76f..aa7b990e 100644 --- a/lib/copy/__tests__/ncp/broken-symlink.test.js +++ b/lib/copy/__tests__/ncp/broken-symlink.test.js @@ -1,38 +1,38 @@ -var assert = require('assert') -var fs = require('fs') -var path = require('path') -var os = require('os') -var fse = require(process.cwd()) -var copy = require('../../copy') +'use strict' + +const fs = require('fs') +const os = require('os') +const fse = require(process.cwd()) +const ncp = require('../../copy') +const path = require('path') +const assert = require('assert') /* global afterEach, beforeEach, describe, it */ -describe('ncp broken symlink', function () { - var TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-broken-symlinks') - var src = path.join(TEST_DIR, 'src') - var out = path.join(TEST_DIR, 'out') +describe('ncp broken symlink', () => { + const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-broken-symlinks') + const src = path.join(TEST_DIR, 'src') + const out = path.join(TEST_DIR, 'out') - beforeEach(function (done) { - fse.emptyDir(TEST_DIR, function (err) { + beforeEach(done => { + fse.emptyDir(TEST_DIR, err => { assert.ifError(err) createFixtures(src, done) }) }) - afterEach(function (done) { - fse.remove(TEST_DIR, done) - }) + afterEach(done => fse.remove(TEST_DIR, done)) - it('should copy broken symlinks by default', function (done) { - copy(src, out, function (err) { + it('should copy broken symlinks by default', done => { + ncp(src, out, err => { if (err) return done(err) assert.equal(fs.readlinkSync(path.join(out, 'broken-symlink')), path.join(src, 'does-not-exist')) done() }) }) - it('should return an error when dereference=true', function (done) { - copy(src, out, {dereference: true}, function (err) { + it('should return an error when dereference=true', done => { + ncp(src, out, {dereference: true}, err => { assert.equal(err.code, 'ENOENT') done() }) @@ -40,12 +40,15 @@ describe('ncp broken symlink', function () { }) function createFixtures (srcDir, callback) { - fs.mkdir(srcDir, function (err) { + fs.mkdir(srcDir, err => { + let brokenFile + let brokenFileLink + if (err) return callback(err) try { - var brokenFile = path.join(srcDir, 'does-not-exist') - var brokenFileLink = path.join(srcDir, 'broken-symlink') + brokenFile = path.join(srcDir, 'does-not-exist') + brokenFileLink = path.join(srcDir, 'broken-symlink') fs.writeFileSync(brokenFile, 'does not matter') fs.symlinkSync(brokenFile, brokenFileLink, 'file') } catch (err) { diff --git a/lib/copy/__tests__/ncp/ncp-error-perm.test.js b/lib/copy/__tests__/ncp/ncp-error-perm.test.js index 2df093ae..18876e69 100644 --- a/lib/copy/__tests__/ncp/ncp-error-perm.test.js +++ b/lib/copy/__tests__/ncp/ncp-error-perm.test.js @@ -1,11 +1,13 @@ +'use strict' + // file in reference: https://github.com/jprichardson/node-fs-extra/issues/56 -var assert = require('assert') -var fs = require('fs') -var path = require('path') -var os = require('os') -var fse = require(process.cwd()) -var ncp = require('../../copy') +const fs = require('fs') +const os = require('os') +const fse = require(process.cwd()) +const ncp = require('../../copy') +const path = require('path') +const assert = require('assert') /* global afterEach, beforeEach, describe, it */ @@ -14,34 +16,32 @@ var ncp = require('../../copy') // if (os.platform().indexOf('win') === 0) return // eslint-enable globalReturn */ -describe('ncp / error / dest-permission', function () { - var TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-error-dest-perm') - var src = path.join(TEST_DIR, 'src') - var dest = path.join(TEST_DIR, 'dest') +describe('ncp / error / dest-permission', () => { + const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-error-dest-perm') + const src = path.join(TEST_DIR, 'src') + const dest = path.join(TEST_DIR, 'dest') if (os.platform().indexOf('win') === 0) return - beforeEach(function (done) { - fse.emptyDir(TEST_DIR, function (err) { + beforeEach(done => { + fse.emptyDir(TEST_DIR, err => { assert.ifError(err) done() }) }) - afterEach(function (done) { - fse.remove(TEST_DIR, done) - }) + afterEach(done => fse.remove(TEST_DIR, done)) - it('should return an error', function (done) { - var someFile = path.join(src, 'some-file') + it('should return an error', done => { + const someFile = path.join(src, 'some-file') fse.outputFileSync(someFile, 'hello') fse.mkdirsSync(dest) fs.chmodSync(dest, parseInt('444', 8)) - var subdest = path.join(dest, 'another-dir') + const subdest = path.join(dest, 'another-dir') - ncp(src, subdest, function (err) { + ncp(src, subdest, err => { assert(err) assert.equal(err.code, 'EACCES') done() diff --git a/lib/copy/__tests__/ncp/ncp.test.js b/lib/copy/__tests__/ncp/ncp.test.js index 8d387fbc..31ae8296 100644 --- a/lib/copy/__tests__/ncp/ncp.test.js +++ b/lib/copy/__tests__/ncp/ncp.test.js @@ -1,32 +1,28 @@ 'use strict' -var assert = require('assert') -var fs = require('fs') -var path = require('path') -var rimraf = require('rimraf') -var readDirFiles = require('read-dir-files').read // temporary, will remove -var ncp = require('../../copy') +const fs = require('fs') +const ncp = require('../../copy') +const path = require('path') +const rimraf = require('rimraf') +const assert = require('assert') +const readDirFiles = require('read-dir-files').read // temporary, will remove /* eslint-env mocha */ -var fixturesDir = path.join(__dirname, 'fixtures') +const fixturesDir = path.join(__dirname, 'fixtures') -describe('ncp', function () { - describe('regular files and directories', function () { - var fixtures = path.join(fixturesDir, 'regular-fixtures') - var src = path.join(fixtures, 'src') - var out = path.join(fixtures, 'out') +describe('ncp', () => { + describe('regular files and directories', () => { + const fixtures = path.join(fixturesDir, 'regular-fixtures') + const src = path.join(fixtures, 'src') + const out = path.join(fixtures, 'out') - before(function (cb) { - rimraf(out, function () { - ncp(src, out, cb) - }) - }) + before(cb => rimraf(out, () => ncp(src, out, cb))) - describe('when copying a directory of files', function () { - it('files are copied correctly', function (cb) { - readDirFiles(src, 'utf8', function (srcErr, srcFiles) { - readDirFiles(out, 'utf8', function (outErr, outFiles) { + describe('when copying a directory of files', () => { + it('files are copied correctly', cb => { + readDirFiles(src, 'utf8', (srcErr, srcFiles) => { + readDirFiles(out, 'utf8', (outErr, outFiles) => { assert.ifError(srcErr) assert.deepEqual(srcFiles, outFiles) cb() @@ -35,21 +31,18 @@ describe('ncp', function () { }) }) - describe('when copying files using filter', function () { - before(function (cb) { - var filter = function (name) { - return name.substr(name.length - 1) !== 'a' - } - rimraf(out, function () { - ncp(src, out, {filter: filter}, cb) - }) + describe('when copying files using filter', () => { + before(cb => { + const filter = name => name.substr(name.length - 1) !== 'a' + + rimraf(out, () => ncp(src, out, { filter }, cb)) }) - it('files are copied correctly', function (cb) { - readDirFiles(src, 'utf8', function (srcErr, srcFiles) { + it('files are copied correctly', cb => { + readDirFiles(src, 'utf8', (srcErr, srcFiles) => { function filter (files) { - for (var fileName in files) { - var curFile = files[fileName] + for (let fileName in files) { + const curFile = files[fileName] if (curFile instanceof Object) { return filter(curFile) } @@ -60,7 +53,7 @@ describe('ncp', function () { } } filter(srcFiles) - readDirFiles(out, 'utf8', function (outErr, outFiles) { + readDirFiles(out, 'utf8', (outErr, outFiles) => { assert.ifError(outErr) assert.deepEqual(srcFiles, outFiles) cb() @@ -69,7 +62,7 @@ describe('ncp', function () { }) }) - describe('when using overwrite=true', function () { + describe('when using overwrite=true', () => { before(function () { this.originalCreateReadStream = fs.createReadStream }) @@ -78,41 +71,41 @@ describe('ncp', function () { fs.createReadStream = this.originalCreateReadStream }) - it('the copy is complete after callback', function (done) { - ncp(src, out, {overwrite: true}, function (err) { - fs.createReadStream = function () { - done(new Error('createReadStream after callback')) - } + it('the copy is complete after callback', done => { + ncp(src, out, {overwrite: true}, err => { + fs.createReadStream = () => done(new Error('createReadStream after callback')) + assert.ifError(err) process.nextTick(done) }) }) }) - describe('when using overwrite=false', function () { - beforeEach(function (done) { - rimraf(out, done) - }) - it('works', function (cb) { - ncp(src, out, {overwrite: false}, function (err) { + describe('when using overwrite=false', () => { + beforeEach(done => rimraf(out, done)) + + it('works', cb => { + ncp(src, out, {overwrite: false}, err => { assert.ifError(err) cb() }) }) - it('should not error if files exist', function (cb) { - ncp(src, out, function () { - ncp(src, out, {overwrite: false}, function (err) { + + it('should not error if files exist', cb => { + ncp(src, out, () => { + ncp(src, out, {overwrite: false}, err => { assert.ifError(err) cb() }) }) }) - it('should error if errorOnExist and file exists', function (cb) { - ncp(src, out, function () { + + it('should error if errorOnExist and file exists', cb => { + ncp(src, out, () => { ncp(src, out, { overwrite: false, errorOnExist: true - }, function (err) { + }, err => { assert(err) cb() }) @@ -120,17 +113,15 @@ describe('ncp', function () { }) }) - describe('clobber', function () { - beforeEach(function (done) { - rimraf(out, done) - }) + describe('clobber', () => { + beforeEach(done => rimraf(out, done)) - it('is an alias for overwrite', function (cb) { - ncp(src, out, function () { + it('is an alias for overwrite', cb => { + ncp(src, out, () => { ncp(src, out, { clobber: false, errorOnExist: true - }, function (err) { + }, err => { assert(err) cb() }) @@ -138,10 +129,10 @@ describe('ncp', function () { }) }) - describe('when using transform', function () { - it('file descriptors are passed correctly', function (cb) { + describe('when using transform', () => { + it('file descriptors are passed correctly', cb => { ncp(src, out, { - transform: function (read, write, file) { + transform: (read, write, file) => { assert.notEqual(file.name, undefined) assert.strictEqual(typeof file.mode, 'number') read.pipe(write) @@ -152,20 +143,20 @@ describe('ncp', function () { }) // see https://github.com/AvianFlu/ncp/issues/71 - describe('Issue 71: Odd Async Behaviors', function (cb) { - var fixtures = path.join(__dirname, 'fixtures', 'regular-fixtures') - var src = path.join(fixtures, 'src') - var out = path.join(fixtures, 'out') + describe('Issue 71: Odd Async Behaviors', cb => { + const fixtures = path.join(__dirname, 'fixtures', 'regular-fixtures') + const src = path.join(fixtures, 'src') + const out = path.join(fixtures, 'out') - var totalCallbacks = 0 + let totalCallbacks = 0 function copyAssertAndCount (callback) { // rimraf(out, function() { - ncp(src, out, function (err) { + ncp(src, out, err => { assert(!err) totalCallbacks += 1 - readDirFiles(src, 'utf8', function (srcErr, srcFiles) { - readDirFiles(out, 'utf8', function (outErr, outFiles) { + readDirFiles(src, 'utf8', (srcErr, srcFiles) => { + readDirFiles(out, 'utf8', (outErr, outFiles) => { assert.ifError(srcErr) assert.deepEqual(srcFiles, outFiles) callback() @@ -175,15 +166,15 @@ describe('ncp', function () { // }) } - describe('when copying a directory of files without cleaning the destination', function () { - it('callback fires once per run and directories are equal', function (done) { - var expected = 10 - var count = 10 + describe('when copying a directory of files without cleaning the destination', () => { + it('callback fires once per run and directories are equal', done => { + const expected = 10 + let count = 10 function next () { if (count > 0) { - setTimeout(function () { - copyAssertAndCount(function () { + setTimeout(() => { + copyAssertAndCount(() => { count -= 1 next() }) diff --git a/lib/copy/__tests__/ncp/symlink.test.js b/lib/copy/__tests__/ncp/symlink.test.js index 16102b5a..ff481a5b 100644 --- a/lib/copy/__tests__/ncp/symlink.test.js +++ b/lib/copy/__tests__/ncp/symlink.test.js @@ -1,30 +1,30 @@ -var assert = require('assert') -var fs = require('fs') -var path = require('path') -var os = require('os') -var fse = require(process.cwd()) -var ncp = require('../../copy') +'use strict' + +const fs = require('fs') +const os = require('os') +const fse = require(process.cwd()) +const ncp = require('../../copy') +const path = require('path') +const assert = require('assert') /* global afterEach, beforeEach, describe, it */ -describe('ncp / symlink', function () { - var TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-symlinks') - var src = path.join(TEST_DIR, 'src') - var out = path.join(TEST_DIR, 'out') +describe('ncp / symlink', () => { + const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-symlinks') + const src = path.join(TEST_DIR, 'src') + const out = path.join(TEST_DIR, 'out') - beforeEach(function (done) { - fse.emptyDir(TEST_DIR, function (err) { + beforeEach(done => { + fse.emptyDir(TEST_DIR, err => { assert.ifError(err) createFixtures(src, done) }) }) - afterEach(function (done) { - fse.remove(TEST_DIR, done) - }) + afterEach(done => fse.remove(TEST_DIR, done)) - it('copies symlinks by default', function (done) { - ncp(src, out, function (err) { + it('copies symlinks by default', done => { + ncp(src, out, err => { assert.ifError(err) assert.equal(fs.readlinkSync(path.join(out, 'file-symlink')), path.join(src, 'foo')) @@ -34,15 +34,15 @@ describe('ncp / symlink', function () { }) }) - it('copies file contents when dereference=true', function (done) { - ncp(src, out, {dereference: true}, function (err) { + it('copies file contents when dereference=true', done => { + ncp(src, out, {dereference: true}, err => { assert.ifError(err) - var fileSymlinkPath = path.join(out, 'file-symlink') + const fileSymlinkPath = path.join(out, 'file-symlink') assert.ok(fs.lstatSync(fileSymlinkPath).isFile()) assert.equal(fs.readFileSync(fileSymlinkPath), 'foo contents') - var dirSymlinkPath = path.join(out, 'dir-symlink') + const dirSymlinkPath = path.join(out, 'dir-symlink') assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory()) assert.deepEqual(fs.readdirSync(dirSymlinkPath), ['bar']) @@ -52,20 +52,20 @@ describe('ncp / symlink', function () { }) function createFixtures (srcDir, callback) { - fs.mkdir(srcDir, function (err) { + fs.mkdir(srcDir, err => { if (err) return callback(err) // note: third parameter in symlinkSync is type e.g. 'file' or 'dir' // https://nodejs.org/api/fs.html#fs_fs_symlink_srcpath_dstpath_type_callback try { - var fooFile = path.join(srcDir, 'foo') - var fooFileLink = path.join(srcDir, 'file-symlink') + const fooFile = path.join(srcDir, 'foo') + const fooFileLink = path.join(srcDir, 'file-symlink') fs.writeFileSync(fooFile, 'foo contents') fs.symlinkSync(fooFile, fooFileLink, 'file') - var dir = path.join(srcDir, 'dir') - var dirFile = path.join(dir, 'bar') - var dirLink = path.join(srcDir, 'dir-symlink') + const dir = path.join(srcDir, 'dir') + const dirFile = path.join(dir, 'bar') + const dirLink = path.join(srcDir, 'dir-symlink') fs.mkdirSync(dir) fs.writeFileSync(dirFile, 'bar contents') fs.symlinkSync(dir, dirLink, 'dir') From 0d81c2ce1b451f9a9971b572c1ef6fc894887e21 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Wed, 8 Mar 2017 15:22:00 -0800 Subject: [PATCH 08/14] Rewrite copy: Apply requested changes for copy.js and its tests --- .../copy-prevent-copying-into-itself.test.js | 13 +- lib/copy/__tests__/copy.test.js | 24 +- lib/copy/copy.js | 32 +- lib/move/__tests__/move.test.js | 373 ------------------ 4 files changed, 32 insertions(+), 410 deletions(-) delete mode 100644 lib/move/__tests__/move.test.js diff --git a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js index 37db411e..505fbc68 100644 --- a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js +++ b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js @@ -63,13 +63,10 @@ describe('+ copySync() - prevent copying into itself', () => { src = path.join(TEST_DIR, 'src') fs.mkdirsSync(src) - // ensureFileSync creates required parent dirs for us:) - FILES.forEach(f => fs.ensureFileSync(path.join(src, f))) - - fs.writeFileSync(path.join(src, FILES[0]), dat0) - fs.writeFileSync(path.join(src, FILES[1]), dat1) - fs.writeFileSync(path.join(src, FILES[2]), dat2) - fs.writeFileSync(path.join(src, FILES[3]), dat3) + fs.outputFileSync(path.join(src, FILES[0]), dat0) + fs.outputFileSync(path.join(src, FILES[1]), dat1) + fs.outputFileSync(path.join(src, FILES[2]), dat2) + fs.outputFileSync(path.join(src, FILES[3]), dat3) done() }) @@ -348,7 +345,7 @@ describe('+ copySync() - prevent copying into itself', () => { const srcLink = path.join(TEST_DIR, 'src_symlink') fs.symlinkSync(src, srcLink, 'dir') - dest = path.join(TEST_DIR, 'src_src', 'dest') + dest = path.join(TEST_DIR, 'srcsrc', 'dest') testSuccess(srcLink, dest, () => { const link = fs.readlinkSync(dest) assert.strictEqual(link, src) diff --git a/lib/copy/__tests__/copy.test.js b/lib/copy/__tests__/copy.test.js index 9e5e9550..7ae763d3 100644 --- a/lib/copy/__tests__/copy.test.js +++ b/lib/copy/__tests__/copy.test.js @@ -39,7 +39,7 @@ describe('fs-extra', () => { let destMd5 = '' fse.copy(fileSrc, fileDest, err => { - assert(!err) + assert.ifError(err) destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex') assert.strictEqual(srcMd5, destMd5) done() @@ -63,7 +63,7 @@ describe('fs-extra', () => { fs.writeFileSync(fileSrc, '') fse.copy(fileSrc, fileDest, err => { - assert(!err) + assert.ifError(err) fs.statSync(fileDest) done() }) @@ -104,14 +104,14 @@ describe('fs-extra', () => { const dest = path.join(TEST_DIR, 'dest') fse.mkdirs(src, err => { - assert(!err) + assert.ifError(err) for (let i = 0; i < FILES; ++i) { fs.writeFileSync(path.join(src, i.toString()), crypto.randomBytes(SIZE)) } const subdir = path.join(src, 'subdir') fse.mkdirs(subdir, err => { - assert(!err) + assert.ifError(err) for (let i = 0; i < FILES; ++i) { fs.writeFileSync(path.join(subdir, i.toString()), crypto.randomBytes(SIZE)) } @@ -179,7 +179,7 @@ describe('fs-extra', () => { const filter = s => s.split('.').pop() !== 'css' fse.copy(srcFile1, destFile1, filter, err => { - assert(!err) + assert.ifError(err) assert(!fs.existsSync(destFile1)) done() }) @@ -192,7 +192,7 @@ describe('fs-extra', () => { const options = { filter: s => /.html$|.css$/i.test(s) } fse.copy(srcFile1, destFile1, options, (err) => { - assert(!err) + assert.ifError(err) assert(!fs.existsSync(destFile1)) done() }) @@ -205,7 +205,7 @@ describe('fs-extra', () => { const filter = /.html$|.css$/i fse.copy(srcFile1, destFile1, filter, (err) => { - assert(!err) + assert.ifError(err) assert(!fs.existsSync(destFile1)) done() }) @@ -231,7 +231,7 @@ describe('fs-extra', () => { } const dest = path.join(TEST_DIR, 'dest') fse.copy(src, dest, filter, err => { - assert(!err) + assert.ifError(err) assert(fs.existsSync(dest)) assert(FILES > 1) @@ -294,7 +294,7 @@ describe('fs-extra', () => { fse.mkdirsSync(dest) fse.copy(src, dest, filter, err => { - assert(!err) + assert.ifError(err) assert(!fs.existsSync(path.join(dest, 'subdir'))) done() }) @@ -322,15 +322,15 @@ describe('fs-extra', () => { const destFile3 = path.join(dest, 'dest3.jade') fse.copy(srcFile1, destFile1, filter, err => { - assert(!err) + assert.ifError(err) assert(fs.existsSync(destFile1)) fse.copy(srcFile2, destFile2, filter, err => { - assert(!err) + assert.ifError(err) assert(!fs.existsSync(destFile2)) fse.copy(srcFile3, destFile3, filter, err => { - assert(!err) + assert.ifError(err) assert(fs.existsSync(destFile3)) done() }) diff --git a/lib/copy/copy.js b/lib/copy/copy.js index c6cb8a0a..5cf4af31 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -5,8 +5,8 @@ const path = require('path') const mkdirs = require('../mkdirs').mkdirs const utimes = require('../util/utimes').utimesMillis -const DEST_NOENT = -1 -const DEST_EXISTS = 1 +const DEST_NOENT = Symbol('DEST_NOENT') +const DEST_EXISTS = Symbol('DEST_EXISTS') function copy (src, dest, options, cb) { if (typeof options === 'function' && !cb) { @@ -31,7 +31,7 @@ function copy (src, dest, options, cb) { // Warn about using preserveTimestamps on 32-bit node if (options.preserveTimestamps && process.arch === 'ia32') { console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n - see https://github.com/jprichardson/node-fs-extra/issues/269`) + see https://github.com/jprichardson/node-fs-extra/issues/269`) } // don't allow src and dest to be the same @@ -51,7 +51,7 @@ function copy (src, dest, options, cb) { const destParent = path.dirname(dest) mkdirs(destParent, err => { - if (err && err.code !== 'EEXIST') return cb(err) + if (err) return cb(err) let stat = options.dereference ? fs.stat : fs.lstat stat(src, (err, srcStat) => { @@ -73,11 +73,11 @@ function checkDest (dest, cb) { if (err) { if (err.code === 'ENOENT') return cb(null, DEST_NOENT) // dest exists but is not a link - else if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, DEST_EXISTS) - else return cb(err) - } else { // dest exists and is a link - return cb(null, resolvedDestPath) + if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, DEST_EXISTS) + return cb(err) } + // dest exists and is a link + return cb(null, resolvedDestPath) }) } @@ -113,7 +113,8 @@ function cpFile (src, srcStat, dest, options, cb) { if (err) return cb(err) if (options.preserveTimestamps) { return utimes(dest, srcStat.atime, srcStat.mtime, cb) - } else return cb() + } + return cb() }) }) } @@ -138,7 +139,7 @@ function onDir (src, srcStat, dest, options, cb) { if (isSrcSubdir(src, res)) return cb(new Error(`Cannot copy directory '${src}' into itself '${res}'`)) if (src === res) return cb() return cpDir(src, dest, options, cb) - } else return cb() + } }) } @@ -152,11 +153,8 @@ function cpDir (src, dest, options, cb) { else resolve() }) }) - })).then(() => { - return cb() - }).catch(err => { - return cb(err) - }) + })).then(() => cb()) + .catch(cb) }) } @@ -204,8 +202,8 @@ function onLink (src, dest, options, cb) { function isSrcSubdir (src, dest) { try { return src !== dest && - dest.indexOf(src) > -1 && - dest.split(path.dirname(src) + path.sep)[1].split(path.sep)[0] === path.basename(src) + dest.indexOf(src) > -1 && + dest.split(path.dirname(src) + path.sep)[1].split(path.sep)[0] === path.basename(src) } catch (e) { return false } diff --git a/lib/move/__tests__/move.test.js b/lib/move/__tests__/move.test.js deleted file mode 100644 index 1d0e51d0..00000000 --- a/lib/move/__tests__/move.test.js +++ /dev/null @@ -1,373 +0,0 @@ -var assert = require('assert') -var os = require('os') -var path = require('path') -var rimraf = require('rimraf') -var fs = require('graceful-fs') -var fse = require(process.cwd()) - -/* global afterEach, beforeEach, describe, it */ - -function createAsyncErrFn (errCode) { - var fn = function () { - fn.callCount++ - var callback = arguments[arguments.length - 1] - setTimeout(function () { - var err = new Error() - err.code = errCode - callback(err) - }, 10) - } - fn.callCount = 0 - return fn -} - -var originalRename = fs.rename -var originalLink = fs.link - -function setUpMockFs (errCode) { - fs.rename = createAsyncErrFn(errCode) - fs.link = createAsyncErrFn(errCode) -} - -function tearDownMockFs () { - fs.rename = originalRename - fs.link = originalLink -} - -describe('move', function () { - var TEST_DIR - - beforeEach(function () { - TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move') - - fse.emptyDirSync(TEST_DIR) - - // Create fixtures: - fs.writeFileSync(path.join(TEST_DIR, 'a-file'), 'sonic the hedgehog\n') - fs.mkdirSync(path.join(TEST_DIR, 'a-folder')) - fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-file'), 'tails\n') - fs.mkdirSync(path.join(TEST_DIR, 'a-folder/another-folder')) - fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-folder/file3'), 'knuckles\n') - }) - - afterEach(function (done) { - rimraf(TEST_DIR, done) - }) - - it('should rename a file on the same device', function (done) { - var src = TEST_DIR + '/a-file' - var dest = TEST_DIR + '/a-file-dest' - - fse.move(src, dest, function (err) { - assert.ifError(err) - fs.readFile(dest, 'utf8', function (err, contents) { - var expected = /^sonic the hedgehog\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - done() - }) - }) - }) - - it('should not overwrite the destination by default', function (done) { - var src = TEST_DIR + '/a-file' - var dest = TEST_DIR + '/a-folder/another-file' - - // verify file exists already - assert(fs.existsSync(dest)) - - fse.move(src, dest, function (err) { - assert.ok(err && err.code === 'EEXIST', 'throw EEXIST') - done() - }) - }) - - it('should not overwrite if overwrite = false', function (done) { - var src = TEST_DIR + '/a-file' - var dest = TEST_DIR + '/a-folder/another-file' - - // verify file exists already - assert(fs.existsSync(dest)) - - fse.move(src, dest, {overwrite: false}, function (err) { - assert.ok(err && err.code === 'EEXIST', 'throw EEXIST') - done() - }) - }) - - it('should overwrite file if overwrite = true', function (done) { - var src = TEST_DIR + '/a-file' - var dest = TEST_DIR + '/a-folder/another-file' - - // verify file exists already - assert(fs.existsSync(dest)) - - fse.move(src, dest, {overwrite: true}, function (err) { - assert.ifError(err) - fs.readFile(dest, 'utf8', function (err, contents) { - var expected = /^sonic the hedgehog\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - done() - }) - }) - }) - - it('should overwrite the destination directory if overwrite = true', function (done) { - // Tests fail on appveyor/Windows due to - // https://github.com/isaacs/node-graceful-fs/issues/98. - // Workaround by increasing the timeout by a minute (because - // graceful times out after a minute). - this.timeout(90000) - - // Create src - var src = path.join(TEST_DIR, 'src') - fse.ensureDirSync(src) - fse.mkdirsSync(path.join(src, 'some-folder')) - fs.writeFileSync(path.join(src, 'some-file'), 'hi') - - var dest = path.join(TEST_DIR, 'a-folder') - - // verify dest has stuff in it - var paths = fs.readdirSync(dest) - assert(paths.indexOf('another-file') >= 0) - assert(paths.indexOf('another-folder') >= 0) - - fse.move(src, dest, {overwrite: true}, function (err) { - assert.ifError(err) - - // verify dest does not have old stuff - var paths = fs.readdirSync(dest) - assert.strictEqual(paths.indexOf('another-file'), -1) - assert.strictEqual(paths.indexOf('another-folder'), -1) - - // verify dest has new stuff - assert(paths.indexOf('some-file') >= 0) - assert(paths.indexOf('some-folder') >= 0) - - done() - }) - }) - - it('should not create directory structure if mkdirp is false', function (done) { - var src = TEST_DIR + '/a-file' - var dest = TEST_DIR + '/does/not/exist/a-file-dest' - - // verify dest directory does not exist - assert(!fs.existsSync(path.dirname(dest))) - - fse.move(src, dest, {mkdirp: false}, function (err) { - assert.strictEqual(err.code, 'ENOENT') - done() - }) - }) - - it('should create directory structure by default', function (done) { - var src = TEST_DIR + '/a-file' - var dest = TEST_DIR + '/does/not/exist/a-file-dest' - - // verify dest directory does not exist - assert(!fs.existsSync(path.dirname(dest))) - - fse.move(src, dest, function (err) { - assert.ifError(err) - fs.readFile(dest, 'utf8', function (err, contents) { - var expected = /^sonic the hedgehog\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - done() - }) - }) - }) - - it('should work across devices', function (done) { - var src = TEST_DIR + '/a-file' - var dest = TEST_DIR + '/a-file-dest' - - setUpMockFs('EXDEV') - - fse.move(src, dest, function (err) { - assert.ifError(err) - assert.strictEqual(fs.link.callCount, 1) - - fs.readFile(dest, 'utf8', function (err, contents) { - var expected = /^sonic the hedgehog\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - - tearDownMockFs() - done() - }) - }) - }) - - it('should move folders', function (done) { - var src = TEST_DIR + '/a-folder' - var dest = TEST_DIR + '/a-folder-dest' - - // verify it doesn't exist - assert(!fs.existsSync(dest)) - - fse.move(src, dest, function (err) { - assert.ifError(err) - fs.readFile(dest + '/another-file', 'utf8', function (err, contents) { - var expected = /^tails\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - done() - }) - }) - }) - - it('should move folders across devices with EISDIR error', function (done) { - var src = TEST_DIR + '/a-folder' - var dest = TEST_DIR + '/a-folder-dest' - - setUpMockFs('EISDIR') - - fse.move(src, dest, function (err) { - assert.ifError(err) - assert.strictEqual(fs.link.callCount, 1) - - fs.readFile(dest + '/another-folder/file3', 'utf8', function (err, contents) { - var expected = /^knuckles\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - - tearDownMockFs('EISDIR') - - done() - }) - }) - }) - - it('should overwrite folders across devices', function (done) { - var src = TEST_DIR + '/a-folder' - var dest = TEST_DIR + '/a-folder-dest' - - fs.mkdirSync(dest) - - setUpMockFs('EXDEV') - - fse.move(src, dest, {overwrite: true}, function (err) { - assert.ifError(err) - assert.strictEqual(fs.rename.callCount, 1) - - fs.readFile(dest + '/another-folder/file3', 'utf8', function (err, contents) { - var expected = /^knuckles\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - - tearDownMockFs('EXDEV') - - done() - }) - }) - }) - - it('should move folders across devices with EXDEV error', function (done) { - var src = TEST_DIR + '/a-folder' - var dest = TEST_DIR + '/a-folder-dest' - - setUpMockFs('EXDEV') - - fse.move(src, dest, function (err) { - assert.ifError(err) - assert.strictEqual(fs.link.callCount, 1) - - fs.readFile(dest + '/another-folder/file3', 'utf8', function (err, contents) { - var expected = /^knuckles\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - - tearDownMockFs() - - done() - }) - }) - }) - - describe('clobber', function () { - it('should be an alias for overwrite', function (done) { - var src = TEST_DIR + '/a-file' - var dest = TEST_DIR + '/a-folder/another-file' - - // verify file exists already - assert(fs.existsSync(dest)) - - fse.move(src, dest, {overwrite: true}, function (err) { - assert.ifError(err) - fs.readFile(dest, 'utf8', function (err, contents) { - var expected = /^sonic the hedgehog\r?\n$/ - assert.ifError(err) - assert.ok(contents.match(expected), `${contents} match ${expected}`) - done() - }) - }) - }) - }) - - describe('> when trying to a move a folder into itself', function () { - it('should produce an error', function (done) { - var SRC_DIR = path.join(TEST_DIR, 'test') - var DEST_DIR = path.join(TEST_DIR, 'test', 'test') - - assert(!fs.existsSync(SRC_DIR)) - fs.mkdirSync(SRC_DIR) - assert(fs.existsSync(SRC_DIR)) - - fse.move(SRC_DIR, DEST_DIR, function (err) { - assert(fs.existsSync(SRC_DIR)) - assert(err) - done() - }) - }) - }) - - // tested on Linux ubuntu 3.13.0-32-generic #57-Ubuntu SMP i686 i686 GNU/Linux - // this won't trigger a bug on Mac OS X Yosimite with a USB drive (/Volumes) - // see issue #108 - describe('> when actually trying to a move a folder across devices', function () { - var differentDevice = '/mnt' - var __skipTests = false - - // must set this up, if not, exit silently - if (!fs.existsSync(differentDevice)) { - console.log('Skipping cross-device move test') - __skipTests = true - } - - // make sure we have permission on device - try { - fs.writeFileSync(path.join(differentDevice, 'file'), 'hi') - } catch (err) { - console.log("Can't write to device. Skipping test.") - __skipTests = true - } - - var _it = __skipTests ? it.skip : it - - describe('> just the folder', function () { - _it('should move the folder', function (done) { - var src = '/mnt/some/weird/dir-really-weird' - var dest = path.join(TEST_DIR, 'device-weird') - - if (!fs.existsSync(src)) { - fse.mkdirpSync(src) - } - - assert(!fs.existsSync(dest)) - - assert(fs.lstatSync(src).isDirectory()) - - fse.move(src, dest, function (err) { - assert.ifError(err) - assert(fs.existsSync(dest)) - // console.log(path.normalize(dest)) - assert(fs.lstatSync(dest).isDirectory()) - done() - }) - }) - }) - }) -}) From 469d55cca4a981a6fd8d4f9e5af7c529c4d2d3ea Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Wed, 8 Mar 2017 15:26:07 -0800 Subject: [PATCH 09/14] Add latest move test --- lib/move/__tests__/move.test.js | 373 ++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 lib/move/__tests__/move.test.js diff --git a/lib/move/__tests__/move.test.js b/lib/move/__tests__/move.test.js new file mode 100644 index 00000000..9045b7ea --- /dev/null +++ b/lib/move/__tests__/move.test.js @@ -0,0 +1,373 @@ +'use strict' + +const fs = require('graceful-fs') +const os = require('os') +const fse = require(process.cwd()) +const path = require('path') +const assert = require('assert') +const rimraf = require('rimraf') + +/* global afterEach, beforeEach, describe, it */ + +function createAsyncErrFn (errCode) { + const fn = function () { + fn.callCount++ + const callback = arguments[arguments.length - 1] + setTimeout(() => { + const err = new Error() + err.code = errCode + callback(err) + }, 10) + } + fn.callCount = 0 + return fn +} + +const originalRename = fs.rename +const originalLink = fs.link + +function setUpMockFs (errCode) { + fs.rename = createAsyncErrFn(errCode) + fs.link = createAsyncErrFn(errCode) +} + +function tearDownMockFs () { + fs.rename = originalRename + fs.link = originalLink +} + +describe('move', () => { + let TEST_DIR + + beforeEach(() => { + TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move') + + fse.emptyDirSync(TEST_DIR) + + // Create fixtures: + fs.writeFileSync(path.join(TEST_DIR, 'a-file'), 'sonic the hedgehog\n') + fs.mkdirSync(path.join(TEST_DIR, 'a-folder')) + fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-file'), 'tails\n') + fs.mkdirSync(path.join(TEST_DIR, 'a-folder/another-folder')) + fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-folder/file3'), 'knuckles\n') + }) + + afterEach(done => rimraf(TEST_DIR, done)) + + it('should rename a file on the same device', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-file-dest` + + fse.move(src, dest, 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), `${contents} match ${expected}`) + done() + }) + }) + }) + + it('should not overwrite the destination by default', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-folder/another-file` + + // verify file exists already + assert(fs.existsSync(dest)) + + fse.move(src, dest, err => { + assert.ok(err && err.code === 'EEXIST', 'throw EEXIST') + done() + }) + }) + + it('should not overwrite if overwrite = false', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-folder/another-file` + + // verify file exists already + assert(fs.existsSync(dest)) + + fse.move(src, dest, {overwrite: false}, err => { + assert.ok(err && err.code === 'EEXIST', 'throw EEXIST') + done() + }) + }) + + it('should overwrite file if overwrite = true', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-folder/another-file` + + // verify file exists already + assert(fs.existsSync(dest)) + + fse.move(src, dest, {overwrite: true}, 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), `${contents} match ${expected}`) + done() + }) + }) + }) + + it('should overwrite the destination directory if overwrite = true', function (done) { + // Tests fail on appveyor/Windows due to + // https://github.com/isaacs/node-graceful-fs/issues/98. + // Workaround by increasing the timeout by a minute (because + // graceful times out after a minute). + this.timeout(90000) + + // Create src + const src = path.join(TEST_DIR, 'src') + fse.ensureDirSync(src) + fse.mkdirsSync(path.join(src, 'some-folder')) + fs.writeFileSync(path.join(src, 'some-file'), 'hi') + + const dest = path.join(TEST_DIR, 'a-folder') + + // verify dest has stuff in it + const paths = fs.readdirSync(dest) + assert(paths.indexOf('another-file') >= 0) + assert(paths.indexOf('another-folder') >= 0) + + fse.move(src, dest, {overwrite: true}, err => { + assert.ifError(err) + + // verify dest does not have old stuff + const paths = fs.readdirSync(dest) + assert.strictEqual(paths.indexOf('another-file'), -1) + assert.strictEqual(paths.indexOf('another-folder'), -1) + + // verify dest has new stuff + assert(paths.indexOf('some-file') >= 0) + assert(paths.indexOf('some-folder') >= 0) + + done() + }) + }) + + it('should not create directory structure if mkdirp is false', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/does/not/exist/a-file-dest` + + // verify dest directory does not exist + assert(!fs.existsSync(path.dirname(dest))) + + fse.move(src, dest, {mkdirp: false}, err => { + assert.strictEqual(err.code, 'ENOENT') + done() + }) + }) + + it('should create directory structure by default', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/does/not/exist/a-file-dest` + + // verify dest directory does not exist + assert(!fs.existsSync(path.dirname(dest))) + + fse.move(src, dest, 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), `${contents} match ${expected}`) + done() + }) + }) + }) + + it('should work across devices', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-file-dest` + + setUpMockFs('EXDEV') + + fse.move(src, dest, err => { + assert.ifError(err) + assert.strictEqual(fs.link.callCount, 1) + + fs.readFile(dest, 'utf8', (err, contents) => { + const expected = /^sonic the hedgehog\r?\n$/ + assert.ifError(err) + assert.ok(contents.match(expected), `${contents} match ${expected}`) + + tearDownMockFs() + done() + }) + }) + }) + + it('should move folders', done => { + const src = `${TEST_DIR}/a-folder` + const dest = `${TEST_DIR}/a-folder-dest` + + // verify it doesn't exist + assert(!fs.existsSync(dest)) + + fse.move(src, dest, err => { + assert.ifError(err) + fs.readFile(dest + '/another-file', 'utf8', (err, contents) => { + const expected = /^tails\r?\n$/ + assert.ifError(err) + assert.ok(contents.match(expected), `${contents} match ${expected}`) + done() + }) + }) + }) + + it('should move folders across devices with EISDIR error', done => { + const src = `${TEST_DIR}/a-folder` + const dest = `${TEST_DIR}/a-folder-dest` + + setUpMockFs('EISDIR') + + fse.move(src, dest, err => { + assert.ifError(err) + assert.strictEqual(fs.link.callCount, 1) + + fs.readFile(dest + '/another-folder/file3', 'utf8', (err, contents) => { + const expected = /^knuckles\r?\n$/ + assert.ifError(err) + assert.ok(contents.match(expected), `${contents} match ${expected}`) + + tearDownMockFs('EISDIR') + + done() + }) + }) + }) + + it('should overwrite folders across devices', done => { + const src = `${TEST_DIR}/a-folder` + const dest = `${TEST_DIR}/a-folder-dest` + + fs.mkdirSync(dest) + + setUpMockFs('EXDEV') + + fse.move(src, dest, {overwrite: true}, err => { + assert.ifError(err) + assert.strictEqual(fs.rename.callCount, 1) + + fs.readFile(dest + '/another-folder/file3', 'utf8', (err, contents) => { + const expected = /^knuckles\r?\n$/ + assert.ifError(err) + assert.ok(contents.match(expected), `${contents} match ${expected}`) + + tearDownMockFs('EXDEV') + + done() + }) + }) + }) + + it('should move folders across devices with EXDEV error', done => { + const src = `${TEST_DIR}/a-folder` + const dest = `${TEST_DIR}/a-folder-dest` + + setUpMockFs('EXDEV') + + fse.move(src, dest, err => { + assert.ifError(err) + assert.strictEqual(fs.link.callCount, 1) + + fs.readFile(dest + '/another-folder/file3', 'utf8', (err, contents) => { + const expected = /^knuckles\r?\n$/ + assert.ifError(err) + assert.ok(contents.match(expected), `${contents} match ${expected}`) + + tearDownMockFs() + + done() + }) + }) + }) + + describe('clobber', () => { + it('should be an alias for overwrite', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-folder/another-file` + + // verify file exists already + assert(fs.existsSync(dest)) + + fse.move(src, dest, {clobber: true}, 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), `${contents} match ${expected}`) + done() + }) + }) + }) + }) + + describe.skip('> when trying to move a folder into itself', () => { + it('should produce an error', done => { + const SRC_DIR = path.join(TEST_DIR, 'test') + const DEST_DIR = path.join(TEST_DIR, 'test', 'test') + + assert(!fs.existsSync(SRC_DIR)) + fs.mkdirSync(SRC_DIR) + assert(fs.existsSync(SRC_DIR)) + + fse.move(SRC_DIR, DEST_DIR, err => { + assert(fs.existsSync(SRC_DIR)) + assert(err) + done() + }) + }) + }) + + // tested on Linux ubuntu 3.13.0-32-generic #57-Ubuntu SMP i686 i686 GNU/Linux + // this won't trigger a bug on Mac OS X Yosimite with a USB drive (/Volumes) + // see issue #108 + describe('> when actually trying to move a folder across devices', () => { + const differentDevice = '/mnt' + let __skipTests = false + + // must set this up, if not, exit silently + if (!fs.existsSync(differentDevice)) { + console.log('Skipping cross-device move test') + __skipTests = true + } + + // make sure we have permission on device + try { + fs.writeFileSync(path.join(differentDevice, 'file'), 'hi') + } catch (err) { + console.log("Can't write to device. Skipping move test.") + __skipTests = true + } + + const _it = __skipTests ? it.skip : it + + describe('> just the folder', () => { + _it('should move the folder', done => { + const src = '/mnt/some/weird/dir-really-weird' + const dest = path.join(TEST_DIR, 'device-weird') + + if (!fs.existsSync(src)) { + fse.mkdirpSync(src) + } + + assert(!fs.existsSync(dest)) + + assert(fs.lstatSync(src).isDirectory()) + + fse.move(src, dest, err => { + assert.ifError(err) + assert(fs.existsSync(dest)) + // console.log(path.normalize(dest)) + assert(fs.lstatSync(dest).isDirectory()) + done() + }) + }) + }) + }) +}) From 436a5878d7a4866139234143f2d481e09caa12d0 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Wed, 8 Mar 2017 15:33:00 -0800 Subject: [PATCH 10/14] lib/copy__tests__/copy.test.js: Remove unnecessary test --- lib/copy/__tests__/copy.test.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/copy/__tests__/copy.test.js b/lib/copy/__tests__/copy.test.js index 7ae763d3..141a1497 100644 --- a/lib/copy/__tests__/copy.test.js +++ b/lib/copy/__tests__/copy.test.js @@ -158,17 +158,6 @@ describe('fs-extra', () => { }) }) }) - - /* It is redundant. See above, we already tested for this case. - describe('> when src dir does not exist', () => { - it('should return an error', done => { - fse.copy('/does/not/exist', '/something/else', err => { - assert(err instanceof Error) - done() - }) - }) - }) - */ }) describe('> when filter is used', () => { From 7d1857d28e6723a816f9340cee22aa4b1016e29c Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Fri, 28 Apr 2017 15:42:19 -0700 Subject: [PATCH 11/14] handle copying symlinks by throwing err when src is a dest's kid and vice versa, add tests --- .../__tests__/{ => async}/copy-gh-89.test.js | 0 .../copy-prevent-copying-into-itself.test.js | 147 ++++------ lib/copy/__tests__/copy-symlink.test.js | 2 +- lib/copy/copy.js | 254 ++++++++++-------- 4 files changed, 197 insertions(+), 206 deletions(-) rename lib/copy/__tests__/{ => async}/copy-gh-89.test.js (100%) diff --git a/lib/copy/__tests__/copy-gh-89.test.js b/lib/copy/__tests__/async/copy-gh-89.test.js similarity index 100% rename from lib/copy/__tests__/copy-gh-89.test.js rename to lib/copy/__tests__/async/copy-gh-89.test.js diff --git a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js index 505fbc68..4cb4719d 100644 --- a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js +++ b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js @@ -31,7 +31,7 @@ function testSuccess (src, dest, done) { const destlen = klawSync(dest).length // assert src and dest length are the same - assert.strictEqual(destlen, srclen, 'src and dest length should be equal') + assert.equal(destlen, srclen, 'src and dest length should be equal') FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied')) @@ -40,26 +40,26 @@ function testSuccess (src, dest, done) { const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8') const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8') - assert.strictEqual(o0, dat0, 'file contents matched') - assert.strictEqual(o1, dat1, 'file contents matched') - assert.strictEqual(o2, dat2, 'file contents matched') - assert.strictEqual(o3, dat3, 'file contents matched') + assert.equal(o0, dat0, 'file contents matched') + assert.equal(o1, dat1, 'file contents matched') + assert.equal(o2, dat2, 'file contents matched') + assert.equal(o3, dat3, 'file contents matched') done() }) } function testError (src, dest, done) { fs.copy(src, dest, err => { - assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) + assert.equal(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) done() }) } -describe('+ copySync() - prevent copying into itself', () => { +describe('+ copy() - prevent copying into itself', () => { let TEST_DIR, src, dest beforeEach(done => { - TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-prevent-infinite-recursion') + TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-prevent-copying-into-itself') src = path.join(TEST_DIR, 'src') fs.mkdirsSync(src) @@ -83,7 +83,7 @@ describe('+ copySync() - prevent copying into itself', () => { assert(fs.existsSync(destFile, 'file copied')) const out = fs.readFileSync(destFile, 'utf8') - assert.strictEqual(out, dat0, 'file contents matched') + assert.equal(out, dat0, 'file contents matched') done() }) }) @@ -177,7 +177,7 @@ describe('+ copySync() - prevent copying into itself', () => { describe('>> when dest is a symlink', () => { it('should not copy and return when dest points exactly to src', done => { - const destLink = path.join(TEST_DIR, 'dest_symlink') + const destLink = path.join(TEST_DIR, 'dest-symlink') fs.symlinkSync(src, destLink, 'dir') const srclenBefore = klawSync(src).length @@ -187,16 +187,16 @@ describe('+ copySync() - prevent copying into itself', () => { assert.ifError(err) const srclenAfter = klawSync(src).length - assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change') + assert.equal(srclenAfter, srclenBefore, 'src length should not change') const link = fs.readlinkSync(destLink) - assert.strictEqual(link, src) + assert.equal(link, src) done() }) }) it('should throw an error when resolved dest path is a subdir of src', done => { - const destLink = path.join(TEST_DIR, 'dest_symlink') + const destLink = path.join(TEST_DIR, 'dest-symlink') const resolvedDestPath = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest') fs.ensureFileSync(path.join(resolvedDestPath, 'subdir', 'somefile.txt')) @@ -204,14 +204,14 @@ describe('+ copySync() - prevent copying into itself', () => { fs.symlinkSync(resolvedDestPath, destLink, 'dir') fs.copy(src, destLink, err => { - assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${resolvedDestPath}'`) + assert.equal(err.message, `Cannot copy directory '${src}' into itself '${resolvedDestPath}'`) done() }) }) it('should copy the directory successfully when src is a subdir of resolved dest path', done => { const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src') - const destLink = path.join(TEST_DIR, 'dest_symlink') + const destLink = path.join(TEST_DIR, 'dest-symlink') fs.copySync(src, srcInDest) // put some stuff in srcInDest dest = path.join(TEST_DIR, 'dest') @@ -227,7 +227,7 @@ describe('+ copySync() - prevent copying into itself', () => { const destlenAfter = klawSync(dest).length // assert dest length is oldlen + length of stuff copied from src - assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth') + assert.equal(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth') FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied')) @@ -236,10 +236,10 @@ describe('+ copySync() - prevent copying into itself', () => { const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8') const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8') - assert.strictEqual(o0, dat0, 'files contents matched') - assert.strictEqual(o1, dat1, 'files contents matched') - assert.strictEqual(o2, dat2, 'files contents matched') - assert.strictEqual(o3, dat3, 'files contents matched') + assert.equal(o0, dat0, 'files contents matched') + assert.equal(o1, dat1, 'files contents matched') + assert.equal(o2, dat2, 'files contents matched') + assert.equal(o3, dat3, 'files contents matched') done() }) }) @@ -249,7 +249,7 @@ describe('+ copySync() - prevent copying into itself', () => { describe('> when source is a symlink', () => { describe('>> when dest is a directory', () => { it('should not copy and return when resolved src path points to dest', done => { - const srcLink = path.join(TEST_DIR, 'src_symlink') + const srcLink = path.join(TEST_DIR, 'src-symlink') fs.symlinkSync(src, srcLink, 'dir') dest = path.join(TEST_DIR, 'src') @@ -258,97 +258,75 @@ describe('+ copySync() - prevent copying into itself', () => { assert.ifError(err) // assert source not affected const link = fs.readlinkSync(srcLink) - assert.strictEqual(link, src) + assert.equal(link, src) done() }) }) it('should throw an error when dest not exist and is a subdir of resolved src path', done => { - const srcLink = path.join(TEST_DIR, 'src_symlink') + const srcLink = path.join(TEST_DIR, 'src-symlink') fs.symlinkSync(src, srcLink, 'dir') dest = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest') fs.copy(srcLink, dest, err => { - assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) + assert.equal(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) const link = fs.readlinkSync(srcLink) - assert.strictEqual(link, src) + assert.equal(link, src) done() }) }) it('should throw an error when dest exists and is a subdir of resolved src path', done => { - const srcLink = path.join(TEST_DIR, 'src_symlink') + const srcLink = path.join(TEST_DIR, 'src-symlink') fs.symlinkSync(src, srcLink, 'dir') dest = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest') fs.mkdirsSync(dest) fs.copy(srcLink, dest, err => { - assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) + assert.equal(err.message, `Cannot copy directory '${src}' into itself '${dest}'`) const link = fs.readlinkSync(srcLink) - assert.strictEqual(link, src) + assert.equal(link, src) done() }) }) - it('should copy the directory successfully when resolved src path is a subdir of dest', done => { - const srcLink = path.join(TEST_DIR, 'src_symlink') - const resolvedSrcPath = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src') - fs.copySync(src, resolvedSrcPath) + it('should throw an error when resolved src path is a subdir of dest', done => { + dest = path.join(TEST_DIR, 'dest') + const resolvedSrcPath = path.join(dest, 'contains', 'src') + const srcLink = path.join(TEST_DIR, 'src-symlink') + fs.copySync(src, resolvedSrcPath) // make symlink that points to a subdir in dest fs.symlinkSync(resolvedSrcPath, srcLink, 'dir') - dest = path.join(TEST_DIR, 'dest') - - const srclen = klawSync(resolvedSrcPath).length - assert(srclen > 2) - const destlenBefore = klawSync(dest).length - fs.copy(srcLink, dest, err => { - assert.ifError(err) - - const destlenAfter = klawSync(dest).length - - // assert dest length is oldlen + length of stuff copied from src - assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth') - - FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied')) - - const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8') - const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8') - const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8') - const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8') - - assert.strictEqual(o0, dat0, 'files contents matched') - assert.strictEqual(o1, dat1, 'files contents matched') - assert.strictEqual(o2, dat2, 'files contents matched') - assert.strictEqual(o3, dat3, 'files contents matched') + assert.equal(err.message, `'${dest}' already exists and contains '${srcLink}'`) done() }) }) it(`should copy the directory successfully when dest is 'src_src/dest'`, done => { - const srcLink = path.join(TEST_DIR, 'src_symlink') + const srcLink = path.join(TEST_DIR, 'src-symlink') fs.symlinkSync(src, srcLink, 'dir') dest = path.join(TEST_DIR, 'src_src', 'dest') testSuccess(srcLink, dest, () => { const link = fs.readlinkSync(dest) - assert.strictEqual(link, src) + assert.equal(link, src) done() }) }) it(`should copy the directory successfully when dest is 'srcsrc/dest'`, done => { - const srcLink = path.join(TEST_DIR, 'src_symlink') + const srcLink = path.join(TEST_DIR, 'src-symlink') fs.symlinkSync(src, srcLink, 'dir') dest = path.join(TEST_DIR, 'srcsrc', 'dest') testSuccess(srcLink, dest, () => { const link = fs.readlinkSync(dest) - assert.strictEqual(link, src) + assert.equal(link, src) done() }) }) @@ -356,9 +334,9 @@ describe('+ copySync() - prevent copying into itself', () => { describe('>> when dest is a symlink', () => { it('should not copy and return when resolved dest path is exactly the same as resolved src path', done => { - const srcLink = path.join(TEST_DIR, 'src_symlink') + const srcLink = path.join(TEST_DIR, 'src-symlink') fs.symlinkSync(src, srcLink, 'dir') - const destLink = path.join(TEST_DIR, 'dest_symlink') + const destLink = path.join(TEST_DIR, 'dest-symlink') fs.symlinkSync(src, destLink, 'dir') const srclenBefore = klawSync(srcLink).length @@ -370,70 +348,49 @@ describe('+ copySync() - prevent copying into itself', () => { assert.ifError(err) const srclenAfter = klawSync(srcLink).length - assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change') + assert.equal(srclenAfter, srclenBefore, 'src length should not change') const destlenAfter = klawSync(destLink).length - assert.strictEqual(destlenAfter, destlenBefore, 'dest length should not change') + assert.equal(destlenAfter, destlenBefore, 'dest length should not change') const srcln = fs.readlinkSync(srcLink) - assert.strictEqual(srcln, src) + assert.equal(srcln, src) const destln = fs.readlinkSync(destLink) - assert.strictEqual(destln, src) + assert.equal(destln, src) done() }) }) it('should not copy and throw error when resolved dest path is a subdir of resolved src path', done => { - const srcLink = path.join(TEST_DIR, 'src_symlink') + const srcLink = path.join(TEST_DIR, 'src-symlink') fs.symlinkSync(src, srcLink, 'dir') - const destLink = path.join(TEST_DIR, 'dest_symlink') + const destLink = path.join(TEST_DIR, 'dest-symlink') const resolvedDestPath = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest') fs.ensureFileSync(path.join(resolvedDestPath, 'subdir', 'somefile.txt')) fs.symlinkSync(resolvedDestPath, destLink, 'dir') fs.copy(srcLink, destLink, err => { - assert.strictEqual(err.message, `Cannot copy directory '${src}' into itself '${resolvedDestPath}'`) + assert.equal(err.message, `Cannot copy directory '${src}' into itself '${resolvedDestPath}'`) done() }) }) - it('should copy the directory correctly when resolved src path is a subdir of resolved dest path', done => { + it('should throw an error when resolved src path is a subdir of resolved dest path', done => { const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src') - const srcLink = path.join(TEST_DIR, 'src_symlink') + const srcLink = path.join(TEST_DIR, 'src-symlink') // put some stuff in resolved src path - fs.copySync(src, srcInDest) + // fs.copySync(src, srcInDest) fs.symlinkSync(srcInDest, srcLink, 'dir') dest = path.join(TEST_DIR, 'dest') - const destLink = path.join(TEST_DIR, 'dest_symlink') + const destLink = path.join(TEST_DIR, 'dest-symlink') fs.symlinkSync(dest, destLink, 'dir') - const srclen = klawSync(srcInDest).length - assert(srclen > 2) - const destlenBefore = klawSync(dest).length - fs.copy(srcLink, destLink, err => { - assert.ifError(err) - - const destlenAfter = klawSync(dest).length - - // assert dest length is oldlen + length of stuff copied from src - assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth') - - FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied')) - - const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8') - const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8') - const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8') - const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8') - - assert.strictEqual(o0, dat0, 'files contents matched') - assert.strictEqual(o1, dat1, 'files contents matched') - assert.strictEqual(o2, dat2, 'files contents matched') - assert.strictEqual(o3, dat3, 'files contents matched') + assert.equal(err.message, `'${destLink}' already exists and contains '${srcLink}'`) done() }) }) diff --git a/lib/copy/__tests__/copy-symlink.test.js b/lib/copy/__tests__/copy-symlink.test.js index 107a2dea..f263e4b4 100644 --- a/lib/copy/__tests__/copy-symlink.test.js +++ b/lib/copy/__tests__/copy-symlink.test.js @@ -10,7 +10,7 @@ const copy = require('../copy') /* global afterEach, beforeEach, describe, it */ describe('copy() - symlink', () => { - const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-symlinks') + const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-symlink') const src = path.join(TEST_DIR, 'src') const out = path.join(TEST_DIR, 'out') diff --git a/lib/copy/copy.js b/lib/copy/copy.js index 5cf4af31..dae99c28 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -3,6 +3,7 @@ const fs = require('graceful-fs') const path = require('path') const mkdirs = require('../mkdirs').mkdirs +const remove = require('../remove').remove const utimes = require('../util/utimes').utimesMillis const DEST_NOENT = Symbol('DEST_NOENT') @@ -31,13 +32,14 @@ function copy (src, dest, options, cb) { // Warn about using preserveTimestamps on 32-bit node if (options.preserveTimestamps && process.arch === 'ia32') { console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n - see https://github.com/jprichardson/node-fs-extra/issues/269`) + see https://github.com/jprichardson/node-fs-extra/issues/269`) } // don't allow src and dest to be the same const basePath = process.cwd() src = path.resolve(basePath, src) dest = path.resolve(basePath, dest) + if (src === dest) return cb(new Error('Source and destination must not be the same.')) if (options.filter) { @@ -58,152 +60,184 @@ function copy (src, dest, options, cb) { if (err) return cb(err) if (srcStat.isDirectory()) { - return onDir(src, srcStat, dest, options, cb) + return onDir(srcStat) } else if (srcStat.isFile() || srcStat.isCharacterDevice() || srcStat.isBlockDevice()) { - return onFile(src, srcStat, dest, options, cb) + return onFile(srcStat) } else if (srcStat.isSymbolicLink()) { - return onLink(src, dest, options, cb) + return onLink() } }) }) -} -function checkDest (dest, cb) { - fs.readlink(dest, (err, resolvedDestPath) => { - if (err) { - if (err.code === 'ENOENT') return cb(null, DEST_NOENT) - // dest exists but is not a link - if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, DEST_EXISTS) - return cb(err) - } - // dest exists and is a link - return cb(null, resolvedDestPath) - }) -} + function onFile (srcStat) { + checkDest(dest, (err, res) => { + if (err) return cb(err) + if (res === DEST_NOENT) { + return cpFile() + } else if (res === DEST_EXISTS) { + maybeCopy() + } else { // dest is a link + if (src === res) return cb() + maybeCopy() + } + }) -function onFile (src, srcStat, dest, options, cb) { - checkDest(dest, (err, res) => { - if (err) return cb(err) - if (res === DEST_NOENT) { - return cpFile(src, srcStat, dest, options, cb) - } else if (res === DEST_EXISTS) { + function maybeCopy () { if (options.overwrite) { fs.unlink(dest, err => { if (err) return cb(err) - return cpFile(src, srcStat, dest, options, cb) + return cpFile() }) } else if (options.errorOnExist) { - return cb(new Error(dest + ' already exists')) + return cb(new Error(`'${dest}' already exists`)) } else return cb() - } else return cb() - }) -} - -function cpFile (src, srcStat, dest, options, cb) { - const rs = fs.createReadStream(src) - const ws = fs.createWriteStream(dest, { mode: srcStat.mode }) + } - rs.on('error', err => cb(err)) - ws.on('error', err => cb(err)) + function cpFile () { + const rs = fs.createReadStream(src) + const ws = fs.createWriteStream(dest, { mode: srcStat.mode }) - ws.on('open', () => { - rs.pipe(ws) - }).once('close', () => { - fs.chmod(dest, srcStat.mode, err => { - if (err) return cb(err) - if (options.preserveTimestamps) { - return utimes(dest, srcStat.atime, srcStat.mtime, cb) - } - return cb() - }) - }) -} + rs.on('error', err => cb(err)) + ws.on('error', err => cb(err)) -function onDir (src, srcStat, dest, options, cb) { - checkDest(dest, (err, res) => { - if (err) return cb(err) - if (res === DEST_NOENT) { - // if dest is a subdir of src, prevent copying into itself - if (isSrcSubdir(src, dest)) return cb(new Error(`Cannot copy directory '${src}' into itself '${dest}'`)) - fs.mkdir(dest, srcStat.mode, err => { - if (err) return cb(err) + ws.on('open', () => { + rs.pipe(ws) + }).once('close', () => { fs.chmod(dest, srcStat.mode, err => { if (err) return cb(err) - return cpDir(src, dest, options, cb) + if (options.preserveTimestamps) { + return utimes(dest, srcStat.atime, srcStat.mtime, cb) + } + return cb() }) }) - } else if (res === DEST_EXISTS) { - if (isSrcSubdir(src, dest)) return cb(new Error(`Cannot copy directory '${src}' into itself '${dest}'`)) - return cpDir(src, dest, options, cb) - } else if (res && typeof res !== 'number') { // dest exists and is a link - if (isSrcSubdir(src, res)) return cb(new Error(`Cannot copy directory '${src}' into itself '${res}'`)) - if (src === res) return cb() - return cpDir(src, dest, options, cb) } - }) -} + } -function cpDir (src, dest, options, cb) { - fs.readdir(src, (err, items) => { - if (err) return cb(err) - Promise.all(items.map(item => { - return new Promise((resolve, reject) => { - copy(path.join(src, item), path.join(dest, item), options, err => { - if (err) reject(err) - else resolve() + function onDir (srcStat) { + checkDest(dest, (err, res) => { + if (err) return cb(err) + if (res === DEST_NOENT) { + // if dest is a subdir of src, prevent copying into itself + if (isSrcKid(src, dest)) return cb(new Error(`Cannot copy directory '${src}' into itself '${dest}'`)) + fs.mkdir(dest, srcStat.mode, err => { + if (err) return cb(err) + fs.chmod(dest, srcStat.mode, err => { + if (err) return cb(err) + return cpDir() + }) }) - }) - })).then(() => cb()) - .catch(cb) - }) -} - -function onLink (src, dest, options, cb) { - fs.readlink(src, (err, resolvedSrcPath) => { - if (err) return cb(err) + } else if (res === DEST_EXISTS) { + if (isSrcKid(src, dest)) return cb(new Error(`Cannot copy directory '${src}' into itself '${dest}'`)) + return cpDir() + } else { // dest exists and is a link + if (src === res) return cb() + if (isSrcKid(src, res)) return cb(new Error(`Cannot copy directory '${src}' into itself '${res}'`)) + return cpDir() + } + }) - if (options.dereference) { - resolvedSrcPath = path.resolve(process.cwd(), resolvedSrcPath) + function cpDir () { + fs.readdir(src, (err, items) => { + if (err) return cb(err) + Promise.all(items.map(item => { + return new Promise((resolve, reject) => { + copy(path.join(src, item), path.join(dest, item), options, err => { + if (err) reject(err) + else resolve() + }) + }) + })).then(() => cb()) + .catch(cb) + }) } + } - checkDest(dest, (err, resolvedDestPath) => { + function onLink () { + fs.readlink(src, (err, resolvedSrcPath) => { if (err) return cb(err) - if (resolvedDestPath === DEST_NOENT) { - // if dest is a subdir of resolved src path, prevent copying into itself - if (isSrcSubdir(resolvedSrcPath, dest)) { - return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${dest}'`)) - } - return fs.symlink(resolvedSrcPath, dest, cb) - } else if (resolvedDestPath === DEST_EXISTS) { // dest exists but is not a link - if (isSrcSubdir(resolvedSrcPath, dest)) { - return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${dest}'`)) - } - // if src points to dest - if (resolvedSrcPath === dest) return cb() - return copy(resolvedSrcPath, dest, options, cb) - } else if (resolvedDestPath && typeof resolvedDestPath !== 'number') { // dest is a link - if (options.dereference) { - resolvedDestPath = path.resolve(process.cwd(), resolvedDestPath) - } - // if resolved dest path is a subdir of resolved src path, prevent copying into itself - if (isSrcSubdir(resolvedSrcPath, resolvedDestPath)) { - return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${resolvedDestPath}'`)) + if (options.dereference) { + resolvedSrcPath = path.resolve(process.cwd(), resolvedSrcPath) + } + + checkDest(dest, (err, resolvedDestPath) => { + if (err) return cb(err) + + if (resolvedDestPath === DEST_NOENT) { + // if dest is a subdir of resolved src path + if (isSrcKid(resolvedSrcPath, dest)) { + return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${dest}'`)) + } + return fs.symlink(resolvedSrcPath, dest, cb) + } else if (resolvedDestPath === DEST_EXISTS) { // dest exists but is not a link + // if src points to dest + if (resolvedSrcPath === dest) return cb() + + // if dest is a subdir of src + if (isSrcKid(resolvedSrcPath, dest)) { + return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${dest}'`)) + } + + // if src is a subdir of dest, prevent making broken link + if (isSrcKid(dest, resolvedSrcPath)) { + return cb(new Error(`'${dest}' already exists and contains '${src}'`)) + } + + // we cannot use fs.unlink here since dest is not a link + remove(dest, err => { + if (err) return cb(err) + return fs.symlink(resolvedSrcPath, dest, cb) + }) + } else { // dest exists and is a link + if (options.dereference) { + resolvedDestPath = path.resolve(process.cwd(), resolvedDestPath) + } + if (resolvedSrcPath === resolvedDestPath) return cb() + + // if resolved dest path is a subdir of resolved src path, prevent copying into itself + if (isSrcKid(resolvedSrcPath, resolvedDestPath)) { + return cb(new Error(`Cannot copy directory '${resolvedSrcPath}' into itself '${resolvedDestPath}'`)) + } + + // if src is a subdir of dest, prevent making broken link + if (isSrcKid(resolvedDestPath, resolvedSrcPath)) { + return cb(new Error(`'${dest}' already exists and contains '${src}'`)) + } + + fs.unlink(dest, err => { + if (err) return cb(err) + return fs.symlink(resolvedSrcPath, dest, cb) + }) } - if (resolvedSrcPath === resolvedDestPath) return cb() - return copy(resolvedSrcPath, dest, options, cb) - } else return cb() + }) }) + } +} + +// check dest to see if it exists and/or is a link +function checkDest (dest, cb) { + fs.readlink(dest, (err, resolvedDestPath) => { + if (err) { + if (err.code === 'ENOENT') return cb(null, DEST_NOENT) + // dest exists but is not a link, Windows throws UNKNOWN error + if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, DEST_EXISTS) + return cb(err) + } + // dest exists and is a link + return cb(null, resolvedDestPath) }) } // return true if dest is a subdir of src, otherwise false. // extract dest base dir and check if that is the same as src basename -function isSrcSubdir (src, dest) { +function isSrcKid (src, dest) { + src = path.resolve(src) + dest = path.resolve(dest) try { return src !== dest && - dest.indexOf(src) > -1 && - dest.split(path.dirname(src) + path.sep)[1].split(path.sep)[0] === path.basename(src) + dest.indexOf(src) > -1 && + dest.split(path.dirname(src) + path.sep)[1].split(path.sep)[0] === path.basename(src) } catch (e) { return false } From 70f2c7c09dcff4158dae14833aa155ac0affbe22 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Fri, 28 Apr 2017 17:10:45 -0700 Subject: [PATCH 12/14] put checkDest() in the block of copy() --- lib/copy/copy.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/copy/copy.js b/lib/copy/copy.js index dae99c28..a5df9350 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -69,8 +69,22 @@ function copy (src, dest, options, cb) { }) }) + // check dest to see if it exists and/or is a link + function checkDest (callback) { + fs.readlink(dest, (err, resolvedDestPath) => { + if (err) { + if (err.code === 'ENOENT') return callback(null, DEST_NOENT) + // dest exists but is not a link, Windows throws UNKNOWN error + if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return callback(null, DEST_EXISTS) + return callback(err) + } + // dest exists and is a link + return callback(null, resolvedDestPath) + }) + } + function onFile (srcStat) { - checkDest(dest, (err, res) => { + checkDest((err, res) => { if (err) return cb(err) if (res === DEST_NOENT) { return cpFile() @@ -115,7 +129,7 @@ function copy (src, dest, options, cb) { } function onDir (srcStat) { - checkDest(dest, (err, res) => { + checkDest((err, res) => { if (err) return cb(err) if (res === DEST_NOENT) { // if dest is a subdir of src, prevent copying into itself @@ -161,7 +175,7 @@ function copy (src, dest, options, cb) { resolvedSrcPath = path.resolve(process.cwd(), resolvedSrcPath) } - checkDest(dest, (err, resolvedDestPath) => { + checkDest((err, resolvedDestPath) => { if (err) return cb(err) if (resolvedDestPath === DEST_NOENT) { @@ -215,20 +229,6 @@ function copy (src, dest, options, cb) { } } -// check dest to see if it exists and/or is a link -function checkDest (dest, cb) { - fs.readlink(dest, (err, resolvedDestPath) => { - if (err) { - if (err.code === 'ENOENT') return cb(null, DEST_NOENT) - // dest exists but is not a link, Windows throws UNKNOWN error - if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, DEST_EXISTS) - return cb(err) - } - // dest exists and is a link - return cb(null, resolvedDestPath) - }) -} - // return true if dest is a subdir of src, otherwise false. // extract dest base dir and check if that is the same as src basename function isSrcKid (src, dest) { From 6271e98628b52d88b252913d385f516ead6db5e6 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Sat, 6 May 2017 17:26:33 -0700 Subject: [PATCH 13/14] Add improved filter option and its tests --- .../copy-prevent-copying-into-itself.test.js | 2 +- lib/copy/__tests__/copy.test.js | 50 +++++++++- lib/copy/copy.js | 96 ++++++++++++------- 3 files changed, 113 insertions(+), 35 deletions(-) diff --git a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js index 4cb4719d..ec80fd6c 100644 --- a/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js +++ b/lib/copy/__tests__/copy-prevent-copying-into-itself.test.js @@ -61,7 +61,7 @@ describe('+ copy() - prevent copying into itself', () => { beforeEach(done => { TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-prevent-copying-into-itself') src = path.join(TEST_DIR, 'src') - fs.mkdirsSync(src) + fs.mkdirpSync(src) fs.outputFileSync(path.join(src, FILES[0]), dat0) fs.outputFileSync(path.join(src, FILES[1]), dat1) diff --git a/lib/copy/__tests__/copy.test.js b/lib/copy/__tests__/copy.test.js index 141a1497..91e17d20 100644 --- a/lib/copy/__tests__/copy.test.js +++ b/lib/copy/__tests__/copy.test.js @@ -270,7 +270,7 @@ describe('fs-extra', () => { it('should apply filter when it is applied only to dest', done => { const timeCond = new Date().getTime() - const filter = (s, d) => fs.statSync(d).birthtime.getTime() < timeCond + const filter = (s, d) => fs.existsSync(d) && fs.statSync(d).birthtime.getTime() < timeCond const src = path.join(TEST_DIR, 'src') fse.mkdirsSync(src) @@ -327,6 +327,54 @@ describe('fs-extra', () => { }) }, 1000) }) + + it('should apply filter recursively on failed dirs by default', done => { + const srcDir = path.join(TEST_DIR, 'src') + const srcFile1 = path.join(srcDir, '1.js') + const srcFile2 = path.join(srcDir, '2.css') + const srcFile3 = path.join(srcDir, 'node_modules', '3.css') + fse.outputFileSync(srcFile1, 'file 1 stuff') + fse.outputFileSync(srcFile2, 'file 2 stuff') + fse.outputFileSync(srcFile3, 'file 3 stuff') + const destDir = path.join(TEST_DIR, 'dest') + const destFile1 = path.join(destDir, '1.js') + const destFile2 = path.join(destDir, '2.css') + const destFile3 = path.join(destDir, 'node_modules', '3.css') + + const filter = s => path.extname(s) === '.css' + + fse.copy(srcDir, destDir, filter, err => { + assert(!err) + assert(!fs.existsSync(destFile1)) + assert(fs.existsSync(destFile2)) + assert(fs.existsSync(destFile3)) + done() + }) + }) + + it('should not apply filter recursively on failed dirs when noRecurseOnFailedFilter is true', done => { + const srcDir = path.join(TEST_DIR, 'src') + const srcFile1 = path.join(srcDir, '1.js') + const srcFile2 = path.join(srcDir, '2.css') + const srcFile3 = path.join(srcDir, 'node_modules', '3.css') + fse.outputFileSync(srcFile1, 'file 1 stuff') + fse.outputFileSync(srcFile2, 'file 2 stuff') + fse.outputFileSync(srcFile3, 'file 3 stuff') + const destDir = path.join(TEST_DIR, 'dest') + const destFile1 = path.join(destDir, '1.js') + const destFile2 = path.join(destDir, '2.css') + const destFile3 = path.join(destDir, 'node_modules', '3.css') + + const filter = s => path.extname(s) === '.css' || s === srcDir + + fse.copy(srcDir, destDir, {filter: filter, noRecurseOnFailedFilter: true}, err => { + assert(!err) + assert(!fs.existsSync(destFile1)) + assert(fs.existsSync(destFile2)) + assert(!fs.existsSync(destFile3)) + done() + }) + }) }) }) }) diff --git a/lib/copy/copy.js b/lib/copy/copy.js index a5df9350..8d971f35 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -2,7 +2,7 @@ const fs = require('graceful-fs') const path = require('path') -const mkdirs = require('../mkdirs').mkdirs +const mkdirp = require('../mkdirs').mkdirs const remove = require('../remove').remove const utimes = require('../util/utimes').utimesMillis @@ -26,7 +26,6 @@ function copy (src, dest, options, cb) { options.overwrite = 'overwrite' in options ? !!options.overwrite : options.clobber options.dereference = 'dereference' in options ? !!options.dereference : false options.preserveTimestamps = 'preserveTimestamps' in options ? !!options.preserveTimestamps : false - options.filter = options.filter || function () { return true } // Warn about using preserveTimestamps on 32-bit node @@ -42,44 +41,76 @@ function copy (src, dest, options, cb) { if (src === dest) return cb(new Error('Source and destination must not be the same.')) - if (options.filter) { - if (options.filter instanceof RegExp) { - console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function') - if (!options.filter.test(src)) return cb() - } else if (typeof options.filter === 'function') { - if (!options.filter(src, dest)) return cb() + let stat = options.dereference ? fs.stat : fs.lstat + stat(src, (err, st) => { + if (err) return cb(err) + + if (st.isDirectory()) { + if (options.filter) return applyFilterOnDir() + return onDir(st) + } else if (st.isFile() || st.isCharacterDevice() || st.isBlockDevice()) { + if (options.filter) return applyFilterOnFile() + return onFile(st) + } else if (st.isSymbolicLink()) { + return onLink() } - } - const destParent = path.dirname(dest) - mkdirs(destParent, err => { - if (err) return cb(err) + function applyFilterOnDir () { + if (options.filter instanceof RegExp) { + console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function') + if (!options.filter.test(src)) { + if (options.noRecurseOnFailedFilter) return cb() + return readFailedDir() + } + return onDir(st) + } else if (typeof options.filter === 'function') { + if (!options.filter(src, dest)) { + if (options.noRecurseOnFailedFilter) return cb() + return readFailedDir() + } + return onDir(st) + } - let stat = options.dereference ? fs.stat : fs.lstat - stat(src, (err, srcStat) => { - if (err) return cb(err) + function readFailedDir () { + fs.readdir(src, (err, items) => { + if (err) return cb(err) + Promise.all(items.map(item => { + return new Promise((resolve, reject) => { + copy(path.join(src, item), path.join(dest, item), options, err => { + if (err) reject(err) + else resolve() + }) + }) + })).then(() => cb()).catch(cb) + }) + } + } - if (srcStat.isDirectory()) { - return onDir(srcStat) - } else if (srcStat.isFile() || srcStat.isCharacterDevice() || srcStat.isBlockDevice()) { - return onFile(srcStat) - } else if (srcStat.isSymbolicLink()) { - return onLink() + function applyFilterOnFile () { + if (options.filter instanceof RegExp) { + if (!options.filter.test(src)) return cb() + return onFile(st) + } else if (typeof options.filter === 'function') { + if (!options.filter(src, dest)) return cb() + return onFile(st) } - }) + } }) // check dest to see if it exists and/or is a link function checkDest (callback) { - fs.readlink(dest, (err, resolvedDestPath) => { - if (err) { - if (err.code === 'ENOENT') return callback(null, DEST_NOENT) - // dest exists but is not a link, Windows throws UNKNOWN error - if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return callback(null, DEST_EXISTS) - return callback(err) - } - // dest exists and is a link - return callback(null, resolvedDestPath) + mkdirp(path.dirname(dest), err => { + if (err) return callback(err) + fs.readlink(dest, (err, resolvedDestPath) => { + if (err) { + if (err.code === 'ENOENT') return callback(null, DEST_NOENT) + // dest exists but is not a link, Windows throws UNKNOWN error + if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return callback(null, DEST_EXISTS) + return callback(err) + } + // dest exists and is a link + return callback(null, resolvedDestPath) + }) }) } @@ -161,8 +192,7 @@ function copy (src, dest, options, cb) { else resolve() }) }) - })).then(() => cb()) - .catch(cb) + })).then(() => cb()).catch(cb) }) } } From cecd18477e3abff8a75d37acf613ea122341f9d7 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Mon, 8 May 2017 08:56:39 -0700 Subject: [PATCH 14/14] Change function names to filterDir and filterFile --- lib/copy/copy.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/copy/copy.js b/lib/copy/copy.js index 8d971f35..284eeaa8 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -46,16 +46,16 @@ function copy (src, dest, options, cb) { if (err) return cb(err) if (st.isDirectory()) { - if (options.filter) return applyFilterOnDir() + if (options.filter) return filterDir() return onDir(st) } else if (st.isFile() || st.isCharacterDevice() || st.isBlockDevice()) { - if (options.filter) return applyFilterOnFile() + if (options.filter) return filterFile() return onFile(st) } else if (st.isSymbolicLink()) { return onLink() } - function applyFilterOnDir () { + function filterDir () { if (options.filter instanceof RegExp) { console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function') if (!options.filter.test(src)) { @@ -86,7 +86,7 @@ function copy (src, dest, options, cb) { } } - function applyFilterOnFile () { + function filterFile () { if (options.filter instanceof RegExp) { if (!options.filter.test(src)) return cb() return onFile(st)