Add files via upload

This commit is contained in:
bakustarver 2024-07-01 20:44:23 +03:00 committed by GitHub
parent c62db468c2
commit fdf637ab4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 6431 additions and 0 deletions

View file

@ -0,0 +1,29 @@
'use strict'
/* eslint-env mocha */
const assert = require('assert')
const fse = require('..')
const methods = [
'emptyDir',
'ensureFile',
'ensureDir',
'mkdirs',
'readJson',
'readJSON',
'remove'
]
describe('promise support', () => {
methods.forEach(method => {
it(method, done => {
fse[method]().catch(() => done())
})
})
it('provides fse.promises API', () => {
assert.ok(fse.promises)
assert.strictEqual(typeof fse.promises.writeFile, 'function')
})
})

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')
const { copy } = require('../')
/* global afterEach, beforeEach, describe, it */
describe('copy / broken symlink', () => {
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-broken-symlink')
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'dest')
beforeEach(done => {
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
createFixtures(src, done)
})
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('when symlink is broken', () => {
it('should not throw error if dereference is false', done => {
copy(src, dest, err => {
assert.strictEqual(err, null)
done()
})
})
it('should throw error if dereference is true', done => {
copy(src, dest, { dereference: true }, err => {
assert.strictEqual(err.code, 'ENOENT')
done()
})
})
})
})
function createFixtures (srcDir, callback) {
fs.mkdir(srcDir, err => {
let brokenFile
let brokenFileLink
if (err) return callback(err)
try {
brokenFile = path.join(srcDir, 'does-not-exist')
brokenFileLink = path.join(srcDir, 'broken-symlink')
fs.writeFileSync(brokenFile, 'does not matter')
fs.symlinkSync(brokenFile, brokenFileLink, 'file')
} catch (err) {
callback(err)
}
// break the symlink now
fse.remove(brokenFile, callback)
})
}

View file

@ -0,0 +1,108 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
const platform = os.platform()
/* global beforeEach, afterEach, describe, it */
describe('+ copy() - case insensitive paths', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-case-insensitive-paths')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
describe('> when src is a directory', () => {
it('should behave correctly based on the OS', done => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
dest = path.join(TEST_DIR, 'srcDir')
fs.copy(src, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
done()
})
})
})
describe('> when src is a file', () => {
it('should behave correctly based on the OS', done => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
dest = path.join(TEST_DIR, 'srcFile')
fs.copy(src, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
done()
})
})
})
describe('> when src is a symlink', () => {
it('should behave correctly based on the OS, 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.copy(srcLink, dest, err => {
if (platform === 'linux') {
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)
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
done()
})
})
it('should behave correctly based on the OS, 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.copy(srcLink, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
done()
})
})
})
})

View file

@ -0,0 +1,36 @@
'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 */
let TEST_DIR = ''
describe('+ copy() - copy /dev/null', () => {
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'test', 'fs-extra', 'copy-dev-null')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('> when src is /dev/null', () => {
it('should copy successfully', done => {
// no /dev/null on windows
if (process.platform === 'win32') return done()
const tmpFile = path.join(TEST_DIR, 'foo')
fse.copy('/dev/null', tmpFile, err => {
assert.ifError(err)
const stats = fs.lstatSync(tmpFile)
assert.strictEqual(stats.size, 0)
done()
})
})
})
})

View file

@ -0,0 +1,56 @@
'use strict'
// relevant: https://github.com/jprichardson/node-fs-extra/issues/89
// come up with better file name
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('copy() - gh #89', () => {
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-gh-89')
beforeEach(done => {
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => {
fse.remove(TEST_DIR, done)
})
it('should copy successfully', done => {
const A = path.join(TEST_DIR, 'A')
const B = path.join(TEST_DIR, 'B')
fs.mkdirSync(A)
fs.mkdirSync(B)
const one = path.join(A, 'one.txt')
const two = path.join(A, 'two.txt')
const three = path.join(B, 'three.txt')
const four = path.join(B, 'four.txt')
fs.writeFileSync(one, '1')
fs.writeFileSync(two, '2')
fs.writeFileSync(three, '3')
fs.writeFileSync(four, '4')
const C = path.join(TEST_DIR, 'C')
fse.copy(A, C, err => {
if (err) return done(err)
fse.copy(B, C, err => {
if (err) return done(err)
assert(fs.existsSync(path.join(C, 'one.txt')))
assert(fs.existsSync(path.join(C, 'two.txt')))
assert(fs.existsSync(path.join(C, 'three.txt')))
assert(fs.existsSync(path.join(C, 'four.txt')))
done()
})
})
})
})

View file

@ -0,0 +1,106 @@
'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('copy', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy')
fse.emptyDir(TEST_DIR, done)
})
// pretty UNIX specific, may not pass on windows... only tested on Mac OS X 10.9
it('should maintain file permissions and ownership', done => {
if (process.platform === 'win32') return done()
// var userid = require('userid')
// http://man7.org/linux/man-pages/man2/stat.2.html
const S_IFREG = 0o100000 // regular file
const S_IFDIR = 0o40000 // directory
// these are Mac specific I think (at least staff), should find Linux equivalent
let gidWheel
let gidStaff
try {
gidWheel = process.getgid() // userid.gid('wheel')
} catch {
gidWheel = process.getgid()
}
try {
gidStaff = process.getgid() // userid.gid('staff')
} catch {
gidStaff = process.getgid()
}
const permDir = path.join(TEST_DIR, 'perms')
fs.mkdirSync(permDir)
const srcDir = path.join(permDir, 'src')
fs.mkdirSync(srcDir)
const f1 = path.join(srcDir, 'f1.txt')
fs.writeFileSync(f1, '')
fs.chmodSync(f1, 0o666)
fs.chownSync(f1, process.getuid(), gidWheel)
const f1stats = fs.lstatSync(f1)
assert.strictEqual(f1stats.mode - S_IFREG, 0o666)
const d1 = path.join(srcDir, 'somedir')
fs.mkdirSync(d1)
fs.chmodSync(d1, 0o777)
fs.chownSync(d1, process.getuid(), gidStaff)
const d1stats = fs.lstatSync(d1)
assert.strictEqual(d1stats.mode - S_IFDIR, 0o777)
const f2 = path.join(d1, 'f2.bin')
fs.writeFileSync(f2, '')
fs.chmodSync(f2, 0o777)
fs.chownSync(f2, process.getuid(), gidStaff)
const f2stats = fs.lstatSync(f2)
assert.strictEqual(f2stats.mode - S_IFREG, 0o777)
const d2 = path.join(srcDir, 'crazydir')
fs.mkdirSync(d2)
fs.chmodSync(d2, 0o444)
fs.chownSync(d2, process.getuid(), gidWheel)
const d2stats = fs.lstatSync(d2)
assert.strictEqual(d2stats.mode - S_IFDIR, 0o444)
const destDir = path.join(permDir, 'dest')
fse.copy(srcDir, destDir, err => {
assert.ifError(err)
const newf1stats = fs.lstatSync(path.join(permDir, 'dest/f1.txt'))
const newd1stats = fs.lstatSync(path.join(permDir, 'dest/somedir'))
const newf2stats = fs.lstatSync(path.join(permDir, 'dest/somedir/f2.bin'))
const newd2stats = fs.lstatSync(path.join(permDir, 'dest/crazydir'))
assert.strictEqual(newf1stats.mode, f1stats.mode)
assert.strictEqual(newd1stats.mode, d1stats.mode)
assert.strictEqual(newf2stats.mode, f2stats.mode)
assert.strictEqual(newd2stats.mode, d2stats.mode)
assert.strictEqual(newf1stats.gid, f1stats.gid)
assert.strictEqual(newd1stats.gid, d1stats.gid)
assert.strictEqual(newf2stats.gid, f2stats.gid)
assert.strictEqual(newd2stats.gid, d2stats.gid)
assert.strictEqual(newf1stats.uid, f1stats.uid)
assert.strictEqual(newd1stats.uid, d1stats.uid)
assert.strictEqual(newf2stats.uid, f2stats.uid)
assert.strictEqual(newd2stats.uid, d2stats.uid)
done()
})
})
})

View file

@ -0,0 +1,74 @@
'use strict'
const fs = require('../../')
const os = require('os')
const path = require('path')
const { copy } = require('../')
const utimesSync = require('../../util/utimes').utimesMillisSync
const assert = require('assert')
/* global beforeEach, afterEach, describe, it */
if (process.arch === 'ia32') console.warn('32 bit arch; skipping copy timestamp tests')
const describeIfPractical = process.arch === 'ia32' ? describe.skip : describe
describeIfPractical('copy() - preserve timestamp', () => {
let TEST_DIR, SRC, DEST, FILES
function setupFixture (readonly) {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-preserve-timestamp')
SRC = path.join(TEST_DIR, 'src')
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(done => fs.remove(TEST_DIR, done))
describe('> when preserveTimestamps option is true', () => {
;[
{ 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 on copy', done => {
copy(SRC, DEST, { preserveTimestamps: true }, (err) => {
if (err) return done(err)
FILES.forEach(testFile({ preserveTimestamps: true }))
done()
})
})
})
})
})
function testFile (options) {
return function (file) {
const a = path.join(SRC, file)
const b = path.join(DEST, file)
const fromStat = fs.statSync(a)
const toStat = fs.statSync(b)
if (options.preserveTimestamps) {
// Windows sub-second precision fixed: https://github.com/nodejs/io.js/issues/2069
assert.strictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime(), 'different mtime values')
assert.strictEqual(toStat.atime.getTime(), fromStat.atime.getTime(), 'different atime values')
} else {
// the access time might actually be the same, so check only modification time
assert.notStrictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime(), 'same mtime 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('+ copy() - prevent copying identical files and dirs', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-prevent-copying-identical')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
it('should return an error if src and dest are the same', done => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_copy')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
fs.ensureFileSync(fileSrc)
fs.copy(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.copy(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.copy(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.copy(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.copy(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.copy(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.copy(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.copy(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', 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.copy(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 dereference 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.copy(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.copy(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,397 @@
'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('+ copy() - prevent copying into itself', () => {
let TEST_DIR, src
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-prevent-copying-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 copy 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.copy(srcFile, destFile, err => {
assert.ifError(err)
assert(fs.existsSync(destFile, 'file copied'))
const out = fs.readFileSync(destFile, 'utf8')
assert.strictEqual(out, dat0, 'file contents matched')
done()
})
})
})
// test for these cases:
// - src is directory, dest is directory
// - src is directory, dest is symlink
// - src is symlink, dest is directory
// - src is symlink, dest is symlink
describe('> when source is a directory', () => {
describe('>> when dest is a directory', () => {
it('of not itself', done => {
const dest = path.join(TEST_DIR, src.replace(/^\w:/, ''))
return testSuccess(src, dest, done)
})
it('of itself', done => {
const dest = path.join(src, 'dest')
return testError(src, dest, done)
})
it("should copy the directory successfully when dest is 'src_dest'", done => {
const dest = path.join(TEST_DIR, 'src_dest')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'src-dest'", done => {
const dest = path.join(TEST_DIR, 'src-dest')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'dest_src'", done => {
const dest = path.join(TEST_DIR, 'dest_src')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'src_dest/src'", done => {
const dest = path.join(TEST_DIR, 'src_dest', 'src')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'src-dest/src'", done => {
const dest = path.join(TEST_DIR, 'src-dest', 'src')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'dest_src/src'", done => {
const dest = path.join(TEST_DIR, 'dest_src', 'src')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'src_src/dest'", done => {
const dest = path.join(TEST_DIR, 'src_src', 'dest')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'src-src/dest'", done => {
const dest = path.join(TEST_DIR, 'src-src', 'dest')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'srcsrc/dest'", done => {
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
return testSuccess(src, dest, done)
})
it("should copy the directory successfully when dest is 'dest/src'", done => {
const dest = path.join(TEST_DIR, 'dest', 'src')
return testSuccess(src, dest, done)
})
it('should copy the directory successfully when dest is very nested that all its parents need to be created', done => {
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 testSuccess(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 points exactly to src and dereference is true', done => {
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.symlinkSync(src, destLink, 'dir')
const srclenBefore = klawSync(src).length
assert(srclenBefore > 2)
fs.copy(src, destLink, { dereference: true }, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
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 (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.copy(src, dest, err => {
assert.strictEqual(err.message, `Cannot copy '${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.copy(src, dest, err => {
assert.strictEqual(err.message, `Cannot copy '${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()
})
})
it('should copy the directory successfully when src is a subdir of resolved dest path and dereference is true', done => {
const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src')
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.copySync(src, srcInDest) // put some stuff in srcInDest
const dest = path.join(TEST_DIR, 'dest')
fs.symlinkSync(dest, destLink, 'dir')
const srclen = klawSync(srcInDest).length
const destlenBefore = klawSync(dest).length
assert(srclen > 2)
fs.copy(srcInDest, destLink, { dereference: true }, err => {
assert.ifError(err)
const destlenAfter = klawSync(dest).length
// assert dest length is oldlen + length of stuff copied from src
assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth')
FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied'))
const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8')
const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8')
const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8')
const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8')
assert.strictEqual(o0, dat0, 'files contents matched')
assert.strictEqual(o1, dat1, 'files contents matched')
assert.strictEqual(o2, dat2, 'files contents matched')
assert.strictEqual(o3, dat3, 'files contents matched')
done()
})
})
})
})
describe('> when source is a symlink', () => {
describe('>> when dest is a directory', () => {
it('should 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.copy(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.copy(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.copy(srcLink, dest, err => {
assert(err)
done()
})
})
it("should copy the directory successfully when dest is 'src_src/dest'", done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'src_src', 'dest')
testSuccess(srcLink, dest, () => {
const link = fs.readlinkSync(dest)
assert.strictEqual(link, src)
done()
})
})
it("should copy the directory successfully when dest is 'srcsrc/dest'", done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
testSuccess(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 dereference 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.copy(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()
})
})
it('should error when resolved dest path is a subdir of resolved src path', done => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const destLink = path.join(TEST_DIR, 'dest-symlink')
const resolvedDestPath = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest')
fs.ensureFileSync(path.join(resolvedDestPath, 'subdir', 'somefile.txt'))
fs.symlinkSync(resolvedDestPath, destLink, 'dir')
fs.copy(srcLink, destLink, err => {
assert.strictEqual(err.message, `Cannot copy '${src}' to a subdirectory of itself, '${resolvedDestPath}'.`)
const destln = fs.readlinkSync(destLink)
assert.strictEqual(destln, resolvedDestPath)
done()
})
})
})
})
})
function testSuccess (src, dest, done) {
const srclen = klawSync(src).length
assert(srclen > 2)
fs.copy(src, dest, err => {
assert.ifError(err)
FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied'))
const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8')
const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8')
const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8')
const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8')
assert.strictEqual(o0, dat0, 'file contents matched')
assert.strictEqual(o1, dat1, 'file contents matched')
assert.strictEqual(o2, dat2, 'file contents matched')
assert.strictEqual(o3, dat3, 'file contents matched')
done()
})
}
function testError (src, dest, done) {
fs.copy(src, dest, err => {
assert.strictEqual(err.message, `Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`)
done()
})
}

View file

@ -0,0 +1,56 @@
'use strict'
// relevant: https://github.com/jprichardson/node-fs-extra/issues/599
const fs = require('../../')
const os = require('os')
const fse = require('../../')
const path = require('path')
const assert = require('assert')
const klawSync = require('klaw-sync')
/* global afterEach, beforeEach, describe, it */
let TEST_DIR = ''
const FILES = [
path.join('dir1', 'file1.txt'),
path.join('dir1', 'dir2', 'file2.txt'),
path.join('dir1', 'dir2', 'dir3', 'file3.txt')
]
describe('+ copy() - copy a readonly directory with content', () => {
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'test', 'fs-extra', 'copy-readonly-dir')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => {
klawSync(TEST_DIR).forEach(data => fs.chmodSync(data.path, 0o777))
fse.remove(TEST_DIR, done)
})
describe('> when src is readonly directory with content', () => {
it('should copy successfully', done => {
FILES.forEach(file => {
fs.outputFileSync(path.join(TEST_DIR, file), file)
})
const sourceDir = path.join(TEST_DIR, 'dir1')
const sourceHierarchy = klawSync(sourceDir)
sourceHierarchy.forEach(source => fs.chmodSync(source.path, source.stats.isDirectory() ? 0o555 : 0o444))
const targetDir = path.join(TEST_DIR, 'target')
fse.copy(sourceDir, targetDir, err => {
assert.ifError(err)
// Make sure copy was made and mode was preserved
assert(fs.existsSync(targetDir))
const targetHierarchy = klawSync(targetDir)
assert(targetHierarchy.length === sourceHierarchy.length)
targetHierarchy.forEach(target => assert(target.stats.mode === target.stats.isDirectory() ? 0o555 : 0o444))
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')
const copySync = require('../copy-sync')
/* global afterEach, beforeEach, describe, it */
describe('copy-sync / broken symlink', () => {
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-broken-symlink')
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'dest')
beforeEach(done => {
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
createFixtures(src, done)
})
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('when symlink is broken', () => {
it('should not throw error if dereference is false', () => {
let err = null
try {
copySync(src, dest)
} catch (e) {
err = e
}
assert.strictEqual(err, null)
})
it('should throw error if dereference is true', () => {
assert.throws(() => copySync(src, dest, { dereference: true }), err => err.code === 'ENOENT')
})
})
})
function createFixtures (srcDir, callback) {
fs.mkdir(srcDir, err => {
let brokenFile
let brokenFileLink
if (err) return callback(err)
try {
brokenFile = path.join(srcDir, 'does-not-exist')
brokenFileLink = path.join(srcDir, 'broken-symlink')
fs.writeFileSync(brokenFile, 'does not matter')
fs.symlinkSync(brokenFile, brokenFileLink, 'file')
} catch (err) {
callback(err)
}
// break the symlink now
fse.remove(brokenFile, callback)
})
}

View file

@ -0,0 +1,124 @@
'use strict'
const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
const platform = os.platform()
/* global beforeEach, afterEach, describe, it */
describe('+ copySync() - case insensitive paths', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-case-insensitive-paths')
fs.emptyDir(TEST_DIR, done)
})
afterEach(() => fs.removeSync(TEST_DIR))
describe('> when src is a directory', () => {
it('should behave correctly based on the OS', () => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
dest = path.join(TEST_DIR, 'srcDir')
let errThrown = false
try {
fs.copySync(src, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
assert(!errThrown)
}
})
})
describe('> when src is a file', () => {
it('should behave correctly based on the OS', () => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
dest = path.join(TEST_DIR, 'srcFile')
let errThrown = false
try {
fs.copySync(src, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
assert(!errThrown)
}
})
})
describe('> when src is a symlink', () => {
it('should behave correctly based on the OS, 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')
let errThrown = false
try {
fs.copySync(srcLink, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
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)
assert(!errThrown)
}
})
it('should behave correctly based on the OS, 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')
let errThrown = false
try {
fs.copySync(srcLink, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
assert(!errThrown)
}
})
})
})

View file

@ -0,0 +1,230 @@
'use strict'
const fs = require('../../')
const os = require('os')
const path = require('path')
const assert = require('assert')
const crypto = require('crypto')
/* global beforeEach, afterEach, describe, it */
describe('+ copySync() / dir', () => {
const SIZE = 16 * 64 * 1024 + 7
let TEST_DIR
let src, dest
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-dir')
src = path.join(TEST_DIR, 'src')
dest = path.join(TEST_DIR, 'dest')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
describe('> when src is a directory', () => {
describe('> when dest exists and is a file', () => {
it('should throw error', () => {
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'file.txt')
fs.mkdirSync(src)
fs.ensureFileSync(dest)
try {
fs.copySync(src, dest)
} catch (err) {
assert.strictEqual(err.message, `Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
}
})
})
it('should copy the directory synchronously', () => {
const FILES = 2
src = path.join(TEST_DIR, 'src')
dest = path.join(TEST_DIR, 'dest')
fs.mkdirSync(src)
for (let i = 0; i < FILES; ++i) {
fs.writeFileSync(path.join(src, i.toString()), crypto.randomBytes(SIZE))
}
const subdir = path.join(src, 'subdir')
fs.mkdirSync(subdir)
for (let i = 0; i < FILES; ++i) {
fs.writeFileSync(path.join(subdir, i.toString()), crypto.randomBytes(SIZE))
}
fs.copySync(src, dest)
assert(fs.existsSync(dest))
for (let i = 0; i < FILES; ++i) {
assert(fs.existsSync(path.join(dest, i.toString())))
}
const destSub = path.join(dest, 'subdir')
for (let j = 0; j < FILES; ++j) {
assert(fs.existsSync(path.join(destSub, j.toString())))
}
})
it('should preserve symbolic links', () => {
const srcTarget = path.join(TEST_DIR, 'destination')
fs.mkdirSync(src)
fs.mkdirSync(srcTarget)
fs.symlinkSync(srcTarget, path.join(src, 'symlink'), 'dir')
fs.copySync(src, dest)
const link = fs.readlinkSync(path.join(dest, 'symlink'))
assert.strictEqual(link, srcTarget)
})
describe('> when the destination dir does not exist', () => {
it('should create the destination directory and copy the file', () => {
const src = path.join(TEST_DIR, 'data/')
fs.mkdirSync(src)
const d1 = 'file1'
const d2 = 'file2'
fs.writeFileSync(path.join(src, 'f1.txt'), d1)
fs.writeFileSync(path.join(src, 'f2.txt'), d2)
const dest = path.join(TEST_DIR, 'this/path/does/not/exist/outputDir')
fs.copySync(src, dest)
const o1 = fs.readFileSync(path.join(dest, 'f1.txt'), 'utf8')
const o2 = fs.readFileSync(path.join(dest, 'f2.txt'), 'utf8')
assert.strictEqual(d1, o1)
assert.strictEqual(d2, o2)
})
})
})
describe('> when filter is used', () => {
it('should do nothing if filter fails', () => {
const srcDir = path.join(TEST_DIR, 'src')
const srcFile = path.join(srcDir, 'srcfile.css')
fs.outputFileSync(srcFile, 'src contents')
const destDir = path.join(TEST_DIR, 'dest')
const destFile = path.join(destDir, 'destfile.css')
const filter = s => path.extname(s) !== '.css' && !fs.statSync(s).isDirectory()
fs.copySync(srcFile, destFile, filter)
assert(!fs.existsSync(destDir))
})
it('should should apply filter recursively', () => {
const FILES = 2
// Don't match anything that ends with a digit higher than 0:
const filter = s => /(0|\D)$/i.test(s)
fs.mkdirSync(src)
for (let i = 0; i < FILES; ++i) {
fs.writeFileSync(path.join(src, i.toString()), crypto.randomBytes(SIZE))
}
const subdir = path.join(src, 'subdir')
fs.mkdirSync(subdir)
for (let i = 0; i < FILES; ++i) {
fs.writeFileSync(path.join(subdir, i.toString()), crypto.randomBytes(SIZE))
}
fs.copySync(src, dest, filter)
assert(fs.existsSync(dest))
assert(FILES > 1)
for (let i = 0; i < FILES; ++i) {
if (i === 0) {
assert(fs.existsSync(path.join(dest, i.toString())))
} else {
assert(!fs.existsSync(path.join(dest, i.toString())))
}
}
const destSub = path.join(dest, 'subdir')
for (let j = 0; j < FILES; ++j) {
if (j === 0) {
assert(fs.existsSync(path.join(destSub, j.toString())))
} else {
assert(!fs.existsSync(path.join(destSub, j.toString())))
}
}
})
it('should apply the filter to directory names', () => {
const IGNORE = 'ignore'
const filter = p => !~p.indexOf(IGNORE)
fs.mkdirSync(src)
const ignoreDir = path.join(src, IGNORE)
fs.mkdirSync(ignoreDir)
fs.writeFileSync(path.join(ignoreDir, 'file'), crypto.randomBytes(SIZE))
fs.copySync(src, dest, filter)
assert(!fs.existsSync(path.join(dest, IGNORE)), 'directory was not ignored')
assert(!fs.existsSync(path.join(dest, IGNORE, 'file')), 'file was not ignored')
})
it('should apply filter when it is applied only to dest', done => {
const timeCond = new Date().getTime()
const filter = (s, d) => fs.statSync(d).mtime.getTime() < timeCond
const dest = path.join(TEST_DIR, 'dest')
setTimeout(() => {
fs.outputFileSync(path.join(src, 'somefile.html'), 'some data')
fs.mkdirSync(dest)
fs.copySync(src, dest, filter)
assert(!fs.existsSync(path.join(dest, 'somefile.html')))
done()
}, 1000)
})
it('should apply filter when it is applied to both src and dest', done => {
const timeCond = new Date().getTime()
const filter = (s, d) => s.split('.').pop() !== 'css' && fs.statSync(path.dirname(d)).mtime.getTime() > timeCond
const dest = path.join(TEST_DIR, 'dest')
setTimeout(() => {
const srcFile1 = path.join(TEST_DIR, '1.html')
const srcFile2 = path.join(TEST_DIR, '2.css')
const srcFile3 = path.join(TEST_DIR, '3.jade')
fs.writeFileSync(srcFile1, '')
fs.writeFileSync(srcFile2, '')
fs.writeFileSync(srcFile3, '')
const destFile1 = path.join(dest, 'dest1.html')
const destFile2 = path.join(dest, 'dest2.css')
const destFile3 = path.join(dest, 'dest3.jade')
fs.mkdirSync(dest)
fs.copySync(srcFile1, destFile1, filter)
fs.copySync(srcFile2, destFile2, filter)
fs.copySync(srcFile3, destFile3, filter)
assert(fs.existsSync(destFile1))
assert(!fs.existsSync(destFile2))
assert(fs.existsSync(destFile3))
done()
}, 1000)
})
})
})

View file

@ -0,0 +1,257 @@
'use strict'
const fs = require('../../')
const os = require('os')
const path = require('path')
const assert = require('assert')
const crypto = require('crypto')
/* global afterEach, beforeEach, describe, it */
const SIZE = 16 * 64 * 1024 + 7
describe('+ copySync() / file', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-file')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
describe('> when src is a file', () => {
describe('> when dest exists and is a directory', () => {
it('should throw error', () => {
const src = path.join(TEST_DIR, 'file.txt')
const dest = path.join(TEST_DIR, 'dir')
fs.ensureFileSync(src)
fs.ensureDirSync(dest)
try {
fs.copySync(src, dest)
} catch (err) {
assert.strictEqual(err.message, `Cannot overwrite directory '${dest}' with non-directory '${src}'.`)
}
})
})
it('should copy the file synchronously', () => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))
const srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
let destMd5 = ''
fs.copySync(fileSrc, fileDest)
destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
assert.strictEqual(srcMd5, destMd5)
})
it('should follow symlinks', () => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
const linkSrc = path.join(TEST_DIR, 'TEST_fs-extra_copy_link')
fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))
const srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
let destMd5 = ''
fs.symlinkSync(fileSrc, linkSrc)
fs.copySync(linkSrc, fileDest)
destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
assert.strictEqual(srcMd5, destMd5)
})
it('should maintain file mode', () => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))
fs.chmodSync(fileSrc, 0o750)
fs.copySync(fileSrc, fileDest)
const statSrc = fs.statSync(fileSrc)
const statDest = fs.statSync(fileDest)
assert.strictEqual(statSrc.mode, statDest.mode)
})
it('should only copy files allowed by filter fn', () => {
const srcFile1 = path.join(TEST_DIR, '1.html')
const srcFile2 = path.join(TEST_DIR, '2.css')
const srcFile3 = path.join(TEST_DIR, '3.jade')
fs.writeFileSync(srcFile1, '')
fs.writeFileSync(srcFile2, '')
fs.writeFileSync(srcFile3, '')
const destFile1 = path.join(TEST_DIR, 'dest1.html')
const destFile2 = path.join(TEST_DIR, 'dest2.css')
const destFile3 = path.join(TEST_DIR, 'dest3.jade')
const filter = s => s.split('.').pop() !== 'css'
fs.copySync(srcFile1, destFile1, filter)
fs.copySync(srcFile2, destFile2, filter)
fs.copySync(srcFile3, destFile3, filter)
assert(fs.existsSync(destFile1))
assert(!fs.existsSync(destFile2))
assert(fs.existsSync(destFile3))
})
it('should not call filter fn more than needed', () => {
const src = path.join(TEST_DIR, 'foo')
fs.writeFileSync(src, '')
const dest = path.join(TEST_DIR, 'bar')
let filterCallCount = 0
const filter = () => {
filterCallCount++
return true
}
fs.copySync(src, dest, filter)
assert.strictEqual(filterCallCount, 1)
assert(fs.existsSync(dest))
})
describe('> when the destination dir does not exist', () => {
it('should create the destination directory and copy the file', () => {
const src = path.join(TEST_DIR, 'file.txt')
const dest = path.join(TEST_DIR, 'this/path/does/not/exist/copied.txt')
const data = 'did it copy?\n'
fs.writeFileSync(src, data, 'utf8')
fs.copySync(src, dest)
const data2 = fs.readFileSync(dest, 'utf8')
assert.strictEqual(data, data2)
})
})
describe('> when src file does not have write permissions', () => {
it('should be able to copy contents of file', () => {
const fileSrc = path.join(TEST_DIR, 'file.txt')
const fileDest = path.join(TEST_DIR, 'file-copy.txt')
const data = 'did it copy?'
fs.writeFileSync(fileSrc, data, 'utf8')
fs.chmodSync(fileSrc, '0444')
fs.copySync(fileSrc, fileDest)
const data2 = fs.readFileSync(fileDest, 'utf8')
assert.strictEqual(data, data2)
})
})
describe('> when overwrite option is passed', () => {
const srcData = 'some src data'
let src, dest
beforeEach(() => {
src = path.join(TEST_DIR, 'src-file')
dest = path.join(TEST_DIR, 'des-file')
// source file must always exist in these cases
fs.writeFileSync(src, srcData)
})
describe('> when destination file does NOT exist', () => {
describe('> when overwrite is true', () => {
it('should copy the file and not throw an error', () => {
fs.copySync(src, dest, { overwrite: true })
const destData = fs.readFileSync(dest, 'utf8')
assert.strictEqual(srcData, destData)
})
})
describe('> when overwrite is false', () => {
it('should copy the file and not throw an error', () => {
fs.copySync(src, dest, { overwrite: false })
const destData = fs.readFileSync(dest, 'utf8')
assert.strictEqual(srcData, destData)
})
})
})
describe('when destination file does exist', () => {
let destData
beforeEach(() => {
destData = 'some dest data'
fs.writeFileSync(dest, destData)
})
describe('> when overwrite is true', () => {
it('should copy the file and not throw an error', () => {
fs.copySync(src, dest, { overwrite: true })
destData = fs.readFileSync(dest, 'utf8')
assert.strictEqual(srcData, destData)
})
})
describe('> when overwrite is false', () => {
it('should not throw an error', () => {
fs.copySync(src, dest, { overwrite: false })
// copy never happened
const destDataNew = fs.readFileSync(dest, 'utf8')
assert.strictEqual(destData, destDataNew)
})
it('should throw an error when errorOnExist is true', () => {
assert.throws(() => fs.copySync(src, dest, { overwrite: false, errorOnExist: true }))
// copy never happened
const destDataNew = fs.readFileSync(dest, 'utf8')
assert.strictEqual(destData, destDataNew)
})
})
describe('> when overwrite is true and dest is readonly', () => {
it('should copy the file and not throw an error', () => {
try {
fs.chmodSync(dest, 0o444)
fs.copySync(src, dest, { overwrite: true })
destData = fs.readFileSync(dest, 'utf8')
assert.strictEqual(srcData, destData)
} finally {
// destination file is readonly so just remove it so we don't affect other tests
fs.unlinkSync(dest)
}
})
})
})
})
describe('clobber', () => {
let src, dest, srcData, destData
beforeEach(() => {
src = path.join(TEST_DIR, 'src-file')
dest = path.join(TEST_DIR, 'des-file')
srcData = 'some src data'
destData = 'some dest data'
fs.writeFileSync(src, srcData)
fs.writeFileSync(dest, destData)
})
it('is an alias for overwrite', () => {
fs.copySync(src, dest, { clobber: false })
// copy never happened
const destDataNew = fs.readFileSync(dest, 'utf8')
assert.strictEqual(destData, destDataNew)
})
})
})
})

View file

@ -0,0 +1,71 @@
'use strict'
const fs = require('../../')
const os = require('os')
const path = require('path')
const copySync = require('../copy-sync')
const utimesSync = require('../../util/utimes').utimesMillisSync
const assert = require('assert')
/* global beforeEach, afterEach, describe, it */
if (process.arch === 'ia32') console.warn('32 bit arch; skipping copySync timestamp tests')
const describeIfPractical = process.arch === 'ia32' ? describe.skip : describe
describeIfPractical('copySync() - preserveTimestamps option', () => {
let TEST_DIR, SRC, DEST, FILES
function setupFixture (readonly) {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-preserve-timestamp')
SRC = path.join(TEST_DIR, 'src')
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(done => fs.remove(TEST_DIR, done))
describe('> when preserveTimestamps option is true', () => {
;[
{ 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 on copy', () => {
copySync(SRC, DEST, { preserveTimestamps: true })
FILES.forEach(testFile({ preserveTimestamps: true }))
})
})
})
})
function testFile (options) {
return function (file) {
const a = path.join(SRC, file)
const b = path.join(DEST, file)
const fromStat = fs.statSync(a)
const toStat = fs.statSync(b)
if (options.preserveTimestamps) {
// Windows sub-second precision fixed: https://github.com/nodejs/io.js/issues/2069
assert.strictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime(), 'different mtime values')
assert.strictEqual(toStat.atime.getTime(), fromStat.atime.getTime(), 'different atime values')
} else {
// the access time might actually be the same, so check only modification time
assert.notStrictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime(), 'same mtime 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('+ copySync() - prevent copying identical files and dirs', () => {
let TEST_DIR = ''
let src = ''
let dest = ''
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-prevent-copying-identical')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => fs.remove(TEST_DIR, done))
it('should return an error if src and dest are the same', () => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_copy_sync')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy_sync')
fs.ensureFileSync(fileSrc)
try {
fs.copySync(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.copySync(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.copySync(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.copySync(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.copySync(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 dereference 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.copySync(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.copySync(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 dereferene 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.copySync(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.copySync(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.copySync(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.copySync(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,423 @@
'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('+ copySync() - prevent copying into itself', () => {
let TEST_DIR, src
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-prevent-copying-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 copy 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.copySync(srcFile, destFile)
assert(fs.existsSync(destFile, 'file copied'))
const out = fs.readFileSync(destFile, 'utf8')
assert.strictEqual(out, dat0, 'file contents matched')
})
})
// test for these cases:
// - src is directory, dest is directory
// - src is directory, dest is symlink
// - src is symlink, dest is directory
// - src is symlink, dest is symlink
describe('> when source is a directory', () => {
describe('>> when dest is a directory', () => {
it('of not itself', () => {
const dest = path.join(TEST_DIR, src.replace(/^\w:/, ''))
return testSuccess(src, dest)
})
it('of itself', () => {
const dest = path.join(src, 'dest')
return testError(src, dest)
})
it("should copy the directory successfully when dest is 'src_dest'", () => {
const dest = path.join(TEST_DIR, 'src_dest')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'src-dest'", () => {
const dest = path.join(TEST_DIR, 'src-dest')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'dest_src'", () => {
const dest = path.join(TEST_DIR, 'dest_src')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'src_dest/src'", () => {
const dest = path.join(TEST_DIR, 'src_dest', 'src')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'src-dest/src'", () => {
const dest = path.join(TEST_DIR, 'src-dest', 'src')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'dest_src/src'", () => {
const dest = path.join(TEST_DIR, 'dest_src', 'src')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'src_src/dest'", () => {
const dest = path.join(TEST_DIR, 'src_src', 'dest')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'src-src/dest'", () => {
const dest = path.join(TEST_DIR, 'src-src', 'dest')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'srcsrc/dest'", () => {
const dest = path.join(TEST_DIR, 'srcsrc', 'dest')
return testSuccess(src, dest)
})
it("should copy the directory successfully when dest is 'dest/src'", () => {
const dest = path.join(TEST_DIR, 'dest', 'src')
return testSuccess(src, dest)
})
it('should copy 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 testSuccess(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)
try {
fs.copySync(src, destLink, { dereference: true })
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
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.copySync(src, dest)
} catch (err) {
assert.strictEqual(err.message, `Cannot copy '${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.copySync(src, dest)
} catch (err) {
assert.strictEqual(err.message, `Cannot copy '${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)
}
})
it('should copy the directory successfully when src is a subdir of resolved dest path and dereferene is true', () => {
const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src')
const destLink = path.join(TEST_DIR, 'dest-symlink')
fs.copySync(src, srcInDest) // put some stuff in srcInDest
const dest = path.join(TEST_DIR, 'dest')
fs.symlinkSync(dest, destLink, 'dir')
const srclen = klawSync(srcInDest).length
const destlenBefore = klawSync(dest).length
assert(srclen > 2)
fs.copySync(srcInDest, destLink, { dereference: true })
const destlenAfter = klawSync(dest).length
// assert dest length is oldlen + length of stuff copied from src
assert.strictEqual(destlenAfter, destlenBefore + srclen, 'dest length should be equal to old length + copied legnth')
FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied'))
const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8')
const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8')
const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8')
const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8')
assert.strictEqual(o0, dat0, 'files contents matched')
assert.strictEqual(o1, dat1, 'files contents matched')
assert.strictEqual(o2, dat2, 'files contents matched')
assert.strictEqual(o3, dat3, 'files contents matched')
})
})
})
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')
try {
fs.copySync(srcLink, dest)
} catch (err) {
assert(err)
}
// 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)
try {
fs.copySync(srcLink, dest)
} catch (err) {
assert(err)
}
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')
try {
fs.copySync(srcLink, dest)
} catch (err) {
assert(err)
}
})
it("should copy 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')
testSuccess(srcLink, dest)
const link = fs.readlinkSync(dest)
assert.strictEqual(link, src)
})
it("should copy 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')
testSuccess(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 dereference 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)
try {
fs.copySync(srcLink, destLink, { dereference: true })
} catch (err) {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
} finally {
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)
}
})
it('should error when resolved dest path is a subdir of resolved src path', () => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
const destLink = path.join(TEST_DIR, 'dest-symlink')
const resolvedDestPath = path.join(TEST_DIR, 'src', 'some', 'nested', 'dest')
fs.ensureFileSync(path.join(resolvedDestPath, 'subdir', 'somefile.txt'))
fs.symlinkSync(resolvedDestPath, destLink, 'dir')
try {
fs.copySync(srcLink, destLink)
} catch (err) {
assert.strictEqual(err.message, `Cannot copy '${src}' to a subdirectory of itself, '${resolvedDestPath}'.`)
} finally {
const destln = fs.readlinkSync(destLink)
assert.strictEqual(destln, resolvedDestPath)
}
})
it('should error when resolved src path is a subdir of resolved dest path', () => {
const srcInDest = path.join(TEST_DIR, 'dest', 'some', 'nested', 'src')
const srcLink = path.join(TEST_DIR, 'src-symlink')
const destLink = path.join(TEST_DIR, 'dest-symlink')
const dest = path.join(TEST_DIR, 'dest')
fs.ensureDirSync(srcInDest)
fs.ensureSymlinkSync(srcInDest, srcLink, 'dir')
fs.ensureSymlinkSync(dest, destLink, 'dir')
try {
fs.copySync(srcLink, destLink)
} catch (err) {
assert.strictEqual(err.message, `Cannot overwrite '${dest}' with '${srcInDest}'.`)
} finally {
const destln = fs.readlinkSync(destLink)
assert.strictEqual(destln, dest)
}
})
})
})
})
function testSuccess (src, dest) {
const srclen = klawSync(src).length
assert(srclen > 2)
fs.copySync(src, dest)
FILES.forEach(f => assert(fs.existsSync(path.join(dest, f)), 'file copied'))
const o0 = fs.readFileSync(path.join(dest, FILES[0]), 'utf8')
const o1 = fs.readFileSync(path.join(dest, FILES[1]), 'utf8')
const o2 = fs.readFileSync(path.join(dest, FILES[2]), 'utf8')
const o3 = fs.readFileSync(path.join(dest, FILES[3]), 'utf8')
assert.strictEqual(o0, dat0, 'file contents matched')
assert.strictEqual(o1, dat1, 'file contents matched')
assert.strictEqual(o2, dat2, 'file contents matched')
assert.strictEqual(o3, dat3, 'file contents matched')
}
function testError (src, dest) {
try {
fs.copySync(src, dest)
} catch (err) {
assert.strictEqual(err.message, `Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`)
}
}

View file

@ -0,0 +1,51 @@
'use strict'
// relevant: https://github.com/jprichardson/node-fs-extra/issues/599
const os = require('os')
const fs = require('../../')
const path = require('path')
const assert = require('assert')
const klawSync = require('klaw-sync')
/* global afterEach, beforeEach, describe, it */
let TEST_DIR = ''
const FILES = [
path.join('dir1', 'file1.txt'),
path.join('dir1', 'dir2', 'file2.txt'),
path.join('dir1', 'dir2', 'dir3', 'file3.txt')
]
describe('+ copySync() - copy a readonly directory with content', () => {
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'test', 'fs-extra', 'copy-readonly-dir')
fs.emptyDir(TEST_DIR, done)
})
afterEach(done => {
klawSync(TEST_DIR).forEach(data => fs.chmodSync(data.path, 0o777))
fs.remove(TEST_DIR, done)
})
describe('> when src is readonly directory with content', () => {
it('should copy successfully', () => {
FILES.forEach(file => {
fs.outputFileSync(path.join(TEST_DIR, file), file)
})
const sourceDir = path.join(TEST_DIR, 'dir1')
const sourceHierarchy = klawSync(sourceDir)
sourceHierarchy.forEach(source => fs.chmodSync(source.path, source.stats.isDirectory() ? 0o555 : 0o444))
const targetDir = path.join(TEST_DIR, 'target')
fs.copySync(sourceDir, targetDir)
// Make sure copy was made and mode was preserved
assert(fs.existsSync(targetDir))
const targetHierarchy = klawSync(targetDir)
assert(targetHierarchy.length === sourceHierarchy.length)
targetHierarchy.forEach(target => assert(target.stats.mode === target.stats.isDirectory() ? 0o555 : 0o444))
})
})
})

View file

@ -0,0 +1,77 @@
'use strict'
const os = require('os')
const fs = require('../..')
const path = require('path')
const assert = require('assert')
const copySync = require('../copy-sync')
/* global afterEach, beforeEach, describe, it */
describe('copy-sync / symlink', () => {
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync-symlinks')
const src = path.join(TEST_DIR, 'src')
const out = path.join(TEST_DIR, 'out')
beforeEach(done => {
fs.emptyDir(TEST_DIR, err => {
assert.ifError(err)
createFixtures(src, done)
})
})
afterEach(done => {
fs.remove(TEST_DIR, done)
})
it('copies symlinks by default', () => {
assert.doesNotThrow(() => {
copySync(src, out)
})
assert.strictEqual(fs.readlinkSync(path.join(out, 'file-symlink')), path.join(src, 'foo'))
assert.strictEqual(fs.readlinkSync(path.join(out, 'dir-symlink')), path.join(src, 'dir'))
})
it('copies file contents when dereference=true', () => {
try {
copySync(src, out, { dereference: true })
} catch (err) {
assert.ifError(err)
}
const fileSymlinkPath = path.join(out, 'file-symlink')
assert.ok(fs.lstatSync(fileSymlinkPath).isFile())
assert.strictEqual(fs.readFileSync(fileSymlinkPath, 'utf8'), 'foo contents')
const dirSymlinkPath = path.join(out, 'dir-symlink')
assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory())
assert.deepStrictEqual(fs.readdirSync(dirSymlinkPath), ['bar'])
})
})
function createFixtures (srcDir, callback) {
fs.mkdir(srcDir, err => {
if (err) return callback(err)
// note: third parameter in symlinkSync is type e.g. 'file' or 'dir'
// https://nodejs.org/api/fs.html#fs_fs_symlink_srcpath_dstpath_type_callback
try {
const fooFile = path.join(srcDir, 'foo')
const fooFileLink = path.join(srcDir, 'file-symlink')
fs.writeFileSync(fooFile, 'foo contents')
fs.symlinkSync(fooFile, fooFileLink, 'file')
const dir = path.join(srcDir, 'dir')
const dirFile = path.join(dir, 'bar')
const dirLink = path.join(srcDir, 'dir-symlink')
fs.mkdirSync(dir)
fs.writeFileSync(dirFile, 'bar contents')
fs.symlinkSync(dir, dirLink, 'dir')
} catch (err) {
callback(err)
}
callback()
})
}

View file

@ -0,0 +1,461 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../../')
const path = require('path')
const assert = require('assert')
const crypto = require('crypto')
/* global afterEach, beforeEach, describe, it */
const SIZE = 16 * 64 * 1024 + 7
let TEST_DIR = ''
describe('fs-extra', () => {
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('+ copy()', () => {
it('should return an error if src and dest are the same', done => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_copy')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
fse.ensureFileSync(fileSrc)
fse.copy(fileSrc, fileDest, err => {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
done()
})
})
it('should error when overwrite=false and file exists', done => {
const src = path.join(TEST_DIR, 'src.txt')
const dest = path.join(TEST_DIR, 'dest.txt')
fse.ensureFileSync(src)
fse.ensureFileSync(dest)
fse.copy(src, dest, { overwrite: false, errorOnExist: true }, err => {
assert(err)
done()
})
})
it('should error when overwrite=false and file exists in a dir', done => {
const src = path.join(TEST_DIR, 'src', 'sfile.txt')
const dest = path.join(TEST_DIR, 'dest', 'dfile.txt')
fse.ensureFileSync(src)
fse.ensureFileSync(dest)
fse.copy(src, dest, { overwrite: false, errorOnExist: true }, err => {
assert(err)
done()
})
})
describe('> when src is a file', () => {
it('should copy the file asynchronously', done => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))
const srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
let destMd5 = ''
fse.copy(fileSrc, fileDest, err => {
assert(!err)
destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
assert.strictEqual(srcMd5, destMd5)
done()
})
})
it('should work with promises', () => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))
const srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
let destMd5 = ''
return fse.copy(fileSrc, fileDest).then(() => {
destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
assert.strictEqual(srcMd5, destMd5)
})
})
it('should return an error if src file does not exist', done => {
const fileSrc = 'we-simply-assume-this-file-does-not-exist.bin'
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
fse.copy(fileSrc, fileDest, err => {
assert(err)
done()
})
})
it('should copy to a destination file with two \'$\' characters in name (eg: TEST_fs-extra_$$_copy)', done => {
const fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
const fileDest = path.join(TEST_DIR, 'TEST_fs-extra_$$_copy')
fs.writeFileSync(fileSrc, '')
fse.copy(fileSrc, fileDest, err => {
assert(!err)
fs.statSync(fileDest)
done()
})
})
describe('> when the destination dir does not exist', () => {
it('should create the destination directory and copy the file', done => {
const src = path.join(TEST_DIR, 'file.txt')
const dest = path.join(TEST_DIR, 'this/path/does/not/exist/copied.txt')
const data = 'did it copy?\n'
fs.writeFileSync(src, data, 'utf8')
fse.copy(src, dest, err => {
const data2 = fs.readFileSync(dest, 'utf8')
assert.strictEqual(data, data2)
done(err)
})
})
})
describe('> when dest exists and is a directory', () => {
it('should return an error', done => {
const src = path.join(TEST_DIR, 'file.txt')
const dest = path.join(TEST_DIR, 'dir')
fse.ensureFileSync(src)
fse.ensureDirSync(dest)
fse.copy(src, dest, err => {
assert.strictEqual(err.message, `Cannot overwrite directory '${dest}' with non-directory '${src}'.`)
done()
})
})
})
})
describe('> when src is a directory', () => {
describe('> when src directory does not exist', () => {
it('should return an error', done => {
const ts = path.join(TEST_DIR, 'this_dir_does_not_exist')
const td = path.join(TEST_DIR, 'this_dir_really_does_not_matter')
fse.copy(ts, td, err => {
assert(err)
done()
})
})
})
describe('> when dest exists and is a file', () => {
it('should return an error', done => {
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'file.txt')
fs.mkdirSync(src)
fse.ensureFileSync(dest)
fse.copy(src, dest, err => {
assert.strictEqual(err.message, `Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
done()
})
})
})
it('should preserve symbolic links', done => {
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'dest')
const srcTarget = path.join(TEST_DIR, 'destination')
fse.mkdirSync(src)
fse.mkdirSync(srcTarget)
// symlink type is only used for Windows and the default is 'file'.
// https://nodejs.org/api/fs.html#fs_fs_symlink_target_path_type_callback
fse.symlinkSync(srcTarget, path.join(src, 'symlink'), 'dir')
fse.copy(src, dest, err => {
assert.ifError(err)
const link = fs.readlinkSync(path.join(dest, 'symlink'))
assert.strictEqual(link, srcTarget)
done()
})
})
it('should copy the directory asynchronously', done => {
const FILES = 2
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'dest')
fse.mkdirs(src, err => {
assert(!err)
for (let i = 0; i < FILES; ++i) {
fs.writeFileSync(path.join(src, i.toString()), crypto.randomBytes(SIZE))
}
const subdir = path.join(src, 'subdir')
fse.mkdirs(subdir, err => {
assert(!err)
for (let i = 0; i < FILES; ++i) {
fs.writeFileSync(path.join(subdir, i.toString()), crypto.randomBytes(SIZE))
}
fse.copy(src, dest, err => {
assert.ifError(err)
assert(fs.existsSync(dest))
for (let i = 0; i < FILES; ++i) {
assert(fs.existsSync(path.join(dest, i.toString())))
}
const destSub = path.join(dest, 'subdir')
for (let j = 0; j < FILES; ++j) {
assert(fs.existsSync(path.join(destSub, j.toString())))
}
done()
})
})
})
})
describe('> when the destination dir does not exist', () => {
it('should create the destination directory and copy the file', done => {
const src = path.join(TEST_DIR, 'data/')
fse.mkdirsSync(src)
const d1 = 'file1'
const d2 = 'file2'
fs.writeFileSync(path.join(src, 'f1.txt'), d1)
fs.writeFileSync(path.join(src, 'f2.txt'), d2)
const dest = path.join(TEST_DIR, 'this/path/does/not/exist/outputDir')
fse.copy(src, dest, err => {
const o1 = fs.readFileSync(path.join(dest, 'f1.txt'), 'utf8')
const o2 = fs.readFileSync(path.join(dest, 'f2.txt'), 'utf8')
assert.strictEqual(d1, o1)
assert.strictEqual(d2, o2)
done(err)
})
})
})
describe('> when src dir does not exist', () => {
it('should return an error', done => {
fse.copy('/does/not/exist', '/something/else', err => {
assert(err instanceof Error)
done()
})
})
})
})
describe('> when filter is used', () => {
it('should do nothing if filter fails', done => {
const srcDir = path.join(TEST_DIR, 'src')
const srcFile = path.join(srcDir, 'srcfile.css')
fse.outputFileSync(srcFile, 'src contents')
const destDir = path.join(TEST_DIR, 'dest')
const destFile = path.join(destDir, 'destfile.css')
const filter = s => path.extname(s) !== '.css' && !fs.statSync(s).isDirectory()
fse.copy(srcFile, destFile, filter, err => {
assert.ifError(err)
assert(!fs.existsSync(destDir))
done()
})
})
it('should only copy files allowed by filter fn', done => {
const srcFile1 = path.join(TEST_DIR, '1.css')
fs.writeFileSync(srcFile1, '')
const destFile1 = path.join(TEST_DIR, 'dest1.css')
const filter = s => s.split('.').pop() !== 'css'
fse.copy(srcFile1, destFile1, filter, err => {
assert(!err)
assert(!fs.existsSync(destFile1))
done()
})
})
it('should not call filter fn more than needed', done => {
const src = path.join(TEST_DIR, 'foo')
fs.writeFileSync(src, '')
const dest = path.join(TEST_DIR, 'bar')
let filterCallCount = 0
const filter = () => {
filterCallCount++
return true
}
fse.copy(src, dest, filter, err => {
assert(!err)
assert.strictEqual(filterCallCount, 1)
assert(fs.existsSync(dest))
done()
})
})
it('accepts options object in place of filter', done => {
const srcFile1 = path.join(TEST_DIR, '1.jade')
fs.writeFileSync(srcFile1, '')
const destFile1 = path.join(TEST_DIR, 'dest1.jade')
const options = { filter: s => /.html$|.css$/i.test(s) }
fse.copy(srcFile1, destFile1, options, (err) => {
assert(!err)
assert(!fs.existsSync(destFile1))
done()
})
})
it('allows filter fn to return a promise', done => {
const srcFile1 = path.join(TEST_DIR, '1.css')
fs.writeFileSync(srcFile1, '')
const destFile1 = path.join(TEST_DIR, 'dest1.css')
const filter = s => Promise.resolve(s.split('.').pop() !== 'css')
fse.copy(srcFile1, destFile1, filter, err => {
assert(!err)
assert(!fs.existsSync(destFile1))
done()
})
})
it('should apply filter recursively', done => {
const FILES = 2
// Don't match anything that ends with a digit higher than 0:
const filter = s => /(0|\D)$/i.test(s)
const src = path.join(TEST_DIR, 'src')
fse.mkdirsSync(src)
for (let i = 0; i < FILES; ++i) {
fs.writeFileSync(path.join(src, i.toString()), crypto.randomBytes(SIZE))
}
const subdir = path.join(src, 'subdir')
fse.mkdirsSync(subdir)
for (let i = 0; i < FILES; ++i) {
fs.writeFileSync(path.join(subdir, i.toString()), crypto.randomBytes(SIZE))
}
const dest = path.join(TEST_DIR, 'dest')
fse.copy(src, dest, filter, err => {
assert(!err)
assert(fs.existsSync(dest))
assert(FILES > 1)
for (let i = 0; i < FILES; ++i) {
if (i === 0) {
assert(fs.existsSync(path.join(dest, i.toString())))
} else {
assert(!fs.existsSync(path.join(dest, i.toString())))
}
}
const destSub = path.join(dest, 'subdir')
for (let j = 0; j < FILES; ++j) {
if (j === 0) {
assert(fs.existsSync(path.join(destSub, j.toString())))
} else {
assert(!fs.existsSync(path.join(destSub, j.toString())))
}
}
done()
})
})
it('should apply filter to directory names', done => {
const IGNORE = 'ignore'
const filter = p => !~p.indexOf(IGNORE)
const src = path.join(TEST_DIR, 'src')
fse.mkdirsSync(src)
const ignoreDir = path.join(src, IGNORE)
fse.mkdirsSync(ignoreDir)
fse.writeFileSync(path.join(ignoreDir, 'file'), crypto.randomBytes(SIZE))
const dest = path.join(TEST_DIR, 'dest')
fse.copy(src, dest, filter, err => {
assert.ifError(err)
assert(!fs.existsSync(path.join(dest, IGNORE)), 'directory was not ignored')
assert(!fs.existsSync(path.join(dest, IGNORE, 'file')), 'file was not ignored')
done()
})
})
it('should apply filter when it is applied only to dest', done => {
const timeCond = new Date().getTime()
const filter = (s, d) => fs.statSync(d).mtime.getTime() < timeCond
const src = path.join(TEST_DIR, 'src')
fse.mkdirsSync(src)
const subdir = path.join(src, 'subdir')
fse.mkdirsSync(subdir)
const dest = path.join(TEST_DIR, 'dest')
setTimeout(() => {
fse.mkdirsSync(dest)
fse.copy(src, dest, filter, err => {
assert(!err)
assert(!fs.existsSync(path.join(dest, 'subdir')))
done()
})
}, 1000)
})
it('should apply filter when it is applied to both src and dest', done => {
const timeCond = new Date().getTime()
const filter = (s, d) => s.split('.').pop() !== 'css' && fs.statSync(path.dirname(d)).mtime.getTime() > timeCond
const dest = path.join(TEST_DIR, 'dest')
setTimeout(() => {
fse.mkdirsSync(dest)
const srcFile1 = path.join(TEST_DIR, '1.html')
const srcFile2 = path.join(TEST_DIR, '2.css')
const srcFile3 = path.join(TEST_DIR, '3.jade')
fse.writeFileSync(srcFile1, '')
fse.writeFileSync(srcFile2, '')
fse.writeFileSync(srcFile3, '')
const destFile1 = path.join(dest, 'dest1.html')
const destFile2 = path.join(dest, 'dest2.css')
const destFile3 = path.join(dest, 'dest3.jade')
fse.copy(srcFile1, destFile1, filter, err => {
assert(!err)
assert(fs.existsSync(destFile1))
fse.copy(srcFile2, destFile2, filter, err => {
assert(!err)
assert(!fs.existsSync(destFile2))
fse.copy(srcFile3, destFile3, filter, err => {
assert(!err)
assert(fs.existsSync(destFile3))
done()
})
})
})
}, 1000)
})
})
})
})

View file

@ -0,0 +1 @@
These tests came from: https://github.com/AvianFlu/ncp/tree/v1.0.1/test

View file

@ -0,0 +1,60 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../../..')
const { copy: ncp } = require('../../')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('ncp broken symlink', () => {
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-broken-symlinks')
const src = path.join(TEST_DIR, 'src')
const out = path.join(TEST_DIR, 'out')
beforeEach(done => {
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
createFixtures(src, done)
})
})
afterEach(done => fse.remove(TEST_DIR, done))
it('should not error if symlink is broken', done => {
ncp(src, out, err => {
assert.strictEqual(err, null)
done()
})
})
it('should return an error if symlink is broken and dereference=true', done => {
ncp(src, out, { dereference: true }, err => {
assert.strictEqual(err.code, 'ENOENT')
done()
})
})
})
function createFixtures (srcDir, callback) {
fs.mkdir(srcDir, err => {
let brokenFile
let brokenFileLink
if (err) return callback(err)
try {
brokenFile = path.join(srcDir, 'does-not-exist')
brokenFileLink = path.join(srcDir, 'broken-symlink')
fs.writeFileSync(brokenFile, 'does not matter')
fs.symlinkSync(brokenFile, brokenFileLink, 'file')
} catch (err) {
callback(err)
}
// break the symlink now
fse.remove(brokenFile, callback)
})
}

View file

@ -0,0 +1 @@
Hello nodejitsu

View file

@ -0,0 +1 @@
Hello nodejitsu

View file

@ -0,0 +1,52 @@
'use strict'
// file in reference: https://github.com/jprichardson/node-fs-extra/issues/56
const fs = require('fs')
const os = require('os')
const fse = require('../../..')
const { copy: ncp } = require('../../')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
// skip test for windows
// eslint-disable globalReturn */
// if (os.platform().indexOf('win') === 0) return
// eslint-enable globalReturn */
describe('ncp / error / dest-permission', () => {
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-error-dest-perm')
const src = path.join(TEST_DIR, 'src')
const dest = path.join(TEST_DIR, 'dest')
// when we are root, then we will be able to create the subdirectory even if
// we don't have the permissions to do so, so no point in running this test
if (os.platform().indexOf('win') === 0 || os.userInfo().uid === 0) return
beforeEach(done => {
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
done()
})
})
afterEach(done => fse.remove(TEST_DIR, done))
it('should return an error', done => {
const someFile = path.join(src, 'some-file')
fse.outputFileSync(someFile, 'hello')
fse.mkdirsSync(dest)
fs.chmodSync(dest, 0o444)
const subdest = path.join(dest, 'another-dir')
ncp(src, subdest, err => {
assert(err)
assert.strictEqual(err.code, 'EACCES')
done()
})
})
})

View file

@ -0,0 +1,191 @@
'use strict'
const fs = require('fs')
const { copy: ncp } = require('../../')
const path = require('path')
const rimraf = require('rimraf')
const assert = require('assert')
const readDirFiles = require('read-dir-files').read // temporary, will remove
/* eslint-env mocha */
const fixturesDir = path.join(__dirname, 'fixtures')
describe('ncp', () => {
describe('regular files and directories', () => {
const fixtures = path.join(fixturesDir, 'regular-fixtures')
const src = path.join(fixtures, 'src')
const out = path.join(fixtures, 'out')
before(cb => rimraf(out, () => ncp(src, out, cb)))
describe('when copying a directory of files', () => {
it('files are copied correctly', cb => {
readDirFiles(src, 'utf8', (srcErr, srcFiles) => {
readDirFiles(out, 'utf8', (outErr, outFiles) => {
assert.ifError(srcErr)
assert.deepStrictEqual(srcFiles, outFiles)
cb()
})
})
})
})
describe('when copying files using filter', () => {
before(cb => {
const filter = name => name.slice(-1) !== 'a'
rimraf(out, () => ncp(src, out, { filter }, cb))
})
it('files are copied correctly', cb => {
readDirFiles(src, 'utf8', (srcErr, srcFiles) => {
function filter (files) {
for (const fileName in files) {
const curFile = files[fileName]
if (curFile instanceof Object) {
filter(curFile)
} else if (fileName.slice(-1) === 'a') {
delete files[fileName]
}
}
}
filter(srcFiles)
readDirFiles(out, 'utf8', (outErr, outFiles) => {
assert.ifError(outErr)
assert.deepStrictEqual(srcFiles, outFiles)
cb()
})
})
})
})
describe('when using overwrite=true', () => {
before(function () {
this.originalCreateReadStream = fs.createReadStream
})
after(function () {
fs.createReadStream = this.originalCreateReadStream
})
it('the copy is complete after callback', done => {
ncp(src, out, { overwrite: true }, err => {
fs.createReadStream = () => done(new Error('createReadStream after callback'))
assert.ifError(err)
process.nextTick(done)
})
})
})
describe('when using overwrite=false', () => {
beforeEach(done => rimraf(out, done))
it('works', cb => {
ncp(src, out, { overwrite: false }, err => {
assert.ifError(err)
cb()
})
})
it('should not error if files exist', cb => {
ncp(src, out, () => {
ncp(src, out, { overwrite: false }, err => {
assert.ifError(err)
cb()
})
})
})
it('should error if errorOnExist and file exists', cb => {
ncp(src, out, () => {
ncp(src, out, {
overwrite: false,
errorOnExist: true
}, err => {
assert(err)
cb()
})
})
})
})
describe('clobber', () => {
beforeEach(done => rimraf(out, done))
it('is an alias for overwrite', cb => {
ncp(src, out, () => {
ncp(src, out, {
clobber: false,
errorOnExist: true
}, err => {
assert(err)
cb()
})
})
})
})
describe('when using transform', () => {
it('file descriptors are passed correctly', cb => {
ncp(src, out, {
transform: (read, write, file) => {
assert.notStrictEqual(file.name, undefined)
assert.strictEqual(typeof file.mode, 'number')
read.pipe(write)
}
}, cb)
})
})
})
// see https://github.com/AvianFlu/ncp/issues/71
describe('Issue 71: Odd Async Behaviors', () => {
const fixtures = path.join(__dirname, 'fixtures', 'regular-fixtures')
const src = path.join(fixtures, 'src')
const out = path.join(fixtures, 'out')
let totalCallbacks = 0
function copyAssertAndCount (callback) {
// rimraf(out, function() {
ncp(src, out, err => {
assert(!err)
totalCallbacks += 1
readDirFiles(src, 'utf8', (srcErr, srcFiles) => {
readDirFiles(out, 'utf8', (outErr, outFiles) => {
assert.ifError(srcErr)
assert.deepStrictEqual(srcFiles, outFiles)
callback()
})
})
})
// })
}
describe('when copying a directory of files without cleaning the destination', () => {
it('callback fires once per run and directories are equal', done => {
const expected = 10
let count = 10
function next () {
if (count > 0) {
setTimeout(() => {
copyAssertAndCount(() => {
count -= 1
next()
})
}, 100)
} else {
// console.log('Total callback count is', totalCallbacks)
assert.strictEqual(totalCallbacks, expected)
done()
}
}
next()
})
})
})
})

View file

@ -0,0 +1,78 @@
'use strict'
const fs = require('fs')
const os = require('os')
const fse = require('../../..')
const { copy: ncp } = require('../../')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('ncp / symlink', () => {
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ncp-symlinks')
const src = path.join(TEST_DIR, 'src')
const out = path.join(TEST_DIR, 'out')
beforeEach(done => {
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
createFixtures(src, done)
})
})
afterEach(done => fse.remove(TEST_DIR, done))
it('copies symlinks by default', done => {
ncp(src, out, err => {
assert.ifError(err)
assert.strictEqual(fs.readlinkSync(path.join(out, 'file-symlink')), path.join(src, 'foo'))
assert.strictEqual(fs.readlinkSync(path.join(out, 'dir-symlink')), path.join(src, 'dir'))
done()
})
})
it('copies file contents when dereference=true', done => {
ncp(src, out, { dereference: true }, err => {
assert.ifError(err)
const fileSymlinkPath = path.join(out, 'file-symlink')
assert.ok(fs.lstatSync(fileSymlinkPath).isFile())
assert.strictEqual(fs.readFileSync(fileSymlinkPath, 'utf8'), 'foo contents')
const dirSymlinkPath = path.join(out, 'dir-symlink')
assert.ok(fs.lstatSync(dirSymlinkPath).isDirectory())
assert.deepStrictEqual(fs.readdirSync(dirSymlinkPath), ['bar'])
done()
})
})
})
function createFixtures (srcDir, callback) {
fs.mkdir(srcDir, err => {
if (err) return callback(err)
// note: third parameter in symlinkSync is type e.g. 'file' or 'dir'
// https://nodejs.org/api/fs.html#fs_fs_symlink_srcpath_dstpath_type_callback
try {
const fooFile = path.join(srcDir, 'foo')
const fooFileLink = path.join(srcDir, 'file-symlink')
fs.writeFileSync(fooFile, 'foo contents')
fs.symlinkSync(fooFile, fooFileLink, 'file')
const dir = path.join(srcDir, 'dir')
const dirFile = path.join(dir, 'bar')
const dirLink = path.join(srcDir, 'dir-symlink')
fs.mkdirSync(dir)
fs.writeFileSync(dirFile, 'bar contents')
fs.symlinkSync(dir, dirLink, 'dir')
} catch (err) {
callback(err)
}
callback()
})
}

View file

@ -0,0 +1,161 @@
'use strict'
const fs = require('graceful-fs')
const path = require('path')
const mkdirsSync = require('../mkdirs').mkdirsSync
const utimesMillisSync = require('../util/utimes').utimesMillisSync
const stat = require('../util/stat')
function copySync (src, dest, opts) {
if (typeof opts === 'function') {
opts = { filter: opts }
}
opts = opts || {}
opts.clobber = 'clobber' in opts ? !!opts.clobber : true // default to true for now
opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber // overwrite falls back to clobber
// Warn about using preserveTimestamps on 32-bit node
if (opts.preserveTimestamps && process.arch === 'ia32') {
process.emitWarning(
'Using the preserveTimestamps option in 32-bit node is not recommended;\n\n' +
'\tsee https://github.com/jprichardson/node-fs-extra/issues/269',
'Warning', 'fs-extra-WARN0002'
)
}
const { srcStat, destStat } = stat.checkPathsSync(src, dest, 'copy', opts)
stat.checkParentPathsSync(src, srcStat, dest, 'copy')
if (opts.filter && !opts.filter(src, dest)) return
const destParent = path.dirname(dest)
if (!fs.existsSync(destParent)) mkdirsSync(destParent)
return getStats(destStat, src, dest, opts)
}
function getStats (destStat, src, dest, opts) {
const statSync = opts.dereference ? fs.statSync : fs.lstatSync
const srcStat = statSync(src)
if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts)
else if (srcStat.isFile() ||
srcStat.isCharacterDevice() ||
srcStat.isBlockDevice()) return onFile(srcStat, destStat, src, dest, opts)
else if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts)
else if (srcStat.isSocket()) throw new Error(`Cannot copy a socket file: ${src}`)
else if (srcStat.isFIFO()) throw new Error(`Cannot copy a FIFO pipe: ${src}`)
throw new Error(`Unknown file: ${src}`)
}
function onFile (srcStat, destStat, src, dest, opts) {
if (!destStat) return copyFile(srcStat, src, dest, opts)
return mayCopyFile(srcStat, src, dest, opts)
}
function mayCopyFile (srcStat, src, dest, opts) {
if (opts.overwrite) {
fs.unlinkSync(dest)
return copyFile(srcStat, src, dest, opts)
} else if (opts.errorOnExist) {
throw new Error(`'${dest}' already exists`)
}
}
function copyFile (srcStat, src, dest, opts) {
fs.copyFileSync(src, dest)
if (opts.preserveTimestamps) handleTimestamps(srcStat.mode, src, dest)
return setDestMode(dest, srcStat.mode)
}
function handleTimestamps (srcMode, src, dest) {
// Make sure the file is writable before setting the timestamp
// otherwise open fails with EPERM when invoked with 'r+'
// (through utimes call)
if (fileIsNotWritable(srcMode)) makeFileWritable(dest, srcMode)
return setDestTimestamps(src, dest)
}
function fileIsNotWritable (srcMode) {
return (srcMode & 0o200) === 0
}
function makeFileWritable (dest, srcMode) {
return setDestMode(dest, srcMode | 0o200)
}
function setDestMode (dest, srcMode) {
return fs.chmodSync(dest, srcMode)
}
function setDestTimestamps (src, dest) {
// The initial srcStat.atime cannot be trusted
// because it is modified by the read(2) system call
// (See https://nodejs.org/api/fs.html#fs_stat_time_values)
const updatedSrcStat = fs.statSync(src)
return utimesMillisSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime)
}
function onDir (srcStat, destStat, src, dest, opts) {
if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts)
return copyDir(src, dest, opts)
}
function mkDirAndCopy (srcMode, src, dest, opts) {
fs.mkdirSync(dest)
copyDir(src, dest, opts)
return setDestMode(dest, srcMode)
}
function copyDir (src, dest, opts) {
fs.readdirSync(src).forEach(item => copyDirItem(item, src, dest, opts))
}
function copyDirItem (item, src, dest, opts) {
const srcItem = path.join(src, item)
const destItem = path.join(dest, item)
if (opts.filter && !opts.filter(srcItem, destItem)) return
const { destStat } = stat.checkPathsSync(srcItem, destItem, 'copy', opts)
return getStats(destStat, srcItem, destItem, opts)
}
function onLink (destStat, src, dest, opts) {
let resolvedSrc = fs.readlinkSync(src)
if (opts.dereference) {
resolvedSrc = path.resolve(process.cwd(), resolvedSrc)
}
if (!destStat) {
return fs.symlinkSync(resolvedSrc, dest)
} else {
let resolvedDest
try {
resolvedDest = fs.readlinkSync(dest)
} catch (err) {
// dest exists and is a regular file or directory,
// Windows may throw UNKNOWN error. If dest already exists,
// fs throws error anyway, so no need to guard against it here.
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return fs.symlinkSync(resolvedSrc, dest)
throw err
}
if (opts.dereference) {
resolvedDest = path.resolve(process.cwd(), resolvedDest)
}
if (stat.isSrcSubdir(resolvedSrc, resolvedDest)) {
throw new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`)
}
// prevent copy if src is a subdir of dest since unlinking
// dest in this case would result in removing src contents
// and therefore a broken symlink would be created.
if (stat.isSrcSubdir(resolvedDest, resolvedSrc)) {
throw new Error(`Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.`)
}
return copyLink(resolvedSrc, dest)
}
}
function copyLink (resolvedSrc, dest) {
fs.unlinkSync(dest)
return fs.symlinkSync(resolvedSrc, dest)
}
module.exports = copySync

View file

@ -0,0 +1,177 @@
'use strict'
const fs = require('../fs')
const path = require('path')
const { mkdirs } = require('../mkdirs')
const { pathExists } = require('../path-exists')
const { utimesMillis } = require('../util/utimes')
const stat = require('../util/stat')
async function copy (src, dest, opts = {}) {
if (typeof opts === 'function') {
opts = { filter: opts }
}
opts.clobber = 'clobber' in opts ? !!opts.clobber : true // default to true for now
opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber // overwrite falls back to clobber
// Warn about using preserveTimestamps on 32-bit node
if (opts.preserveTimestamps && process.arch === 'ia32') {
process.emitWarning(
'Using the preserveTimestamps option in 32-bit node is not recommended;\n\n' +
'\tsee https://github.com/jprichardson/node-fs-extra/issues/269',
'Warning', 'fs-extra-WARN0001'
)
}
const { srcStat, destStat } = await stat.checkPaths(src, dest, 'copy', opts)
await stat.checkParentPaths(src, srcStat, dest, 'copy')
const include = await runFilter(src, dest, opts)
if (!include) return
// check if the parent of dest exists, and create it if it doesn't exist
const destParent = path.dirname(dest)
const dirExists = await pathExists(destParent)
if (!dirExists) {
await mkdirs(destParent)
}
await getStatsAndPerformCopy(destStat, src, dest, opts)
}
async function runFilter (src, dest, opts) {
if (!opts.filter) return true
return opts.filter(src, dest)
}
async function getStatsAndPerformCopy (destStat, src, dest, opts) {
const statFn = opts.dereference ? fs.stat : fs.lstat
const srcStat = await statFn(src)
if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts)
if (
srcStat.isFile() ||
srcStat.isCharacterDevice() ||
srcStat.isBlockDevice()
) return onFile(srcStat, destStat, src, dest, opts)
if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts)
if (srcStat.isSocket()) throw new Error(`Cannot copy a socket file: ${src}`)
if (srcStat.isFIFO()) throw new Error(`Cannot copy a FIFO pipe: ${src}`)
throw new Error(`Unknown file: ${src}`)
}
async function onFile (srcStat, destStat, src, dest, opts) {
if (!destStat) return copyFile(srcStat, src, dest, opts)
if (opts.overwrite) {
await fs.unlink(dest)
return copyFile(srcStat, src, dest, opts)
}
if (opts.errorOnExist) {
throw new Error(`'${dest}' already exists`)
}
}
async function copyFile (srcStat, src, dest, opts) {
await fs.copyFile(src, dest)
if (opts.preserveTimestamps) {
// Make sure the file is writable before setting the timestamp
// otherwise open fails with EPERM when invoked with 'r+'
// (through utimes call)
if (fileIsNotWritable(srcStat.mode)) {
await makeFileWritable(dest, srcStat.mode)
}
// Set timestamps and mode correspondingly
// Note that The initial srcStat.atime cannot be trusted
// because it is modified by the read(2) system call
// (See https://nodejs.org/api/fs.html#fs_stat_time_values)
const updatedSrcStat = await fs.stat(src)
await utimesMillis(dest, updatedSrcStat.atime, updatedSrcStat.mtime)
}
return fs.chmod(dest, srcStat.mode)
}
function fileIsNotWritable (srcMode) {
return (srcMode & 0o200) === 0
}
function makeFileWritable (dest, srcMode) {
return fs.chmod(dest, srcMode | 0o200)
}
async function onDir (srcStat, destStat, src, dest, opts) {
// the dest directory might not exist, create it
if (!destStat) {
await fs.mkdir(dest)
}
const items = await fs.readdir(src)
// loop through the files in the current directory to copy everything
await Promise.all(items.map(async item => {
const srcItem = path.join(src, item)
const destItem = path.join(dest, item)
// skip the item if it is matches by the filter function
const include = await runFilter(srcItem, destItem, opts)
if (!include) return
const { destStat } = await stat.checkPaths(srcItem, destItem, 'copy', opts)
// If the item is a copyable file, `getStatsAndPerformCopy` will copy it
// If the item is a directory, `getStatsAndPerformCopy` will call `onDir` recursively
return getStatsAndPerformCopy(destStat, srcItem, destItem, opts)
}))
if (!destStat) {
await fs.chmod(dest, srcStat.mode)
}
}
async function onLink (destStat, src, dest, opts) {
let resolvedSrc = await fs.readlink(src)
if (opts.dereference) {
resolvedSrc = path.resolve(process.cwd(), resolvedSrc)
}
if (!destStat) {
return fs.symlink(resolvedSrc, dest)
}
let resolvedDest = null
try {
resolvedDest = await fs.readlink(dest)
} catch (e) {
// dest exists and is a regular file or directory,
// Windows may throw UNKNOWN error. If dest already exists,
// fs throws error anyway, so no need to guard against it here.
if (e.code === 'EINVAL' || e.code === 'UNKNOWN') return fs.symlink(resolvedSrc, dest)
throw e
}
if (opts.dereference) {
resolvedDest = path.resolve(process.cwd(), resolvedDest)
}
if (stat.isSrcSubdir(resolvedSrc, resolvedDest)) {
throw new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`)
}
// do not copy if src is a subdir of dest since unlinking
// dest in this case would result in removing src contents
// and therefore a broken symlink would be created.
if (stat.isSrcSubdir(resolvedDest, resolvedSrc)) {
throw new Error(`Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.`)
}
// copy the link
await fs.unlink(dest)
return fs.symlink(resolvedSrc, dest)
}
module.exports = copy

View file

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

View file

@ -0,0 +1,54 @@
'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('+ emptyDir()', () => {
let TEST_DIR
beforeEach(() => {
TEST_DIR = path.join(os.tmpdir(), 'test-fs-extra', 'empty-dir')
if (fs.existsSync(TEST_DIR)) {
fse.removeSync(TEST_DIR)
}
fse.ensureDirSync(TEST_DIR)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('> when directory exists and contains items', () => {
it('should delete all of the items', () => {
// verify nothing
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
fse.ensureFileSync(path.join(TEST_DIR, 'some-file'))
fse.ensureFileSync(path.join(TEST_DIR, 'some-file-2'))
fse.ensureDirSync(path.join(TEST_DIR, 'some-dir'))
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 3)
fse.emptyDirSync(TEST_DIR)
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
})
})
describe('> when directory exists and contains no items', () => {
it('should do nothing', () => {
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
fse.emptyDirSync(TEST_DIR)
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
})
})
describe('> when directory does not exist', () => {
it('should create it', () => {
fse.removeSync(TEST_DIR)
assert(!fs.existsSync(TEST_DIR))
fse.emptyDirSync(TEST_DIR)
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
})
})
})

View file

@ -0,0 +1,63 @@
'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('+ emptyDir()', () => {
let TEST_DIR
beforeEach(() => {
TEST_DIR = path.join(os.tmpdir(), 'test-fs-extra', 'empty-dir')
if (fs.existsSync(TEST_DIR)) {
fse.removeSync(TEST_DIR)
}
fse.ensureDirSync(TEST_DIR)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('> when directory exists and contains items', () => {
it('should delete all of the items', done => {
// verify nothing
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
fse.ensureFileSync(path.join(TEST_DIR, 'some-file'))
fse.ensureFileSync(path.join(TEST_DIR, 'some-file-2'))
fse.ensureDirSync(path.join(TEST_DIR, 'some-dir'))
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 3)
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
done()
})
})
})
describe('> when directory exists and contains no items', () => {
it('should do nothing', done => {
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
done()
})
})
})
describe('> when directory does not exist', () => {
it('should create it', done => {
fse.removeSync(TEST_DIR)
assert(!fs.existsSync(TEST_DIR))
fse.emptyDir(TEST_DIR, err => {
assert.ifError(err)
assert.strictEqual(fs.readdirSync(TEST_DIR).length, 0)
done()
})
})
})
})

View file

@ -0,0 +1,39 @@
'use strict'
const u = require('universalify').fromPromise
const fs = require('../fs')
const path = require('path')
const mkdir = require('../mkdirs')
const remove = require('../remove')
const emptyDir = u(async function emptyDir (dir) {
let items
try {
items = await fs.readdir(dir)
} catch {
return mkdir.mkdirs(dir)
}
return Promise.all(items.map(item => remove.remove(path.join(dir, item))))
})
function emptyDirSync (dir) {
let items
try {
items = fs.readdirSync(dir)
} catch {
return mkdir.mkdirsSync(dir)
}
items.forEach(item => {
item = path.join(dir, item)
remove.removeSync(item)
})
}
module.exports = {
emptyDirSync,
emptydirSync: emptyDirSync,
emptyDir,
emptydir: emptyDir
}

View file

@ -0,0 +1,94 @@
'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', 'create')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('+ createFile', () => {
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.createFile(file, err => {
assert.ifError(err)
assert(fs.existsSync(file))
done()
})
})
})
describe('> when the file does exist', () => {
it('should not 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.createFile(file, err => {
assert.ifError(err)
assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello world')
done()
})
})
it('should give clear error if node in directory tree is a file', done => {
const existingFile = path.join(TEST_DIR, Math.random() + 'ts-e', Math.random() + '.txt')
fse.mkdirsSync(path.dirname(existingFile))
fs.writeFileSync(existingFile, '')
const file = path.join(existingFile, Math.random() + '.txt')
fse.createFile(file, err => {
assert.strictEqual(err.code, 'ENOTDIR')
done()
})
})
})
})
describe('+ createFileSync', () => {
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.createFileSync(file)
assert(fs.existsSync(file))
})
})
describe('> when the file does exist', () => {
it('should not 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.createFileSync(file)
assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello world')
})
it('should give clear error if node in directory tree is a file', () => {
const existingFile = path.join(TEST_DIR, Math.random() + 'ts-e', Math.random() + '.txt')
fse.mkdirsSync(path.dirname(existingFile))
fs.writeFileSync(existingFile, '')
const file = path.join(existingFile, Math.random() + '.txt')
try {
fse.createFileSync(file)
assert.fail()
} catch (err) {
assert.strictEqual(err.code, 'ENOTDIR')
}
})
})
})
})

View file

@ -0,0 +1,153 @@
'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', 'ensure')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('+ ensureFile()', () => {
describe('> when file exists', () => {
it('should not do anything', done => {
const file = path.join(TEST_DIR, 'file.txt')
fs.writeFileSync(file, 'blah')
assert(fs.existsSync(file))
fse.ensureFile(file, err => {
assert.ifError(err)
assert(fs.existsSync(file))
done()
})
})
})
describe('> when file does not exist', () => {
it('should create the file', done => {
const file = path.join(TEST_DIR, 'dir/that/does/not/exist', 'file.txt')
assert(!fs.existsSync(file))
fse.ensureFile(file, err => {
assert.ifError(err)
assert(fs.existsSync(file))
done()
})
})
})
describe('> when there is a directory at that path', () => {
it('should error', done => {
const p = path.join(TEST_DIR, 'somedir')
fs.mkdirSync(p)
fse.ensureFile(p, err => {
assert(err)
assert.strictEqual(err.code, 'EISDIR')
done()
})
})
})
})
describe('+ ensureFileSync()', () => {
describe('> when file exists', () => {
it('should not do anything', () => {
const file = path.join(TEST_DIR, 'file.txt')
fs.writeFileSync(file, 'blah')
assert(fs.existsSync(file))
fse.ensureFileSync(file)
assert(fs.existsSync(file))
})
})
describe('> when file does not exist', () => {
it('should create the file', () => {
const file = path.join(TEST_DIR, 'dir/that/does/not/exist', 'file.txt')
assert(!fs.existsSync(file))
fse.ensureFileSync(file)
assert(fs.existsSync(file))
})
})
describe('> when there is a directory at that path', () => {
it('should error', () => {
const p = path.join(TEST_DIR, 'somedir2')
fs.mkdirSync(p)
assert.throws(() => {
try {
fse.ensureFileSync(p)
} catch (e) {
assert.strictEqual(e.code, 'EISDIR')
throw e
}
})
})
})
})
describe('+ ensureDir()', () => {
describe('> when dir exists', () => {
it('should not do anything', done => {
const dir = path.join(TEST_DIR, 'dir/does/not/exist')
fse.mkdirpSync(dir)
assert(fs.existsSync(dir))
fse.ensureDir(dir, err => {
assert.ifError(err)
assert(fs.existsSync(dir))
done()
})
})
})
describe('> when dir does not exist', () => {
it('should create the dir', done => {
const dir = path.join(TEST_DIR, 'dir/that/does/not/exist')
assert(!fs.existsSync(dir))
fse.ensureDir(dir, err => {
assert.ifError(err)
assert(fs.existsSync(dir))
done()
})
})
})
})
describe('+ ensureDirSync()', () => {
describe('> when dir exists', () => {
it('should not do anything', () => {
const dir = path.join(TEST_DIR, 'dir/does/not/exist')
fse.mkdirpSync(dir)
assert(fs.existsSync(dir))
fse.ensureDirSync(dir)
assert(fs.existsSync(dir))
})
})
describe('> when dir does not exist', () => {
it('should create the dir', () => {
const dir = path.join(TEST_DIR, 'dir/that/does/not/exist')
assert(!fs.existsSync(dir))
fse.ensureDirSync(dir)
assert(fs.existsSync(dir))
})
})
})
})

View file

@ -0,0 +1,217 @@
'use strict'
const CWD = process.cwd()
const fs = require('graceful-fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
const ensureLink = fse.ensureLink
const ensureLinkSync = fse.ensureLinkSync
/* global afterEach, beforeEach, describe, it, after, before */
describe('fse-ensure-link', () => {
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure-symlink')
const tests = [
// [[srcpath, dstpath], fs.link expect, ensureLink expect]
[['./foo.txt', './link.txt'], 'file-success', 'file-success'],
[['./foo.txt', './dir-foo/link.txt'], 'file-success', 'file-success'],
[['./foo.txt', './empty-dir/link.txt'], 'file-success', 'file-success'],
[['./foo.txt', './real-alpha/link.txt'], 'file-success', 'file-success'],
[['./foo.txt', './real-alpha/real-beta/link.txt'], 'file-success', 'file-success'],
[['./foo.txt', './real-alpha/real-beta/real-gamma/link.txt'], 'file-success', 'file-success'],
[['./foo.txt', './alpha/link.txt'], 'file-error', 'file-success'],
[['./foo.txt', './alpha/beta/link.txt'], 'file-error', 'file-success'],
[['./foo.txt', './alpha/beta/gamma/link.txt'], 'file-error', 'file-success'],
[['./foo.txt', './link-foo.txt'], 'file-error', 'file-success'],
[['./dir-foo/foo.txt', './link-foo.txt'], 'file-error', 'file-error'],
[['./missing.txt', './link.txt'], 'file-error', 'file-error'],
[['./missing.txt', './missing-dir/link.txt'], 'file-error', 'file-error'],
[['./foo.txt', './link.txt'], 'file-success', 'file-success'],
[['./dir-foo/foo.txt', './link.txt'], 'file-success', 'file-success'],
[['./missing.txt', './link.txt'], 'file-error', 'file-error'],
[['../foo.txt', './link.txt'], 'file-error', 'file-error'],
[['../dir-foo/foo.txt', './link.txt'], 'file-error', 'file-error'],
// error is thrown if destination path exists
[['./foo.txt', './dir-foo/foo.txt'], 'file-error', 'file-error'],
[[path.resolve(path.join(TEST_DIR, './foo.txt')), './link.txt'], 'file-success', 'file-success'],
[[path.resolve(path.join(TEST_DIR, './dir-foo/foo.txt')), './link.txt'], 'file-success', 'file-success'],
[[path.resolve(path.join(TEST_DIR, './missing.txt')), './link.txt'], 'file-error', 'file-error'],
[[path.resolve(path.join(TEST_DIR, '../foo.txt')), './link.txt'], 'file-error', 'file-error'],
[[path.resolve(path.join(TEST_DIR, '../dir-foo/foo.txt')), './link.txt'], 'file-error', 'file-error']
]
before(() => {
fse.emptyDirSync(TEST_DIR)
process.chdir(TEST_DIR)
})
beforeEach(() => {
fs.writeFileSync('./foo.txt', 'foo\n')
fse.mkdirsSync('empty-dir')
fse.mkdirsSync('dir-foo')
fs.writeFileSync('dir-foo/foo.txt', 'dir-foo\n')
fse.mkdirsSync('dir-bar')
fs.writeFileSync('dir-bar/bar.txt', 'dir-bar\n')
fse.mkdirsSync('real-alpha/real-beta/real-gamma')
fs.linkSync('foo.txt', 'link-foo.txt')
})
afterEach(done => fse.emptyDir(TEST_DIR, done))
after(() => {
process.chdir(CWD)
fse.removeSync(TEST_DIR)
})
function fileSuccess (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create link file using src ${srcpath} and dst ${dstpath}`, done => {
const callback = err => {
if (err) return done(err)
const srcContent = fs.readFileSync(srcpath, 'utf8')
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isFile()
const dstContent = fs.readFileSync(dstpath, 'utf8')
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert.strictEqual(srcContent, dstContent)
assert(dstDirContents.indexOf(dstBasename) >= 0)
return done()
}
args.push(callback)
return fn(...args)
})
}
function fileError (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should return error when creating link file using src ${srcpath} and dst ${dstpath}`, done => {
const dstdirExistsBefore = fs.existsSync(path.dirname(dstpath))
const callback = err => {
assert.strictEqual(err instanceof Error, true)
// ensure that directories aren't created if there's an error
const dstdirExistsAfter = fs.existsSync(path.dirname(dstpath))
assert.strictEqual(dstdirExistsBefore, dstdirExistsAfter)
return done()
}
args.push(callback)
return fn(...args)
})
}
function fileSuccessSync (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create link file using src ${srcpath} and dst ${dstpath}`, () => {
fn(...args)
const srcContent = fs.readFileSync(srcpath, 'utf8')
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isFile()
const dstContent = fs.readFileSync(dstpath, 'utf8')
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert.strictEqual(srcContent, dstContent)
assert(dstDirContents.indexOf(dstBasename) >= 0)
})
}
function fileErrorSync (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should throw error using src ${srcpath} and dst ${dstpath}`, () => {
// will fail if dstdir is created and there's an error
const dstdirExistsBefore = fs.existsSync(path.dirname(dstpath))
let err = null
try {
fn(...args)
} catch (e) {
err = e
}
assert.strictEqual(err instanceof Error, true)
const dstdirExistsAfter = fs.existsSync(path.dirname(dstpath))
assert.strictEqual(dstdirExistsBefore, dstdirExistsAfter)
})
}
describe('fs.link()', () => {
const fn = fs.link
tests.forEach(test => {
const args = test[0].slice(0)
const nativeBehavior = test[1]
// const newBehavior = test[2]
if (nativeBehavior === 'file-success') fileSuccess(args, fn)
if (nativeBehavior === 'file-error') fileError(args, fn)
})
})
describe('ensureLink()', () => {
const fn = ensureLink
tests.forEach(test => {
const args = test[0].slice(0)
// const nativeBehavior = test[1]
const newBehavior = test[2]
if (newBehavior === 'file-success') fileSuccess(args, fn)
if (newBehavior === 'file-error') fileError(args, fn)
})
})
describe('ensureLink() promise support', () => {
tests.filter(test => test[2] === 'file-success').forEach(test => {
const args = test[0].slice(0)
const srcpath = args[0]
const dstpath = args[1]
it(`should create link file using src ${srcpath} and dst ${dstpath}`, () => {
return ensureLink(srcpath, dstpath)
.then(() => {
const srcContent = fs.readFileSync(srcpath, 'utf8')
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isFile()
const dstContent = fs.readFileSync(dstpath, 'utf8')
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert.strictEqual(srcContent, dstContent)
assert(dstDirContents.indexOf(dstBasename) >= 0)
})
})
})
})
describe('fs.linkSync()', () => {
const fn = fs.linkSync
tests.forEach(test => {
const args = test[0].slice(0)
const nativeBehavior = test[1]
// const newBehavior = test[2]
if (nativeBehavior === 'file-success') fileSuccessSync(args, fn)
if (nativeBehavior === 'file-error') fileErrorSync(args, fn)
})
})
describe('ensureLinkSync()', () => {
const fn = ensureLinkSync
tests.forEach(test => {
const args = test[0].slice(0)
// const nativeBehavior = test[1]
const newBehavior = test[2]
if (newBehavior === 'file-success') fileSuccessSync(args, fn)
if (newBehavior === 'file-error') fileErrorSync(args, fn)
})
})
})

View file

@ -0,0 +1,89 @@
'use strict'
const CWD = process.cwd()
const fs = require('graceful-fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
const _symlinkPaths = require('../symlink-paths')
const symlinkPaths = _symlinkPaths.symlinkPaths
const symlinkPathsSync = _symlinkPaths.symlinkPathsSync
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure-symlink')
/* global afterEach, beforeEach, describe, it, after, before */
describe('symlink-type', () => {
before(() => {
fse.emptyDirSync(TEST_DIR)
process.chdir(TEST_DIR)
})
beforeEach(() => {
fs.writeFileSync('./foo.txt', 'foo\n')
fse.mkdirsSync('./empty-dir')
fse.mkdirsSync('./dir-foo')
fs.writeFileSync('./dir-foo/foo.txt', 'dir-foo\n')
fse.mkdirsSync('./dir-bar')
fs.writeFileSync('./dir-bar/bar.txt', 'dir-bar\n')
fse.mkdirsSync('./real-alpha/real-beta/real-gamma')
})
afterEach(done => fse.emptyDir(TEST_DIR, done))
after(() => {
process.chdir(CWD)
fse.removeSync(TEST_DIR)
})
const tests = [
[['foo.txt', 'symlink.txt'], { toCwd: 'foo.txt', toDst: 'foo.txt' }], // smart && nodestyle
[['foo.txt', 'empty-dir/symlink.txt'], { toCwd: 'foo.txt', toDst: '../foo.txt' }], // smart
[['../foo.txt', 'empty-dir/symlink.txt'], { toCwd: 'foo.txt', toDst: '../foo.txt' }], // nodestyle
[['foo.txt', 'dir-bar/symlink.txt'], { toCwd: 'foo.txt', toDst: '../foo.txt' }], // smart
[['../foo.txt', 'dir-bar/symlink.txt'], { toCwd: 'foo.txt', toDst: '../foo.txt' }], // nodestyle
// this is to preserve node's symlink capability these arguments say create
// a link to 'dir-foo/foo.txt' this works because it exists this is unlike
// the previous example with 'empty-dir' because 'empty-dir/foo.txt' does not exist.
[['foo.txt', 'dir-foo/symlink.txt'], { toCwd: 'dir-foo/foo.txt', toDst: 'foo.txt' }], // nodestyle
[['foo.txt', 'real-alpha/real-beta/real-gamma/symlink.txt'], { toCwd: 'foo.txt', toDst: '../../../foo.txt' }]
]
// formats paths to pass on multiple operating systems
tests.forEach(test => {
test[0][0] = path.join(test[0][0])
test[0][1] = path.join(test[0][1])
test[1] = {
toCwd: path.join(test[1].toCwd),
toDst: path.join(test[1].toDst)
}
})
describe('symlinkPaths()', () => {
tests.forEach(test => {
const args = test[0].slice(0)
const expectedRelativePaths = test[1]
it(`should return '${JSON.stringify(expectedRelativePaths)}' when src '${args[0]}' and dst is '${args[1]}'`, done => {
const callback = (err, relativePaths) => {
if (err) done(err)
assert.deepStrictEqual(relativePaths, expectedRelativePaths)
done()
}
args.push(callback)
return symlinkPaths(...args)
})
})
})
describe('symlinkPathsSync()', () => {
tests.forEach(test => {
const args = test[0].slice(0)
const expectedRelativePaths = test[1]
it(`should return '${JSON.stringify(expectedRelativePaths)}' when src '${args[0]}' and dst is '${args[1]}'`, () => {
const relativePaths = symlinkPathsSync(...args)
assert.deepStrictEqual(relativePaths, expectedRelativePaths)
})
})
})
})

View file

@ -0,0 +1,120 @@
'use strict'
const CWD = process.cwd()
const fs = require('graceful-fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
const _symlinkType = require('../symlink-type')
const symlinkType = _symlinkType.symlinkType
const symlinkTypeSync = _symlinkType.symlinkTypeSync
const TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure-symlink')
/* global afterEach, beforeEach, describe, it, after, before */
describe('symlink-type', () => {
before(() => {
fse.emptyDirSync(TEST_DIR)
process.chdir(TEST_DIR)
})
beforeEach(() => {
fs.writeFileSync('./foo.txt', 'foo\n')
fse.mkdirsSync('./empty-dir')
fse.mkdirsSync('./dir-foo')
fs.writeFileSync('./dir-foo/foo.txt', 'dir-foo\n')
fse.mkdirsSync('./dir-bar')
fs.writeFileSync('./dir-bar/bar.txt', 'dir-bar\n')
fse.mkdirsSync('./real-alpha/real-beta/real-gamma')
})
afterEach(done => fse.emptyDir(TEST_DIR, done))
after(() => {
process.chdir(CWD)
fse.removeSync(TEST_DIR)
})
const tests = {
success: [
// [{arguments} [srcpath, dirpath, [type] , result]
// smart file type checking
[['./foo.txt'], 'file'],
[['./empty-dir'], 'dir'],
[['./dir-foo/foo.txt'], 'file'],
[['./dir-bar'], 'dir'],
[['./dir-bar/bar.txt'], 'file'],
[['./real-alpha/real-beta/real-gamma'], 'dir'],
// force dir
[['./foo.txt', 'dir'], 'dir'],
[['./empty-dir', 'dir'], 'dir'],
[['./dir-foo/foo.txt', 'dir'], 'dir'],
[['./dir-bar', 'dir'], 'dir'],
[['./dir-bar/bar.txt', 'dir'], 'dir'],
[['./real-alpha/real-beta/real-gamma', 'dir'], 'dir'],
// force file
[['./foo.txt', 'file'], 'file'],
[['./empty-dir', 'file'], 'file'],
[['./dir-foo/foo.txt', 'file'], 'file'],
[['./dir-bar', 'file'], 'file'],
[['./dir-bar/bar.txt', 'file'], 'file'],
[['./real-alpha/real-beta/real-gamma', 'file'], 'file'],
// default for files or dirs that don't exist is file
[['./missing.txt'], 'file'],
[['./missing'], 'file'],
[['./missing.txt'], 'file'],
[['./missing'], 'file'],
[['./empty-dir/missing.txt'], 'file'],
[['./empty-dir/missing'], 'file'],
[['./empty-dir/missing.txt'], 'file'],
[['./empty-dir/missing'], 'file'],
// when src doesnt exist and provided type 'file'
[['./missing.txt', 'file'], 'file'],
[['./missing', 'file'], 'file'],
[['./missing.txt', 'file'], 'file'],
[['./missing', 'file'], 'file'],
[['./empty-dir/missing.txt', 'file'], 'file'],
[['./empty-dir/missing', 'file'], 'file'],
[['./empty-dir/missing.txt', 'file'], 'file'],
[['./empty-dir/missing', 'file'], 'file'],
// when src doesnt exist and provided type 'dir'
[['./missing.txt', 'dir'], 'dir'],
[['./missing', 'dir'], 'dir'],
[['./missing.txt', 'dir'], 'dir'],
[['./missing', 'dir'], 'dir'],
[['./empty-dir/missing.txt', 'dir'], 'dir'],
[['./empty-dir/missing', 'dir'], 'dir'],
[['./empty-dir/missing.txt', 'dir'], 'dir'],
[['./empty-dir/missing', 'dir'], 'dir']
]
}
describe('symlinkType()', () => {
tests.success.forEach(test => {
const args = test[0].slice(0)
const expectedType = test[1]
it(`should return '${expectedType}' when src '${args[0]}'`, done => {
const callback = (err, type) => {
if (err) done(err)
assert.strictEqual(type, expectedType)
done()
}
args.push(callback)
return symlinkType(...args)
})
})
})
describe('symlinkTypeSync()', () => {
tests.success.forEach(test => {
const args = test[0]
const expectedType = test[1]
it(`should return '${expectedType}' when src '${args[0]}'`, () => {
const type = symlinkTypeSync(...args)
assert.strictEqual(type, expectedType)
})
})
})
})

View file

@ -0,0 +1,359 @@
'use strict'
const CWD = process.cwd()
const fs = require('graceful-fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
const _symlinkPaths = require('../symlink-paths')
const symlinkPathsSync = _symlinkPaths.symlinkPathsSync
const ensureSymlink = fse.ensureSymlink
const ensureSymlinkSync = fse.ensureSymlinkSync
/* global afterEach, beforeEach, describe, it, after, before */
let TEST_DIR
describe('fse-ensure-symlink', () => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure-symlink')
const tests = [
// [[srcpath, dstpath], fs.symlink expect, fse.ensureSymlink expect]
[['./foo.txt', './symlink.txt'], 'file-success', 'file-success'],
[['../foo.txt', './empty-dir/symlink.txt'], 'file-success', 'file-success'],
[['../foo.txt', './empty-dir/symlink.txt'], 'file-success', 'file-success'],
[['./foo.txt', './dir-foo/symlink.txt'], 'file-success', 'file-success'],
[['./foo.txt', './empty-dir/symlink.txt'], 'file-broken', 'file-success'],
[['./foo.txt', './real-alpha/symlink.txt'], 'file-broken', 'file-success'],
[['./foo.txt', './real-alpha/real-beta/symlink.txt'], 'file-broken', 'file-success'],
[['./foo.txt', './real-alpha/real-beta/real-gamma/symlink.txt'], 'file-broken', 'file-success'],
[['./foo.txt', './alpha/symlink.txt'], 'file-error', 'file-success'],
[['./foo.txt', './alpha/beta/symlink.txt'], 'file-error', 'file-success'],
[['./foo.txt', './alpha/beta/gamma/symlink.txt'], 'file-error', 'file-success'],
[['./foo.txt', './real-symlink.txt'], 'file-error', 'file-success'],
[['./dir-foo/foo.txt', './real-symlink.txt'], 'file-error', 'file-error'],
[['./missing.txt', './symlink.txt'], 'file-broken', 'file-error'],
[['./missing.txt', './missing-dir/symlink.txt'], 'file-error', 'file-error'],
// error is thrown if destination path exists
[['./foo.txt', './dir-foo/foo.txt'], 'file-error', 'file-error'],
[['./dir-foo', './symlink-dir-foo'], 'dir-success', 'dir-success'],
[['../dir-bar', './dir-foo/symlink-dir-bar'], 'dir-success', 'dir-success'],
[['./dir-bar', './dir-foo/symlink-dir-bar'], 'dir-broken', 'dir-success'],
[['./dir-bar', './empty-dir/symlink-dir-bar'], 'dir-broken', 'dir-success'],
[['./dir-bar', './real-alpha/symlink-dir-bar'], 'dir-broken', 'dir-success'],
[['./dir-bar', './real-alpha/real-beta/symlink-dir-bar'], 'dir-broken', 'dir-success'],
[['./dir-bar', './real-alpha/real-beta/real-gamma/symlink-dir-bar'], 'dir-broken', 'dir-success'],
[['./dir-foo', './alpha/dir-foo'], 'dir-error', 'dir-success'],
[['./dir-foo', './alpha/beta/dir-foo'], 'dir-error', 'dir-success'],
[['./dir-foo', './alpha/beta/gamma/dir-foo'], 'dir-error', 'dir-success'],
[['./dir-foo', './real-symlink-dir-foo'], 'dir-error', 'dir-success'],
[['./dir-bar', './real-symlink-dir-foo'], 'dir-error', 'dir-error'],
[['./missing', './dir-foo/symlink-dir-missing'], 'dir-broken', 'dir-error'],
// error is thrown if destination path exists
[['./dir-foo', './real-alpha/real-beta'], 'dir-error', 'dir-error'],
[[path.resolve(path.join(TEST_DIR, './foo.txt')), './symlink.txt'], 'file-success', 'file-success'],
[[path.resolve(path.join(TEST_DIR, './dir-foo/foo.txt')), './symlink.txt'], 'file-success', 'file-success'],
[[path.resolve(path.join(TEST_DIR, './missing.txt')), './symlink.txt'], 'file-broken', 'file-error'],
[[path.resolve(path.join(TEST_DIR, '../foo.txt')), './symlink.txt'], 'file-broken', 'file-error'],
[[path.resolve(path.join(TEST_DIR, '../dir-foo/foo.txt')), './symlink.txt'], 'file-broken', 'file-error']
]
before(() => {
fse.emptyDirSync(TEST_DIR)
process.chdir(TEST_DIR)
})
beforeEach(() => {
fs.writeFileSync('./foo.txt', 'foo\n')
fse.mkdirsSync('empty-dir')
fse.mkdirsSync('dir-foo')
fs.writeFileSync('dir-foo/foo.txt', 'dir-foo\n')
fse.mkdirsSync('dir-bar')
fs.writeFileSync('dir-bar/bar.txt', 'dir-bar\n')
fse.mkdirsSync('real-alpha/real-beta/real-gamma')
fs.symlinkSync('foo.txt', 'real-symlink.txt')
fs.symlinkSync('dir-foo', 'real-symlink-dir-foo')
})
afterEach(done => fse.emptyDir(TEST_DIR, done))
after(() => {
process.chdir(CWD)
fse.removeSync(TEST_DIR)
})
function fileSuccess (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create symlink file using src ${srcpath} and dst ${dstpath}`, done => {
const callback = err => {
if (err) return done(err)
const relative = symlinkPathsSync(srcpath, dstpath)
const srcContent = fs.readFileSync(relative.toCwd, 'utf8')
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstContent = fs.readFileSync(dstpath, 'utf8')
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert.strictEqual(srcContent, dstContent)
assert(dstDirContents.indexOf(dstBasename) >= 0)
return done()
}
args.push(callback)
return fn(...args)
})
}
function fileBroken (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create broken symlink file using src ${srcpath} and dst ${dstpath}`, done => {
const callback = err => {
if (err) return done(err)
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert(dstDirContents.indexOf(dstBasename) >= 0)
assert.throws(() => fs.readFileSync(dstpath, 'utf8'), Error)
return done()
}
args.push(callback)
return fn(...args)
})
}
function fileError (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should return error when creating symlink file using src ${srcpath} and dst ${dstpath}`, done => {
const dstdirExistsBefore = fs.existsSync(path.dirname(dstpath))
const callback = err => {
assert.strictEqual(err instanceof Error, true)
// ensure that directories aren't created if there's an error
const dstdirExistsAfter = fs.existsSync(path.dirname(dstpath))
assert.strictEqual(dstdirExistsBefore, dstdirExistsAfter)
return done()
}
args.push(callback)
return fn(...args)
})
}
function dirSuccess (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create symlink dir using src ${srcpath} and dst ${dstpath}`, done => {
const callback = err => {
if (err) return done(err)
const relative = symlinkPathsSync(srcpath, dstpath)
const srcContents = fs.readdirSync(relative.toCwd)
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstContents = fs.readdirSync(dstpath)
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert.deepStrictEqual(srcContents, dstContents)
assert(dstDirContents.indexOf(dstBasename) >= 0)
return done()
}
args.push(callback)
return fn(...args)
})
}
function dirBroken (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create broken symlink dir using src ${srcpath} and dst ${dstpath}`, done => {
const callback = err => {
if (err) return done(err)
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert(dstDirContents.indexOf(dstBasename) >= 0)
assert.throws(() => fs.readdirSync(dstpath), Error)
return done()
}
args.push(callback)
return fn(...args)
})
}
function dirError (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should return error when creating symlink dir using src ${srcpath} and dst ${dstpath}`, done => {
const dstdirExistsBefore = fs.existsSync(path.dirname(dstpath))
const callback = err => {
assert.strictEqual(err instanceof Error, true)
// ensure that directories aren't created if there's an error
const dstdirExistsAfter = fs.existsSync(path.dirname(dstpath))
assert.strictEqual(dstdirExistsBefore, dstdirExistsAfter)
return done()
}
args.push(callback)
return fn(...args)
})
}
function fileSuccessSync (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create symlink file using src ${srcpath} and dst ${dstpath}`, () => {
fn(...args)
const relative = symlinkPathsSync(srcpath, dstpath)
const srcContent = fs.readFileSync(relative.toCwd, 'utf8')
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstContent = fs.readFileSync(dstpath, 'utf8')
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert.strictEqual(srcContent, dstContent)
assert(dstDirContents.indexOf(dstBasename) >= 0)
})
}
function fileBrokenSync (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create broken symlink file using src ${srcpath} and dst ${dstpath}`, () => {
fn(...args)
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert(dstDirContents.indexOf(dstBasename) >= 0)
assert.throws(() => fs.readFileSync(dstpath, 'utf8'), Error)
})
}
function fileErrorSync (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should throw error using src ${srcpath} and dst ${dstpath}`, () => {
const dstdirExistsBefore = fs.existsSync(path.dirname(dstpath))
let err = null
try {
fn(...args)
} catch (e) {
err = e
}
assert.strictEqual(err instanceof Error, true)
const dstdirExistsAfter = fs.existsSync(path.dirname(dstpath))
assert.strictEqual(dstdirExistsBefore, dstdirExistsAfter)
})
}
function dirSuccessSync (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create symlink dir using src ${srcpath} and dst ${dstpath}`, () => {
fn(...args)
const relative = symlinkPathsSync(srcpath, dstpath)
const srcContents = fs.readdirSync(relative.toCwd)
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstContents = fs.readdirSync(dstpath)
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert.deepStrictEqual(srcContents, dstContents)
assert(dstDirContents.indexOf(dstBasename) >= 0)
})
}
function dirBrokenSync (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should create broken symlink dir using src ${srcpath} and dst ${dstpath}`, () => {
fn(...args)
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert(dstDirContents.indexOf(dstBasename) >= 0)
assert.throws(() => fs.readdirSync(dstpath), Error)
})
}
function dirErrorSync (args, fn) {
const srcpath = args[0]
const dstpath = args[1]
it(`should throw error when creating symlink dir using src ${srcpath} and dst ${dstpath}`, () => {
const dstdirExistsBefore = fs.existsSync(path.dirname(dstpath))
let err = null
try {
fn(...args)
} catch (e) {
err = e
}
assert.strictEqual(err instanceof Error, true)
const dstdirExistsAfter = fs.existsSync(path.dirname(dstpath))
assert.strictEqual(dstdirExistsBefore, dstdirExistsAfter)
})
}
describe('ensureSymlink()', () => {
const fn = ensureSymlink
tests.forEach(test => {
const args = test[0]
// const nativeBehavior = test[1]
const newBehavior = test[2]
if (newBehavior === 'file-success') fileSuccess(args, fn)
if (newBehavior === 'file-broken') fileBroken(args, fn)
if (newBehavior === 'file-error') fileError(args, fn)
if (newBehavior === 'dir-success') dirSuccess(args, fn)
if (newBehavior === 'dir-broken') dirBroken(args, fn)
if (newBehavior === 'dir-error') dirError(args, fn)
})
})
describe('ensureSymlink() promise support', () => {
tests.filter(test => test[2] === 'file-success').forEach(test => {
const args = test[0]
const srcpath = args[0]
const dstpath = args[1]
it(`should create symlink file using src ${srcpath} and dst ${dstpath}`, () => {
return ensureSymlink(srcpath, dstpath)
.then(() => {
const relative = symlinkPathsSync(srcpath, dstpath)
const srcContent = fs.readFileSync(relative.toCwd, 'utf8')
const dstDir = path.dirname(dstpath)
const dstBasename = path.basename(dstpath)
const isSymlink = fs.lstatSync(dstpath).isSymbolicLink()
const dstContent = fs.readFileSync(dstpath, 'utf8')
const dstDirContents = fs.readdirSync(dstDir)
assert.strictEqual(isSymlink, true)
assert.strictEqual(srcContent, dstContent)
assert(dstDirContents.indexOf(dstBasename) >= 0)
})
})
})
})
describe('ensureSymlinkSync()', () => {
const fn = ensureSymlinkSync
tests.forEach(test => {
const args = test[0]
// const nativeBehavior = test[1]
const newBehavior = test[2]
if (newBehavior === 'file-success') fileSuccessSync(args, fn)
if (newBehavior === 'file-broken') fileBrokenSync(args, fn)
if (newBehavior === 'file-error') fileErrorSync(args, fn)
if (newBehavior === 'dir-success') dirSuccessSync(args, fn)
if (newBehavior === 'dir-broken') dirBrokenSync(args, fn)
if (newBehavior === 'dir-error') dirErrorSync(args, fn)
})
})
})

View file

@ -0,0 +1,66 @@
'use strict'
const u = require('universalify').fromPromise
const path = require('path')
const fs = require('../fs')
const mkdir = require('../mkdirs')
async function createFile (file) {
let stats
try {
stats = await fs.stat(file)
} catch { }
if (stats && stats.isFile()) return
const dir = path.dirname(file)
let dirStats = null
try {
dirStats = await fs.stat(dir)
} catch (err) {
// if the directory doesn't exist, make it
if (err.code === 'ENOENT') {
await mkdir.mkdirs(dir)
await fs.writeFile(file, '')
return
} else {
throw err
}
}
if (dirStats.isDirectory()) {
await fs.writeFile(file, '')
} else {
// parent is not a directory
// This is just to cause an internal ENOTDIR error to be thrown
await fs.readdir(dir)
}
}
function createFileSync (file) {
let stats
try {
stats = fs.statSync(file)
} catch { }
if (stats && stats.isFile()) return
const dir = path.dirname(file)
try {
if (!fs.statSync(dir).isDirectory()) {
// parent is not a directory
// This is just to cause an internal ENOTDIR error to be thrown
fs.readdirSync(dir)
}
} catch (err) {
// If the stat call above failed because the directory doesn't exist, create it
if (err && err.code === 'ENOENT') mkdir.mkdirsSync(dir)
else throw err
}
fs.writeFileSync(file, '')
}
module.exports = {
createFile: u(createFile),
createFileSync
}

View file

@ -0,0 +1,23 @@
'use strict'
const { createFile, createFileSync } = require('./file')
const { createLink, createLinkSync } = require('./link')
const { createSymlink, createSymlinkSync } = require('./symlink')
module.exports = {
// file
createFile,
createFileSync,
ensureFile: createFile,
ensureFileSync: createFileSync,
// link
createLink,
createLinkSync,
ensureLink: createLink,
ensureLinkSync: createLinkSync,
// symlink
createSymlink,
createSymlinkSync,
ensureSymlink: createSymlink,
ensureSymlinkSync: createSymlinkSync
}

View file

@ -0,0 +1,64 @@
'use strict'
const u = require('universalify').fromPromise
const path = require('path')
const fs = require('../fs')
const mkdir = require('../mkdirs')
const { pathExists } = require('../path-exists')
const { areIdentical } = require('../util/stat')
async function createLink (srcpath, dstpath) {
let dstStat
try {
dstStat = await fs.lstat(dstpath)
} catch {
// ignore error
}
let srcStat
try {
srcStat = await fs.lstat(srcpath)
} catch (err) {
err.message = err.message.replace('lstat', 'ensureLink')
throw err
}
if (dstStat && areIdentical(srcStat, dstStat)) return
const dir = path.dirname(dstpath)
const dirExists = await pathExists(dir)
if (!dirExists) {
await mkdir.mkdirs(dir)
}
await fs.link(srcpath, dstpath)
}
function createLinkSync (srcpath, dstpath) {
let dstStat
try {
dstStat = fs.lstatSync(dstpath)
} catch {}
try {
const srcStat = fs.lstatSync(srcpath)
if (dstStat && areIdentical(srcStat, dstStat)) return
} catch (err) {
err.message = err.message.replace('lstat', 'ensureLink')
throw err
}
const dir = path.dirname(dstpath)
const dirExists = fs.existsSync(dir)
if (dirExists) return fs.linkSync(srcpath, dstpath)
mkdir.mkdirsSync(dir)
return fs.linkSync(srcpath, dstpath)
}
module.exports = {
createLink: u(createLink),
createLinkSync
}

View file

@ -0,0 +1,101 @@
'use strict'
const path = require('path')
const fs = require('../fs')
const { pathExists } = require('../path-exists')
const u = require('universalify').fromPromise
/**
* Function that returns two types of paths, one relative to symlink, and one
* relative to the current working directory. Checks if path is absolute or
* relative. If the path is relative, this function checks if the path is
* relative to symlink or relative to current working directory. This is an
* initiative to find a smarter `srcpath` to supply when building symlinks.
* This allows you to determine which path to use out of one of three possible
* types of source paths. The first is an absolute path. This is detected by
* `path.isAbsolute()`. When an absolute path is provided, it is checked to
* see if it exists. If it does it's used, if not an error is returned
* (callback)/ thrown (sync). The other two options for `srcpath` are a
* relative url. By default Node's `fs.symlink` works by creating a symlink
* using `dstpath` and expects the `srcpath` to be relative to the newly
* created symlink. If you provide a `srcpath` that does not exist on the file
* system it results in a broken symlink. To minimize this, the function
* checks to see if the 'relative to symlink' source file exists, and if it
* does it will use it. If it does not, it checks if there's a file that
* exists that is relative to the current working directory, if does its used.
* This preserves the expectations of the original fs.symlink spec and adds
* the ability to pass in `relative to current working direcotry` paths.
*/
async function symlinkPaths (srcpath, dstpath) {
if (path.isAbsolute(srcpath)) {
try {
await fs.lstat(srcpath)
} catch (err) {
err.message = err.message.replace('lstat', 'ensureSymlink')
throw err
}
return {
toCwd: srcpath,
toDst: srcpath
}
}
const dstdir = path.dirname(dstpath)
const relativeToDst = path.join(dstdir, srcpath)
const exists = await pathExists(relativeToDst)
if (exists) {
return {
toCwd: relativeToDst,
toDst: srcpath
}
}
try {
await fs.lstat(srcpath)
} catch (err) {
err.message = err.message.replace('lstat', 'ensureSymlink')
throw err
}
return {
toCwd: srcpath,
toDst: path.relative(dstdir, srcpath)
}
}
function symlinkPathsSync (srcpath, dstpath) {
if (path.isAbsolute(srcpath)) {
const exists = fs.existsSync(srcpath)
if (!exists) throw new Error('absolute srcpath does not exist')
return {
toCwd: srcpath,
toDst: srcpath
}
}
const dstdir = path.dirname(dstpath)
const relativeToDst = path.join(dstdir, srcpath)
const exists = fs.existsSync(relativeToDst)
if (exists) {
return {
toCwd: relativeToDst,
toDst: srcpath
}
}
const srcExists = fs.existsSync(srcpath)
if (!srcExists) throw new Error('relative srcpath does not exist')
return {
toCwd: srcpath,
toDst: path.relative(dstdir, srcpath)
}
}
module.exports = {
symlinkPaths: u(symlinkPaths),
symlinkPathsSync
}

View file

@ -0,0 +1,34 @@
'use strict'
const fs = require('../fs')
const u = require('universalify').fromPromise
async function symlinkType (srcpath, type) {
if (type) return type
let stats
try {
stats = await fs.lstat(srcpath)
} catch {
return 'file'
}
return (stats && stats.isDirectory()) ? 'dir' : 'file'
}
function symlinkTypeSync (srcpath, type) {
if (type) return type
let stats
try {
stats = fs.lstatSync(srcpath)
} catch {
return 'file'
}
return (stats && stats.isDirectory()) ? 'dir' : 'file'
}
module.exports = {
symlinkType: u(symlinkType),
symlinkTypeSync
}

View file

@ -0,0 +1,67 @@
'use strict'
const u = require('universalify').fromPromise
const path = require('path')
const fs = require('../fs')
const { mkdirs, mkdirsSync } = require('../mkdirs')
const { symlinkPaths, symlinkPathsSync } = require('./symlink-paths')
const { symlinkType, symlinkTypeSync } = require('./symlink-type')
const { pathExists } = require('../path-exists')
const { areIdentical } = require('../util/stat')
async function createSymlink (srcpath, dstpath, type) {
let stats
try {
stats = await fs.lstat(dstpath)
} catch { }
if (stats && stats.isSymbolicLink()) {
const [srcStat, dstStat] = await Promise.all([
fs.stat(srcpath),
fs.stat(dstpath)
])
if (areIdentical(srcStat, dstStat)) return
}
const relative = await symlinkPaths(srcpath, dstpath)
srcpath = relative.toDst
const toType = await symlinkType(relative.toCwd, type)
const dir = path.dirname(dstpath)
if (!(await pathExists(dir))) {
await mkdirs(dir)
}
return fs.symlink(srcpath, dstpath, toType)
}
function createSymlinkSync (srcpath, dstpath, type) {
let stats
try {
stats = fs.lstatSync(dstpath)
} catch { }
if (stats && stats.isSymbolicLink()) {
const srcStat = fs.statSync(srcpath)
const dstStat = fs.statSync(dstpath)
if (areIdentical(srcStat, dstStat)) return
}
const relative = symlinkPathsSync(srcpath, dstpath)
srcpath = relative.toDst
type = symlinkTypeSync(relative.toCwd, type)
const dir = path.dirname(dstpath)
const exists = fs.existsSync(dir)
if (exists) return fs.symlinkSync(srcpath, dstpath, type)
mkdirsSync(dir)
return fs.symlinkSync(srcpath, dstpath, type)
}
module.exports = {
createSymlink: u(createSymlink),
createSymlinkSync
}

View file

@ -0,0 +1,29 @@
'use strict'
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* eslint-env mocha */
describe('fs.copyFile', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'fs-copyfile')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
it('supports promises', () => {
const src = path.join(TEST_DIR, 'init.txt')
const dest = path.join(TEST_DIR, 'copy.txt')
fse.writeFileSync(src, 'hello')
return fse.copyFile(src, dest).then(() => {
const data = fse.readFileSync(dest, 'utf8')
assert.strictEqual(data, 'hello')
})
})
})

View file

@ -0,0 +1,32 @@
'use strict'
const os = require('os')
const fs = require('fs')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
/* global afterEach, beforeEach, describe, it */
describe('native fs', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'native-fs')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
it('should use native fs methods', () => {
const file = path.join(TEST_DIR, 'write.txt')
fse.writeFileSync(file, 'hello')
const data = fse.readFileSync(file, 'utf8')
assert.strictEqual(data, 'hello')
})
it('should have native fs constants', () => {
assert.strictEqual(fse.constants.F_OK, fs.constants.F_OK)
assert.strictEqual(fse.F_OK, fs.F_OK) // soft deprecated usage, but still available
})
})

View file

@ -0,0 +1,270 @@
'use strict'
/* eslint-env mocha */
const assert = require('assert')
const path = require('path')
const crypto = require('crypto')
const os = require('os')
const fs = require('../..')
const SIZE = 1000
describe('fs.read()', () => {
let TEST_FILE
let TEST_DATA
let TEST_FD
beforeEach(() => {
TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'read-test-file')
TEST_DATA = crypto.randomBytes(SIZE)
fs.writeFileSync(TEST_FILE, TEST_DATA)
TEST_FD = fs.openSync(TEST_FILE, 'r')
})
afterEach(() => {
return fs.close(TEST_FD)
.then(() => fs.remove(TEST_FILE))
})
describe('with promises', () => {
it('returns an object', () => {
return fs.read(TEST_FD, Buffer.alloc(SIZE), 0, SIZE, 0)
.then(results => {
const bytesRead = results.bytesRead
const buffer = results.buffer
assert.strictEqual(bytesRead, SIZE, 'bytesRead is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
})
})
it('returns an object when position is not set', () => {
return fs.read(TEST_FD, Buffer.alloc(SIZE), 0, SIZE)
.then(results => {
const bytesRead = results.bytesRead
const buffer = results.buffer
assert.strictEqual(bytesRead, SIZE, 'bytesRead is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
})
})
})
describe('with callbacks', () => {
it('works', done => {
fs.read(TEST_FD, Buffer.alloc(SIZE), 0, SIZE, 0, (err, bytesRead, buffer) => {
assert.ifError(err)
assert.strictEqual(bytesRead, SIZE, 'bytesRead is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
done()
})
})
it('works when position is null', done => {
fs.read(TEST_FD, Buffer.alloc(SIZE), 0, SIZE, null, (err, bytesRead, buffer) => {
assert.ifError(err)
assert.strictEqual(bytesRead, SIZE, 'bytesRead is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
done()
})
})
})
})
describe('fs.write()', () => {
let TEST_FILE
let TEST_DATA
let TEST_FD
beforeEach(() => {
TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'write-test-file')
TEST_DATA = crypto.randomBytes(SIZE)
fs.ensureDirSync(path.dirname(TEST_FILE))
TEST_FD = fs.openSync(TEST_FILE, 'w')
})
afterEach(() => {
return fs.close(TEST_FD)
.then(() => fs.remove(TEST_FILE))
})
describe('with promises', () => {
it('returns an object', () => {
return fs.write(TEST_FD, TEST_DATA, 0, SIZE, 0)
.then(results => {
const bytesWritten = results.bytesWritten
const buffer = results.buffer
assert.strictEqual(bytesWritten, SIZE, 'bytesWritten is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
})
})
it('returns an object when minimal arguments are passed', () => {
return fs.write(TEST_FD, TEST_DATA)
.then(results => {
const bytesWritten = results.bytesWritten
const buffer = results.buffer
assert.strictEqual(bytesWritten, SIZE, 'bytesWritten is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
})
})
it('returns an object when writing a string', () => {
const message = 'Hello World!'
return fs.write(TEST_FD, message)
.then(results => {
const bytesWritten = results.bytesWritten
const buffer = results.buffer
assert.strictEqual(bytesWritten, message.length, 'bytesWritten is correct')
assert.strictEqual(buffer, message, 'data is correct')
})
})
})
describe('with callbacks', () => {
it('works', done => {
fs.write(TEST_FD, TEST_DATA, 0, SIZE, 0, (err, bytesWritten, buffer) => {
assert.ifError(err)
assert.strictEqual(bytesWritten, SIZE, 'bytesWritten is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
done()
})
})
it('works when minimal arguments are passed', done => {
fs.write(TEST_FD, TEST_DATA, (err, bytesWritten, buffer) => {
assert.ifError(err)
assert.strictEqual(bytesWritten, SIZE, 'bytesWritten is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
done()
})
})
it('works when writing a string', done => {
const message = 'Hello World!'
return fs.write(TEST_FD, message, (err, bytesWritten, buffer) => {
assert.ifError(err)
assert.strictEqual(bytesWritten, message.length, 'bytesWritten is correct')
assert.strictEqual(buffer, message, 'data is correct')
done()
})
})
})
})
describe('fs.readv()', () => {
let TEST_FILE
let TEST_DATA
let TEST_FD
beforeEach(() => {
TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'readv-test-file')
TEST_DATA = crypto.randomBytes(SIZE)
fs.writeFileSync(TEST_FILE, TEST_DATA)
TEST_FD = fs.openSync(TEST_FILE, 'r')
})
afterEach(() => {
return fs.close(TEST_FD)
.then(() => fs.remove(TEST_FILE))
})
describe('with promises', () => {
it('returns an object', () => {
const bufferArray = [Buffer.alloc(SIZE / 2), Buffer.alloc(SIZE / 2)]
return fs.readv(TEST_FD, bufferArray, 0)
.then(({ bytesRead, buffers }) => {
assert.strictEqual(bytesRead, SIZE, 'bytesRead is correct')
assert.deepStrictEqual(buffers, bufferArray, 'returned data matches mutated input param')
assert.deepStrictEqual(Buffer.concat(buffers), TEST_DATA, 'data is correct')
})
})
it('returns an object when minimal arguments are passed', () => {
const bufferArray = [Buffer.alloc(SIZE / 2), Buffer.alloc(SIZE / 2)]
return fs.readv(TEST_FD, bufferArray)
.then(({ bytesRead, buffers }) => {
assert.strictEqual(bytesRead, SIZE, 'bytesRead is correct')
assert.deepStrictEqual(buffers, bufferArray, 'returned data matches mutated input param')
assert.deepStrictEqual(Buffer.concat(buffers), TEST_DATA, 'data is correct')
})
})
})
describe('with callbacks', () => {
it('works', done => {
const bufferArray = [Buffer.alloc(SIZE / 2), Buffer.alloc(SIZE / 2)]
fs.readv(TEST_FD, bufferArray, 0, (err, bytesRead, buffers) => {
assert.ifError(err)
assert.strictEqual(bytesRead, SIZE, 'bytesRead is correct')
assert.deepStrictEqual(buffers, bufferArray, 'returned data matches mutated input param')
assert.deepStrictEqual(Buffer.concat(buffers), TEST_DATA, 'data is correct')
done()
})
})
it('works when minimal arguments are passed', done => {
const bufferArray = [Buffer.alloc(SIZE / 2), Buffer.alloc(SIZE / 2)]
fs.readv(TEST_FD, bufferArray, (err, bytesRead, buffers) => {
assert.ifError(err)
assert.strictEqual(bytesRead, SIZE, 'bytesRead is correct')
assert.deepStrictEqual(buffers, bufferArray, 'returned data matches mutated input param')
assert.deepStrictEqual(Buffer.concat(buffers), TEST_DATA, 'data is correct')
done()
})
})
})
})
describe('fs.writev()', () => {
let TEST_FILE
let TEST_DATA
let TEST_FD
beforeEach(() => {
TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'writev-test-file')
TEST_DATA = [crypto.randomBytes(SIZE / 2), crypto.randomBytes(SIZE / 2)]
fs.ensureDirSync(path.dirname(TEST_FILE))
TEST_FD = fs.openSync(TEST_FILE, 'w')
})
afterEach(() => {
return fs.close(TEST_FD)
.then(() => fs.remove(TEST_FILE))
})
describe('with promises', () => {
it('returns an object', () => {
return fs.writev(TEST_FD, TEST_DATA, 0)
.then(({ bytesWritten, buffers }) => {
assert.strictEqual(bytesWritten, SIZE, 'bytesWritten is correct')
assert.deepStrictEqual(buffers, TEST_DATA, 'data is correct')
})
})
it('returns an object when minimal arguments are passed', () => {
return fs.writev(TEST_FD, TEST_DATA)
.then(({ bytesWritten, buffers }) => {
assert.strictEqual(bytesWritten, SIZE, 'bytesWritten is correct')
assert.deepStrictEqual(buffers, TEST_DATA, 'data is correct')
})
})
})
describe('with callbacks', () => {
it('works', done => {
fs.writev(TEST_FD, TEST_DATA, 0, (err, bytesWritten, buffers) => {
assert.ifError(err)
assert.strictEqual(bytesWritten, SIZE, 'bytesWritten is correct')
assert.deepStrictEqual(buffers, TEST_DATA, 'data is correct')
done()
})
})
it('works when minimal arguments are passed', done => {
fs.writev(TEST_FD, TEST_DATA, (err, bytesWritten, buffers) => {
assert.ifError(err)
assert.strictEqual(bytesWritten, SIZE, 'bytesWritten is correct')
assert.deepStrictEqual(buffers, TEST_DATA, 'data is correct')
done()
})
})
})
})

View file

@ -0,0 +1,51 @@
'use strict'
// This is adapted from https://github.com/normalize/mz
// Copyright (c) 2014-2016 Jonathan Ong me@jongleberry.com and Contributors
/* eslint-env mocha */
const assert = require('assert')
const fs = require('../..')
describe('fs', () => {
it('.stat()', done => {
fs.stat(__filename).then(stats => {
assert.strictEqual(typeof stats.size, 'number')
done()
}).catch(done)
})
it('.statSync()', () => {
const stats = fs.statSync(__filename)
assert.strictEqual(typeof stats.size, 'number')
})
it('.exists()', done => {
fs.exists(__filename).then(exists => {
assert(exists)
done()
}).catch(done)
})
it('.existsSync()', () => {
const exists = fs.existsSync(__filename)
assert(exists)
})
describe('callback support', () => {
it('.stat()', done => {
fs.stat(__filename, (err, stats) => {
assert(!err)
assert.strictEqual(typeof stats.size, 'number')
done()
})
})
// This test is different from mz/fs, since we are a drop-in replacement for native fs
it('.exists()', done => {
fs.exists(__filename, exists => {
assert(exists)
done()
})
})
})
})

View file

@ -0,0 +1,85 @@
'use strict'
const fs = require('fs')
const path = require('path')
const assert = require('assert')
/* eslint-env mocha */
describe('realpath.native does not exist', () => {
let warning
const warningListener = error => {
if (error.name === 'Warning') {
if (error.code.startsWith('fs-extra-WARN0003')) {
warning = error
}
}
}
const realpathNativeBackup = fs.realpath.native
const clearFseCache = () => {
const fsePath = path.dirname(require.resolve('../..'))
for (const entry in require.cache) {
if (entry.startsWith(fsePath)) {
delete require.cache[entry]
}
}
}
before(() => {
process.on('warning', warningListener)
// clear existing require.cache
clearFseCache()
// simulate fs monkey-patch
delete fs.realpath.native
})
after(() => {
process.off('warning', warningListener)
// clear stubbed require.cache
clearFseCache()
// reinstate fs.realpath.native
fs.realpath.native = realpathNativeBackup
})
it('fse should not export realpath.native', done => {
const fse = require('../..')
// next event loop to allow event emitter/listener to happen
setImmediate(() => {
assert(warning, 'fs-extra-WARN0003 should be emitted')
done()
})
assert(!fse.realpath.native)
})
})
describe('realpath.native', () => {
const fse = require('../..')
it('works with callbacks', () => {
fse.realpath.native(__dirname, (err, path) => {
assert.ifError(err)
assert.strictEqual(path, __dirname)
})
})
it('works with promises', (done) => {
fse.realpath.native(__dirname)
.then(path => {
assert.strictEqual(path, __dirname)
done()
})
.catch(done)
})
it('works with sync version', () => {
const path = fse.realpathSync.native(__dirname)
assert.strictEqual(path, __dirname)
})
})

View file

@ -0,0 +1,26 @@
'use strict'
const fse = require('../..')
const os = require('os')
const path = require('path')
const assert = require('assert')
/* eslint-env mocha */
describe('fs.rm', () => {
let TEST_FILE
beforeEach(done => {
TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'fs-rm')
fse.remove(TEST_FILE, done)
})
afterEach(done => fse.remove(TEST_FILE, done))
it('supports promises', () => {
fse.writeFileSync(TEST_FILE, 'hello')
return fse.rm(TEST_FILE).then(() => {
assert(!fse.pathExistsSync(TEST_FILE))
})
})
})

View file

@ -0,0 +1,140 @@
'use strict'
// This is adapted from https://github.com/normalize/mz
// Copyright (c) 2014-2016 Jonathan Ong me@jongleberry.com and Contributors
const u = require('universalify').fromCallback
const fs = require('graceful-fs')
const api = [
'access',
'appendFile',
'chmod',
'chown',
'close',
'copyFile',
'fchmod',
'fchown',
'fdatasync',
'fstat',
'fsync',
'ftruncate',
'futimes',
'lchmod',
'lchown',
'link',
'lstat',
'mkdir',
'mkdtemp',
'open',
'opendir',
'readdir',
'readFile',
'readlink',
'realpath',
'rename',
'rm',
'rmdir',
'stat',
'symlink',
'truncate',
'unlink',
'utimes',
'writeFile'
].filter(key => {
// Some commands are not available on some systems. Ex:
// fs.cp was added in Node.js v16.7.0
// fs.lchown is not available on at least some Linux
return typeof fs[key] === 'function'
})
// Export cloned fs:
Object.assign(exports, fs)
// Universalify async methods:
api.forEach(method => {
exports[method] = u(fs[method])
})
// We differ from mz/fs in that we still ship the old, broken, fs.exists()
// since we are a drop-in replacement for the native module
exports.exists = function (filename, callback) {
if (typeof callback === 'function') {
return fs.exists(filename, callback)
}
return new Promise(resolve => {
return fs.exists(filename, resolve)
})
}
// fs.read(), fs.write(), fs.readv(), & fs.writev() need special treatment due to multiple callback args
exports.read = function (fd, buffer, offset, length, position, callback) {
if (typeof callback === 'function') {
return fs.read(fd, buffer, offset, length, position, callback)
}
return new Promise((resolve, reject) => {
fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => {
if (err) return reject(err)
resolve({ bytesRead, buffer })
})
})
}
// Function signature can be
// fs.write(fd, buffer[, offset[, length[, position]]], callback)
// OR
// fs.write(fd, string[, position[, encoding]], callback)
// We need to handle both cases, so we use ...args
exports.write = function (fd, buffer, ...args) {
if (typeof args[args.length - 1] === 'function') {
return fs.write(fd, buffer, ...args)
}
return new Promise((resolve, reject) => {
fs.write(fd, buffer, ...args, (err, bytesWritten, buffer) => {
if (err) return reject(err)
resolve({ bytesWritten, buffer })
})
})
}
// Function signature is
// s.readv(fd, buffers[, position], callback)
// We need to handle the optional arg, so we use ...args
exports.readv = function (fd, buffers, ...args) {
if (typeof args[args.length - 1] === 'function') {
return fs.readv(fd, buffers, ...args)
}
return new Promise((resolve, reject) => {
fs.readv(fd, buffers, ...args, (err, bytesRead, buffers) => {
if (err) return reject(err)
resolve({ bytesRead, buffers })
})
})
}
// Function signature is
// s.writev(fd, buffers[, position], callback)
// We need to handle the optional arg, so we use ...args
exports.writev = function (fd, buffers, ...args) {
if (typeof args[args.length - 1] === 'function') {
return fs.writev(fd, buffers, ...args)
}
return new Promise((resolve, reject) => {
fs.writev(fd, buffers, ...args, (err, bytesWritten, buffers) => {
if (err) return reject(err)
resolve({ bytesWritten, buffers })
})
})
}
// fs.realpath.native sometimes not available if fs is monkey-patched
if (typeof fs.realpath.native === 'function') {
exports.realpath.native = u(fs.realpath.native)
} else {
process.emitWarning(
'fs.realpath.native is not a function. Is fs being monkey-patched?',
'Warning', 'fs-extra-WARN0003'
)
}

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('jsonfile-integration', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'json')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('+ writeJsonSync / spaces', () => {
it('should read a file and parse the json', () => {
const obj1 = {
firstName: 'JP',
lastName: 'Richardson'
}
const file = path.join(TEST_DIR, 'file.json')
fse.writeJsonSync(file, obj1)
const data = fs.readFileSync(file, 'utf8')
assert.strictEqual(data, JSON.stringify(obj1) + '\n')
})
})
describe('+ writeJsonSync / EOL', () => {
it('should read a file and parse the json', () => {
const obj1 = {
firstName: 'JP',
lastName: 'Richardson'
}
const file = path.join(TEST_DIR, 'file.json')
fse.writeJsonSync(file, obj1, { spaces: 2, EOL: '\r\n' })
const data = fs.readFileSync(file, 'utf8')
assert.strictEqual(
data,
JSON.stringify(obj1, null, 2).replace(/\n/g, '\r\n') + '\r\n'
)
})
})
})

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 beforeEach, describe, it */
describe('json', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra-output-json-sync')
fse.emptyDir(TEST_DIR, done)
})
describe('+ outputJsonSync(file, data)', () => {
it('should write the file regardless of whether the directory exists or not', () => {
const file = path.join(TEST_DIR, 'this-dir', 'does-not', 'exist', 'file.json')
assert(!fs.existsSync(file))
const data = { name: 'JP' }
fse.outputJsonSync(file, data)
assert(fs.existsSync(file))
const newData = JSON.parse(fs.readFileSync(file, 'utf8'))
assert.strictEqual(data.name, newData.name)
})
describe('> when an option is passed, like JSON replacer', () => {
it('should pass the option along to jsonfile module', () => {
const file = path.join(TEST_DIR, 'this-dir', 'does-not', 'exist', 'really', 'file.json')
assert(!fs.existsSync(file))
const replacer = (k, v) => v === 'JP' ? 'Jon Paul' : v
const data = { name: 'JP' }
fse.outputJsonSync(file, data, { replacer })
const newData = JSON.parse(fs.readFileSync(file, 'utf8'))
assert.strictEqual(newData.name, 'Jon Paul')
})
})
})
})

View file

@ -0,0 +1,81 @@
'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('json', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra-output-json')
fse.emptyDir(TEST_DIR, done)
})
describe('+ outputJson(file, data)', () => {
it('should write the file regardless of whether the directory exists or not', done => {
const file = path.join(TEST_DIR, 'this-dir', 'prob-does-not', 'exist', 'file.json')
assert(!fs.existsSync(file))
const data = { name: 'JP' }
fse.outputJson(file, data, err => {
if (err) return done(err)
assert(fs.existsSync(file))
const newData = JSON.parse(fs.readFileSync(file, 'utf8'))
assert.strictEqual(data.name, newData.name)
done()
})
})
it('should be mutation-proof', async () => {
const dir = path.join(TEST_DIR, 'this-dir', 'certanly-does-not', 'exist')
const file = path.join(dir, 'file.json')
assert(!fs.existsSync(dir), 'directory cannot exist')
const name = 'JP'
const data = { name }
const promise = fse.outputJson(file, data)
// Mutate data right after call
data.name = 'Ryan'
// now await for the call to finish
await promise
assert(fs.existsSync(file))
const newData = JSON.parse(fs.readFileSync(file, 'utf8'))
// mutation did not change data
assert.strictEqual(newData.name, name)
})
it('should support Promises', () => {
const file = path.join(TEST_DIR, 'this-dir', 'prob-does-not', 'exist', 'file.json')
assert(!fs.existsSync(file))
const data = { name: 'JP' }
return fse.outputJson(file, data)
})
describe('> when an option is passed, like JSON replacer', () => {
it('should pass the option along to jsonfile module', done => {
const file = path.join(TEST_DIR, 'this-dir', 'does-not', 'exist', 'really', 'file.json')
assert(!fs.existsSync(file))
const replacer = (k, v) => v === 'JP' ? 'Jon Paul' : v
const data = { name: 'JP' }
fse.outputJson(file, data, { replacer }, err => {
assert.ifError(err)
const newData = JSON.parse(fs.readFileSync(file, 'utf8'))
assert.strictEqual(newData.name, 'Jon Paul')
done()
})
})
})
})
})

View file

@ -0,0 +1,38 @@
'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('json promise support', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'json')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
;['writeJson', 'writeJSON'].forEach(method => {
describe(method, () => {
it('should support promises', () => {
const obj1 = {
firstName: 'JP',
lastName: 'Richardson'
}
const file = path.join(TEST_DIR, 'promise.json')
return fse[method](file, obj1)
.then(() => {
const data = fs.readFileSync(file, 'utf8')
assert.strictEqual(data, JSON.stringify(obj1) + '\n')
})
})
})
})
})

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('read', () => {
let TEST_DIR
beforeEach(done => {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'read-json')
fse.emptyDir(TEST_DIR, done)
})
afterEach(done => fse.remove(TEST_DIR, done))
describe('+ readJSON', () => {
it('should read a file and parse the json', done => {
const obj1 = {
firstName: 'JP',
lastName: 'Richardson'
}
const file = path.join(TEST_DIR, 'file.json')
fs.writeFileSync(file, JSON.stringify(obj1))
fse.readJSON(file, (err, obj2) => {
assert.ifError(err)
assert.strictEqual(obj1.firstName, obj2.firstName)
assert.strictEqual(obj1.lastName, obj2.lastName)
done()
})
})
it('should error if it cant parse the json', done => {
const file = path.join(TEST_DIR, 'file2.json')
fs.writeFileSync(file, '%asdfasdff444')
fse.readJSON(file, (err, obj) => {
assert(err)
assert(!obj)
done()
})
})
})
})

View file

@ -0,0 +1,16 @@
'use strict'
const u = require('universalify').fromPromise
const jsonFile = require('./jsonfile')
jsonFile.outputJson = u(require('./output-json'))
jsonFile.outputJsonSync = require('./output-json-sync')
// aliases
jsonFile.outputJSON = jsonFile.outputJson
jsonFile.outputJSONSync = jsonFile.outputJsonSync
jsonFile.writeJSON = jsonFile.writeJson
jsonFile.writeJSONSync = jsonFile.writeJsonSync
jsonFile.readJSON = jsonFile.readJson
jsonFile.readJSONSync = jsonFile.readJsonSync
module.exports = jsonFile

View file

@ -0,0 +1,11 @@
'use strict'
const jsonFile = require('jsonfile')
module.exports = {
// jsonfile exports
readJson: jsonFile.readFile,
readJsonSync: jsonFile.readFileSync,
writeJson: jsonFile.writeFile,
writeJsonSync: jsonFile.writeFileSync
}

View file

@ -0,0 +1,12 @@
'use strict'
const { stringify } = require('jsonfile/utils')
const { outputFileSync } = require('../output-file')
function outputJsonSync (file, data, options) {
const str = stringify(data, options)
outputFileSync(file, str, options)
}
module.exports = outputJsonSync

View file

@ -0,0 +1,12 @@
'use strict'
const { stringify } = require('jsonfile/utils')
const { outputFile } = require('../output-file')
async function outputJson (file, data, options = {}) {
const str = stringify(data, options)
await outputFile(file, str, options)
}
module.exports = outputJson