Skip to content

Commit

Permalink
Require Node.js 8
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Jun 11, 2019
1 parent 44200b8 commit 7229126
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 134 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Expand Up @@ -3,4 +3,3 @@ node_js:
- '12'
- '10'
- '8'
- '6'
65 changes: 29 additions & 36 deletions index.d.ts
Expand Up @@ -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);
Expand Down Expand Up @@ -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<string>,
destination: string,
options?: cpy.Options
): Promise<string[]> & cpy.ProgressEmitter;

// TODO: Remove this for the next major release, refactor the whole definition to:
// declare function cpy(
// source: string | ReadonlyArray<string>,
// destination: string,
// options?: cpy.Options
// ): Promise<void> & 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<string>,
destination: string,
options?: cpy.Options
): Promise<string[]> & cpy.ProgressEmitter;

export = cpy;
143 changes: 71 additions & 72 deletions index.js
Expand Up @@ -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;
Expand All @@ -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;
15 changes: 9 additions & 6 deletions package.json
Expand Up @@ -10,7 +10,7 @@
"url": "sindresorhus.com"
},
"engines": {
"node": ">=6"
"node": ">=8"
},
"scripts": {
"test": "xo && ava && tsd"
Expand All @@ -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"
}
Expand Down
10 changes: 7 additions & 3 deletions readme.md
Expand Up @@ -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}`
});
})();
```


Expand Down
32 changes: 16 additions & 16 deletions test.js
Expand Up @@ -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);
});

Expand All @@ -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 => {
Expand Down Expand Up @@ -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 => {
Expand Down

0 comments on commit 7229126

Please sign in to comment.