pax_global_header00006660000000000000000000000064131501254240014507gustar00rootroot0000000000000052 comment=3381d20cdafa55a7aa42ab7b79a2b34f971a44c4 copy-concurrently-1.0.5/000077500000000000000000000000001315012542400152115ustar00rootroot00000000000000copy-concurrently-1.0.5/.gitignore000066400000000000000000000000511315012542400171750ustar00rootroot00000000000000*~ .#* node_modules .nyc_output coverage copy-concurrently-1.0.5/.travis.yml000066400000000000000000000000771315012542400173260ustar00rootroot00000000000000language: node_js sudo: false node_js: - "8" - "6" - "4" copy-concurrently-1.0.5/LICENSE000066400000000000000000000013601315012542400162160ustar00rootroot00000000000000Copyright (c) 2017, Rebecca Turner Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. copy-concurrently-1.0.5/README.md000066400000000000000000000140101315012542400164640ustar00rootroot00000000000000# copy-concurrently Copy files, directories and symlinks ``` const copy = require('copy-concurrently') copy('/path/to/thing', '/new/path/thing').then(() => { // this is now copied }).catch(err => { // oh noooo }) ``` Copies files, directories and symlinks. Ownership is maintained when running as root, permissions are always maintained. On Windows, if symlinks are unavailable then junctions will be used. ## PUBLIC INTERFACE ### copy(from, to, [options]) → Promise Recursively copies `from` to `to` and resolves its promise when finished. If `to` already exists then the promise will be rejected with an `EEXIST` error. Options are: * maxConcurrency – (Default: `1`) The maximum number of concurrent copies to do at once. * recurseWith - (Default: `copy.item`) The function to call on each file after recursing into a directory. * isWindows - (Default: `process.platform === 'win32'`) If true enables Windows symlink semantics. This requires an extra `stat` to determine if the destination of a symlink is a file or directory. If symlinking a directory fails then we'll try making a junction instead. Options can also include dependency injection: * Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. * fs - (Default: `require('fs')`) The filesystem module to use. Can be used to use `graceful-fs` or to inject a mock. * writeStreamAtomic - (Default: `require('fs-write-stream-atomic')`) The implementation of `writeStreamAtomic` to use. Used to inject a mock. * getuid - (Default: `process.getuid`) A function that returns the current UID. Used to inject a mock. ## EXTENSION INTERFACE Ordinarily you'd only call `copy` above. But it's possible to use it's component functions directly. This is useful if, say, you're writing [move-concurently](https://npmjs.com/package/move-concurrently). ### copy.file(from, to, options) → Promise Copies an ordinary file `from` to destination `to`. Uses `fs-write-stream-atomic` to ensure that the file is either entirely copied or not at all. Options are: * uid, gid - (Optional) If `getuid()` is `0` then this and gid will be used to set the user and group of `to`. If uid is present then gid must be too. * mode - (Optional) If set then `to` will have its perms set to `mode`. * fs - (Default: `require('fs')`) The filesystem module to use. Can be used to use `graceful-fs` or to inject a mock. * Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. * writeStreamAtomic - (Default `require('fs-write-stream-atomic')`) The implementation of `writeStreamAtomic` to use. Used to inject a mock. ### copy.symlink(from, to, options) → Promise Copies a symlink `from` to destination `to`. If you're using Windows and symlinking fails and what you're linking is a directory then junctions will be tried instead. Options are: * top - The top level the copy is being run from. This is used to determine if the symlink destination is within the set of files we're copying or outside it. * fs - (Default: `require('fs')`) The filesystem module to use. Can be used to use `graceful-fs` or to inject a mock. * Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. * isWindows - (Default: `process.platform === 'win32'`) If true enables Windows symlink semantics. This requires an extra `stat` to determine if the destination of a symlink is a file or directory. If symlinking a directory fails then we'll try making a junction instead. ### copy.recurse(from, to, options) → Promise Reads all of the files in directory `from` and adds them to the `queue` using `recurseWith` (by default `copy.item`). Options are: * queue - A [`run-queue`](https://npmjs.com/package/run-queue) object to add files found inside `from` to. * recurseWith - (Default: `copy.item`) The function to call on each file after recursing into a directory. * uid, gid - (Optional) If `getuid()` is `0` then this and gid will be used to set the user and group of `to`. If uid is present then gid must be too. * mode - (Optional) If set then `to` will have its perms set to `mode`. * fs - (Default: `require('fs')`) The filesystem module to use. Can be used to use `graceful-fs` or to inject a mock. * getuid - (Default: `process.getuid`) A function that returns the current UID. Used to inject a mock. ### copy.item(from, to, options) → Promise Copies some kind of `from` to destination `to`. This looks at the filetype and calls `copy.file`, `copy.symlink` or `copy.recurse` as appropriate. Symlink copies are queued with a priority such that they happen after all file and directory copies as you can't create a junction on windows to a file that doesn't exist yet. Options are: * top - The top level the copy is being run from. This is used to determine if the symlink destination is within the set of files we're copying or outside it. * queue - The [`run-queue`](https://npmjs.com/package/run-queue) object to pass to `copy.recurse` if `from` is a directory. * recurseWith - (Default: `copy.item`) The function to call on each file after recursing into a directory. * uid, gid - (Optional) If `getuid()` is `0` then this and gid will be used to set the user and group of `to`. If uid is present then gid must be too. * mode - (Optional) If set then `to` will have its perms set to `mode`. * fs - (Default: `require('fs')`) The filesystem module to use. Can be used to use `graceful-fs` or to inject a mock. * getuid - (Default: `process.getuid`) A function that returns the current UID. Used to inject a mock. * isWindows - (Default: `process.platform === 'win32'`) If true enables Windows symlink semantics. This requires an extra `stat` to determine if the destination of a symlink is a file or directory. If symlinking a directory fails then we'll try making a junction instead. * Promise - (Default: `global.Promise`) The promise implementation to use, defaults to Node's. * writeStreamAtomic - (Default `require('fs-write-stream-atomic')`) The implementation of `writeStreamAtomic` to use. Used to inject a mock. copy-concurrently-1.0.5/appveyor.yml000066400000000000000000000005221315012542400176000ustar00rootroot00000000000000environment: matrix: - nodejs_version: "7" - nodejs_version: "6" - nodejs_version: "4" - nodejs_version: "0.12" platform: - x64 install: - ps: Install-Product node $env:nodejs_version $env:platform - npm config set spin false - npm install test_script: - npm test matrix: fast_finish: true build: off copy-concurrently-1.0.5/copy.js000066400000000000000000000163611315012542400165300ustar00rootroot00000000000000'use strict' module.exports = copy module.exports.item = copyItem module.exports.recurse = recurseDir module.exports.symlink = copySymlink module.exports.file = copyFile var nodeFs = require('fs') var path = require('path') var validate = require('aproba') var stockWriteStreamAtomic = require('fs-write-stream-atomic') var mkdirp = require('mkdirp') var rimraf = require('rimraf') var isWindows = require('./is-windows') var RunQueue = require('run-queue') var extend = Object.assign || require('util')._extend function promisify (Promise, fn) { return function () { var args = [].slice.call(arguments) return new Promise(function (resolve, reject) { return fn.apply(null, args.concat(function (err, value) { if (err) { reject(err) } else { resolve(value) } })) }) } } function copy (from, to, opts) { validate('SSO|SS', arguments) opts = extend({}, opts || {}) var Promise = opts.Promise || global.Promise var fs = opts.fs || nodeFs if (opts.isWindows == null) opts.isWindows = isWindows if (!opts.Promise) opts.Promise = Promise if (!opts.fs) opts.fs = fs if (!opts.recurseWith) opts.recurseWith = copyItem if (!opts.lstat) opts.lstat = promisify(opts.Promise, fs.lstat) if (!opts.stat) opts.stat = promisify(opts.Promise, fs.stat) if (!opts.chown) opts.chown = promisify(opts.Promise, fs.chown) if (!opts.readdir) opts.readdir = promisify(opts.Promise, fs.readdir) if (!opts.readlink) opts.readlink = promisify(opts.Promise, fs.readlink) if (!opts.symlink) opts.symlink = promisify(opts.Promise, fs.symlink) if (!opts.chmod) opts.chmod = promisify(opts.Promise, fs.chmod) opts.top = from opts.mkdirpAsync = promisify(opts.Promise, mkdirp) var rimrafAsync = promisify(opts.Promise, rimraf) var queue = new RunQueue({ maxConcurrency: opts.maxConcurrency, Promise: Promise }) opts.queue = queue queue.add(0, copyItem, [from, to, opts]) return queue.run().catch(function (err) { // if the target already exists don't clobber it if (err.code === 'EEXIST' || err.code === 'EPERM') { return passThroughError() } else { return remove(to).then(passThroughError, passThroughError) } function passThroughError () { return Promise.reject(err) } }) function remove (target) { var opts = { unlink: fs.unlink, chmod: fs.chmod, stat: fs.stat, lstat: fs.lstat, rmdir: fs.rmdir, readdir: fs.readdir, glob: false } return rimrafAsync(target, opts) } } function copyItem (from, to, opts) { validate('SSO', [from, to, opts]) var fs = opts.fs || nodeFs var Promise = opts.Promise || global.Promise var lstat = opts.lstat || promisify(Promise, fs.lstat) return lstat(to).then(function () { return Promise.reject(eexists(from, to)) }, function (err) { if (err && err.code !== 'ENOENT') return Promise.reject(err) return lstat(from) }).then(function (fromStat) { var cmdOpts = extend(extend({}, opts), fromStat) if (fromStat.isDirectory()) { return recurseDir(from, to, cmdOpts) } else if (fromStat.isSymbolicLink()) { opts.queue.add(1, copySymlink, [from, to, cmdOpts]) } else if (fromStat.isFile()) { return copyFile(from, to, cmdOpts) } else if (fromStat.isBlockDevice()) { return Promise.reject(eunsupported(from + " is a block device, and we don't know how to copy those.")) } else if (fromStat.isCharacterDevice()) { return Promise.reject(eunsupported(from + " is a character device, and we don't know how to copy those.")) } else if (fromStat.isFIFO()) { return Promise.reject(eunsupported(from + " is a FIFO, and we don't know how to copy those.")) } else if (fromStat.isSocket()) { return Promise.reject(eunsupported(from + " is a socket, and we don't know how to copy those.")) } else { return Promise.reject(eunsupported("We can't tell what " + from + " is and so we can't copy it.")) } }) } function recurseDir (from, to, opts) { validate('SSO', [from, to, opts]) var recurseWith = opts.recurseWith || copyItem var fs = opts.fs || nodeFs var chown = opts.chown || promisify(Promise, fs.chown) var readdir = opts.readdir || promisify(Promise, fs.readdir) var mkdirpAsync = opts.mkdirpAsync || promisify(Promise, mkdirp) return mkdirpAsync(to, {fs: fs, mode: opts.mode}).then(function () { var getuid = opts.getuid || process.getuid if (getuid && opts.uid != null && getuid() === 0) { return chown(to, opts.uid, opts.gid) } }).then(function () { return readdir(from) }).then(function (files) { files.forEach(function (file) { opts.queue.add(0, recurseWith, [path.join(from, file), path.join(to, file), opts]) }) }) } function copySymlink (from, to, opts) { validate('SSO', [from, to, opts]) var fs = opts.fs || nodeFs var readlink = opts.readlink || promisify(Promise, fs.readlink) var stat = opts.stat || promisify(Promise, fs.symlink) var symlink = opts.symlink || promisify(Promise, fs.symlink) var Promise = opts.Promise || global.Promise return readlink(from).then(function (fromDest) { var absoluteDest = path.resolve(path.dirname(from), fromDest) // Treat absolute paths that are inside the tree we're // copying as relative. This necessary to properly support junctions // on windows (which are always absolute) but is also DWIM with symlinks. var relativeDest = path.relative(opts.top, absoluteDest) var linkFrom = relativeDest.substr(0, 2) === '..' ? fromDest : path.relative(path.dirname(from), absoluteDest) if (opts.isWindows) { return stat(absoluteDest).catch(function () { return null }).then(function (destStat) { var isDir = destStat && destStat.isDirectory() var type = isDir ? 'dir' : 'file' return symlink(linkFrom, to, type).catch(function (err) { if (type === 'dir') { return symlink(linkFrom, to, 'junction') } else { return Promise.reject(err) } }) }) } else { return symlink(linkFrom, to) } }) } function copyFile (from, to, opts) { validate('SSO', [from, to, opts]) var fs = opts.fs || nodeFs var writeStreamAtomic = opts.writeStreamAtomic || stockWriteStreamAtomic var Promise = opts.Promise || global.Promise var chmod = opts.chmod || promisify(Promise, fs.chmod) var writeOpts = {} var getuid = opts.getuid || process.getuid if (getuid && opts.uid != null && getuid() === 0) { writeOpts.chown = { uid: opts.uid, gid: opts.gid } } return new Promise(function (resolve, reject) { var errored = false function onError (err) { errored = true reject(err) } fs.createReadStream(from) .once('error', onError) .pipe(writeStreamAtomic(to, writeOpts)) .once('error', onError) .once('close', function () { if (errored) return if (opts.mode != null) { resolve(chmod(to, opts.mode)) } else { resolve() } }) }) } function eexists (from, to) { var err = new Error('Could not move ' + from + ' to ' + to + ': destination already exists.') err.code = 'EEXIST' return err } function eunsupported (msg) { var err = new Error(msg) err.code = 'EUNSUPPORTED' return err } copy-concurrently-1.0.5/is-windows.js000066400000000000000000000000731315012542400176520ustar00rootroot00000000000000'use strict' module.exports = process.platform === 'win32' copy-concurrently-1.0.5/package.json000066400000000000000000000020121315012542400174720ustar00rootroot00000000000000{ "name": "copy-concurrently", "version": "1.0.5", "description": "Promises of copies of files, directories and symlinks, with concurrency controls and win32 junction fallback.", "main": "copy.js", "scripts": { "test": "standard && tap --coverage test" }, "keywords": [ "copy", "cpr" ], "author": "Rebecca Turner (http://re-becca.org/)", "license": "ISC", "dependencies": { "aproba": "^1.1.1", "fs-write-stream-atomic": "^1.0.8", "iferr": "^0.1.5", "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.0" }, "devDependencies": { "standard": "^8.6.0", "tacks": "^1.2.6", "tap": "^10.1.1" }, "files": [ "copy.js", "is-windows.js" ], "directories": { "test": "test" }, "repository": { "type": "git", "url": "git+https://github.com/npm/copy-concurrently.git" }, "bugs": { "url": "https://github.com/npm/copy-concurrently/issues" }, "homepage": "https://www.npmjs.com/package/copy-concurrently" } copy-concurrently-1.0.5/test/000077500000000000000000000000001315012542400161705ustar00rootroot00000000000000copy-concurrently-1.0.5/test/copy.js000066400000000000000000000323641315012542400175100ustar00rootroot00000000000000'use strict' var stream = require('stream') var path = require('path') var test = require('tap').test var validate = require('aproba') var extend = Object.assign || require('util')._extend function assign () { var args = [].slice.call(arguments) var base = args.shift() while (args.length) { base = extend(base, args.shift()) } return base } var copy = require('../copy.js') function enoent () { var err = new Error('ENOENT') err.code = 'ENOENT' return err } function eperm () { var err = new Error('EPERM') err.code = 'EPERM' return err } function eread () { var err = new Error('EREAD') err.code = 'EREAD' return err } function ewrite () { var err = new Error('EWRITE') err.code = 'EWRITE' return err } var isNothing = { isDirectory: function () { return false }, isSymbolicLink: function () { return false }, isFile: function () { return false }, isBlockDevice: function () { return false }, isCharacterDevice: function () { return false }, isFIFO: function () { return false }, isSocket: function () { return false } } var fileExists = assign({}, isNothing, {isFile: function () { return true }}) var blockExists = assign({}, isNothing, {isBlockDevice: function () { return true }}) var charExists = assign({}, isNothing, {isCharacterDevice: function () { return true }}) var fifoExists = assign({}, isNothing, {isFIFO: function () { return true }}) var socketExists = assign({}, isNothing, {isSocket: function () { return true }}) var unknownExists = isNothing var dirExists = assign({}, isNothing, {isDirectory: function () { return true }}) var symlinkExists = assign({}, isNothing, {isSymbolicLink: function () { return true }}) function nextTick (fn) { var args = [].slice.call(arguments, 1) process.nextTick(function () { fn.apply(null, args) }) } function rejects (t, code, msg, promise) { promise.then(function () { t.fail(msg) }).catch(function (err) { t.is(err.code, code, msg) }) } function resolves (t, msg, promise) { promise.then(function () { t.pass(msg) }).catch(function (err) { t.ifError(err, msg) }) } test('copy errors', function (t) { t.plan(8) var mockFs = { lstat: function (to, cb) { validate('SF', [to, cb]) if (to === 'src:src-does-not-exist') { nextTick(cb, enoent()) } else if (to === 'dest:src-does-not-exist') { nextTick(cb, enoent()) } else if (to === 'src:dest-exists') { nextTick(cb, null, fileExists) } else if (to === 'dest:dest-exists') { nextTick(cb, null, fileExists) } else if (to === 'src:dest-perm') { nextTick(cb, null, fileExists) } else if (to === 'dest:dest-perm') { nextTick(cb, eperm()) } else if (to === 'src:block') { nextTick(cb, null, blockExists) } else if (to === 'src:char') { nextTick(cb, null, charExists) } else if (to === 'src:fifo') { nextTick(cb, null, fifoExists) } else if (to === 'src:socket') { nextTick(cb, null, socketExists) } else if (to === 'src:unknown') { nextTick(cb, null, unknownExists) } else if (to === 'dest:block') { nextTick(cb, enoent()) } else if (to === 'dest:char') { nextTick(cb, enoent()) } else if (to === 'dest:fifo') { nextTick(cb, enoent()) } else if (to === 'dest:socket') { nextTick(cb, enoent()) } else if (to === 'dest:unknown') { nextTick(cb, enoent()) } else { t.fail('unexpected lstat ' + to) nextTick(cb, enoent()) } }, createReadStream: function () { throw new Error('SHOULD NOT BE READING') } } var mocks = { fs: mockFs, writeStreamAtomic: function () { throw new Error('SHOULD NOT BE WRITING') } } rejects(t, 'ENOENT', 'source does not exist', copy('src:src-does-not-exist', 'dest:src-does-not-exist', mocks)) rejects(t, 'EEXIST', 'dest exists', copy('src:dest-exists', 'dest:dest-exists', mocks)) rejects(t, 'EPERM', 'dest perm error', copy('src:dest-perm', 'dest:dest-perm', mocks)) rejects(t, 'EUNSUPPORTED', 'block devices unsupported', copy('src:block', 'dest:block', mocks)) rejects(t, 'EUNSUPPORTED', 'char devices unsupported', copy('src:char', 'dest:char', mocks)) rejects(t, 'EUNSUPPORTED', 'FIFOs unsupported', copy('src:fifo', 'dest:fifo', mocks)) rejects(t, 'EUNSUPPORTED', 'sockets unsupported', copy('src:socket', 'dest:socket', mocks)) rejects(t, 'EUNSUPPORTED', 'unknown unsupported', copy('src:unknown', 'dest:unknown', mocks)) }) test('copy file', function (t) { t.plan(7) var mockFs = { lstat: function (to, cb) { validate('SF', arguments) if (to === 'src:ok') { nextTick(cb, null, fileExists) } else if (to === 'dest:ok') { nextTick(cb, enoent()) } else if (to === 'src:chown') { nextTick(cb, null, assign({uid: 100, gid: 100}, fileExists)) } else if (to === 'dest:chown') { nextTick(cb, enoent()) } else if (to === 'src:chmod') { nextTick(cb, null, assign({mode: 33188}, fileExists)) } else if (to === 'dest:chmod') { nextTick(cb, enoent()) } else if (to === 'src:read-error') { nextTick(cb, null, fileExists) } else if (to === 'dest:read-error') { nextTick(cb, enoent()) } else if (to === 'src:write-error') { nextTick(cb, null, fileExists) } else if (to === 'dest:write-error') { nextTick(cb, enoent()) } else { t.fail('unexpected lstat ' + to) nextTick(cb, enoent()) } }, chmod: function (file, mode, cb) { validate('SNF', arguments) if (file === 'dest:chmod') { t.pass('CHMOD: ' + file + ' ' + mode) nextTick(cb) } else { t.fail('unexpected chmod ' + file) nextTick(cb, enoent()) } }, createReadStream: function (from) { validate('S', arguments) var read = new stream.PassThrough() if (from === 'src:read-error') { nextTick(function () { read.emit('error', eread()) }) } else { setTimeout(function () { read.end('content') }, 10) } return read } } var mocks = { fs: mockFs, getuid: function () { return 0 }, writeStreamAtomic: function (to, opts) { validate('SO', arguments) var write = new stream.PassThrough() write.on('data', function (chunk) { t.comment('WROTE ' + to + ': ' + chunk) }) if (opts.chown) { t.is(opts.chown.uid, 100, 'CHOWN uid:100') } if (to === 'dest:write-error') { nextTick(function () { write.emit('error', ewrite()) }) } write.on('finish', function () { write.emit('close') }) return write } } resolves(t, 'copy ok', copy('src:ok', 'dest:ok', mocks)) resolves(t, 'copy w/chmod ok', copy('src:chmod', 'dest:chmod', mocks)) resolves(t, 'copy w/chown ok', copy('src:chown', 'dest:chown', mocks)) rejects(t, 'EREAD', 'read errors propagate', copy('src:read-error', 'dest:read-error', mocks)) rejects(t, 'EWRITE', 'write errors propagate', copy('src:write-error', 'dest:write-error', mocks)) }) test('copy symlink unix', function (t) { t.plan(6) var mockFs = { lstat: function (to, cb) { validate('SF', arguments) if (to === '/full/path/src:ok') { nextTick(cb, null, symlinkExists) } else if (to === '/full/path/src:dir-ok') { nextTick(cb, null, symlinkExists) } else if (to === '/full/path/src:bad') { nextTick(cb, null, symlinkExists) } else if (to === '/full/path/src:dir-bad') { nextTick(cb, null, symlinkExists) } else if (to === 'dest:ok') { nextTick(cb, enoent()) } else { t.fail('unexpected lstat ' + to) nextTick(cb, enoent()) } }, readlink: function (from, cb) { validate('SF', arguments) if (from === '/full/path/src:ok') { nextTick(cb, null, '../other/src:ok') } else if (from === '/full/path/src:dir-ok') { nextTick(cb, null, '../other/src:dir-ok') } else if (from === '/full/path/src:bad') { nextTick(cb, null, '../other/src:bad') } else if (from === '/full/path/src:dir-bad') { nextTick(cb, null, '../other/src:dir-bad') } else { t.fail('unexpected readlink ' + from) nextTick(cb, enoent()) } }, stat: function (to, cb) { validate('SF', arguments) t.fail('unexpected stat ' + to) nextTick(cb, enoent()) }, symlink: function (from, to, type, cb) { validate('SSSF|SSF', arguments) if (arguments.length === 3) { cb = type } if (from === '../other/src:ok') { t.pass('symlink type ' + from + ' → ' + to) nextTick(cb) } else if (from === '../other/src:dir-ok') { t.pass('symlink type ' + from + ' → ' + to) nextTick(cb) } else if (from === '../other/src:bad' || from === '../other/src:dir-bad') { nextTick(cb, eperm()) } else { t.fail('unexpected symlink ' + from + ' → ' + to) nextTick(cb, enoent()) } }, chmod: function () { throw new Error('SHOULD NOT BE CHMODING') }, createReadStream: function () { throw new Error('SHOULD NOT BE READING') } } var mocks = { isWindows: false, fs: mockFs, writeStreamAtomic: function () { throw new Error('SHOULD NOT BE WRITING') } } resolves(t, 'file symlink ok', copy('/full/path/src:ok', 'dest:ok', mocks)) resolves(t, 'dir symlink ok', copy('/full/path/src:dir-ok', 'dest:ok', mocks)) rejects(t, 'EPERM', 'failed file symlink fails', copy('/full/path/src:bad', 'dest:ok', mocks)) rejects(t, 'EPERM', 'failed dir symlink fails', copy('/full/path/src:dir-bad', 'dest:ok', mocks)) }) test('copy symlink windows', function (t) { t.plan(8) var mockFs = { lstat: function (to, cb) { validate('SF', arguments) if (to === '/full/path/src:ok') { nextTick(cb, null, symlinkExists) } else if (to === '/full/path/src:dir-ok') { nextTick(cb, null, symlinkExists) } else if (to === '/full/path/src:junction-ok') { nextTick(cb, null, symlinkExists) } else if (to === '/full/path/src:bad') { nextTick(cb, null, symlinkExists) } else if (to === '/full/path/src:dir-bad') { nextTick(cb, null, symlinkExists) } else if (to === 'dest:ok') { nextTick(cb, enoent()) } else { t.fail('unexpected lstat ' + to) nextTick(cb, enoent()) } }, readlink: function (from, cb) { validate('SF', arguments) if (from === '/full/path/src:ok') { nextTick(cb, null, '../other/src:ok') } else if (from === '/full/path/src:dir-ok') { nextTick(cb, null, '../other/src:dir-ok') } else if (from === '/full/path/src:junction-ok') { nextTick(cb, null, '../other/src:junction-ok') } else if (from === '/full/path/src:bad') { nextTick(cb, null, '../other/src:bad') } else if (from === '/full/path/src:dir-bad') { nextTick(cb, null, '../other/src:dir-bad') } else { t.fail('unexpected readlink ' + from) nextTick(cb, enoent()) } }, stat: function (to, cb) { validate('SF', arguments) if (to === path.resolve('/full/other/src:ok')) { nextTick(cb, null, fileExists) } else if (to === path.resolve('/full/other/src:dir-ok')) { nextTick(cb, null, dirExists) } else if (to === path.resolve('/full/other/src:junction-ok')) { nextTick(cb, null, dirExists) } else if (to === path.resolve('/full/other/src:bad')) { nextTick(cb, null, fileExists) } else if (to === path.resolve('/full/other/src:dir-bad')) { nextTick(cb, null, dirExists) } else { t.fail('unexpected stat ' + to) nextTick(cb, enoent()) } }, symlink: function (from, to, type, cb) { validate('SSSF|SSF', arguments) if (arguments.length === 3) { cb = type type = 'file' } if (from === '../other/src:ok') { t.is(type, 'file', 'symlink type ' + from + ' → ' + to) nextTick(cb) } else if (from === '../other/src:dir-ok') { t.is(type, 'dir', 'symlink type ' + from + ' → ' + to) nextTick(cb) } else if (from === '../other/src:junction-ok') { if (type === 'dir') { nextTick(cb, eperm()) } else { t.is(type, 'junction', 'symlink type ' + from + ' → ' + to) nextTick(cb) } } else if (from === '../other/src:bad' || from === '../other/src:dir-bad') { nextTick(cb, eperm()) } else { t.fail('unexpected symlink ' + from + ' → ' + to) nextTick(cb, enoent()) } }, chmod: function () { throw new Error('SHOULD NOT BE CHMODING') }, createReadStream: function () { throw new Error('SHOULD NOT BE READING') } } var mocks = { isWindows: true, fs: mockFs, writeStreamAtomic: function () { throw new Error('SHOULD NOT BE WRITING') } } resolves(t, 'file symlink ok', copy('/full/path/src:ok', 'dest:ok', mocks)) resolves(t, 'dir symlink ok', copy('/full/path/src:dir-ok', 'dest:ok', mocks)) resolves(t, 'dir junction fallback ok', copy('/full/path/src:junction-ok', 'dest:ok', mocks)) rejects(t, 'EPERM', 'failed file symlink fails', copy('/full/path/src:bad', 'dest:ok', mocks)) rejects(t, 'EPERM', 'failed dir symlink fails', copy('/full/path/src:dir-bad', 'dest:ok', mocks)) }) copy-concurrently-1.0.5/test/integration.js000066400000000000000000000056231315012542400210570ustar00rootroot00000000000000'use strict' var fs = require('fs') var path = require('path') var test = require('tap').test var copy = require('../copy.js') var Tacks = require('tacks') var loadFromDir = require('tacks/load-from-dir') var areTheSame = require('tacks/tap.js').areTheSame var File = Tacks.File var Symlink = Tacks.Symlink var Dir = Tacks.Dir var isWindows = require('../is-windows.js') var basedir = path.join(__dirname, path.basename(__filename, '.js')) function testdir (dir) { return path.join(basedir, dir) } var testContent = { test: 'this' } var fixture = new Tacks(Dir({ 'test-dir': Dir({ subdir: Dir({ 'file1.json': File(testContent), 'file2.json': File(testContent) }), subdir2: Dir({ 'linky': Symlink(path.join('..', 'subdir')), 'file2.json': File(testContent), subsub: Dir({ 'aaaa': Symlink('bbbb'), 'bbbb': Dir(), 'zzzz.json': File(testContent) }) }) }), 'test-dir-symlink': Symlink('test-dir'), 'test-file.json': File(testContent), 'test-symlink.json': Symlink('test-file.json'), 'existing': File('') })) function readFile (file) { return JSON.parse(fs.readFileSync(testdir(file))) } function readSymlink (file) { return path.relative(basedir, path.resolve(basedir, fs.readlinkSync(testdir(file)))) } var testDirContent test('setup', function (t) { fixture.remove(basedir) fixture.create(basedir) testDirContent = loadFromDir(testdir('test-dir')) t.done() }) test('copy', function (t) { t.plan(5 + (isWindows ? 0 : 2)) copy(testdir('test-file.json'), testdir('copy-test-file.json')).then(function () { t.isDeeply(readFile('copy-test-file.json'), testContent, 'copied file content') return copy(testdir('test-file.json'), testdir('existing')).catch(function (err) { t.is(err.code, 'EEXIST', "won't overwrite files") }) }).catch(t.fail) if (!isWindows) { // skip file symlink test on windows, 'cause as a rule, it's not supported copy(testdir('test-symlink.json'), testdir('copy-test-symlink.json')).then(function () { t.is(readSymlink('copy-test-symlink.json'), 'test-file.json', 'copied symlink') return copy(testdir('test-symlink.json'), testdir('existing')).catch(function (err) { t.is(err.code, 'EEXIST', "won't overwrite symlinks") }) }).catch(t.fail) } copy(testdir('test-dir-symlink'), testdir('copy-test-dir-symlink')).then(function () { t.is(readSymlink('copy-test-dir-symlink'), 'test-dir', 'copied dir symlink') }).catch(t.fail) copy(testdir('test-dir'), testdir('copy-test-dir')).then(function () { var copied = loadFromDir(testdir('copy-test-dir')) areTheSame(t, copied, testDirContent, 'copied test directory') return copy(testdir('test-dir'), testdir('existing')).catch(function (err) { t.is(err && err.code, 'EEXIST', "won't overwrite dirs") }) }).catch(t.fail) }) test('cleanup', function (t) { fixture.remove(basedir) t.done() })