diff --git a/.travis.yml b/.travis.yml index e155464..f98fed0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,3 @@ node_js: - '12' - '10' - '8' - - '6' diff --git a/index.d.ts b/index.d.ts index 9491a4f..e010a37 100644 --- a/index.d.ts +++ b/index.d.ts @@ -22,9 +22,13 @@ declare namespace cpy { @example ``` - cpy('foo.js', 'destination', { - rename: basename => `prefix-${basename}` - }); + import cpy = require('cpy'); + + (async () => { + await cpy('foo.js', 'destination', { + rename: basename => `prefix-${basename}` + }); + })(); ``` */ readonly rename?: string | ((basename: string) => string); @@ -60,38 +64,27 @@ declare namespace cpy { } } -declare const cpy: { - /** - Copy files. - - @param source - Files to copy. - @param destination - Destination directory. - @param options - Options are passed to [cp-file](https://github.com/sindresorhus/cp-file#options) and [globby](https://github.com/sindresorhus/globby#options). - - @example - ``` - import cpy = require('cpy'); - - (async () => { - await cpy(['source/*.png', '!source/goat.png'], 'destination'); - console.log('Files copied!'); - })(); - ``` - */ - ( - source: string | ReadonlyArray, - destination: string, - options?: cpy.Options - ): Promise & cpy.ProgressEmitter; - - // TODO: Remove this for the next major release, refactor the whole definition to: - // declare function cpy( - // source: string | ReadonlyArray, - // destination: string, - // options?: cpy.Options - // ): Promise & cpy.ProgressEmitter; - // export = cpy; - default: typeof cpy; -}; +/** +Copy files. + +@param source - Files to copy. +@param destination - Destination directory. +@param options - In addition to the options defined here, options are passed to [globby](https://github.com/sindresorhus/globby#options). + +@example +``` +import cpy = require('cpy'); + +(async () => { + await cpy(['source/*.png', '!source/goat.png'], 'destination'); + console.log('Files copied!'); +})(); +``` +*/ +declare function cpy( + source: string | ReadonlyArray, + destination: string, + options?: cpy.Options +): Promise & cpy.ProgressEmitter; export = cpy; diff --git a/index.js b/index.js index a57ab9d..39fabae 100644 --- a/index.js +++ b/index.js @@ -6,11 +6,11 @@ const globby = require('globby'); const cpFile = require('cp-file'); const CpyError = require('./cpy-error'); -const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; +const preprocessSourcePath = (source, options) => options.cwd ? path.resolve(options.cwd, source) : source; -const preprocessDestPath = (srcPath, dest, options) => { - let basename = path.basename(srcPath); - const dirname = path.dirname(srcPath); +const preprocessDestinationPath = (source, destination, options) => { + let basename = path.basename(source); + const dirname = path.dirname(source); if (typeof options.rename === 'string') { basename = options.rename; @@ -19,90 +19,89 @@ const preprocessDestPath = (srcPath, dest, options) => { } if (options.cwd) { - dest = path.resolve(options.cwd, dest); + destination = path.resolve(options.cwd, destination); } if (options.parents) { - return path.join(dest, dirname, basename); + return path.join(destination, dirname, basename); } - return path.join(dest, basename); + return path.join(destination, basename); }; -const cpy = (src, dest, options = {}) => { - src = arrify(src); - +module.exports = (source, destination, options = {}) => { const progressEmitter = new EventEmitter(); - if (src.length === 0 || !dest) { - const promise = Promise.reject(new CpyError('`files` and `destination` required')); - promise.on = (...args) => { - progressEmitter.on(...args); - return promise; - }; - - return promise; - } - - const copyStatus = new Map(); - let completedFiles = 0; - let completedSize = 0; + const promise = (async () => { + source = arrify(source); + + if (source.length === 0 || !destination) { + throw new CpyError('`source` and `destination` required'); + } + + const copyStatus = new Map(); + let completedFiles = 0; + let completedSize = 0; + + let files; + try { + files = await globby(source, options); + } catch (error) { + throw new CpyError(`Cannot glob \`${source}\`: ${error.message}`, error); + } + + if (files.length === 0) { + progressEmitter.emit('progress', { + totalFiles: 0, + percent: 1, + completedFiles: 0, + completedSize: 0 + }); + } + + const fileProgressHandler = event => { + const fileStatus = copyStatus.get(event.src) || {written: 0, percent: 0}; + + if (fileStatus.written !== event.written || fileStatus.percent !== event.percent) { + completedSize -= fileStatus.written; + completedSize += event.written; + + if (event.percent === 1 && fileStatus.percent !== 1) { + completedFiles++; + } + + copyStatus.set(event.src, { + written: event.written, + percent: event.percent + }); - const promise = globby(src, options) - .catch(error => { - throw new CpyError(`Cannot glob \`${src}\`: ${error.message}`, error); - }) - .then(files => { - if (files.length === 0) { progressEmitter.emit('progress', { - totalFiles: 0, - percent: 1, - completedFiles: 0, - completedSize: 0 + totalFiles: files.length, + percent: completedFiles / files.length, + completedFiles, + completedSize }); } + }; + + return Promise.all(files.map(async sourcePath => { + const from = preprocessSourcePath(sourcePath, options); + const to = preprocessDestinationPath(sourcePath, destination, options); + + try { + await cpFile(from, to, options).on('progress', fileProgressHandler); + } catch (error) { + throw new CpyError(`Cannot copy from \`${from}\` to \`${to}\`: ${error.message}`, error); + } + + return to; + })); + })(); - return Promise.all(files.map(srcPath => { - const from = preprocessSrcPath(srcPath, options); - const to = preprocessDestPath(srcPath, dest, options); - - return cpFile(from, to, options) - .on('progress', event => { - const fileStatus = copyStatus.get(event.src) || {written: 0, percent: 0}; - - if (fileStatus.written !== event.written || fileStatus.percent !== event.percent) { - completedSize -= fileStatus.written; - completedSize += event.written; - - if (event.percent === 1 && fileStatus.percent !== 1) { - completedFiles++; - } - - copyStatus.set(event.src, {written: event.written, percent: event.percent}); - - progressEmitter.emit('progress', { - totalFiles: files.length, - percent: completedFiles / files.length, - completedFiles, - completedSize - }); - } - }) - .then(() => to) - .catch(error => { - throw new CpyError(`Cannot copy from \`${from}\` to \`${to}\`: ${error.message}`, error); - }); - })); - }); - - promise.on = (...args) => { - progressEmitter.on(...args); + promise.on = (...arguments_) => { + progressEmitter.on(...arguments_); return promise; }; return promise; }; - -module.exports = cpy; -// TODO: Remove this for the next major release -module.exports.default = cpy; diff --git a/package.json b/package.json index b586580..946ffb7 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "url": "sindresorhus.com" }, "engines": { - "node": ">=6" + "node": ">=8" }, "scripts": { "test": "xo && ava && tsd" @@ -36,18 +36,21 @@ "quick", "data", "content", - "contents" + "contents", + "cpx", + "directory", + "directories" ], "dependencies": { - "arrify": "^1.0.1", - "cp-file": "^6.1.0", + "arrify": "^2.0.1", + "cp-file": "^7.0.0", "globby": "^9.2.0", "nested-error-stacks": "^2.1.0" }, "devDependencies": { - "ava": "^1.4.1", + "ava": "^2.1.0", "rimraf": "^2.6.3", - "tempfile": "^2.0.0", + "tempfile": "^3.0.0", "tsd": "^0.7.2", "xo": "^0.24.0" } diff --git a/readme.md b/readme.md index c26480f..c06a349 100644 --- a/readme.md +++ b/readme.md @@ -85,9 +85,13 @@ Type: `string | Function` Filename or function returning a filename used to rename every file in `source`. ```js -cpy('foo.js', 'destination', { - rename: basename => `prefix-${basename}` -}); +const cpy = require('cpy'); + +(async () => { + await cpy('foo.js', 'destination', { + rename: basename => `prefix-${basename}` + }); +})(); ``` diff --git a/test.js b/test.js index 279ced5..b26d710 100644 --- a/test.js +++ b/test.js @@ -20,14 +20,14 @@ test.afterEach(t => { rimraf.sync(t.context.EPERM); }); -test('reject Errors on missing `files`', async t => { - const error1 = await t.throwsAsync(cpy, /`files`/); +test('reject Errors on missing `source`', async t => { + const error1 = await t.throwsAsync(cpy, /`source`/); t.true(error1 instanceof CpyError); - const error2 = await t.throwsAsync(cpy(null, 'dest'), /`files`/); + const error2 = await t.throwsAsync(cpy(null, 'destination'), /`source`/); t.true(error2 instanceof CpyError); - const error3 = await t.throwsAsync(cpy([], 'dest'), /`files`/); + const error3 = await t.throwsAsync(cpy([], 'destination'), /`source`/); t.true(error3 instanceof CpyError); }); @@ -54,9 +54,9 @@ test('cwd', async t => { fs.mkdirSync(path.join(t.context.tmp, 'cwd')); fs.writeFileSync(path.join(t.context.tmp, 'cwd/hello.js'), 'console.log("hello");'); - await cpy(['hello.js'], 'dest', {cwd: path.join(t.context.tmp, 'cwd')}); + await cpy(['hello.js'], 'destination', {cwd: path.join(t.context.tmp, 'cwd')}); - t.is(read(t.context.tmp, 'cwd/hello.js'), read(t.context.tmp, 'cwd/dest/hello.js')); + t.is(read(t.context.tmp, 'cwd/hello.js'), read(t.context.tmp, 'cwd/destination/hello.js')); }); test('do not overwrite', async t => { @@ -90,34 +90,34 @@ test('path structure', async t => { test('rename filenames but not filepaths', async t => { fs.mkdirSync(t.context.tmp); - fs.mkdirSync(path.join(t.context.tmp, 'src')); + fs.mkdirSync(path.join(t.context.tmp, 'source')); fs.writeFileSync(path.join(t.context.tmp, 'hello.js'), 'console.log("hello");'); - fs.writeFileSync(path.join(t.context.tmp, 'src/hello.js'), 'console.log("hello");'); + fs.writeFileSync(path.join(t.context.tmp, 'source/hello.js'), 'console.log("hello");'); - await cpy(['hello.js', 'src/hello.js'], 'dest/subdir', { + await cpy(['hello.js', 'source/hello.js'], 'destination/subdir', { cwd: t.context.tmp, parents: true, rename: 'hi.js' }); - t.is(read(t.context.tmp, 'hello.js'), read(t.context.tmp, 'dest/subdir/hi.js')); - t.is(read(t.context.tmp, 'src/hello.js'), read(t.context.tmp, 'dest/subdir/src/hi.js')); + t.is(read(t.context.tmp, 'hello.js'), read(t.context.tmp, 'destination/subdir/hi.js')); + t.is(read(t.context.tmp, 'source/hello.js'), read(t.context.tmp, 'destination/subdir/source/hi.js')); }); test('rename filenames using a function', async t => { fs.mkdirSync(t.context.tmp); - fs.mkdirSync(path.join(t.context.tmp, 'src')); + fs.mkdirSync(path.join(t.context.tmp, 'source')); fs.writeFileSync(path.join(t.context.tmp, 'foo.js'), 'console.log("foo");'); - fs.writeFileSync(path.join(t.context.tmp, 'src/bar.js'), 'console.log("bar");'); + fs.writeFileSync(path.join(t.context.tmp, 'source/bar.js'), 'console.log("bar");'); - await cpy(['foo.js', 'src/bar.js'], 'dest/subdir', { + await cpy(['foo.js', 'source/bar.js'], 'destination/subdir', { cwd: t.context.tmp, parents: true, rename: basename => `prefix-${basename}` }); - t.is(read(t.context.tmp, 'foo.js'), read(t.context.tmp, 'dest/subdir/prefix-foo.js')); - t.is(read(t.context.tmp, 'src/bar.js'), read(t.context.tmp, 'dest/subdir/src/prefix-bar.js')); + t.is(read(t.context.tmp, 'foo.js'), read(t.context.tmp, 'destination/subdir/prefix-foo.js')); + t.is(read(t.context.tmp, 'source/bar.js'), read(t.context.tmp, 'destination/subdir/source/prefix-bar.js')); }); test('cp-file errors are not glob errors', async t => {