/
index.js
122 lines (97 loc) · 3.15 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
'use strict';
const EventEmitter = require('events');
const path = require('path');
const os = require('os');
const pAll = require('p-all');
const arrify = require('arrify');
const globby = require('globby');
const isGlob = require('is-glob');
const cpFile = require('cp-file');
const junk = require('junk');
const CpyError = require('./cpy-error');
const defaultOptions = {
ignoreJunk: true
};
const preprocessSourcePath = (source, options) => options.cwd ? path.resolve(options.cwd, source) : source;
const preprocessDestinationPath = (source, destination, options) => {
let basename = path.basename(source);
const dirname = path.dirname(source);
if (typeof options.rename === 'string') {
basename = options.rename;
} else if (typeof options.rename === 'function') {
basename = options.rename(basename);
}
if (options.cwd) {
destination = path.resolve(options.cwd, destination);
}
if (options.parents) {
return path.join(destination, dirname, basename);
}
return path.join(destination, basename);
};
module.exports = (source, destination, {
concurrency = (os.cpus().length || 1) * 2,
...options
} = {}) => {
const progressEmitter = new EventEmitter();
options = {...defaultOptions, ...options};
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);
if (options.ignoreJunk) {
files = files.filter(file => junk.not(path.basename(file)));
}
} catch (error) {
throw new CpyError(`Cannot glob \`${source}\`: ${error.message}`, error);
}
const sourcePaths = source.filter(value => !isGlob(value));
if (files.length === 0 || (sourcePaths.length > 0 && !sourcePaths.every(value => files.includes(value)))) {
throw new CpyError(`Cannot copy \`${source}\`: the file doesn't exist`);
}
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
});
progressEmitter.emit('progress', {
totalFiles: files.length,
percent: completedFiles / files.length,
completedFiles,
completedSize
});
}
};
return pAll(files.map(sourcePath => {
return async () => {
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;
};
}), {concurrency});
})();
promise.on = (...arguments_) => {
progressEmitter.on(...arguments_);
return promise;
};
return promise;
};