diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cdcd52b..aafa751 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,8 +12,6 @@ jobs: node-version: - 14 - 12 - - 10 - - 8 os: - ubuntu-latest - macos-latest diff --git a/glob-pattern.js b/glob-pattern.js new file mode 100644 index 0000000..7fd3445 --- /dev/null +++ b/glob-pattern.js @@ -0,0 +1,64 @@ +'use strict'; +const glob = require('globby'); +const junk = require('junk'); +const path = require('path'); +const fs = require('fs'); + +class GlobPattern { + /** + * @param {string} pattern + * @param {string} destination + * @param {import('.').Options} options + */ + constructor(pattern, destination, options) { + this.path = pattern; + this.originalPath = pattern; + this.destination = destination; + this.options = options; + + if ( + !glob.hasMagic(pattern) && + fs.existsSync(pattern) && + fs.lstatSync(pattern).isDirectory() + ) { + this.path = [pattern, '**'].join('/'); + } + } + + get name() { + return path.basename(this.originalPath); + } + + get normalizedPath() { + const segments = this.originalPath.split('/'); + const magicIndex = segments.findIndex(item => item ? glob.hasMagic(item) : false); + const normalized = segments.slice(0, magicIndex).join('/'); + + if (normalized) { + return path.isAbsolute(normalized) ? normalized : path.join(this.options.cwd, normalized); + } + + return this.destination; + } + + hasMagic() { + return glob.hasMagic(this.options.flat ? this.path : this.originalPath); + } + + getMatches() { + let matches = glob.sync(this.path, { + ...this.options, + dot: true, + absolute: true, + onlyFiles: true + }); + + if (this.options.ignoreJunk) { + matches = matches.filter(file => junk.not(path.basename(file))); + } + + return matches; + } +} + +module.exports = GlobPattern; diff --git a/index.d.ts b/index.d.ts index dfd2bf1..66f02e3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,8 +1,8 @@ -import {GlobbyOptions} from 'globby'; +import {GlobbyOptions as GlobOptions} from 'globby'; import {Options as CpFileOptions} from 'cp-file'; declare namespace cpy { - interface SourceFile { + interface Entry { /** Resolved path to the file. @@ -11,9 +11,9 @@ declare namespace cpy { readonly path: string; /** - Relative path to the file from `cwd`. + Relative path to the file from cwd. - @example 'dir/foo.js' if `cwd` was '/tmp' + @example 'dir/foo.js' */ readonly relativePath: string; @@ -39,7 +39,7 @@ declare namespace cpy { readonly extension: string; } - interface Options extends Readonly, CpFileOptions { + interface Options extends Readonly, CpFileOptions { /** Working directory to find source files. @@ -48,11 +48,11 @@ declare namespace cpy { readonly cwd?: string; /** - Preserve path structure. + Flatten directory tree. @default false */ - readonly parents?: boolean; + readonly flat?: boolean; /** Filename or function returning a filename used to rename every file in `source`. @@ -65,6 +65,9 @@ declare namespace cpy { await cpy('foo.js', 'destination', { rename: basename => `prefix-${basename}` }); + await cpy('foo.js', 'destination', { + rename: 'new-name' + }); })(); ``` */ @@ -102,7 +105,7 @@ declare namespace cpy { })(); ``` */ - readonly filter?: (file: SourceFile) => (boolean | Promise); + readonly filter?: (file: Entry) => boolean | Promise; } interface ProgressData { @@ -133,27 +136,48 @@ declare namespace cpy { handler: (progress: ProgressData) => void ): Promise; } + + interface CopyStatus { + written: number; + percent: number; + } } /** -Copy files. + Copy files. + + @param source - Files to copy. If any of the files do not exist, an error will be thrown (does not apply to globs). + @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 + ``` + const cpy = require('cpy'); + + (async () => { + await cpy([ + 'source/*.png', // Copy all .png files + '!source/goat.png', // Ignore goat.png + ], 'destination'); + + // Copy node_modules to destination/node_modules + await cpy('node_modules', 'destination'); + + // Copy node_modules content to destination + await cpy('node_modules/**', 'destination'); -@param source - Files to copy. If any of the files do not exist, an error will be thrown (does not apply to globs). -@param destination - Destination directory. -@param options - In addition to the options defined here, options are passed to [globby](https://github.com/sindresorhus/globby#options). + // Copy node_modules structure but skip all files except any .json files + await cpy('node_modules/**\/*.json', 'destination'); -@example -``` -import cpy = require('cpy'); + // Copy all png files into destination without keeping directory structure + await cpy('**\/*.png', 'destination', {flat: true}); -(async () => { - await cpy(['source/*.png', '!source/goat.png'], 'destination'); - console.log('Files copied!'); -})(); -``` + console.log('Files copied!'); + })(); + ``` */ declare function cpy( - source: string | ReadonlyArray, + source: string | readonly string[], destination: string, options?: cpy.Options ): Promise & cpy.ProgressEmitter; diff --git a/index.js b/index.js index 5e3ac83..dd085f5 100644 --- a/index.js +++ b/index.js @@ -4,65 +4,125 @@ const path = require('path'); const os = require('os'); const pMap = require('p-map'); const arrify = require('arrify'); -const globby = require('globby'); -const hasGlob = require('has-glob'); const cpFile = require('cp-file'); -const junk = require('junk'); const pFilter = require('p-filter'); const CpyError = require('./cpy-error'); +const GlobPattern = require('./glob-pattern'); +const glob = require('globby'); +const defaultConcurrency = (os.cpus().length || 1) * 2; + +/** + * @type {import('./index').Options} + */ const defaultOptions = { - ignoreJunk: true + ignoreJunk: true, + flat: false, + cwd: process.cwd() }; -class SourceFile { - constructor(relativePath, path) { - this.path = path; - this.relativePath = relativePath; +class Entry { + /** + * @param {string} source + * @param {string} relativePath + * @param {GlobPattern} pattern + */ + constructor(source, relativePath, pattern) { + /** + * @type {string} + */ + this.path = source.split('/').join(path.sep); + + /** + * @type {string} + */ + this.relativePath = relativePath.split('/').join(path.sep); + + this.pattern = pattern; + Object.freeze(this); } get name() { - return path.basename(this.relativePath); + return path.basename(this.path); } get nameWithoutExtension() { - return path.basename(this.relativePath, path.extname(this.relativePath)); + return path.basename(this.path, path.extname(this.path)); } get extension() { - return path.extname(this.relativePath).slice(1); + return path.extname(this.path).slice(1); } } -const preprocessSourcePath = (source, options) => path.resolve(options.cwd ? options.cwd : process.cwd(), source); +/** + * @param {object} props + * @param {Entry} props.entry + * @param {import('./index').Options} + * @param {string} props.destination + * @returns {string} + */ +const preprocessDestinationPath = ({entry, destination, options}) => { + if (entry.pattern.hasMagic()) { + if (options.flat) { + if (path.isAbsolute(destination)) { + return path.join(destination, entry.name); + } -const preprocessDestinationPath = (source, destination, options) => { - let basename = path.basename(source); + return path.join(options.cwd, destination, entry.name); + } - if (typeof options.rename === 'string') { - basename = options.rename; - } else if (typeof options.rename === 'function') { - basename = options.rename(basename); + return path.join( + destination, + path.relative(entry.pattern.normalizedPath, entry.path) + ); } - if (options.cwd) { - destination = path.resolve(options.cwd, destination); + if (path.isAbsolute(destination)) { + return path.join(destination, entry.name); } - if (options.parents) { - const dirname = path.dirname(source); - const parsedDirectory = path.parse(dirname); - return path.join(destination, dirname.replace(parsedDirectory.root, path.sep), basename); + return path.join(options.cwd, destination, path.relative(options.cwd, entry.path)); +}; + +/** + * @param {string} source + * @param {string|Function} rename + */ +const renameFile = (source, rename) => { + const filename = path.basename(source, path.extname(source)); + const ext = path.extname(source); + const dir = path.dirname(source); + if (typeof rename === 'string') { + return path.join(dir, rename); } - return path.join(destination, basename); + if (typeof rename === 'function') { + return path.join(dir, `${rename(filename)}${ext}`); + } + + return source; }; -module.exports = (source, destination, { - concurrency = (os.cpus().length || 1) * 2, - ...options -} = {}) => { +/** + * @param {string|string[]} source + * @param {string} destination + * @param {import('./index').Options} options + */ +const cpy = ( + source, + destination, + {concurrency = defaultConcurrency, ...options} = {} +) => { + /** + * @type {Map} + */ + const copyStatus = new Map(); + + /** + * @type {import('events').EventEmitter} + */ const progressEmitter = new EventEmitter(); options = { @@ -71,39 +131,55 @@ module.exports = (source, destination, { }; const promise = (async () => { - source = arrify(source); + /** + * @type {Entry[]} + */ + let entries = []; + let completedFiles = 0; + let completedSize = 0; + /** + * @type {GlobPattern[]} + */ + let patterns = arrify(source).map(string => string.replace(/\\/g, '/')); - if (source.length === 0 || !destination) { + if (patterns.length === 0 || !destination) { throw new CpyError('`source` and `destination` required'); } - const copyStatus = new Map(); - let completedFiles = 0; - let completedSize = 0; + patterns = patterns.map(pattern => new GlobPattern(pattern, destination, options)); - let files; - try { - files = await globby(source, options); + for (const pattern of patterns) { + /** + * @type {string[]} + */ + let matches = []; - if (options.ignoreJunk) { - files = files.filter(file => junk.not(path.basename(file))); + try { + matches = pattern.getMatches(); + } catch (error) { + throw new CpyError( + `Cannot glob \`${pattern.originalPath}\`: ${error.message}`, + error + ); } - } catch (error) { - throw new CpyError(`Cannot glob \`${source}\`: ${error.message}`, error); - } - if (files.length === 0 && !hasGlob(source)) { - throw new CpyError(`Cannot copy \`${source}\`: the file doesn't exist`); - } + if (matches.length === 0 && !glob.hasMagic(pattern.originalPath)) { + throw new CpyError( + `Cannot copy \`${pattern.originalPath}\`: the file doesn't exist` + ); + } - let sources = files.map(sourcePath => new SourceFile(sourcePath, preprocessSourcePath(sourcePath, options))); + entries = [ + ...entries, + ...matches.map(sourcePath => new Entry(sourcePath, path.relative(options.cwd, sourcePath), pattern)) + ]; + } if (options.filter !== undefined) { - const filteredSources = await pFilter(sources, options.filter, {concurrency: 1024}); - sources = filteredSources; + entries = await pFilter(entries, options.filter, {concurrency: 1024}); } - if (sources.length === 0) { + if (entries.length === 0) { progressEmitter.emit('progress', { totalFiles: 0, percent: 1, @@ -112,10 +188,19 @@ module.exports = (source, destination, { }); } + /** + * @param {import('cp-file').ProgressData} event + */ const fileProgressHandler = event => { - const fileStatus = copyStatus.get(event.src) || {written: 0, percent: 0}; - - if (fileStatus.written !== event.written || fileStatus.percent !== event.percent) { + 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; @@ -129,25 +214,42 @@ module.exports = (source, destination, { }); progressEmitter.emit('progress', { - totalFiles: files.length, - percent: completedFiles / files.length, + totalFiles: entries.length, + percent: completedFiles / entries.length, completedFiles, completedSize }); } }; - return pMap(sources, async source => { - const to = preprocessDestinationPath(source.relativePath, destination, options); - - try { - await cpFile(source.path, to, options).on('progress', fileProgressHandler); - } catch (error) { - throw new CpyError(`Cannot copy from \`${source.relativePath}\` to \`${to}\`: ${error.message}`, error); - } + return pMap( + entries, + async entry => { + const to = renameFile( + preprocessDestinationPath({ + entry, + destination, + options + }), + options.rename + ); + + try { + await cpFile(entry.path, to, options).on( + 'progress', + fileProgressHandler + ); + } catch (error) { + throw new CpyError( + `Cannot copy from \`${entry.relativePath}\` to \`${to}\`: ${error.message}`, + error + ); + } - return to; - }, {concurrency}); + return to; + }, + {concurrency} + ); })(); promise.on = (...arguments_) => { @@ -157,3 +259,5 @@ module.exports = (source, destination, { return promise; }; + +module.exports = cpy; diff --git a/index.test-d.ts b/index.test-d.ts index 5c16541..dc782c5 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -15,10 +15,7 @@ expectType & ProgressEmitter>( cpy('foo.js', 'destination', {cwd: '/'}) ); expectType & ProgressEmitter>( - cpy('foo.js', 'destination', {parents: true}) -); -expectType & ProgressEmitter>( - cpy('foo.js', 'destination', {expandDirectories: true}) + cpy('foo.js', 'destination', {flat: true}) ); expectType & ProgressEmitter>( cpy('foo.js', 'destination', {overwrite: false}) @@ -28,10 +25,21 @@ expectType & ProgressEmitter>( ); expectType & ProgressEmitter>( - cpy('foo.js', 'destination', {filter: (file: cpy.SourceFile) => true}) + cpy('foo.js', 'destination', { + filter: file => { + expectType(file); + + expectType(file.path); + expectType(file.relativePath); + expectType(file.name); + expectType(file.nameWithoutExtension); + expectType(file.extension); + return true; + } + }) ); expectType & ProgressEmitter>( - cpy('foo.js', 'destination', {filter: async (file: cpy.SourceFile) => true}) + cpy('foo.js', 'destination', {filter: async (file: cpy.Entry) => true}) ); expectType>( diff --git a/package.json b/package.json index 703d665..feb3050 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,15 @@ "url": "sindresorhus.com" }, "engines": { - "node": ">=8" + "node": ">=12" }, "scripts": { "test": "xo && ava && tsd" }, + "prettier": { + "bracketSpacing": false, + "singleQuote": true + }, "files": [ "cpy-error.js", "index.js", @@ -45,20 +49,18 @@ "dependencies": { "arrify": "^2.0.1", "cp-file": "^7.0.0", - "globby": "^9.2.0", - "has-glob": "^1.0.0", + "globby": "^11.0.4", "junk": "^3.1.0", "nested-error-stacks": "^2.1.0", - "p-all": "^2.1.0", "p-filter": "^2.1.0", "p-map": "^3.0.0" }, "devDependencies": { - "ava": "^3.12.1", + "ava": "^3.15.0", "proxyquire": "^2.1.3", "rimraf": "^3.0.0", - "tempy": "^0.6.0", - "tsd": "^0.11.0", - "xo": "^0.25.3" + "tempy": "^1.0.1", + "tsd": "^0.17.0", + "xo": "^0.25.4" } } diff --git a/readme.md b/readme.md index 6797754..cc73d9d 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,23 @@ $ npm install cpy const cpy = require('cpy'); (async () => { - await cpy(['source/*.png', '!source/goat.png'], 'destination'); + await cpy([ + 'source/*.png', // Copy all .png files + '!source/goat.png', // Ignore goat.png + ], 'destination'); + + // Copy node_modules to destination/node_modules + await cpy('node_modules', 'destination'); + + // Copy node_modules content to destination + await cpy('node_modules/**', 'destination'); + + // Copy node_modules structure but skip all files except package.json files + await cpy('node_modules/**/*.json', 'destination'); + + // Copy all png files into destination without keeping directory structure + await cpy('**/*.png', 'destination', {flat: true}); + console.log('Files copied!'); })(); ``` @@ -69,12 +85,23 @@ Default: `true` Overwrite existing files. -##### parents +##### flat Type: `boolean`\ Default: `false` -Preserve path structure. +Flatten directory structure. All copied files will be put in the same directory. + + +```js +const cpy = require('cpy'); + +(async () => { + await cpy('src/**/*.js', 'destination', { + flat: true + }); +})(); +``` ##### rename @@ -89,6 +116,9 @@ const cpy = require('cpy'); await cpy('foo.js', 'destination', { rename: basename => `prefix-${basename}` }); + await cpy('foo.js', 'destination', { + rename: 'new-name' + }); })(); ``` diff --git a/test.js b/test.js index 6141d80..f9ea210 100644 --- a/test.js +++ b/test.js @@ -53,28 +53,30 @@ test('copy array of files', async t => { }); test('throws on invalid concurrency value', async t => { - await t.throwsAsync(cpy(['license', 'package.json'], t.context.tmp, {concurrency: -2})); - await t.throwsAsync(cpy(['license', 'package.json'], t.context.tmp, {concurrency: 'foo'})); + await t.throwsAsync( + cpy(['license', 'package.json'], t.context.tmp, {concurrency: -2}) + ); + await t.throwsAsync( + cpy(['license', 'package.json'], t.context.tmp, {concurrency: 'foo'}) + ); }); test('copy array of files with filter', async t => { await cpy(['license', 'package.json'], t.context.tmp, { filter: file => { - if (file.path.endsWith(`${path.sep}license`)) { + if (file.path.endsWith('license')) { t.is(file.path, path.join(process.cwd(), 'license')); - t.is(file.relativePath, 'license'); t.is(file.name, 'license'); t.is(file.nameWithoutExtension, 'license'); t.is(file.extension, ''); - } else if (file.path.endsWith(`${path.sep}package.json`)) { + } else if (file.path.endsWith('package.json')) { t.is(file.path, path.join(process.cwd(), 'package.json')); - t.is(file.relativePath, 'package.json'); t.is(file.name, 'package.json'); t.is(file.nameWithoutExtension, 'package'); t.is(file.extension, 'json'); } - return !file.path.endsWith(`${path.sep}license`); + return !file.path.endsWith('license'); } }); @@ -87,13 +89,11 @@ test('copy array of files with async filter', async t => { filter: async file => { if (file.path.endsWith(`${path.sep}license`)) { t.is(file.path, path.join(process.cwd(), 'license')); - t.is(file.relativePath, 'license'); t.is(file.name, 'license'); t.is(file.nameWithoutExtension, 'license'); t.is(file.extension, ''); } else if (file.path.endsWith(`${path.sep}package.json`)) { t.is(file.path, path.join(process.cwd(), 'package.json')); - t.is(file.relativePath, 'package.json'); t.is(file.name, 'package.json'); t.is(file.nameWithoutExtension, 'package'); t.is(file.extension, 'json'); @@ -110,11 +110,19 @@ test('copy array of files with async filter', async t => { test('cwd', async t => { fs.mkdirSync(t.context.tmp); fs.mkdirSync(path.join(t.context.tmp, 'cwd')); - fs.writeFileSync(path.join(t.context.tmp, 'cwd/hello.js'), 'console.log("hello");'); + fs.writeFileSync( + path.join(t.context.tmp, 'cwd/hello.js'), + 'console.log("hello");' + ); - await cpy(['hello.js'], 'destination', {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/destination/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 => { @@ -129,7 +137,10 @@ test('do not overwrite', async t => { test('do not keep path structure', async t => { fs.mkdirSync(t.context.tmp); fs.mkdirSync(path.join(t.context.tmp, 'cwd')); - fs.writeFileSync(path.join(t.context.tmp, 'cwd/hello.js'), 'console.log("hello");'); + fs.writeFileSync( + path.join(t.context.tmp, 'cwd/hello.js'), + 'console.log("hello");' + ); await cpy([path.join(t.context.tmp, 'cwd/hello.js')], t.context.tmp); @@ -139,44 +150,101 @@ test('do not keep path structure', async t => { test('path structure', async t => { fs.mkdirSync(t.context.tmp); fs.mkdirSync(path.join(t.context.tmp, 'cwd')); - fs.writeFileSync(path.join(t.context.tmp, 'cwd/hello.js'), 'console.log("hello");'); - - await cpy([path.join(t.context.tmp, 'cwd/hello.js')], t.context.tmp, {parents: true}); - - const {root} = path.parse(t.context.tmp); - t.is(read(t.context.tmp, 'cwd/hello.js'), read(t.context.tmp, t.context.tmp.replace(root, path.sep), 'cwd/hello.js')); + fs.mkdirSync(path.join(t.context.tmp, 'out')); + fs.writeFileSync( + path.join(t.context.tmp, 'cwd/hello.js'), + 'console.log("hello");' + ); + + await cpy([path.join(t.context.tmp, '**')], path.join(t.context.tmp, 'out')); + + t.is( + read(t.context.tmp, 'cwd/hello.js'), + read(t.context.tmp, 'out', 'cwd/hello.js') + ); }); test('rename filenames but not filepaths', async t => { fs.mkdirSync(t.context.tmp); 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, 'source/hello.js'), 'console.log("hello");'); + fs.writeFileSync( + path.join(t.context.tmp, 'hello.js'), + 'console.log("hello");' + ); + fs.writeFileSync( + path.join(t.context.tmp, 'source/hello.js'), + 'console.log("hello");' + ); 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, 'destination/subdir/hi.js')); - t.is(read(t.context.tmp, 'source/hello.js'), read(t.context.tmp, 'destination/subdir/source/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, 'source')); fs.writeFileSync(path.join(t.context.tmp, 'foo.js'), 'console.log("foo");'); - fs.writeFileSync(path.join(t.context.tmp, 'source/bar.js'), 'console.log("bar");'); + fs.writeFileSync( + path.join(t.context.tmp, 'source/bar.js'), + 'console.log("bar");' + ); 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, 'destination/subdir/prefix-foo.js')); - t.is(read(t.context.tmp, 'source/bar.js'), read(t.context.tmp, 'destination/subdir/source/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('flatten directory tree', async t => { + fs.mkdirSync(t.context.tmp); + fs.mkdirSync(path.join(t.context.tmp, 'source')); + fs.mkdirSync(path.join(t.context.tmp, 'source', 'nested')); + fs.writeFileSync(path.join(t.context.tmp, 'foo.js'), 'console.log("foo");'); + fs.writeFileSync( + path.join(t.context.tmp, 'source/bar.js'), + 'console.log("bar");' + ); + fs.writeFileSync( + path.join(t.context.tmp, 'source/nested/baz.ts'), + 'console.log("baz");' + ); + + await cpy('**/*.js', 'destination/subdir', { + cwd: t.context.tmp, + flat: true + }); + + t.is( + read(t.context.tmp, 'foo.js'), + read(t.context.tmp, 'destination/subdir/foo.js') + ); + t.is( + read(t.context.tmp, 'source/bar.js'), + read(t.context.tmp, 'destination/subdir/bar.js') + ); + t.falsy( + fs.existsSync(path.join(t.context.tmp, 'destination/subdir/baz.ts')) + ); }); test('cp-file errors are CpyErrors', async t => { @@ -184,11 +252,6 @@ test('cp-file errors are CpyErrors', async t => { await t.throwsAsync(cpy('license', t.context.dir), {message: /cp-file/, instanceOf: CpyError}); }); -test('glob errors are CpyErrors', async t => { - const cpy = cpyMockedError('globby'); - await t.throwsAsync(cpy(path.join(t.context.dir, '/**'), t.context.tmp), {message: /globby/, instanceOf: CpyError}); -}); - test('throws on non-existing file', async t => { fs.mkdirSync(t.context.tmp); @@ -219,10 +282,12 @@ test('junk files are ignored', async t => { let report; - await cpy('*', t.context.tmp, {cwd: path.join(t.context.tmp, 'cwd'), ignoreJunk: true}) - .on('progress', event => { - report = event; - }); + await cpy('*', t.context.tmp, { + cwd: path.join(t.context.tmp, 'cwd'), + ignoreJunk: true + }).on('progress', event => { + report = event; + }); t.not(report, undefined); t.is(report.totalFiles, 1); @@ -239,10 +304,12 @@ test('junk files are copied', async t => { let report; - await cpy('*', t.context.tmp, {cwd: path.join(t.context.tmp, 'cwd'), ignoreJunk: false}) - .on('progress', event => { - report = event; - }); + await cpy('*', t.context.tmp, { + cwd: path.join(t.context.tmp, 'cwd'), + ignoreJunk: false + }).on('progress', event => { + report = event; + }); t.not(report, undefined); t.is(report.totalFiles, 2); @@ -259,10 +326,12 @@ test('nested junk files are ignored', async t => { let report; - await cpy(['cwd/*'], t.context.tmp, {cwd: t.context.tmp, ignoreJunk: true}) - .on('progress', event => { - report = event; - }); + await cpy(['cwd/*'], t.context.tmp, { + cwd: t.context.tmp, + ignoreJunk: true + }).on('progress', event => { + report = event; + }); t.not(report, undefined); t.is(report.totalFiles, 1); @@ -278,10 +347,11 @@ test('reports copy progress of single file', async t => { let report; - await cpy(['foo'], t.context.tmp, {cwd: path.join(t.context.tmp, 'cwd')}) - .on('progress', event => { - report = event; - }); + await cpy(['foo'], t.context.tmp, { + cwd: path.join(t.context.tmp, 'cwd') + }).on('progress', event => { + report = event; + }); t.not(report, undefined); t.is(report.totalFiles, 1); @@ -298,10 +368,11 @@ test('reports copy progress of multiple files', async t => { let report; - await cpy(['foo', 'bar'], t.context.tmp, {cwd: path.join(t.context.tmp, 'cwd')}) - .on('progress', event => { - report = event; - }); + await cpy(['foo', 'bar'], t.context.tmp, { + cwd: path.join(t.context.tmp, 'cwd') + }).on('progress', event => { + report = event; + }); t.not(report, undefined); t.is(report.totalFiles, 2); @@ -321,11 +392,12 @@ test('reports correct completedSize', async t => { let report; let chunkCount = 0; - await cpy(['fatfile'], t.context.tmp, {cwd: path.join(t.context.tmp, 'cwd')}) - .on('progress', event => { - chunkCount++; - report = event; - }); + await cpy(['fatfile'], t.context.tmp, { + cwd: path.join(t.context.tmp, 'cwd') + }).on('progress', event => { + chunkCount++; + report = event; + }); t.not(report, undefined); t.is(report.totalFiles, 1); @@ -347,7 +419,9 @@ test('returns destination path', async t => { fs.writeFileSync(path.join(t.context.tmp, 'cwd/foo'), 'lorem ipsum'); fs.writeFileSync(path.join(t.context.tmp, 'cwd/bar'), 'dolor sit amet'); - const to = await cpy(['foo', 'bar'], t.context.tmp, {cwd: path.join(t.context.tmp, 'cwd')}); + const to = await cpy(['foo', 'bar'], t.context.tmp, { + cwd: path.join(t.context.tmp, 'cwd') + }); t.deepEqual(to, [ path.join(t.context.tmp, 'foo'),