Add files via upload

This commit is contained in:
bakustarver 2024-07-01 20:42:45 +03:00 committed by GitHub
parent a5389c8738
commit c62db468c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 4029 additions and 0 deletions

View file

@ -0,0 +1,68 @@
import _copy from './copy/index.js'
import _empty from './empty/index.js'
import _ensure from './ensure/index.js'
import _json from './json/index.js'
import _mkdirs from './mkdirs/index.js'
import _move from './move/index.js'
import _outputFile from './output-file/index.js'
import _pathExists from './path-exists/index.js'
import _remove from './remove/index.js'
// NOTE: Only exports fs-extra's functions; fs functions must be imported from "node:fs" or "node:fs/promises"
export const copy = _copy.copy
export const copySync = _copy.copySync
export const emptyDirSync = _empty.emptyDirSync
export const emptydirSync = _empty.emptydirSync
export const emptyDir = _empty.emptyDir
export const emptydir = _empty.emptydir
export const createFile = _ensure.createFile
export const createFileSync = _ensure.createFileSync
export const ensureFile = _ensure.ensureFile
export const ensureFileSync = _ensure.ensureFileSync
export const createLink = _ensure.createLink
export const createLinkSync = _ensure.createLinkSync
export const ensureLink = _ensure.ensureLink
export const ensureLinkSync = _ensure.ensureLinkSync
export const createSymlink = _ensure.createSymlink
export const createSymlinkSync = _ensure.createSymlinkSync
export const ensureSymlink = _ensure.ensureSymlink
export const ensureSymlinkSync = _ensure.ensureSymlinkSync
export const readJson = _json.readJson
export const readJSON = _json.readJSON
export const readJsonSync = _json.readJsonSync
export const readJSONSync = _json.readJSONSync
export const writeJson = _json.writeJson
export const writeJSON = _json.writeJSON
export const writeJsonSync = _json.writeJsonSync
export const writeJSONSync = _json.writeJSONSync
export const outputJson = _json.outputJson
export const outputJSON = _json.outputJSON
export const outputJsonSync = _json.outputJsonSync
export const outputJSONSync = _json.outputJSONSync
export const mkdirs = _mkdirs.mkdirs
export const mkdirsSync = _mkdirs.mkdirsSync
export const mkdirp = _mkdirs.mkdirp
export const mkdirpSync = _mkdirs.mkdirpSync
export const ensureDir = _mkdirs.ensureDir
export const ensureDirSync = _mkdirs.ensureDirSync
export const move = _move.move
export const moveSync = _move.moveSync
export const outputFile = _outputFile.outputFile
export const outputFileSync = _outputFile.outputFileSync
export const pathExists = _pathExists.pathExists
export const pathExistsSync = _pathExists.pathExistsSync
export const remove = _remove.remove
export const removeSync = _remove.removeSync
export default {
..._copy,
..._empty,
..._ensure,
..._json,
..._mkdirs,
..._move,
..._outputFile,
..._pathExists,
..._remove
}

View file

@ -0,0 +1,16 @@
'use strict'
module.exports = {
// Export promiseified graceful-fs:
...require('./fs'),
// Export extra methods:
...require('./copy'),
...require('./empty'),
...require('./ensure'),
...require('./json'),
...require('./mkdirs'),
...require('./move'),
...require('./output-file'),
...require('./path-exists'),
...require('./remove')
}

View file

@ -0,0 +1,62 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../../')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('mkdirp / chmod', () => {
let TEST_DIR
let TEST_SUBDIR
beforeEach(done => {
const ps = []
for (let i = 0; i < 15; i++) {
const dir = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
ps.push(dir)
}
TEST_SUBDIR = ps.join(path.sep)
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-chmod')
TEST_SUBDIR = path.join(TEST_DIR, TEST_SUBDIR)
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
it('chmod-pre', done => {
const mode = 0o744
fse.mkdirp(TEST_SUBDIR, mode, err => {
assert.ifError(err, 'should not error')
fs.stat(TEST_SUBDIR, (err, stat) => {
assert.ifError(err, 'should exist')
assert.ok(stat && stat.isDirectory(), 'should be directory')
if (os.platform().indexOf('win') === 0) {
assert.strictEqual(stat && stat.mode & 0o777, 0o666, 'windows shit')
} else {
assert.strictEqual(stat && stat.mode & 0o777, mode, 'should be 0744')
}
done()
})
})
})
it('chmod', done => {
const mode = 0o755
fse.mkdirp(TEST_SUBDIR, mode, err => {
assert.ifError(err, 'should not error')
fs.stat(TEST_SUBDIR, (err, stat) => {
assert.ifError(err, 'should exist')
assert.ok(stat && stat.isDirectory(), 'should be directory')
done()
})
})
})
})

View file

@ -0,0 +1,49 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global before, describe, it */
describe('mkdirp / clobber', () => {
let TEST_DIR
let file
before(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-clobber')
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
const ps = [TEST_DIR]
for (let i = 0; i < 15; i++) {
const dir = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
ps.push(dir)
}
file = ps.join(path.sep)
// a file in the way
const itw = ps.slice(0, 2).join(path.sep)
fs.writeFileSync(itw, 'I AM IN THE WAY, THE TRUTH, AND THE LIGHT.')
fs.stat(itw, (err, stat) => {
assert.ifError(err)
assert.ok(stat && stat.isFile(), 'should be file')
done()
})
})
})
it('should clobber', done => {
fse.mkdirp(file, 0o755, err => {
assert.ok(err)
assert.strictEqual(err.code, 'ENOTDIR')
done()
})
})
})

View file

@ -0,0 +1,39 @@
'use strict'
const assert = require('assert')
const fse = require('../..')
/* global describe, it */
describe('mkdirp: issue-209, win32, when bad path, should return a cleaner error', () => {
// only seems to be an issue on Windows.
if (process.platform !== 'win32') return
it('should return a callback', done => {
const file = './bad?dir'
fse.mkdirp(file, err => {
assert(err, 'error is present')
assert.strictEqual(err.code, 'EINVAL')
const file2 = 'c:\\tmp\foo:moo'
fse.mkdirp(file2, err => {
assert(err, 'error is present')
assert.strictEqual(err.code, 'EINVAL')
done()
})
})
})
describe('> sync', () => {
it('should throw an error', () => {
let didErr
try {
const file = 'c:\\tmp\foo:moo'
fse.mkdirpSync(file)
} catch {
didErr = true
}
assert(didErr)
})
})
})

View file

@ -0,0 +1,45 @@
'use strict'
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
const util = require('util')
/* global before, describe, it */
describe('mkdirp: issue-93, win32, when drive does not exist, it should return a cleaner error', () => {
let TEST_DIR
// only seems to be an issue on Windows.
if (process.platform !== 'win32') return
before(done => {
TEST_DIR = path.join(os.tmpdir(), 'tests', 'fs-extra', 'mkdirp-issue-93')
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
done()
})
})
it('should return a cleaner error than inifinite loop, stack crash', done => {
const file = 'R:\\afasd\\afaff\\fdfd' // hopefully drive 'r' does not exist on appveyor
// Different error codes on different Node versions (matches native mkdir behavior)
const assertErr = (err) => assert(
['EPERM', 'ENOENT'].includes(err.code),
`expected 'EPERM' or 'ENOENT', got ${util.inspect(err.code)}`
)
fse.mkdirp(file, err => {
assertErr(err)
try {
fse.mkdirsSync(file)
} catch (err) {
assertErr(err)
}
done()
})
})
})

View file

@ -0,0 +1,70 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('fs-extra', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdir')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('+ mkdirs()', () => {
it('should make the directory', done => {
const dir = path.join(TEST_DIR, 'tmp-' + Date.now() + Math.random())
assert(!fs.existsSync(dir))
fse.mkdirs(dir, err => {
assert.ifError(err)
assert(fs.existsSync(dir))
done()
})
})
it('should make the entire directory path', done => {
const dir = path.join(TEST_DIR, 'tmp-' + Date.now() + Math.random())
const newDir = path.join(TEST_DIR, 'dfdf', 'ffff', 'aaa')
assert(!fs.existsSync(dir))
fse.mkdirs(newDir, err => {
assert.ifError(err)
assert(fs.existsSync(newDir))
done()
})
})
})
describe('+ mkdirsSync()', () => {
it('should make the directory', done => {
const dir = path.join(TEST_DIR, 'tmp-' + Date.now() + Math.random())
assert(!fs.existsSync(dir))
fse.mkdirsSync(dir)
assert(fs.existsSync(dir))
done()
})
it('should make the entire directory path', done => {
const dir = path.join(TEST_DIR, 'tmp-' + Date.now() + Math.random())
const newDir = path.join(dir, 'dfdf', 'ffff', 'aaa')
assert(!fs.existsSync(newDir))
fse.mkdirsSync(newDir)
assert(fs.existsSync(newDir))
done()
})
})
})

View file

@ -0,0 +1,48 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('mkdirp / mkdirp', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
it('should make the dir', done => {
const x = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
const y = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
const z = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
const file = path.join(TEST_DIR, x, y, z)
fse.mkdirp(file, 0o755, err => {
assert.ifError(err)
fse.pathExists(file, (err, ex) => {
assert.ifError(err)
assert.ok(ex, 'file created')
fs.stat(file, (err, stat) => {
assert.ifError(err)
if (os.platform().indexOf('win') === 0) {
assert.strictEqual(stat.mode & 0o777, 0o666)
} else {
assert.strictEqual(stat.mode & 0o777, 0o755)
}
assert.ok(stat.isDirectory(), 'target not a directory')
done()
})
})
})
})
})

View file

@ -0,0 +1,30 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global beforeEach, describe, it */
describe('mkdirs / opts-undef', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirs')
fse.emptyDir(TEST_DIR, done)
})
// https://github.com/substack/node-mkdirp/issues/45
it('should not hang', done => {
const newDir = path.join(TEST_DIR, 'doest', 'not', 'exist')
assert(!fs.existsSync(newDir))
fse.mkdirs(newDir, undefined, err => {
assert.ifError(err)
assert(fs.existsSync(newDir))
done()
})
})
})

View file

@ -0,0 +1,51 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../../')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('mkdirp / perm', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-perm')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
it('async perm', done => {
const file = path.join(TEST_DIR, (Math.random() * (1 << 30)).toString(16))
fse.mkdirp(file, 0o755, err => {
assert.ifError(err)
fse.pathExists(file, (err, ex) => {
assert.ifError(err)
assert.ok(ex, 'file created')
fs.stat(file, (err, stat) => {
assert.ifError(err)
if (os.platform().indexOf('win') === 0) {
assert.strictEqual(stat.mode & 0o777, 0o666)
} else {
assert.strictEqual(stat.mode & 0o777, 0o755)
}
assert.ok(stat.isDirectory(), 'target not a directory')
done()
})
})
})
})
it('async root perm', done => {
fse.mkdirp(path.join(os.tmpdir(), 'tmp'), 0o755, err => {
assert.ifError(err)
done()
})
})
})

View file

@ -0,0 +1,56 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('mkdirp / perm_sync', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-perm-sync')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
it('sync perm', done => {
const file = path.join(TEST_DIR, (Math.random() * (1 << 30)).toString(16) + '.json')
fse.mkdirpSync(file, 0o755)
fse.pathExists(file, (err, ex) => {
assert.ifError(err)
assert.ok(ex, 'file created')
fs.stat(file, (err, stat) => {
assert.ifError(err)
if (os.platform().indexOf('win') === 0) {
assert.strictEqual(stat.mode & 0o777, 0o666)
} else {
assert.strictEqual(stat.mode & 0o777, 0o755)
}
assert.ok(stat.isDirectory(), 'target not a directory')
done()
})
})
})
it('sync root perm', done => {
const file = TEST_DIR
fse.mkdirpSync(file, 0o755)
fse.pathExists(file, (err, ex) => {
assert.ifError(err)
assert.ok(ex, 'file created')
fs.stat(file, (err, stat) => {
assert.ifError(err)
assert.ok(stat.isDirectory(), 'target not a directory')
done()
})
})
})
})

View file

@ -0,0 +1,62 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('mkdirp / race', () => {
let TEST_DIR
let file
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-race')
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
const ps = [TEST_DIR]
for (let i = 0; i < 15; i++) {
const dir = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
ps.push(dir)
}
file = path.join(...ps)
done()
})
})
afterEach(done => fse.remove(TEST_DIR, done))
it('race', done => {
let res = 2
mk(file, () => --res === 0 ? done() : undefined)
mk(file, () => --res === 0 ? done() : undefined)
function mk (file, callback) {
fse.mkdirp(file, 0o755, err => {
assert.ifError(err)
fse.pathExists(file, (err, ex) => {
assert.ifError(err)
assert.ok(ex, 'file created')
fs.stat(file, (err, stat) => {
assert.ifError(err)
if (os.platform().indexOf('win') === 0) {
assert.strictEqual(stat.mode & 0o777, 0o666)
} else {
assert.strictEqual(stat.mode & 0o777, 0o755)
}
assert.ok(stat.isDirectory(), 'target not a directory')
if (callback) callback()
})
})
})
}
})
})

View file

@ -0,0 +1,60 @@
'use strict'
const CWD = process.cwd()
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('mkdirp / relative', () => {
let TEST_DIR
let file
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-relative')
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
const x = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
const y = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
const z = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
// relative path
file = path.join(x, y, z)
done()
})
})
afterEach(done => fse.remove(TEST_DIR, done))
it('should make the directory with relative path', done => {
process.chdir(TEST_DIR)
fse.mkdirp(file, 0o755, err => {
assert.ifError(err)
fse.pathExists(file, (err, ex) => {
assert.ifError(err)
assert.ok(ex, 'file created')
fs.stat(file, (err, stat) => {
assert.ifError(err)
// restore
process.chdir(CWD)
if (os.platform().indexOf('win') === 0) {
assert.strictEqual(stat.mode & 0o777, 0o666)
} else {
assert.strictEqual(stat.mode & 0o777, 0o755)
}
assert.ok(stat.isDirectory(), 'target not a directory')
done()
})
})
})
})
})

View file

@ -0,0 +1,27 @@
'use strict'
const fs = require('fs')
const fse = require('../../')
const path = require('path')
const assert = require('assert')
/* global describe, it */
describe('mkdirp / root', () => {
// '/' on unix
const dir = path.normalize(path.resolve(path.sep)).toLowerCase()
// Windows does not have permission to mkdir on root
if (process.platform === 'win32') return
it('should', done => {
fse.mkdirp(dir, 0o755, err => {
if (err) return done(err)
fs.stat(dir, (er, stat) => {
if (er) return done(er)
assert.ok(stat.isDirectory(), 'target is a directory')
done()
})
})
})
})

View file

@ -0,0 +1,56 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('mkdirp / sync', () => {
let TEST_DIR
let file
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'mkdirp-sync')
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
const x = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
const y = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
const z = Math.floor(Math.random() * Math.pow(16, 4)).toString(16)
file = path.join(TEST_DIR, x, y, z)
done()
})
})
afterEach(done => fse.remove(TEST_DIR, done))
it('should', done => {
try {
fse.mkdirpSync(file, 0o755)
} catch (err) {
assert.fail(err)
}
fse.pathExists(file, (err, ex) => {
assert.ifError(err)
assert.ok(ex, 'file created')
fs.stat(file, (err, stat) => {
assert.ifError(err)
// http://stackoverflow.com/questions/592448/c-how-to-set-file-permissions-cross-platform
if (os.platform().indexOf('win') === 0) {
assert.strictEqual(stat.mode & 0o777, 0o666)
} else {
assert.strictEqual(stat.mode & 0o777, 0o755)
}
assert.ok(stat.isDirectory(), 'target not a directory')
done()
})
})
})
})

View file

@ -0,0 +1,14 @@
'use strict'
const u = require('universalify').fromPromise
const { makeDir: _makeDir, makeDirSync } = require('./make-dir')
const makeDir = u(_makeDir)
module.exports = {
mkdirs: makeDir,
mkdirsSync: makeDirSync,
// alias
mkdirp: makeDir,
mkdirpSync: makeDirSync,
ensureDir: makeDir,
ensureDirSync: makeDirSync
}

View file

@ -0,0 +1,27 @@
'use strict'
const fs = require('../fs')
const { checkPath } = require('./utils')
const getMode = options => {
const defaults = { mode: 0o777 }
if (typeof options === 'number') return options
return ({ ...defaults, ...options }).mode
}
module.exports.makeDir = async (dir, options) => {
checkPath(dir)
return fs.mkdir(dir, {
mode: getMode(options),
recursive: true
})
}
module.exports.makeDirSync = (dir, options) => {
checkPath(dir)
return fs.mkdirSync(dir, {
mode: getMode(options),
recursive: true
})
}

View file

@ -0,0 +1,21 @@
// Adapted from https://github.com/sindresorhus/make-dir
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'
const path = require('path')
// https://github.com/nodejs/node/issues/8987
// https://github.com/libuv/libuv/pull/1088
module.exports.checkPath = function checkPath (pth) {
if (process.platform === 'win32') {
const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path.parse(pth).root, ''))
if (pathHasInvalidWinCharacters) {
const error = new Error(`Path contains invalid characters: ${pth}`)
error.code = 'EINVAL'
throw error
}
}
}

View file

@ -0,0 +1,20 @@
const fs = require('graceful-fs')
const path = require('path')
const { CROSS_DEVICE_PATH } = process.env
let runCrossDeviceTests = !!CROSS_DEVICE_PATH
if (runCrossDeviceTests) {
// make sure we have permission on device
try {
fs.writeFileSync(path.join(CROSS_DEVICE_PATH, 'file'), 'hi')
} catch {
runCrossDeviceTests = false
throw new Error(`Can't write to device ${CROSS_DEVICE_PATH}`)
}
} else console.log('Skipping cross-device move tests')
module.exports = {
differentDevice: CROSS_DEVICE_PATH,
ifCrossDeviceEnabled: (fn) => runCrossDeviceTests ? fn : fn.skip
}

View file

@ -0,0 +1,87 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
/* global beforeEach, afterEach, describe, it */
describe('+ move() - case insensitive paths', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-case-insensitive-paths')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
describe('> when src is a directory', () => {
it('should move successfully', done => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
dest = path.join(TEST_DIR, 'srcDir')
fs.move(src, dest, err => {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
done()
})
})
})
describe('> when src is a file', () => {
it('should move successfully', done => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
dest = path.join(TEST_DIR, 'srcFile')
fs.move(src, dest, err => {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
done()
})
})
})
describe('> when src is a symlink', () => {
it('should move successfully, symlink dir', done => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
dest = path.join(TEST_DIR, 'src-Symlink')
fs.move(srcLink, dest, err => {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
done()
})
})
it('should move successfully, symlink file', done => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'file')
dest = path.join(TEST_DIR, 'src-Symlink')
fs.move(srcLink, dest, err => {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
done()
})
})
})
})

View file

@ -0,0 +1,79 @@
'use strict'
const fs = require('../../')
const os = require('os')
const path = require('path')
const utimesSync = require('../../util/utimes').utimesMillisSync
const assert = require('assert')
const fse = require('../../index')
const { differentDevice, ifCrossDeviceEnabled } = require('./cross-device-utils')
/* global beforeEach, afterEach, describe, it */
if (process.arch === 'ia32') console.warn('32 bit arch; skipping move timestamp tests')
const describeIfPractical = process.arch === 'ia32' ? describe.skip : describe
describeIfPractical('move() - across different devices', () => {
let TEST_DIR, SRC, DEST, FILES
function setupFixture (readonly) {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync-preserve-timestamp')
SRC = path.join(differentDevice, 'some/weird/dir-really-weird')
DEST = path.join(TEST_DIR, 'dest')
FILES = ['a-file', path.join('a-folder', 'another-file'), path.join('a-folder', 'another-folder', 'file3')]
const timestamp = Date.now() / 1000 - 5
FILES.forEach(f => {
const filePath = path.join(SRC, f)
fs.ensureFileSync(filePath)
// rewind timestamps to make sure that coarser OS timestamp resolution
// does not alter results
utimesSync(filePath, timestamp, timestamp)
if (readonly) {
fs.chmodSync(filePath, 0o444)
}
})
}
afterEach(() => {
fse.removeSync(TEST_DIR)
fse.removeSync(SRC)
})
ifCrossDeviceEnabled(describe)('> default behaviour', () => {
;[
{ subcase: 'writable', readonly: false },
{ subcase: 'readonly', readonly: true }
].forEach(params => {
describe(`>> with ${params.subcase} source files`, () => {
beforeEach(() => setupFixture(params.readonly))
it('should have the same timestamps after move', done => {
const originalTimestamps = FILES.map(file => {
const originalPath = path.join(SRC, file)
const originalStat = fs.statSync(originalPath)
return {
mtime: originalStat.mtime.getTime(),
atime: originalStat.atime.getTime()
}
})
fse.move(SRC, DEST, {}, (err) => {
if (err) return done(err)
FILES.forEach(testFile({}, originalTimestamps))
done()
})
})
})
})
})
function testFile (options, originalTimestamps) {
return function (file, idx) {
const originalTimestamp = originalTimestamps[idx]
const currentPath = path.join(DEST, file)
const currentStats = fs.statSync(currentPath)
assert.strictEqual(currentStats.mtime.getTime(), originalTimestamp.mtime, 'different mtime values')
assert.strictEqual(currentStats.atime.getTime(), originalTimestamp.atime, 'different atime values')
}
}
})

View file

@ -0,0 +1,252 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
const klawSync = require('klaw-sync')
/* global beforeEach, afterEach, describe, it */
describe('+ move() - prevent moving identical files and dirs', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-prevent-moving-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_move')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_move')
fs.ensureFileSync(fileSrc)
fs.move(fileSrc, fileDest, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
done()
})
})
describe('dest with parent symlink', () => {
describe('first parent is symlink', () => {
it('should error when src is file', done => {
const src = path.join(TEST_DIR, 'a', 'file.txt')
const dest = path.join(TEST_DIR, 'b', 'file.txt')
const srcParent = path.join(TEST_DIR, 'a')
const destParent = path.join(TEST_DIR, 'b')
fs.ensureFileSync(src)
fs.ensureSymlinkSync(srcParent, destParent, 'dir')
fs.move(src, dest, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
assert(fs.existsSync(src))
done()
})
})
it('should error when src is directory', done => {
const src = path.join(TEST_DIR, 'a', 'foo')
const dest = path.join(TEST_DIR, 'b', 'foo')
const srcParent = path.join(TEST_DIR, 'a')
const destParent = path.join(TEST_DIR, 'b')
fs.ensureDirSync(src)
fs.ensureSymlinkSync(srcParent, destParent, 'dir')
fs.move(src, dest, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
assert(fs.existsSync(src))
done()
})
})
})
describe('nested dest', () => {
it('should error when src is file', done => {
const src = path.join(TEST_DIR, 'a', 'dir', 'file.txt')
const dest = path.join(TEST_DIR, 'b', 'dir', 'file.txt')
const srcParent = path.join(TEST_DIR, 'a')
const destParent = path.join(TEST_DIR, 'b')
fs.ensureFileSync(src)
fs.ensureSymlinkSync(srcParent, destParent, 'dir')
fs.move(src, dest, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
assert(fs.existsSync(src))
done()
})
})
it('should error when src is directory', done => {
const src = path.join(TEST_DIR, 'a', 'dir', 'foo')
const dest = path.join(TEST_DIR, 'b', 'dir', 'foo')
const srcParent = path.join(TEST_DIR, 'a')
const destParent = path.join(TEST_DIR, 'b')
fs.ensureDirSync(src)
fs.ensureSymlinkSync(srcParent, destParent, 'dir')
fs.move(src, dest, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
assert(fs.existsSync(src))
done()
})
})
})
})
// src is directory:
// src is regular, dest is symlink
// src is symlink, dest is regular
// src is symlink, dest is symlink
describe('> when src is a directory', () => {
describe('>> when src is regular and dest is a symlink that points to src', () => {
it('should error if dereference is true', 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.move(src, destLink, { dereference: true }, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
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 throw error', 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.move(srcLink, dest, err => {
assert.ok(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 error src and dest are the same and dereference is true', 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.move(srcLink, destLink, { dereference: true }, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
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 src is a file', () => {
describe('>> when src is regular and dest is a symlink that points to src', () => {
it('should error if dereference is true', done => {
src = path.join(TEST_DIR, 'src.txt')
fs.outputFileSync(src, 'some data')
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'file')
fs.move(src, destLink, { dereference: true }, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
done()
})
})
})
describe('>> when src is a symlink that points to a regular dest', () => {
it('should throw error if dereferene is true', done => {
dest = path.join(TEST_DIR, 'dest', 'somefile.txt')
fs.outputFileSync(dest, 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(dest, srcLink, 'file')
fs.move(srcLink, dest, { dereference: true }, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
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 error src and dest are the same and dereferene is true', done => {
src = path.join(TEST_DIR, 'src', 'srcfile.txt')
fs.outputFileSync(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.move(srcLink, destLink, { dereference: true }, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
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()
})
})
})
})
})

View file

@ -0,0 +1,362 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
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'
describe('+ move() - prevent moving into itself', () => {
let TEST_DIR, src
beforeEach(() => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-prevent-moving-into-itself')
src = path.join(TEST_DIR, 'src')
fs.mkdirpSync(src)
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)
})
afterEach(() => fs.removeSync(TEST_DIR))
describe('> when source is a file', () => {
it('should move the file successfully even if dest parent 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.move(srcFile, destFile, err => {
assert.ifError(err)
assert(fs.existsSync(destFile))
const out = fs.readFileSync(destFile, 'utf8')
assert.strictEqual(out, dat0, 'file contents matched')
done()
})
})
it("should move the file successfully even when dest parent is 'src/dest'", done => {
const destFile = path.join(TEST_DIR, 'src', 'dest', 'destfile.txt')
return testSuccessFile(src, destFile, done)
})
it("should move the file successfully when dest parent is 'src/src_dest'", done => {
const destFile = path.join(TEST_DIR, 'src', 'src_dest', 'destfile.txt')
return testSuccessFile(src, destFile, done)
})
it("should move the file successfully when dest parent is 'src/dest_src'", done => {
const destFile = path.join(TEST_DIR, 'src', 'dest_src', 'destfile.txt')
return testSuccessFile(src, destFile, done)
})
it("should move the file successfully when dest parent is 'src/dest/src'", done => {
const destFile = path.join(TEST_DIR, 'src', 'dest', 'src', 'destfile.txt')
return testSuccessFile(src, destFile, done)
})
it("should move the file successfully when dest parent is 'srcsrc/dest'", done => {
const destFile = path.join(TEST_DIR, 'srcsrc', 'dest', 'destfile.txt')
return testSuccessFile(src, destFile, done)
})
})
describe('> when source is a directory', () => {
describe('>> when dest is a directory', () => {
it('of not itself', done => {
const dest = path.join(TEST_DIR, src.replace(/^\w:/, ''))
return testSuccessDir(src, dest, done)
})
it('of itself', done => {
const dest = path.join(src, 'dest')
return testError(src, dest, done)
})
it("should move the directory successfully when dest is 'src_dest'", done => {
const dest = path.join(TEST_DIR, 'src_dest')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'src-dest'", done => {
const dest = path.join(TEST_DIR, 'src-dest')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'dest_src'", done => {
const dest = path.join(TEST_DIR, 'dest_src')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'src_dest/src'", done => {
const dest = path.join(TEST_DIR, 'src_dest', 'src')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'src-dest/src'", done => {
const dest = path.join(TEST_DIR, 'src-dest', 'src')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'dest_src/src'", done => {
const dest = path.join(TEST_DIR, 'dest_src', 'src')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'src_src/dest'", done => {
const dest = path.join(TEST_DIR, 'src_src', 'dest')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'src-src/dest'", done => {
const dest = path.join(TEST_DIR, 'src-src', 'dest')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'srcsrc/dest'", done => {
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
return testSuccessDir(src, dest, done)
})
it("should move the directory successfully when dest is 'dest/src'", done => {
const dest = path.join(TEST_DIR, 'dest', 'src')
return testSuccessDir(src, dest, done)
})
it('should move the directory successfully when dest is very nested that all its parents need to be created', done => {
const 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 testSuccessDir(src, dest, done)
})
it("should error when dest is 'src/dest'", done => {
const dest = path.join(TEST_DIR, 'src', 'dest')
return testError(src, dest, done)
})
it("should error when dest is 'src/src_dest'", done => {
const dest = path.join(TEST_DIR, 'src', 'src_dest')
return testError(src, dest, done)
})
it("should error when dest is 'src/dest_src'", done => {
const dest = path.join(TEST_DIR, 'src', 'dest_src')
return testError(src, dest, done)
})
it("should error when dest is 'src/dest/src'", done => {
const dest = path.join(TEST_DIR, 'src', 'dest', 'src')
return testError(src, dest, done)
})
})
describe('>> when dest is a symlink', () => {
it('should error when dest is a subdirectory of src (bind-mounted directory with subdirectory)', done => {
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(src).length
assert(srclenBefore > 2)
const dest = path.join(destLink, 'dir1')
assert(fs.existsSync(dest))
fs.move(src, dest, err => {
assert.strictEqual(err.message, `Cannot move '${src}' to a subdirectory of itself, '${dest}'.`)
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 error when dest is a subdirectory of src (more than one level depth)', done => {
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(src).length
assert(srclenBefore > 2)
const dest = path.join(destLink, 'dir1', 'dir2')
assert(fs.existsSync(dest))
fs.move(src, dest, err => {
assert.strictEqual(err.message, `Cannot move '${src}' to a subdirectory of itself, '${path.join(destLink, 'dir1')}'.`)
const srclenAfter = klawSync(src).length
assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change')
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
done()
})
})
})
})
describe('> when source is a symlink', () => {
describe('>> when dest is a directory', () => {
it('should error when resolved src path points to dest', done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src')
fs.move(srcLink, dest, err => {
assert(err)
// assert source not affected
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, src)
done()
})
})
it('should error when dest is a subdir of resolved src path', done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest')
fs.mkdirsSync(dest)
fs.move(srcLink, dest, err => {
assert(err)
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, src)
done()
})
})
it('should error when resolved src path is a subdir of dest', done => {
const 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')
fs.move(srcLink, dest, err => {
assert(err)
done()
})
})
it("should move the directory successfully when dest is 'src_src/dest'", done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src_src', 'dest')
testSuccessDir(srcLink, dest, () => {
const link = fs.readlinkSync(dest)
assert.strictEqual(link, src)
done()
})
})
it("should move the directory successfully when dest is 'srcsrc/dest'", done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
testSuccessDir(srcLink, dest, () => {
const link = fs.readlinkSync(dest)
assert.strictEqual(link, src)
done()
})
})
})
describe('>> when dest is a symlink', () => {
it('should error when resolved dest path is exactly the same as resolved src path and dereferene is true', 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.move(srcLink, destLink, { dereference: true }, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
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()
})
})
})
})
})
function testSuccessFile (src, destFile, done) {
const srcFile = path.join(src, FILES[0])
fs.move(srcFile, destFile, err => {
assert.ifError(err)
const f0 = fs.readFileSync(destFile, 'utf8')
assert.strictEqual(f0, dat0, 'file contents matched')
assert(!fs.existsSync(srcFile))
return done()
})
}
function testSuccessDir (src, dest, done) {
const srclen = klawSync(src).length
assert(srclen > 2) // assert src has contents
fs.move(src, dest, err => {
assert.ifError(err)
const destlen = klawSync(dest).length
assert.strictEqual(destlen, srclen, 'src and dest length should be equal')
const f0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8')
const f1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8')
const f2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8')
const f3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8')
assert.strictEqual(f0, dat0, 'file contents matched')
assert.strictEqual(f1, dat1, 'file contents matched')
assert.strictEqual(f2, dat2, 'file contents matched')
assert.strictEqual(f3, dat3, 'file contents matched')
assert(!fs.existsSync(src))
return done()
})
}
function testError (src, dest, done) {
fs.move(src, dest, err => {
assert(err)
assert.strictEqual(err.message, `Cannot move '${src}' to a subdirectory of itself, '${dest}'.`)
assert(fs.existsSync(src))
assert(!fs.existsSync(dest))
return done()
})
}

View file

@ -0,0 +1,75 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
/* global beforeEach, afterEach, describe, it */
describe('+ moveSync() - case insensitive paths', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync-case-insensitive-paths')
fs.emptyDir(TEST_DIR, done)
})
afterEach(() => fs.removeSync(TEST_DIR))
describe('> when src is a directory', () => {
it('should move successfully', () => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
dest = path.join(TEST_DIR, 'srcDir')
fs.moveSync(src, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
})
})
describe('> when src is a file', () => {
it('should move successfully', () => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
dest = path.join(TEST_DIR, 'srcFile')
fs.moveSync(src, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
})
})
describe('> when src is a symlink', () => {
it('should move successfully, symlink dir', () => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
dest = path.join(TEST_DIR, 'src-Symlink')
fs.moveSync(srcLink, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
})
it('should move successfully, symlink file', () => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'file')
dest = path.join(TEST_DIR, 'src-Symlink')
fs.moveSync(srcLink, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
})
})
})

View file

@ -0,0 +1,77 @@
'use strict'
const fs = require('../../')
const os = require('os')
const path = require('path')
const utimesSync = require('../../util/utimes').utimesMillisSync
const assert = require('assert')
const fse = require('../../index')
const { differentDevice, ifCrossDeviceEnabled } = require('./cross-device-utils')
/* global beforeEach, afterEach, describe, it */
if (process.arch === 'ia32') console.warn('32 bit arch; skipping move timestamp tests')
const describeIfPractical = process.arch === 'ia32' ? describe.skip : describe
describeIfPractical('moveSync() - across different devices', () => {
let TEST_DIR, SRC, DEST, FILES
function setupFixture (readonly) {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync-preserve-timestamp')
SRC = path.join(differentDevice, 'some/weird/dir-really-weird')
DEST = path.join(TEST_DIR, 'dest')
FILES = ['a-file', path.join('a-folder', 'another-file'), path.join('a-folder', 'another-folder', 'file3')]
const timestamp = Date.now() / 1000 - 5
FILES.forEach(f => {
const filePath = path.join(SRC, f)
fs.ensureFileSync(filePath)
// rewind timestamps to make sure that coarser OS timestamp resolution
// does not alter results
utimesSync(filePath, timestamp, timestamp)
if (readonly) {
fs.chmodSync(filePath, 0o444)
}
})
}
afterEach(() => {
fse.removeSync(TEST_DIR)
fse.removeSync(SRC)
})
ifCrossDeviceEnabled(describe)('> default behaviour', () => {
;[
{ subcase: 'writable', readonly: false },
{ subcase: 'readonly', readonly: true }
].forEach(params => {
describe(`>> with ${params.subcase} source files`, () => {
beforeEach(() => setupFixture(params.readonly))
it('should have the same timestamps after move', () => {
const originalTimestamps = FILES.map(file => {
const originalPath = path.join(SRC, file)
const originalStat = fs.statSync(originalPath)
return {
mtime: originalStat.mtime.getTime(),
atime: originalStat.atime.getTime()
}
})
fse.moveSync(SRC, DEST, {})
FILES.forEach(testFile({}, originalTimestamps))
})
})
})
})
function testFile (options, originalTimestamps) {
return function (file, idx) {
const originalTimestamp = originalTimestamps[idx]
const currentPath = path.join(DEST, file)
const currentStats = fs.statSync(currentPath)
// Windows sub-second precision fixed: https://github.com/nodejs/io.js/issues/2069
assert.strictEqual(currentStats.mtime.getTime(), originalTimestamp.mtime, 'different mtime values')
assert.strictEqual(currentStats.atime.getTime(), originalTimestamp.atime, 'different atime values')
}
}
})

View file

@ -0,0 +1,271 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
const klawSync = require('klaw-sync')
/* global beforeEach, afterEach, describe, it */
describe('+ moveSync() - prevent moving identical files and dirs', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync-prevent-moving-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', () => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_move_sync')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_move_sync')
fs.ensureFileSync(fileSrc)
try {
fs.moveSync(fileSrc, fileDest)
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
})
describe('dest with parent symlink', () => {
describe('first parent is symlink', () => {
it('should error when src is file', () => {
const src = path.join(TEST_DIR, 'a', 'file.txt')
const dest = path.join(TEST_DIR, 'b', 'file.txt')
const srcParent = path.join(TEST_DIR, 'a')
const destParent = path.join(TEST_DIR, 'b')
fs.ensureFileSync(src)
fs.ensureSymlinkSync(srcParent, destParent, 'dir')
try {
fs.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
} finally {
assert(fs.existsSync(src))
}
})
it('should error when src is directory', () => {
const src = path.join(TEST_DIR, 'a', 'foo')
const dest = path.join(TEST_DIR, 'b', 'foo')
const srcParent = path.join(TEST_DIR, 'a')
const destParent = path.join(TEST_DIR, 'b')
fs.ensureDirSync(src)
fs.ensureSymlinkSync(srcParent, destParent, 'dir')
try {
fs.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
} finally {
assert(fs.existsSync(src))
}
})
})
describe('nested dest', () => {
it('should error when src is file', () => {
const src = path.join(TEST_DIR, 'a', 'dir', 'file.txt')
const dest = path.join(TEST_DIR, 'b', 'dir', 'file.txt')
const srcParent = path.join(TEST_DIR, 'a')
const destParent = path.join(TEST_DIR, 'b')
fs.ensureFileSync(src)
fs.ensureSymlinkSync(srcParent, destParent, 'dir')
try {
fs.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
} finally {
assert(fs.existsSync(src))
}
})
it('should error when src is directory', () => {
const src = path.join(TEST_DIR, 'a', 'dir', 'foo')
const dest = path.join(TEST_DIR, 'b', 'dir', 'foo')
const srcParent = path.join(TEST_DIR, 'a')
const destParent = path.join(TEST_DIR, 'b')
fs.ensureDirSync(src)
fs.ensureSymlinkSync(srcParent, destParent, 'dir')
try {
fs.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
} finally {
assert(fs.existsSync(src))
}
})
})
})
// src is directory:
// src is regular, dest is symlink
// src is symlink, dest is regular
// src is symlink, dest is symlink
describe('> when src is a directory', () => {
describe('>> when src is regular and dest is a symlink that points to src', () => {
it('should error if dereferene is true', () => {
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
try {
fs.moveSync(src, destLink, { dereference: true })
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
const newlen = klawSync(src).length
assert.strictEqual(newlen, oldlen)
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
})
})
describe('>> when src is a symlink that points to a regular dest', () => {
it('should throw error', () => {
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
try {
fs.moveSync(srcLink, dest)
} catch (err) {
assert(err)
}
// assert nothing copied
const newlen = klawSync(dest).length
assert.strictEqual(newlen, oldlen)
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, dest)
})
})
describe('>> when src and dest are symlinks that point to the exact same path', () => {
it('should error if src and dest are the same and dereference is true', () => {
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
try {
fs.moveSync(srcLink, destLink, { dereference: true })
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
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)
})
})
})
// src is file:
// src is regular, dest is symlink
// src is symlink, dest is regular
// src is symlink, dest is symlink
describe('> when src is a file', () => {
describe('>> when src is regular and dest is a symlink that points to src', () => {
it('should error if dereference is true', () => {
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')
try {
fs.moveSync(src, destLink, { dereference: true })
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
assert(fs.readFileSync(link, 'utf8'), 'some data')
})
})
describe('>> when src is a symlink that points to a regular dest', () => {
it('should throw error', () => {
dest = path.join(TEST_DIR, 'dest', 'somefile.txt')
fs.outputFileSync(dest, 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(dest, srcLink, 'file')
try {
fs.moveSync(srcLink, dest)
} catch (err) {
assert.ok(err)
}
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, dest)
assert(fs.readFileSync(link, 'utf8'), 'some data')
})
})
describe('>> when src and dest are symlinks that point to the exact same path', () => {
it('should error if src and dest are the same and dereference is true', () => {
src = path.join(TEST_DIR, 'src', 'srcfile.txt')
fs.outputFileSync(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')
try {
fs.moveSync(srcLink, destLink, { dereference: true })
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
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')
})
})
})
})

View file

@ -0,0 +1,400 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
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'
describe('+ moveSync() - prevent moving into itself', () => {
let TEST_DIR, src
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync-prevent-moving-into-itself')
src = path.join(TEST_DIR, 'src')
fs.mkdirpSync(src)
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()
})
afterEach(done => fs.remove(TEST_DIR, done))
describe('> when source is a file', () => {
it('should move the file successfully even if dest parent is a subdir of src', () => {
const srcFile = path.join(TEST_DIR, 'src', 'srcfile.txt')
const destFile = path.join(TEST_DIR, 'src', 'dest', 'destfile.txt')
fs.writeFileSync(srcFile, dat0)
fs.moveSync(srcFile, destFile)
assert(fs.existsSync(destFile))
const out = fs.readFileSync(destFile, 'utf8')
assert.strictEqual(out, dat0, 'file contents matched')
})
})
describe('> when source is a file', () => {
it("should move the file successfully even when dest parent is 'src/dest'", () => {
const destFile = path.join(TEST_DIR, 'src', 'dest', 'destfile.txt')
return testSuccessFile(src, destFile)
})
it("should move the file successfully when dest parent is 'src/src_dest'", () => {
const destFile = path.join(TEST_DIR, 'src', 'src_dest', 'destfile.txt')
return testSuccessFile(src, destFile)
})
it("should move the file successfully when dest parent is 'src/dest_src'", () => {
const destFile = path.join(TEST_DIR, 'src', 'dest_src', 'destfile.txt')
return testSuccessFile(src, destFile)
})
it("should move the file successfully when dest parent is 'src/dest/src'", () => {
const destFile = path.join(TEST_DIR, 'src', 'dest', 'src', 'destfile.txt')
return testSuccessFile(src, destFile)
})
it("should move the file successfully when dest parent is 'srcsrc/dest'", () => {
const destFile = path.join(TEST_DIR, 'srcsrc', 'dest', 'destfile.txt')
return testSuccessFile(src, destFile)
})
})
describe('> when source is a directory', () => {
describe('>> when dest is a directory', () => {
it('of not itself', () => {
const dest = path.join(TEST_DIR, src.replace(/^\w:/, ''))
return testSuccessDir(src, dest)
})
it('of itself', () => {
const dest = path.join(src, 'dest')
return testError(src, dest)
})
it("should move the directory successfully when dest is 'src_dest'", () => {
const dest = path.join(TEST_DIR, 'src_dest')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'src-dest'", () => {
const dest = path.join(TEST_DIR, 'src-dest')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'dest_src'", () => {
const dest = path.join(TEST_DIR, 'dest_src')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'src_dest/src'", () => {
const dest = path.join(TEST_DIR, 'src_dest', 'src')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'src-dest/src'", () => {
const dest = path.join(TEST_DIR, 'src-dest', 'src')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'dest_src/src'", () => {
const dest = path.join(TEST_DIR, 'dest_src', 'src')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'src_src/dest'", () => {
const dest = path.join(TEST_DIR, 'src_src', 'dest')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'src-src/dest'", () => {
const dest = path.join(TEST_DIR, 'src-src', 'dest')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'srcsrc/dest'", () => {
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
return testSuccessDir(src, dest)
})
it("should move the directory successfully when dest is 'dest/src'", () => {
const dest = path.join(TEST_DIR, 'dest', 'src')
return testSuccessDir(src, dest)
})
it('should move the directory successfully when dest is very nested that all its parents need to be created', () => {
const 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 testSuccessDir(src, dest)
})
it("should error when dest is 'src/dest'", () => {
const dest = path.join(TEST_DIR, 'src', 'dest')
return testError(src, dest)
})
it("should error when dest is 'src/src_dest'", () => {
const dest = path.join(TEST_DIR, 'src', 'src_dest')
return testError(src, dest)
})
it("should error when dest is 'src/dest_src'", () => {
const dest = path.join(TEST_DIR, 'src', 'dest_src')
return testError(src, dest)
})
it("should error when dest is 'src/dest/src'", () => {
const dest = path.join(TEST_DIR, 'src', 'dest', 'src')
return testError(src, dest)
})
})
describe('>> when dest is a symlink', () => {
it('should error when dest points exactly to src and dereference is true', () => {
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(src).length
assert(srclenBefore > 2)
let errThrown = false
try {
fs.moveSync(src, destLink, { dereference: true })
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
} finally {
assert(errThrown)
const srclenAfter = klawSync(src).length
assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change')
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
}
})
it('should error when dest is a subdirectory of src (bind-mounted directory with subdirectory)', () => {
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(src).length
assert(srclenBefore > 2)
const dest = path.join(destLink, 'dir1')
assert(fs.existsSync(dest))
let errThrown = false
try {
fs.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, `Cannot move '${src}' to a subdirectory of itself, '${dest}'.`)
errThrown = true
} finally {
assert(errThrown)
const srclenAfter = klawSync(src).length
assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change')
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
}
})
it('should error when dest is a subdirectory of src (more than one level depth)', () => {
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(src).length
assert(srclenBefore > 2)
const dest = path.join(destLink, 'dir1', 'dir2')
assert(fs.existsSync(dest))
let errThrown = false
try {
fs.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, `Cannot move '${src}' to a subdirectory of itself, '${path.join(destLink, 'dir1')}'.`)
errThrown = true
} finally {
assert(errThrown)
const srclenAfter = klawSync(src).length
assert.strictEqual(srclenAfter, srclenBefore, 'src length should not change')
const link = fs.readlinkSync(destLink)
assert.strictEqual(link, src)
}
})
})
})
describe('> when source is a symlink', () => {
describe('>> when dest is a directory', () => {
it('should error when resolved src path points to dest', () => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src')
let errThrown = false
try {
fs.moveSync(srcLink, dest)
} catch (err) {
assert(err)
errThrown = true
} finally {
assert(errThrown)
// assert source not affected
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, src)
}
})
it('should error when dest is a subdir of resolved src path', () => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest')
fs.mkdirsSync(dest)
let errThrown = false
try {
fs.moveSync(srcLink, dest)
} catch (err) {
assert(err)
errThrown = true
} finally {
assert(errThrown)
const link = fs.readlinkSync(srcLink)
assert.strictEqual(link, src)
}
})
it('should error when resolved src path is a subdir of dest', () => {
const 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')
let errThrown = false
try {
fs.moveSync(srcLink, dest)
} catch (err) {
assert(err)
errThrown = true
} finally {
assert(errThrown)
}
})
it("should move the directory successfully when dest is 'src_src/dest'", () => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src_src', 'dest')
testSuccessDir(srcLink, dest)
const link = fs.readlinkSync(dest)
assert.strictEqual(link, src)
})
it("should move the directory successfully when dest is 'srcsrc/dest'", () => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
testSuccessDir(srcLink, dest)
const link = fs.readlinkSync(dest)
assert.strictEqual(link, src)
})
})
describe('>> when dest is a symlink', () => {
it('should error when resolved dest path is exactly the same as resolved src path and dereferene is true', () => {
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)
let errThrown = false
try {
fs.moveSync(srcLink, destLink, { dereference: true })
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
} finally {
assert(errThrown)
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)
}
})
})
})
})
function testSuccessFile (src, destFile) {
const srcFile = path.join(src, FILES[0])
fs.moveSync(srcFile, destFile)
const f0 = fs.readFileSync(destFile, 'utf8')
assert.strictEqual(f0, dat0, 'file contents matched')
assert(!fs.existsSync(srcFile))
}
function testSuccessDir (src, dest) {
const srclen = klawSync(src).length
assert(srclen > 2) // assert src has contents
fs.moveSync(src, dest)
const destlen = klawSync(dest).length
assert.strictEqual(destlen, srclen, 'src and dest length should be equal')
const f0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8')
const f1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8')
const f2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8')
const f3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8')
assert.strictEqual(f0, dat0, 'file contents matched')
assert.strictEqual(f1, dat1, 'file contents matched')
assert.strictEqual(f2, dat2, 'file contents matched')
assert.strictEqual(f3, dat3, 'file contents matched')
assert(!fs.existsSync(src))
}
function testError (src, dest) {
let errThrown = false
try {
fs.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, `Cannot move '${src}' to a subdirectory of itself, '${dest}'.`)
assert(fs.existsSync(src))
assert(!fs.existsSync(dest))
errThrown = true
} finally {
assert(errThrown)
}
}

View file

@ -0,0 +1,308 @@
'use strict'
// TODO: enable this once graceful-fs supports bigint option.
// const fs = require('graceful-fs')
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
const { differentDevice, ifCrossDeviceEnabled } = require('./cross-device-utils')
/* global afterEach, beforeEach, describe, it */
const describeIfWindows = process.platform === 'win32' ? describe : describe.skip
function createSyncErrFn (errCode) {
const fn = function () {
const err = new Error()
err.code = errCode
throw err
}
return fn
}
const originalRenameSync = fs.renameSync
function setUpMockFs (errCode) {
fs.renameSync = createSyncErrFn(errCode)
}
function tearDownMockFs () {
fs.renameSync = originalRenameSync
}
describe('moveSync()', () => {
let TEST_DIR
beforeEach(() => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync')
fse.emptyDirSync(TEST_DIR)
// Create fixtures
fse.outputFileSync(path.join(TEST_DIR, 'a-file'), 'sonic the hedgehog\n')
fse.outputFileSync(path.join(TEST_DIR, 'a-folder/another-file'), 'tails\n')
fse.outputFileSync(path.join(TEST_DIR, 'a-folder/another-folder/file3'), 'knuckles\n')
})
afterEach(() => fse.removeSync(TEST_DIR))
it('should not move if src and dest are the same', () => {
const src = `${TEST_DIR}/a-file`
const dest = `${TEST_DIR}/a-file`
let errThrown = false
try {
fse.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
} finally {
assert(errThrown)
}
// assert src not affected
const contents = fs.readFileSync(src, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert(contents.match(expected))
})
it('should error if src and dest are the same and src does not exist', () => {
const src = `${TEST_DIR}/non-existent`
const dest = src
assert.throws(() => fse.moveSync(src, dest))
})
it('should rename a file on the same device', () => {
const src = `${TEST_DIR}/a-file`
const dest = `${TEST_DIR}/a-file-dest`
fse.moveSync(src, dest)
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert(contents.match(expected))
})
it('should not overwrite the destination by default', () => {
const src = `${TEST_DIR}/a-file`
const dest = `${TEST_DIR}/a-folder/another-file`
// verify file exists already
assert(fs.existsSync(dest))
try {
fse.moveSync(src, dest)
} catch (err) {
assert.strictEqual(err.message, 'dest already exists.')
}
})
it('should not overwrite if overwrite = false', () => {
const src = `${TEST_DIR}/a-file`
const dest = `${TEST_DIR}/a-folder/another-file`
// verify file exists already
assert(fs.existsSync(dest))
try {
fse.moveSync(src, dest, { overwrite: false })
} catch (err) {
assert.strictEqual(err.message, 'dest already exists.')
}
})
it('should overwrite file if overwrite = true', () => {
const src = `${TEST_DIR}/a-file`
const dest = `${TEST_DIR}/a-folder/another-file`
// verify file exists already
assert(fs.existsSync(dest))
fse.moveSync(src, dest, { overwrite: true })
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert.ok(contents.match(expected))
})
it('should overwrite the destination directory if overwrite = true', () => {
// 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 pathsBefore = fs.readdirSync(dest)
assert(pathsBefore.indexOf('another-file') >= 0)
assert(pathsBefore.indexOf('another-folder') >= 0)
fse.moveSync(src, dest, { overwrite: true })
// verify dest does not have old stuff
const pathsAfter = fs.readdirSync(dest)
assert.strictEqual(pathsAfter.indexOf('another-file'), -1)
assert.strictEqual(pathsAfter.indexOf('another-folder'), -1)
// verify dest has new stuff
assert(pathsAfter.indexOf('some-file') >= 0)
assert(pathsAfter.indexOf('some-folder') >= 0)
})
it('should create directory structure by default', () => {
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.moveSync(src, dest)
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert(contents.match(expected))
})
it('should work across devices', () => {
const src = `${TEST_DIR}/a-file`
const dest = `${TEST_DIR}/a-file-dest`
setUpMockFs('EXDEV')
fse.moveSync(src, dest)
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert(contents.match(expected))
tearDownMockFs()
})
it('should move folders', () => {
const src = `${TEST_DIR}/a-folder`
const dest = `${TEST_DIR}/a-folder-dest`
// verify it doesn't exist
assert(!fs.existsSync(dest))
fse.moveSync(src, dest)
const contents = fs.readFileSync(dest + '/another-file', 'utf8')
const expected = /^tails\r?\n$/
assert(contents.match(expected))
})
it('should overwrite folders across devices', () => {
const src = `${TEST_DIR}/a-folder`
const dest = `${TEST_DIR}/a-folder-dest`
fs.mkdirSync(dest)
setUpMockFs('EXDEV')
fse.moveSync(src, dest, { overwrite: true })
const contents = fs.readFileSync(dest + '/another-folder/file3', 'utf8')
const expected = /^knuckles\r?\n$/
assert(contents.match(expected))
tearDownMockFs()
})
it('should move folders across devices with EXDEV error', () => {
const src = `${TEST_DIR}/a-folder`
const dest = `${TEST_DIR}/a-folder-dest`
setUpMockFs('EXDEV')
fse.moveSync(src, dest)
const contents = fs.readFileSync(dest + '/another-folder/file3', 'utf8')
const expected = /^knuckles\r?\n$/
assert(contents.match(expected))
tearDownMockFs()
})
describe('clobber', () => {
it('should be an alias for overwrite', () => {
const src = `${TEST_DIR}/a-file`
const dest = `${TEST_DIR}/a-folder/another-file`
// verify file exists already
assert(fs.existsSync(dest))
fse.moveSync(src, dest, { clobber: true })
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert(contents.match(expected))
})
})
describe('> when trying to move a folder into itself', () => {
it('should produce an error', () => {
const SRC_DIR = path.join(TEST_DIR, 'src')
const DEST_DIR = path.join(TEST_DIR, 'src', 'dest')
assert(!fs.existsSync(SRC_DIR))
fs.mkdirSync(SRC_DIR)
assert(fs.existsSync(SRC_DIR))
try {
fse.moveSync(SRC_DIR, DEST_DIR)
} catch (err) {
assert(err.message, `Cannot move ${SRC_DIR} into itself ${DEST_DIR}.`)
assert(fs.existsSync(SRC_DIR))
assert(!fs.existsSync(DEST_DIR))
}
})
})
describe('> when trying to move a file into its parent subdirectory', () => {
it('should move successfully', () => {
const src = `${TEST_DIR}/a-file`
const dest = `${TEST_DIR}/dest/a-file-dest`
fse.moveSync(src, dest)
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert(contents.match(expected))
})
})
describeIfWindows('> when dest parent is root', () => {
let dest
afterEach(() => fse.removeSync(dest))
it('should not create parent directory', () => {
const src = path.join(TEST_DIR, 'a-file')
dest = path.join(path.parse(TEST_DIR).root, 'another-file')
fse.moveSync(src, dest)
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert(contents.match(expected))
})
})
ifCrossDeviceEnabled(describe)('> when actually trying to move a folder across devices', () => {
describe('> just the folder', () => {
it('should move the folder', () => {
const src = path.join(differentDevice, '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.moveSync(src, dest)
assert(fs.existsSync(dest))
assert(fs.lstatSync(dest).isDirectory())
})
})
})
})

View file

@ -0,0 +1,398 @@
'use strict'
const fs = require('../../fs')
const os = require('os')
const fse = require('../../')
const path = require('path')
const assert = require('assert')
const { differentDevice, ifCrossDeviceEnabled } = require('./cross-device-utils')
/* global afterEach, beforeEach, describe, it */
const describeIfWindows = process.platform === 'win32' ? describe : describe.skip
function createAsyncErrFn (errCode) {
async function fn () {
fn.callCount++
const err = new Error()
err.code = errCode
return Promise.reject(err)
}
fn.callCount = 0
return fn
}
const originalRename = fs.rename
function setUpMockFs (errCode) {
fs.rename = createAsyncErrFn(errCode)
}
function tearDownMockFs () {
fs.rename = originalRename
}
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 => fse.remove(TEST_DIR, done))
describe('> when overwrite = true', () => {
it('should overwrite file', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(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))
done()
})
})
})
it('should overwrite the destination directory', done => {
// 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 overwrite folders across devices', done => {
const src = path.join(TEST_DIR, 'a-folder')
const dest = path.join(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(path.join(dest, 'another-folder', 'file3'), 'utf8', (err, contents) => {
const expected = /^knuckles\r?\n$/
assert.ifError(err)
assert.ok(contents.match(expected))
tearDownMockFs()
done()
})
})
})
})
describe('> when overwrite = false', () => {
it('should rename a file on the same device', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(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))
done()
})
})
})
it('should support promises', async () => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(TEST_DIR, 'a-file-dest')
await fse.move(src, dest)
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert.ok(contents.match(expected))
})
it('should not move a file if source and destination are the same', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = src
fse.move(src, dest, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
done()
})
})
it('should error if source and destination are the same and source does not exist', done => {
const src = path.join(TEST_DIR, 'non-existent')
const dest = src
fse.move(src, dest, err => {
assert(err)
done()
})
})
it('should not move a directory if source and destination are the same', done => {
const src = path.join(TEST_DIR, 'a-folder')
const dest = src
fse.move(src, dest, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
done()
})
})
it('should not overwrite the destination by default', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(TEST_DIR, 'a-folder', 'another-file')
// verify file exists already
assert(fs.existsSync(dest))
fse.move(src, dest, err => {
assert.strictEqual(err.message, 'dest already exists.')
done()
})
})
it('should not overwrite if overwrite = false', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(TEST_DIR, 'a-folder', 'another-file')
// verify file exists already
assert(fs.existsSync(dest))
fse.move(src, dest, { overwrite: false }, err => {
assert.strictEqual(err.message, 'dest already exists.')
done()
})
})
it('should create directory structure by default', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(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))
done()
})
})
})
it('should work across devices', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(TEST_DIR, 'a-file-dest')
setUpMockFs('EXDEV')
fse.move(src, dest, err => {
assert.ifError(err)
assert.strictEqual(fs.rename.callCount, 1)
fs.readFile(dest, 'utf8', (err, contents) => {
const expected = /^sonic the hedgehog\r?\n$/
assert.ifError(err)
assert.ok(contents.match(expected))
tearDownMockFs()
done()
})
})
})
it('should move folders', done => {
const src = path.join(TEST_DIR, 'a-folder')
const dest = path.join(TEST_DIR, 'a-folder-dest')
// verify it doesn't exist
assert(!fs.existsSync(dest))
fse.move(src, dest, err => {
assert.ifError(err)
fs.readFile(path.join(dest, 'another-file'), 'utf8', (err, contents) => {
const expected = /^tails\r?\n$/
assert.ifError(err)
assert.ok(contents.match(expected))
done()
})
})
})
it('should move folders across devices with EXDEV error', done => {
const src = path.join(TEST_DIR, 'a-folder')
const dest = path.join(TEST_DIR, 'a-folder-dest')
setUpMockFs('EXDEV')
fse.move(src, dest, err => {
assert.ifError(err)
assert.strictEqual(fs.rename.callCount, 1)
fs.readFile(path.join(dest, 'another-folder', 'file3'), 'utf8', (err, contents) => {
const expected = /^knuckles\r?\n$/
assert.ifError(err)
assert.ok(contents.match(expected))
tearDownMockFs()
done()
})
})
})
})
describe('> when opts is explicit undefined', () => {
it('works with callbacks', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(TEST_DIR, 'a-file-dest')
fse.move(src, dest, undefined, err => {
assert.ifError(err)
fs.readFile(dest, 'utf8', (err, contents) => {
const expected = /^sonic the hedgehog\r?\n$/
assert.ifError(err)
assert.ok(contents.match(expected))
done()
})
})
})
it('works with promises', async () => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(TEST_DIR, 'a-file-dest')
await fse.move(src, dest, undefined)
const contents = fs.readFileSync(dest, 'utf8')
const expected = /^sonic the hedgehog\r?\n$/
assert.ok(contents.match(expected))
})
})
describeIfWindows('> when dest parent is root', () => {
let dest
afterEach(done => fse.remove(dest, done))
it('should not create parent directory', done => {
const src = path.join(TEST_DIR, 'a-file')
dest = path.join(path.parse(TEST_DIR).root, 'another-file')
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))
done()
})
})
})
})
describe('> clobber', () => {
it('should be an alias for overwrite', done => {
const src = path.join(TEST_DIR, 'a-file')
const dest = path.join(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))
done()
})
})
})
})
describe('> 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.strictEqual(err.message, `Cannot move '${SRC_DIR}' to a subdirectory of itself, '${DEST_DIR}'.`)
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
ifCrossDeviceEnabled(describe)('> when actually trying to move a folder across devices', () => {
describe('>> just the folder', () => {
it('should move the folder', done => {
const src = path.join(differentDevice, '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))
assert(fs.lstatSync(dest).isDirectory())
done()
})
})
})
})
})

View file

@ -0,0 +1,7 @@
'use strict'
const u = require('universalify').fromPromise
module.exports = {
move: u(require('./move')),
moveSync: require('./move-sync')
}

View file

@ -0,0 +1,55 @@
'use strict'
const fs = require('graceful-fs')
const path = require('path')
const copySync = require('../copy').copySync
const removeSync = require('../remove').removeSync
const mkdirpSync = require('../mkdirs').mkdirpSync
const stat = require('../util/stat')
function moveSync (src, dest, opts) {
opts = opts || {}
const overwrite = opts.overwrite || opts.clobber || false
const { srcStat, isChangingCase = false } = stat.checkPathsSync(src, dest, 'move', opts)
stat.checkParentPathsSync(src, srcStat, dest, 'move')
if (!isParentRoot(dest)) mkdirpSync(path.dirname(dest))
return doRename(src, dest, overwrite, isChangingCase)
}
function isParentRoot (dest) {
const parent = path.dirname(dest)
const parsedPath = path.parse(parent)
return parsedPath.root === parent
}
function doRename (src, dest, overwrite, isChangingCase) {
if (isChangingCase) return rename(src, dest, overwrite)
if (overwrite) {
removeSync(dest)
return rename(src, dest, overwrite)
}
if (fs.existsSync(dest)) throw new Error('dest already exists.')
return rename(src, dest, overwrite)
}
function rename (src, dest, overwrite) {
try {
fs.renameSync(src, dest)
} catch (err) {
if (err.code !== 'EXDEV') throw err
return moveAcrossDevice(src, dest, overwrite)
}
}
function moveAcrossDevice (src, dest, overwrite) {
const opts = {
overwrite,
errorOnExist: true,
preserveTimestamps: true
}
copySync(src, dest, opts)
return removeSync(src)
}
module.exports = moveSync

View file

@ -0,0 +1,59 @@
'use strict'
const fs = require('../fs')
const path = require('path')
const { copy } = require('../copy')
const { remove } = require('../remove')
const { mkdirp } = require('../mkdirs')
const { pathExists } = require('../path-exists')
const stat = require('../util/stat')
async function move (src, dest, opts = {}) {
const overwrite = opts.overwrite || opts.clobber || false
const { srcStat, isChangingCase = false } = await stat.checkPaths(src, dest, 'move', opts)
await stat.checkParentPaths(src, srcStat, dest, 'move')
// If the parent of dest is not root, make sure it exists before proceeding
const destParent = path.dirname(dest)
const parsedParentPath = path.parse(destParent)
if (parsedParentPath.root !== destParent) {
await mkdirp(destParent)
}
return doRename(src, dest, overwrite, isChangingCase)
}
async function doRename (src, dest, overwrite, isChangingCase) {
if (!isChangingCase) {
if (overwrite) {
await remove(dest)
} else if (await pathExists(dest)) {
throw new Error('dest already exists.')
}
}
try {
// Try w/ rename first, and try copy + remove if EXDEV
await fs.rename(src, dest)
} catch (err) {
if (err.code !== 'EXDEV') {
throw err
}
await moveAcrossDevice(src, dest, overwrite)
}
}
async function moveAcrossDevice (src, dest, overwrite) {
const opts = {
overwrite,
errorOnExist: true,
preserveTimestamps: true
}
await copy(src, dest, opts)
return remove(src)
}
module.exports = move

View file

@ -0,0 +1,75 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('output', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'output')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('+ outputFile', () => {
describe('> when the file and directory does not exist', () => {
it('should create the file', done => {
const file = path.join(TEST_DIR, Math.random() + 't-ne', Math.random() + '.txt')
assert(!fs.existsSync(file))
fse.outputFile(file, 'hi jp', err => {
assert.ifError(err)
assert(fs.existsSync(file))
assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hi jp')
done()
})
})
it('should support promises', () => {
const file = path.join(TEST_DIR, Math.random() + 't-ne', Math.random() + '.txt')
assert(!fs.existsSync(file))
return fse.outputFile(file, 'hi jp')
})
})
describe('> when the file does exist', () => {
it('should still modify the file', done => {
const file = path.join(TEST_DIR, Math.random() + 't-e', Math.random() + '.txt')
fse.mkdirsSync(path.dirname(file))
fs.writeFileSync(file, 'hello world')
fse.outputFile(file, 'hello jp', err => {
if (err) return done(err)
assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello jp')
done()
})
})
})
})
describe('+ outputFileSync', () => {
describe('> when the file and directory does not exist', () => {
it('should create the file', () => {
const file = path.join(TEST_DIR, Math.random() + 'ts-ne', Math.random() + '.txt')
assert(!fs.existsSync(file))
fse.outputFileSync(file, 'hello man')
assert(fs.existsSync(file))
assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello man')
})
})
describe('> when the file does exist', () => {
it('should still modify the file', () => {
const file = path.join(TEST_DIR, Math.random() + 'ts-e', Math.random() + '.txt')
fse.mkdirsSync(path.dirname(file))
fs.writeFileSync(file, 'hello world')
fse.outputFileSync(file, 'hello man')
assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello man')
})
})
})
})

View file

@ -0,0 +1,31 @@
'use strict'
const u = require('universalify').fromPromise
const fs = require('../fs')
const path = require('path')
const mkdir = require('../mkdirs')
const pathExists = require('../path-exists').pathExists
async function outputFile (file, data, encoding = 'utf-8') {
const dir = path.dirname(file)
if (!(await pathExists(dir))) {
await mkdir.mkdirs(dir)
}
return fs.writeFile(file, data, encoding)
}
function outputFileSync (file, ...args) {
const dir = path.dirname(file)
if (!fs.existsSync(dir)) {
mkdir.mkdirsSync(dir)
}
fs.writeFileSync(file, ...args)
}
module.exports = {
outputFile: u(outputFile),
outputFileSync
}

View file

@ -0,0 +1,28 @@
'use strict'
/* eslint-env mocha */
const fs = require('../..')
const path = require('path')
const os = require('os')
const assert = require('assert')
describe('pathExists()', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'path-exists')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
it('should return false if file does not exist', () => {
assert(!fs.pathExistsSync(path.join(TEST_DIR, 'somefile')))
})
it('should return true if file does exist', () => {
const file = path.join(TEST_DIR, 'exists')
fs.ensureFileSync(file)
assert(fs.pathExistsSync(file))
})
})

View file

@ -0,0 +1,40 @@
'use strict'
/* eslint-env mocha */
const fs = require('../..')
const path = require('path')
const os = require('os')
const assert = require('assert')
describe('pathExists()', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'path-exists')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
it('should return false if file does not exist', () => {
return fs.pathExists(path.join(TEST_DIR, 'somefile'))
.then(exists => assert(!exists))
})
it('should return true if file does exist', () => {
const file = path.join(TEST_DIR, 'exists')
fs.ensureFileSync(file)
return fs.pathExists(file)
.then(exists => assert(exists))
})
it('should pass an empty error parameter to the callback', done => {
const file = path.join(TEST_DIR, 'exists')
fs.ensureFileSync(file)
fs.pathExists(file, (err, exists) => {
assert.ifError(err)
assert(exists)
done()
})
})
})

View file

@ -0,0 +1,12 @@
'use strict'
const u = require('universalify').fromPromise
const fs = require('../fs')
function pathExists (path) {
return fs.access(path).then(() => true).catch(() => false)
}
module.exports = {
pathExists: u(pathExists),
pathExistsSync: fs.existsSync
}

View file

@ -0,0 +1,29 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global beforeEach, describe, it */
describe('remove / async / dir', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'remove-async-dir')
fse.emptyDir(TEST_DIR, done)
})
describe('> when dir does not exist', () => {
it('should not throw an error', done => {
const someDir = path.join(TEST_DIR, 'some-dir/')
assert.strictEqual(fs.existsSync(someDir), false)
fse.remove(someDir, err => {
assert.ifError(err)
done()
})
})
})
})

View file

@ -0,0 +1 @@
// todo

View file

@ -0,0 +1,33 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global beforeEach, describe, it */
describe('remove/sync', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'remove-sync')
fse.emptyDir(TEST_DIR, done)
})
describe('+ removeSync()', () => {
it('should delete directories and files synchronously', () => {
assert(fs.existsSync(TEST_DIR))
fs.writeFileSync(path.join(TEST_DIR, 'somefile'), 'somedata')
fse.removeSync(TEST_DIR)
assert(!fs.existsSync(TEST_DIR))
})
it('should delete an empty directory synchronously', () => {
assert(fs.existsSync(TEST_DIR))
fse.removeSync(TEST_DIR)
assert(!fs.existsSync(TEST_DIR))
})
})
})

View file

@ -0,0 +1,28 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global beforeEach, describe, it */
describe('remove/sync', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'remove-sync')
fse.emptyDir(TEST_DIR, done)
})
describe('+ removeSync()', () => {
it('should delete a file synchronously', () => {
const file = path.join(TEST_DIR, 'file')
fs.writeFileSync(file, 'hello')
assert(fs.existsSync(file))
fse.removeSync(file)
assert(!fs.existsSync(file))
})
})
})

View file

@ -0,0 +1,124 @@
'use strict'
const assert = require('assert')
const fs = require('fs')
const os = require('os')
const path = require('path')
const randomBytes = require('crypto').randomBytes
const fse = require('../..')
/* global afterEach, beforeEach, describe, it */
let TEST_DIR
function buildFixtureDir () {
const buf = randomBytes(5)
const baseDir = path.join(TEST_DIR, `TEST_fs-extra_remove-${Date.now()}`)
fs.mkdirSync(baseDir)
fs.writeFileSync(path.join(baseDir, Math.random() + ''), buf)
fs.writeFileSync(path.join(baseDir, Math.random() + ''), buf)
const subDir = path.join(TEST_DIR, Math.random() + '')
fs.mkdirSync(subDir)
fs.writeFileSync(path.join(subDir, Math.random() + ''), buf)
return baseDir
}
describe('remove', () => {
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'remove')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('+ remove()', () => {
it('should delete an empty directory', done => {
assert(fs.existsSync(TEST_DIR))
fse.remove(TEST_DIR, err => {
assert.ifError(err)
assert(!fs.existsSync(TEST_DIR))
done()
})
})
it('should delete a directory full of directories and files', done => {
buildFixtureDir()
assert(fs.existsSync(TEST_DIR))
fse.remove(TEST_DIR, err => {
assert.ifError(err)
assert(!fs.existsSync(TEST_DIR))
done()
})
})
it('should delete a file', done => {
const file = path.join(TEST_DIR, 'file')
fs.writeFileSync(file, 'hello')
assert(fs.existsSync(file))
fse.remove(file, err => {
assert.ifError(err)
assert(!fs.existsSync(file))
done()
})
})
it('should delete without a callback', done => {
const file = path.join(TEST_DIR, 'file')
fs.writeFileSync(file, 'hello')
assert(fs.existsSync(file))
let existsChecker = setInterval(() => {
fse.pathExists(file, (err, itDoes) => {
assert.ifError(err)
if (!itDoes && existsChecker) {
clearInterval(existsChecker)
existsChecker = null
done()
}
})
}, 25)
fse.remove(file)
})
it('shouldn’t delete glob matches', function (done) {
const file = path.join(TEST_DIR, 'file?')
try {
fs.writeFileSync(file, 'hello')
} catch (ex) {
if (ex.code === 'ENOENT') return this.skip('Windows does not support filenames with ‘?’ or ‘*’ in them.')
throw ex
}
const wrongFile = path.join(TEST_DIR, 'file1')
fs.writeFileSync(wrongFile, 'yo')
assert(fs.existsSync(file))
assert(fs.existsSync(wrongFile))
fse.remove(file, err => {
assert.ifError(err)
assert(!fs.existsSync(file))
assert(fs.existsSync(wrongFile))
done()
})
})
it('shouldn’t delete glob matches when file doesn’t exist', done => {
const nonexistentFile = path.join(TEST_DIR, 'file?')
const wrongFile = path.join(TEST_DIR, 'file1')
fs.writeFileSync(wrongFile, 'yo')
assert(!fs.existsSync(nonexistentFile))
assert(fs.existsSync(wrongFile))
fse.remove(nonexistentFile, err => {
assert.ifError(err)
assert(!fs.existsSync(nonexistentFile))
assert(fs.existsSync(wrongFile))
done()
})
})
})
})

View file

@ -0,0 +1,17 @@
'use strict'
const fs = require('graceful-fs')
const u = require('universalify').fromCallback
function remove (path, callback) {
fs.rm(path, { recursive: true, force: true }, callback)
}
function removeSync (path) {
fs.rmSync(path, { recursive: true, force: true })
}
module.exports = {
remove: u(remove),
removeSync
}

View file

@ -0,0 +1,66 @@
'use strict'
const fs = require('../..')
const os = require('os')
const path = require('path')
const assert = require('assert')
const stat = require('../stat.js')
/* global beforeEach, afterEach, describe, it */
describe('util/stat', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'util-stat')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
describe('should use stats with bigint type', () => {
it('stat.checkPaths()', () => {
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'dest')
fs.ensureFileSync(src)
fs.ensureFileSync(dest)
stat.checkPaths(src, dest, 'copy', {}, (err, stats) => {
assert.ifError(err)
assert.strictEqual(typeof stats.srcStat.ino, 'bigint')
})
})
it('stat.checkPathsSync()', () => {
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'dest')
fs.ensureFileSync(src)
fs.ensureFileSync(dest)
const { srcStat } = stat.checkPathsSync(src, dest, 'copy', {})
assert.strictEqual(typeof srcStat.ino, 'bigint')
})
})
describe('should stop at src or root path and not throw max call stack size error', () => {
it('stat.checkParentPaths()', () => {
const src = path.join(TEST_DIR, 'src')
let dest = path.join(TEST_DIR, 'dest')
fs.ensureFileSync(src)
fs.ensureFileSync(dest)
dest = path.basename(dest)
const srcStat = fs.statSync(src)
stat.checkParentPaths(src, srcStat, dest, 'copy', err => {
assert.ifError(err)
})
})
it('stat.checkParentPathsSync()', () => {
const src = path.join(TEST_DIR, 'src')
let dest = path.join(TEST_DIR, 'dest')
fs.ensureFileSync(src)
fs.ensureFileSync(dest)
dest = path.basename(dest)
const srcStat = fs.statSync(src)
stat.checkParentPathsSync(src, srcStat, dest, 'copy')
})
})
})

View file

@ -0,0 +1,100 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
const proxyquire = require('proxyquire')
const u = require('universalify').fromCallback
let gracefulFsStub
let utimes
/* global beforeEach, describe, it */
// HFS, ext{2,3}, FAT do not
function hasMillisResSync () {
let tmpfile = path.join('millis-test-sync' + Date.now().toString() + Math.random().toString().slice(2))
tmpfile = path.join(os.tmpdir(), tmpfile)
// 550 millis past UNIX epoch
const d = new Date(1435410243862)
fs.writeFileSync(tmpfile, 'https://github.com/jprichardson/node-fs-extra/pull/141')
const fd = fs.openSync(tmpfile, 'r+')
fs.futimesSync(fd, d, d)
fs.closeSync(fd)
return fs.statSync(tmpfile).mtime > 1435410243000
}
describe('utimes', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'utimes')
fse.emptyDir(TEST_DIR, done)
// reset stubs
gracefulFsStub = {}
utimes = proxyquire('../utimes', { '../fs': gracefulFsStub })
})
describe('utimesMillis()', () => {
// see discussion https://github.com/jprichardson/node-fs-extra/pull/141
it('should set the utimes w/ millisecond precision', done => {
const tmpFile = path.join(TEST_DIR, 'someFile')
fs.writeFileSync(tmpFile, 'hello')
let stats = fs.lstatSync(tmpFile)
// Apr 21st, 2012
const awhileAgo = new Date(1334990868773)
const awhileAgoNoMillis = new Date(1334990868000)
assert.notDeepStrictEqual(stats.mtime, awhileAgo)
assert.notDeepStrictEqual(stats.atime, awhileAgo)
utimes.utimesMillis(tmpFile, awhileAgo, awhileAgo, err => {
assert.ifError(err)
stats = fs.statSync(tmpFile)
if (hasMillisResSync()) {
assert.deepStrictEqual(stats.mtime, awhileAgo)
assert.deepStrictEqual(stats.atime, awhileAgo)
} else {
assert.deepStrictEqual(stats.mtime, awhileAgoNoMillis)
assert.deepStrictEqual(stats.atime, awhileAgoNoMillis)
}
done()
})
})
it('should close open file desciptors after encountering an error', done => {
const fakeFd = Math.random()
gracefulFsStub.open = u((pathIgnored, flagsIgnored, modeIgnored, callback) => {
if (typeof modeIgnored === 'function') callback = modeIgnored
process.nextTick(() => callback(null, fakeFd))
})
let closeCalled = false
gracefulFsStub.close = u((fd, callback) => {
assert.strictEqual(fd, fakeFd)
closeCalled = true
if (callback) process.nextTick(callback)
})
let testError
gracefulFsStub.futimes = u((fd, atimeIgnored, mtimeIgnored, callback) => {
process.nextTick(() => {
testError = new Error('A test error')
callback(testError)
})
})
utimes.utimesMillis('ignored', 'ignored', 'ignored', err => {
assert.strictEqual(err, testError)
assert(closeCalled)
done()
})
})
})
})

View file

@ -0,0 +1,158 @@
'use strict'
const fs = require('../fs')
const path = require('path')
const u = require('universalify').fromPromise
function getStats (src, dest, opts) {
const statFunc = opts.dereference
? (file) => fs.stat(file, { bigint: true })
: (file) => fs.lstat(file, { bigint: true })
return Promise.all([
statFunc(src),
statFunc(dest).catch(err => {
if (err.code === 'ENOENT') return null
throw err
})
]).then(([srcStat, destStat]) => ({ srcStat, destStat }))
}
function getStatsSync (src, dest, opts) {
let destStat
const statFunc = opts.dereference
? (file) => fs.statSync(file, { bigint: true })
: (file) => fs.lstatSync(file, { bigint: true })
const srcStat = statFunc(src)
try {
destStat = statFunc(dest)
} catch (err) {
if (err.code === 'ENOENT') return { srcStat, destStat: null }
throw err
}
return { srcStat, destStat }
}
async function checkPaths (src, dest, funcName, opts) {
const { srcStat, destStat } = await getStats(src, dest, opts)
if (destStat) {
if (areIdentical(srcStat, destStat)) {
const srcBaseName = path.basename(src)
const destBaseName = path.basename(dest)
if (funcName === 'move' &&
srcBaseName !== destBaseName &&
srcBaseName.toLowerCase() === destBaseName.toLowerCase()) {
return { srcStat, destStat, isChangingCase: true }
}
throw new Error('Source and destination must not be the same.')
}
if (srcStat.isDirectory() && !destStat.isDirectory()) {
throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
}
if (!srcStat.isDirectory() && destStat.isDirectory()) {
throw new Error(`Cannot overwrite directory '${dest}' with non-directory '${src}'.`)
}
}
if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
throw new Error(errMsg(src, dest, funcName))
}
return { srcStat, destStat }
}
function checkPathsSync (src, dest, funcName, opts) {
const { srcStat, destStat } = getStatsSync(src, dest, opts)
if (destStat) {
if (areIdentical(srcStat, destStat)) {
const srcBaseName = path.basename(src)
const destBaseName = path.basename(dest)
if (funcName === 'move' &&
srcBaseName !== destBaseName &&
srcBaseName.toLowerCase() === destBaseName.toLowerCase()) {
return { srcStat, destStat, isChangingCase: true }
}
throw new Error('Source and destination must not be the same.')
}
if (srcStat.isDirectory() && !destStat.isDirectory()) {
throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
}
if (!srcStat.isDirectory() && destStat.isDirectory()) {
throw new Error(`Cannot overwrite directory '${dest}' with non-directory '${src}'.`)
}
}
if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
throw new Error(errMsg(src, dest, funcName))
}
return { srcStat, destStat }
}
// recursively check if dest parent is a subdirectory of src.
// It works for all file types including symlinks since it
// checks the src and dest inodes. It starts from the deepest
// parent and stops once it reaches the src parent or the root path.
async function checkParentPaths (src, srcStat, dest, funcName) {
const srcParent = path.resolve(path.dirname(src))
const destParent = path.resolve(path.dirname(dest))
if (destParent === srcParent || destParent === path.parse(destParent).root) return
let destStat
try {
destStat = await fs.stat(destParent, { bigint: true })
} catch (err) {
if (err.code === 'ENOENT') return
throw err
}
if (areIdentical(srcStat, destStat)) {
throw new Error(errMsg(src, dest, funcName))
}
return checkParentPaths(src, srcStat, destParent, funcName)
}
function checkParentPathsSync (src, srcStat, dest, funcName) {
const srcParent = path.resolve(path.dirname(src))
const destParent = path.resolve(path.dirname(dest))
if (destParent === srcParent || destParent === path.parse(destParent).root) return
let destStat
try {
destStat = fs.statSync(destParent, { bigint: true })
} catch (err) {
if (err.code === 'ENOENT') return
throw err
}
if (areIdentical(srcStat, destStat)) {
throw new Error(errMsg(src, dest, funcName))
}
return checkParentPathsSync(src, srcStat, destParent, funcName)
}
function areIdentical (srcStat, destStat) {
return destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev
}
// return true if dest is a subdir of src, otherwise false.
// It only checks the path strings.
function isSrcSubdir (src, dest) {
const srcArr = path.resolve(src).split(path.sep).filter(i => i)
const destArr = path.resolve(dest).split(path.sep).filter(i => i)
return srcArr.every((cur, i) => destArr[i] === cur)
}
function errMsg (src, dest, funcName) {
return `Cannot ${funcName} '${src}' to a subdirectory of itself, '${dest}'.`
}
module.exports = {
// checkPaths
checkPaths: u(checkPaths),
checkPathsSync,
// checkParent
checkParentPaths: u(checkParentPaths),
checkParentPathsSync,
// Misc
isSrcSubdir,
areIdentical
}

View file

@ -0,0 +1,36 @@
'use strict'
const fs = require('../fs')
const u = require('universalify').fromPromise
async function utimesMillis (path, atime, mtime) {
// if (!HAS_MILLIS_RES) return fs.utimes(path, atime, mtime, callback)
const fd = await fs.open(path, 'r+')
let closeErr = null
try {
await fs.futimes(fd, atime, mtime)
} finally {
try {
await fs.close(fd)
} catch (e) {
closeErr = e
}
}
if (closeErr) {
throw closeErr
}
}
function utimesMillisSync (path, atime, mtime) {
const fd = fs.openSync(path, 'r+')
fs.futimesSync(fd, atime, mtime)
return fs.closeSync(fd)
}
module.exports = {
utimesMillis: u(utimesMillis),
utimesMillisSync
}