UNPKG

4.88 kBJavaScriptView Raw
1var fs = require('fs'),
2 path = require('path');
3
4module.exports = ncp
5ncp.ncp = ncp
6
7function ncp (source, dest, options, callback) {
8 if (!callback) {
9 callback = options;
10 options = {};
11 }
12
13 var basePath = process.cwd(),
14 currentPath = path.resolve(basePath, source),
15 targetPath = path.resolve(basePath, dest),
16 filter = options.filter,
17 transform = options.transform,
18 clobber = options.clobber !== false,
19 errs = null,
20 started = 0,
21 finished = 0,
22 running = 0,
23 limit = options.limit || ncp.limit || 16;
24
25 limit = (limit < 1) ? 1 : (limit > 512) ? 512 : limit;
26
27 startCopy(currentPath);
28
29 function startCopy(source) {
30 started++;
31 if (filter) {
32 if (filter instanceof RegExp) {
33 if (!filter.test(source)) {
34 return cb(true);
35 }
36 }
37 else if (typeof filter === 'function') {
38 if (!filter(source)) {
39 return cb(true);
40 }
41 }
42 }
43 return getStats(source);
44 }
45
46 function getStats(source) {
47 if (running >= limit) {
48 return process.nextTick(function () {
49 getStats(source);
50 });
51 }
52 running++;
53 fs.lstat(source, function (err, stats) {
54 var item = {};
55 if (err) {
56 return onError(err);
57 }
58
59 // We need to get the mode from the stats object and preserve it.
60 item.name = source;
61 item.mode = stats.mode;
62
63 if (stats.isDirectory()) {
64 return onDir(item);
65 }
66 else if (stats.isFile()) {
67 return onFile(item);
68 }
69 else if (stats.isSymbolicLink()) {
70 // Symlinks don't really need to know about the mode.
71 return onLink(source);
72 }
73 });
74 }
75
76 function onFile(file) {
77 var target = file.name.replace(currentPath, targetPath);
78 isWritable(target, function (writable) {
79 if (writable) {
80 return copyFile(file, target);
81 }
82 if(clobber)
83 rmFile(target, function () {
84 copyFile(file, target);
85 });
86 });
87 }
88
89 function copyFile(file, target) {
90 var readStream = fs.createReadStream(file.name),
91 writeStream = fs.createWriteStream(target, { mode: file.mode });
92 if(transform) {
93 transform(readStream, writeStream);
94 } else {
95 readStream.pipe(writeStream);
96 }
97 readStream.once('end', cb);
98 }
99
100 function rmFile(file, done) {
101 fs.unlink(file, function (err) {
102 if (err) {
103 return onError(err);
104 }
105 return done();
106 });
107 }
108
109 function onDir(dir) {
110 var target = dir.name.replace(currentPath, targetPath);
111 isWritable(target, function (writable) {
112 if (writable) {
113 return mkDir(dir, target);
114 }
115 copyDir(dir.name);
116 });
117 }
118
119 function mkDir(dir, target) {
120 fs.mkdir(target, dir.mode, function (err) {
121 if (err) {
122 return onError(err);
123 }
124 copyDir(dir.name);
125 });
126 }
127
128 function copyDir(dir) {
129 fs.readdir(dir, function (err, items) {
130 if (err) {
131 return onError(err);
132 }
133 items.forEach(function (item) {
134 startCopy(dir + '/' + item);
135 });
136 return cb();
137 });
138 }
139
140 function onLink(link) {
141 var target = link.replace(currentPath, targetPath);
142 fs.readlink(link, function (err, resolvedPath) {
143 if (err) {
144 return onError(err);
145 }
146 checkLink(resolvedPath, target);
147 });
148 }
149
150 function checkLink(resolvedPath, target) {
151 isWritable(target, function (writable) {
152 if (writable) {
153 return makeLink(resolvedPath, target);
154 }
155 fs.readlink(target, function (err, targetDest) {
156 if (err) {
157 return onError(err);
158 }
159 if (targetDest === resolvedPath) {
160 return cb();
161 }
162 return rmFile(target, function () {
163 makeLink(resolvedPath, target);
164 });
165 });
166 });
167 }
168
169 function makeLink(linkPath, target) {
170 fs.symlink(linkPath, target, function (err) {
171 if (err) {
172 return onError(err);
173 }
174 return cb();
175 });
176 }
177
178 function isWritable(path, done) {
179 fs.lstat(path, function (err, stats) {
180 if (err) {
181 if (err.code === 'ENOENT') return done(true);
182 return done(false);
183 }
184 return done(false);
185 });
186 }
187
188 function onError(err) {
189 if (options.stopOnError) {
190 return callback(err);
191 }
192 else if (!errs && options.errs) {
193 errs = fs.createWriteStream(options.errs);
194 }
195 else if (!errs) {
196 errs = [];
197 }
198 else if (options.errs) {
199 if (typeof errs.write === 'undefined') {
200 errs.push(err);
201 }
202 else {
203 errs.write(err.stack + '\n\n');
204 }
205 }
206 return cb();
207 }
208
209 function cb(skipped) {
210 if (!skipped) running--;
211 finished++;
212 if ((started === finished) && (running === 0)) {
213 return errs ? callback(errs) : callback(null);
214 }
215 }
216};
217
218