|
0 |
/*
|
|
1 |
* Copyright 2012, Mozilla Foundation and contributors
|
|
2 |
*
|
|
3 |
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4 |
* you may not use this file except in compliance with the License.
|
|
5 |
* You may obtain a copy of the License at
|
|
6 |
*
|
|
7 |
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8 |
*
|
|
9 |
* Unless required by applicable law or agreed to in writing, software
|
|
10 |
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12 |
* See the License for the specific language governing permissions and
|
|
13 |
* limitations under the License.
|
|
14 |
*/
|
|
15 |
|
|
16 |
var fs = require("fs");
|
|
17 |
var path = require("path");
|
|
18 |
var ujs = require("uglify-js");
|
|
19 |
|
|
20 |
if (!fs.existsSync) {
|
|
21 |
fs.existsSync = path.existsSync;
|
|
22 |
}
|
|
23 |
|
|
24 |
/**
|
|
25 |
* See https://github.com/mozilla/dryice for usage instructions.
|
|
26 |
*/
|
|
27 |
function copy(obj) {
|
|
28 |
var filters = copy.filterFactory(obj.filter);
|
|
29 |
var source = copy.sourceFactory(obj.source, filters);
|
|
30 |
var dest = copy.destFactory(obj.dest, filters);
|
|
31 |
dest.processSource(source);
|
|
32 |
}
|
|
33 |
|
|
34 |
/**
|
|
35 |
* A Location is a base and a path which together point to a file or directory.
|
|
36 |
* It's useful to be able to know in copy operations relative to some project
|
|
37 |
* root to be able to remember where in a destination the file should go
|
|
38 |
*/
|
|
39 |
function Location(base, somePath) {
|
|
40 |
if (base == null) {
|
|
41 |
throw new Error('base == null');
|
|
42 |
}
|
|
43 |
this.base = base;
|
|
44 |
this.path = somePath;
|
|
45 |
}
|
|
46 |
|
|
47 |
Location.prototype.isLocation = true;
|
|
48 |
|
|
49 |
Object.defineProperty(Location.prototype, 'fullname', {
|
|
50 |
get: function() {
|
|
51 |
return path.join(this.base, this.path);
|
|
52 |
}
|
|
53 |
});
|
|
54 |
|
|
55 |
Object.defineProperty(Location.prototype, 'dirname', {
|
|
56 |
get: function() {
|
|
57 |
return path.dirname(this.fullname);
|
|
58 |
}
|
|
59 |
});
|
|
60 |
|
|
61 |
/**
|
|
62 |
* Select the correct implementation of Source for the given source property
|
|
63 |
*/
|
|
64 |
copy.sourceFactory = function(source, filters) {
|
|
65 |
if (source == null) {
|
|
66 |
throw new Error('Missing source');
|
|
67 |
}
|
|
68 |
|
|
69 |
if (source.isSource) {
|
|
70 |
return source;
|
|
71 |
}
|
|
72 |
|
|
73 |
if (typeof source === 'string') {
|
|
74 |
if (copy.isDirectory(source)) {
|
|
75 |
return new copy.DirectorySource(source, null, null, filters);
|
|
76 |
}
|
|
77 |
else {
|
|
78 |
return new copy.FileSource(new Location('', source), filters);
|
|
79 |
}
|
|
80 |
}
|
|
81 |
|
|
82 |
if (Array.isArray(source)) {
|
|
83 |
return new copy.ArraySource(source, filters);
|
|
84 |
}
|
|
85 |
|
|
86 |
if (typeof source === 'function') {
|
|
87 |
return new copy.FunctionSource(source, filters);
|
|
88 |
}
|
|
89 |
|
|
90 |
if (source.root != null) {
|
|
91 |
if (source.require != null) {
|
|
92 |
var project = new CommonJsProject([ source.root ]);
|
|
93 |
return new copy.CommonJsSource(project, source.require, filters);
|
|
94 |
}
|
|
95 |
|
|
96 |
return new copy.DirectorySource(source.root, source.include, source.exclude, filters);
|
|
97 |
}
|
|
98 |
|
|
99 |
if (source.base != null && source.path != null) {
|
|
100 |
return new copy.FileSource(new Location(source.base, source.path), filters);
|
|
101 |
}
|
|
102 |
|
|
103 |
if (typeof source.value === 'string') {
|
|
104 |
return new copy.ValueSource(source.value, null, filters);
|
|
105 |
}
|
|
106 |
|
|
107 |
if (source.project != null && source.require != null) {
|
|
108 |
return new copy.CommonJsSource(source.project, source.require, filters);
|
|
109 |
}
|
|
110 |
|
|
111 |
throw new Error('Can\'t handle type of source: ' + typeof source);
|
|
112 |
};
|
|
113 |
|
|
114 |
copy.debug = false;
|
|
115 |
|
|
116 |
/**
|
|
117 |
* Abstract Source.
|
|
118 |
* Concrete implementations of Source should define the 'get' property.
|
|
119 |
*/
|
|
120 |
copy.Source = function() {
|
|
121 |
};
|
|
122 |
|
|
123 |
/**
|
|
124 |
* @return Either another source, an array of other sources or a string value
|
|
125 |
* when there is nothing else to dig into
|
|
126 |
*/
|
|
127 |
Object.defineProperty(copy.Source.prototype, 'get', {
|
|
128 |
get: function() {
|
|
129 |
throw new Error('Source.get is not implemented');
|
|
130 |
}
|
|
131 |
});
|
|
132 |
|
|
133 |
copy.Source.prototype.isSource = true;
|
|
134 |
|
|
135 |
copy.Source.prototype._runFilters = function(value, location) {
|
|
136 |
this._filters.forEach(function(filter) {
|
|
137 |
if (filter.onRead) {
|
|
138 |
value = filter(value, location);
|
|
139 |
}
|
|
140 |
}, this);
|
|
141 |
return value;
|
|
142 |
};
|
|
143 |
|
|
144 |
/**
|
|
145 |
* Default encoding for all sources
|
|
146 |
*/
|
|
147 |
copy.Source.prototype.encoding = 'utf8';
|
|
148 |
|
|
149 |
/**
|
|
150 |
* An ArraySource is simply an array containing things that can resolve to
|
|
151 |
* implementations of Source when passed to copy.sourceFactory()
|
|
152 |
*/
|
|
153 |
copy.ArraySource = function(array, filters) {
|
|
154 |
copy.Source.call(this);
|
|
155 |
this._array = array;
|
|
156 |
this._filters = filters;
|
|
157 |
};
|
|
158 |
|
|
159 |
copy.ArraySource.prototype = Object.create(copy.Source.prototype);
|
|
160 |
|
|
161 |
Object.defineProperty(copy.ArraySource.prototype, 'get', {
|
|
162 |
get: function() {
|
|
163 |
return this._array.map(function(member) {
|
|
164 |
return copy.sourceFactory(member, this._filters);
|
|
165 |
}, this);
|
|
166 |
}
|
|
167 |
});
|
|
168 |
|
|
169 |
/**
|
|
170 |
* A FunctionSource is something that can be called to resolve to another
|
|
171 |
* Source implementation
|
|
172 |
*/
|
|
173 |
copy.FunctionSource = function(func, filters) {
|
|
174 |
copy.Source.call(this);
|
|
175 |
this._func = func;
|
|
176 |
this._filters = filters;
|
|
177 |
};
|
|
178 |
|
|
179 |
copy.FunctionSource.prototype = Object.create(copy.Source.prototype);
|
|
180 |
|
|
181 |
Object.defineProperty(copy.FunctionSource.prototype, 'get', {
|
|
182 |
get: function() {
|
|
183 |
return copy.sourceFactory(this._func(), this._filters);
|
|
184 |
}
|
|
185 |
});
|
|
186 |
|
|
187 |
/**
|
|
188 |
* A Source that finds files under a given directory with specified include /
|
|
189 |
* exclude patterns.
|
|
190 |
* @param root The root in the filesystem under which the files exist
|
|
191 |
* @param filterOrInclude
|
|
192 |
*/
|
|
193 |
copy.DirectorySource = function(root, filterOrInclude, exclude, filters) {
|
|
194 |
copy.Source.call(this);
|
|
195 |
this._filters = filters;
|
|
196 |
|
|
197 |
this.root = root;
|
|
198 |
if (this.root instanceof CommonJsProject) {
|
|
199 |
this.root = this.root.roots;
|
|
200 |
}
|
|
201 |
|
|
202 |
if (Array.isArray(this.root)) {
|
|
203 |
this.root.map(function(r) {
|
|
204 |
return ensureTrailingSlash(r);
|
|
205 |
});
|
|
206 |
}
|
|
207 |
|
|
208 |
if (typeof filterOrInclude === 'function') {
|
|
209 |
this._searchFilter = filterOrInclude;
|
|
210 |
}
|
|
211 |
else {
|
|
212 |
this._searchFilter = this._createFilter(filterOrInclude, exclude);
|
|
213 |
}
|
|
214 |
};
|
|
215 |
|
|
216 |
copy.DirectorySource.prototype = Object.create(copy.Source.prototype);
|
|
217 |
|
|
218 |
Object.defineProperty(copy.DirectorySource.prototype, 'get', {
|
|
219 |
get: function() {
|
|
220 |
return this._findMatches(this.root, '/');
|
|
221 |
}
|
|
222 |
});
|
|
223 |
|
|
224 |
copy.DirectorySource.prototype._findMatches = function(root, path) {
|
|
225 |
var sources = [];
|
|
226 |
|
|
227 |
if (Array.isArray(root)) {
|
|
228 |
root.forEach(function(r) {
|
|
229 |
var matches = this._findMatches(r, path);
|
|
230 |
sources.push.apply(sources, matches);
|
|
231 |
}, this);
|
|
232 |
return sources;
|
|
233 |
}
|
|
234 |
|
|
235 |
root = ensureTrailingSlash(root);
|
|
236 |
path = ensureTrailingSlash(path);
|
|
237 |
|
|
238 |
if (copy.isDirectory(root + path)) {
|
|
239 |
fs.readdirSync(root + path).forEach(function(entry) {
|
|
240 |
var stat = fs.statSync(root + path + entry);
|
|
241 |
if (stat.isFile()) {
|
|
242 |
if (this._searchFilter(path + entry)) {
|
|
243 |
var location = new Location(root, path + entry);
|
|
244 |
sources.push(new copy.FileSource(location, this._filters));
|
|
245 |
}
|
|
246 |
}
|
|
247 |
else if (stat.isDirectory()) {
|
|
248 |
var matches = this._findMatches(root, path + entry);
|
|
249 |
sources.push.apply(sources, matches);
|
|
250 |
}
|
|
251 |
}, this);
|
|
252 |
}
|
|
253 |
|
|
254 |
return sources;
|
|
255 |
};
|
|
256 |
|
|
257 |
copy.DirectorySource.prototype._createFilter = function(include, exclude) {
|
|
258 |
return function(pathname) {
|
|
259 |
function noPathMatch(pattern) {
|
|
260 |
return !pattern.test(pathname);
|
|
261 |
}
|
|
262 |
if (include instanceof RegExp) {
|
|
263 |
if (noPathMatch(include)) {
|
|
264 |
return false;
|
|
265 |
}
|
|
266 |
}
|
|
267 |
if (typeof include === 'string') {
|
|
268 |
if (noPathMatch(new RegExp(include))) {
|
|
269 |
return false;
|
|
270 |
}
|
|
271 |
}
|
|
272 |
if (Array.isArray(include)) {
|
|
273 |
if (include.every(noPathMatch)) {
|
|
274 |
return false;
|
|
275 |
}
|
|
276 |
}
|
|
277 |
|
|
278 |
function pathMatch(pattern) {
|
|
279 |
return pattern.test(pathname);
|
|
280 |
}
|
|
281 |
if (exclude instanceof RegExp) {
|
|
282 |
if (pathMatch(exclude)) {
|
|
283 |
return false;
|
|
284 |
}
|
|
285 |
}
|
|
286 |
if (typeof exclude === 'string') {
|
|
287 |
if (pathMatch(new RegExp(exclude))) {
|
|
288 |
return false;
|
|
289 |
}
|
|
290 |
}
|
|
291 |
if (Array.isArray(exclude)) {
|
|
292 |
if (exclude.some(pathMatch)) {
|
|
293 |
return false;
|
|
294 |
}
|
|
295 |
}
|
|
296 |
|
|
297 |
return true;
|
|
298 |
};
|
|
299 |
};
|
|
300 |
|
|
301 |
/**
|
|
302 |
* A FileSource gets data directly from a file. It has 2 parts to the filename,
|
|
303 |
* a base and path members, where filename = base + path.
|
|
304 |
* FileSources are important when using CommonJS filters, because it tells the
|
|
305 |
* filter where the root of the hierarchy is, which lets us know the module
|
|
306 |
* name.
|
|
307 |
* If there is no base to the filename, use a base of ''.
|
|
308 |
*/
|
|
309 |
copy.FileSource = function(location, filters) {
|
|
310 |
copy.Source.call(this);
|
|
311 |
this.location = location;
|
|
312 |
this.name = location.fullname;
|
|
313 |
this._filters = filters;
|
|
314 |
};
|
|
315 |
|
|
316 |
copy.FileSource.prototype = Object.create(copy.Source.prototype);
|
|
317 |
|
|
318 |
Object.defineProperty(copy.FileSource.prototype, 'get', {
|
|
319 |
get: function() {
|
|
320 |
var read = fs.readFileSync(this.name);
|
|
321 |
return this._runFilters(read, this.location);
|
|
322 |
}
|
|
323 |
});
|
|
324 |
|
|
325 |
/**
|
|
326 |
*
|
|
327 |
*/
|
|
328 |
copy.ValueSource = function(value, location, filters) {
|
|
329 |
copy.Source.call(this);
|
|
330 |
this._value = value;
|
|
331 |
this._location = location;
|
|
332 |
this._filters = filters;
|
|
333 |
};
|
|
334 |
|
|
335 |
copy.ValueSource.prototype = Object.create(copy.Source.prototype);
|
|
336 |
|
|
337 |
Object.defineProperty(copy.ValueSource.prototype, 'get', {
|
|
338 |
get: function() {
|
|
339 |
return this._runFilters(this._value, this._location);
|
|
340 |
}
|
|
341 |
});
|
|
342 |
|
|
343 |
/**
|
|
344 |
* Read modules from a CommonJS Project using a require property.
|
|
345 |
*/
|
|
346 |
copy.CommonJsSource = function(project, require, filters) {
|
|
347 |
copy.Source.call(this);
|
|
348 |
this._project = project;
|
|
349 |
this._filters = filters;
|
|
350 |
|
|
351 |
if (!project instanceof CommonJsProject) {
|
|
352 |
throw new Error('commonjs project should be a CommonJsProject');
|
|
353 |
}
|
|
354 |
|
|
355 |
if (typeof require === 'string') {
|
|
356 |
this._require = [ require ];
|
|
357 |
}
|
|
358 |
else if (Array.isArray(require)) {
|
|
359 |
this._require = require;
|
|
360 |
}
|
|
361 |
else {
|
|
362 |
throw new Error('Expected commonjs args to have string/array require.');
|
|
363 |
}
|
|
364 |
};
|
|
365 |
|
|
366 |
copy.CommonJsSource.prototype = Object.create(copy.Source.prototype);
|
|
367 |
|
|
368 |
Object.defineProperty(copy.CommonJsSource.prototype, 'get', {
|
|
369 |
get: function() {
|
|
370 |
this._require.forEach(function(moduleName) {
|
|
371 |
this._project.require(moduleName);
|
|
372 |
}, this);
|
|
373 |
return this._project.getCurrentModules().map(function(location) {
|
|
374 |
return new copy.FileSource(location, this._filters);
|
|
375 |
}.bind(this));
|
|
376 |
}
|
|
377 |
});
|
|
378 |
|
|
379 |
|
|
380 |
////////////////////////////////////////////////////////////////////////////////
|
|
381 |
|
|
382 |
copy.filterFactory = function(filter) {
|
|
383 |
if (filter == null) {
|
|
384 |
return [];
|
|
385 |
}
|
|
386 |
|
|
387 |
if (typeof filter === 'function') {
|
|
388 |
return [ filter ];
|
|
389 |
}
|
|
390 |
|
|
391 |
if (Array.isArray(filter)) {
|
|
392 |
return filter;
|
|
393 |
}
|
|
394 |
};
|
|
395 |
|
|
396 |
|
|
397 |
////////////////////////////////////////////////////////////////////////////////
|
|
398 |
|
|
399 |
/**
|
|
400 |
* Select the correct implementation of Destination for the given dest property
|
|
401 |
*/
|
|
402 |
copy.destFactory = function(dest, filters) {
|
|
403 |
if (dest == null) {
|
|
404 |
throw new Error('Missing dest');
|
|
405 |
}
|
|
406 |
|
|
407 |
if (dest.isDestination) {
|
|
408 |
return dest;
|
|
409 |
}
|
|
410 |
|
|
411 |
if (dest.value != null) {
|
|
412 |
return new copy.ValueDestination(dest, filters);
|
|
413 |
}
|
|
414 |
|
|
415 |
if (typeof dest === 'string') {
|
|
416 |
if (copy.isDirectory(dest)) {
|
|
417 |
return new copy.DirectoryDestination(dest, filters);
|
|
418 |
}
|
|
419 |
else {
|
|
420 |
return new copy.FileDestination(dest, filters);
|
|
421 |
}
|
|
422 |
}
|
|
423 |
|
|
424 |
if (Array.isArray(dest)) {
|
|
425 |
return new copy.ArrayDestination(dest, filters);
|
|
426 |
}
|
|
427 |
|
|
428 |
throw new Error('Can\'t handle type of dest: ' + typeof dest);
|
|
429 |
};
|
|
430 |
|
|
431 |
/**
|
|
432 |
* Abstract Destination.
|
|
433 |
* Concrete implementations of Destination should define the 'processSource'
|
|
434 |
* function.
|
|
435 |
*/
|
|
436 |
copy.Destination = function() {
|
|
437 |
};
|
|
438 |
|
|
439 |
copy.Destination.prototype.isDestination = true;
|
|
440 |
|
|
441 |
/**
|
|
442 |
* @return Either another dest, an array of other sources or a string value
|
|
443 |
* when there is nothing else to dig into
|
|
444 |
*/
|
|
445 |
copy.Destination.prototype.processSource = function(source) {
|
|
446 |
throw new Error('Destination.processSource() is not implemented');
|
|
447 |
};
|
|
448 |
|
|
449 |
/**
|
|
450 |
* Helper function to convert an input source to a single string value
|
|
451 |
*/
|
|
452 |
copy.Destination.prototype._sourceToOutput = function(source) {
|
|
453 |
var data = source.get;
|
|
454 |
|
|
455 |
if (data.isSource) {
|
|
456 |
return this._sourceToOutput(data);
|
|
457 |
}
|
|
458 |
|
|
459 |
if (Array.isArray(data)) {
|
|
460 |
var value = '';
|
|
461 |
data.forEach(function(s) {
|
|
462 |
value += this._sourceToOutput(s);
|
|
463 |
}, this);
|
|
464 |
return value;
|
|
465 |
}
|
|
466 |
|
|
467 |
if (typeof data === 'string') {
|
|
468 |
return data;
|
|
469 |
}
|
|
470 |
|
|
471 |
// i.e. a Node Buffer
|
|
472 |
if (typeof data.toString === 'function') {
|
|
473 |
return data.toString();
|
|
474 |
}
|
|
475 |
|
|
476 |
throw new Error('Unexpected value from source.get');
|
|
477 |
};
|
|
478 |
|
|
479 |
copy.Destination.prototype._runFilters = function(value) {
|
|
480 |
this._filters.forEach(function(filter) {
|
|
481 |
if (!filter.onRead) {
|
|
482 |
value = filter(value);
|
|
483 |
}
|
|
484 |
}, this);
|
|
485 |
return value;
|
|
486 |
};
|
|
487 |
|
|
488 |
/**
|
|
489 |
* A Destination that concatenates the sources and writes them to a single
|
|
490 |
* output file.
|
|
491 |
*/
|
|
492 |
copy.FileDestination = function(filename, filters) {
|
|
493 |
this._filename = filename;
|
|
494 |
this._filters = filters;
|
|
495 |
};
|
|
496 |
|
|
497 |
copy.FileDestination.prototype = Object.create(copy.Destination.prototype);
|
|
498 |
|
|
499 |
copy.FileDestination.prototype.processSource = function(source) {
|
|
500 |
var data = this._sourceToOutput(source);
|
|
501 |
data = this._runFilters(data);
|
|
502 |
copy._writeToFile(this._filename, data);
|
|
503 |
};
|
|
504 |
|
|
505 |
/**
|
|
506 |
* A Destination that copies the sources to new files in an alternate directory
|
|
507 |
* structure.
|
|
508 |
*/
|
|
509 |
copy.DirectoryDestination = function(dirname, filters) {
|
|
510 |
this.name = dirname;
|
|
511 |
this._filters = filters;
|
|
512 |
};
|
|
513 |
|
|
514 |
copy.DirectoryDestination.prototype = Object.create(copy.Destination.prototype);
|
|
515 |
|
|
516 |
copy.DirectoryDestination.prototype.processSource = function(source) {
|
|
517 |
var data = source.get;
|
|
518 |
if (typeof data === 'string') {
|
|
519 |
throw new Error('Can\'t write raw data to a directory');
|
|
520 |
}
|
|
521 |
else if (data.isSource) {
|
|
522 |
var destfile = path.join(this.name, data.location.path);
|
|
523 |
var output = this._runFilters(data.get);
|
|
524 |
copy._writeToFile(destfile, output, data.encoding);
|
|
525 |
}
|
|
526 |
else if (Array.isArray(data)) {
|
|
527 |
data.forEach(function(s) {
|
|
528 |
var destfile = path.join(this.name, s.location.path);
|
|
529 |
var output = this._runFilters(s.get);
|
|
530 |
copy._writeToFile(destfile, output, s.encoding);
|
|
531 |
}, this);
|
|
532 |
}
|
|
533 |
else {
|
|
534 |
throw new Error('data is not a source, string, nor can it be converted');
|
|
535 |
}
|
|
536 |
};
|
|
537 |
|
|
538 |
/**
|
|
539 |
* ArrayDestination is a Destination that can feed sources to a number of child
|
|
540 |
* Destinations.
|
|
541 |
*/
|
|
542 |
copy.ArrayDestination = function(array, filters) {
|
|
543 |
this._array = array;
|
|
544 |
this._filters = filters;
|
|
545 |
};
|
|
546 |
|
|
547 |
copy.ArrayDestination.prototype = Object.create(copy.Destination.prototype);
|
|
548 |
|
|
549 |
copy.ArrayDestination.prototype.processSource = function(source) {
|
|
550 |
this._array.forEach(function(member) {
|
|
551 |
var dest = copy.destFactory(member, this._filters);
|
|
552 |
dest.processSource(source);
|
|
553 |
}, this);
|
|
554 |
};
|
|
555 |
|
|
556 |
/**
|
|
557 |
* A Destination that concatenates the sources and writes them to a single
|
|
558 |
* value object.
|
|
559 |
*/
|
|
560 |
copy.ValueDestination = function(value, filters) {
|
|
561 |
this._value = value;
|
|
562 |
this._filters = filters;
|
|
563 |
};
|
|
564 |
|
|
565 |
copy.ValueDestination.prototype = Object.create(copy.Destination.prototype);
|
|
566 |
|
|
567 |
copy.ValueDestination.prototype.processSource = function(source) {
|
|
568 |
var data = this._sourceToOutput(source);
|
|
569 |
data = this._runFilters(data);
|
|
570 |
this._value.value += data;
|
|
571 |
};
|
|
572 |
|
|
573 |
////////////////////////////////////////////////////////////////////////////////
|
|
574 |
|
|
575 |
/**
|
|
576 |
* Check to see if fullPath refers to a directory
|
|
577 |
*/
|
|
578 |
copy.isDirectory = function(fullPath) {
|
|
579 |
return fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory();
|
|
580 |
};
|
|
581 |
|
|
582 |
copy._writeToFile = function(filename, data, encoding) {
|
|
583 |
if (fs.existsSync(filename)) {
|
|
584 |
if (!fs.statSync(filename).isFile()) {
|
|
585 |
throw new Error('Refusing to remove non file: ' + filename);
|
|
586 |
}
|
|
587 |
fs.unlinkSync(filename);
|
|
588 |
}
|
|
589 |
var parent = path.dirname(filename);
|
|
590 |
if (!fs.existsSync(parent)) {
|
|
591 |
copy.mkdirSync(parent, 0755);
|
|
592 |
}
|
|
593 |
fs.writeFileSync(filename, data, encoding);
|
|
594 |
if (copy.debug) {
|
|
595 |
console.log('- wrote ' + data.length + ' bytes to ' + filename);
|
|
596 |
}
|
|
597 |
};
|
|
598 |
|
|
599 |
copy.mkdirSync = function(dirname, mode) {
|
|
600 |
if (copy.isDirectory(dirname)) {
|
|
601 |
return;
|
|
602 |
}
|
|
603 |
var parent = path.dirname(dirname);
|
|
604 |
if (!fs.existsSync(parent)) {
|
|
605 |
copy.mkdirSync(parent, mode);
|
|
606 |
}
|
|
607 |
if (!fs.existsSync(dirname)) {
|
|
608 |
fs.mkdirSync(dirname, mode);
|
|
609 |
}
|
|
610 |
};
|
|
611 |
|
|
612 |
////////////////////////////////////////////////////////////////////////////////
|
|
613 |
|
|
614 |
/**
|
|
615 |
* A holder is an in-memory store of a result of a copy operation.
|
|
616 |
* <pre>
|
|
617 |
* var holder = copy.createDataObject();
|
|
618 |
* copy({ source: 'x.txt', dest: holder });
|
|
619 |
* copy({ source: 'y.txt', dest: holder });
|
|
620 |
* copy({ source: holder, dest: 'z.txt' });
|
|
621 |
* </pre>
|
|
622 |
*/
|
|
623 |
copy.createDataObject = function() {
|
|
624 |
return { value: '' };
|
|
625 |
};
|
|
626 |
|
|
627 |
/**
|
|
628 |
* Read mini_require.js to go with the required modules.
|
|
629 |
*/
|
|
630 |
copy.getMiniRequire = function() {
|
|
631 |
return {
|
|
632 |
value: fs.readFileSync(__dirname + '/mini_require.js').toString('utf8')
|
|
633 |
};
|
|
634 |
};
|
|
635 |
|
|
636 |
/**
|
|
637 |
* Keep track of the files in a project
|
|
638 |
*/
|
|
639 |
function CommonJsProject(opts) {
|
|
640 |
this.roots = opts.roots;
|
|
641 |
this.aliases = opts.aliases;
|
|
642 |
this.textPluginPattern = opts.textPluginPattern || /^text!/;
|
|
643 |
|
|
644 |
opts.roots = this.roots.map(function(root) {
|
|
645 |
if (!copy.isDirectory(root)) {
|
|
646 |
throw new Error('Each commonjs root should be a directory: ' + root);
|
|
647 |
}
|
|
648 |
return ensureTrailingSlash(root);
|
|
649 |
}, this);
|
|
650 |
|
|
651 |
// A module is a Location that also has dep
|
|
652 |
this.currentModules = {};
|
|
653 |
this.ignoredModules = {};
|
|
654 |
}
|
|
655 |
|
|
656 |
CommonJsProject.prototype.report = function() {
|
|
657 |
var reply = 'CommonJS project at ' + this.roots.join(', ') + '\n';
|
|
658 |
|
|
659 |
reply += '- Required modules:\n';
|
|
660 |
var moduleNames = Object.keys(this.currentModules);
|
|
661 |
if (moduleNames.length > 0) {
|
|
662 |
moduleNames.forEach(function(module) {
|
|
663 |
var deps = Object.keys(this.currentModules[module].deps).length;
|
|
664 |
reply += ' - ' + module + ' (' + deps +
|
|
665 |
(deps === 1 ? ' dependency' : ' dependencies') + ')\n';
|
|
666 |
}, this);
|
|
667 |
}
|
|
668 |
else {
|
|
669 |
reply += ' - None\n';
|
|
670 |
}
|
|
671 |
|
|
672 |
reply += '- Ignored modules:\n';
|
|
673 |
var ignoredNames = Object.keys(this.ignoredModules);
|
|
674 |
if (ignoredNames.length > 0) {
|
|
675 |
ignoredNames.forEach(function(moduleName) {
|
|
676 |
reply += ' - ' + moduleName + '\n';
|
|
677 |
}, this);
|
|
678 |
}
|
|
679 |
else {
|
|
680 |
reply += ' - None\n';
|
|
681 |
}
|
|
682 |
|
|
683 |
return reply;
|
|
684 |
};
|
|
685 |
|
|
686 |
/**
|
|
687 |
* Create an experimental GraphML string declaring the node dependencies.
|
|
688 |
*/
|
|
689 |
CommonJsProject.prototype.getDependencyGraphML = function() {
|
|
690 |
var nodes = '';
|
|
691 |
var edges = '';
|
|
692 |
var moduleNames = Object.keys(this.currentModules);
|
|
693 |
moduleNames.forEach(function(moduleName) {
|
|
694 |
nodes += ' <node id="' + moduleName + '">\n';
|
|
695 |
nodes += ' <data key="d0">\n';
|
|
696 |
nodes += ' <y:ShapeNode>\n';
|
|
697 |
nodes += ' <y:NodeLabel textColor="#000000">' + moduleName + '</y:NodeLabel>\n';
|
|
698 |
nodes += ' </y:ShapeNode>\n';
|
|
699 |
nodes += ' </data>\n';
|
|
700 |
nodes += ' </node>\n';
|
|
701 |
var deps = Object.keys(this.currentModules[moduleName].deps);
|
|
702 |
deps.forEach(function(dep) {
|
|
703 |
edges += ' <edge source="' + moduleName + '" target="' + dep + '"/>\n';
|
|
704 |
});
|
|
705 |
}, this);
|
|
706 |
|
|
707 |
var reply = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
708 |
reply += '<graphml\n';
|
|
709 |
reply += ' xmlns="http://graphml.graphdrawing.org/xmlns/graphml"\n';
|
|
710 |
reply += ' xmlns:y="http://www.yworks.com/xml/graphml"\n';
|
|
711 |
reply += ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n';
|
|
712 |
reply += ' xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"\n';
|
|
713 |
reply += ' >\n';
|
|
714 |
reply += ' <key id="d0" for="node" yfiles.type="nodegraphics"/>\n';
|
|
715 |
reply += ' <key id="d1" for="edge" yfiles.type="edgegraphics"/>\n';
|
|
716 |
reply += ' <graph id="commonjs" edgedefault="undirected">\n';
|
|
717 |
reply += nodes;
|
|
718 |
reply += edges;
|
|
719 |
reply += ' </graph>\n';
|
|
720 |
reply += '</graphml>\n';
|
|
721 |
return reply;
|
|
722 |
};
|
|
723 |
|
|
724 |
CommonJsProject.prototype.assumeAllFilesLoaded = function() {
|
|
725 |
Object.keys(this.currentModules).forEach(function(moduleName) {
|
|
726 |
this.ignoredModules[moduleName] = this.currentModules[moduleName];
|
|
727 |
}, this);
|
|
728 |
this.currentModules = {};
|
|
729 |
};
|
|
730 |
|
|
731 |
CommonJsProject.prototype.clone = function() {
|
|
732 |
var clone = new CommonJsProject({
|
|
733 |
roots: this.roots,
|
|
734 |
textPluginPattern: this.textPluginPattern
|
|
735 |
});
|
|
736 |
|
|
737 |
clone.aliases = this.aliases;
|
|
738 |
|
|
739 |
Object.keys(this.currentModules).forEach(function(moduleName) {
|
|
740 |
clone.currentModules[moduleName] = this.currentModules[moduleName];
|
|
741 |
}, this);
|
|
742 |
|
|
743 |
Object.keys(this.ignoredModules).forEach(function(moduleName) {
|
|
744 |
clone.ignoredModules[moduleName] = this.ignoredModules[moduleName];
|
|
745 |
}, this);
|
|
746 |
|
|
747 |
return clone;
|
|
748 |
};
|
|
749 |
|
|
750 |
CommonJsProject.prototype.addRoot = function(root) {
|
|
751 |
this.roots.push(root);
|
|
752 |
};
|
|
753 |
|
|
754 |
function findModuleAt(module, base, somePath) {
|
|
755 |
if (base == null) {
|
|
756 |
throw new Error('base == null for ' + somePath);
|
|
757 |
}
|
|
758 |
// Checking for absolute requires and relative requires that previously
|
|
759 |
// had been resolved to absolute paths.
|
|
760 |
if (/^\//.test(somePath)) {
|
|
761 |
if (isFile(somePath)) {
|
|
762 |
console.log('Warning - using location with base = "/"');
|
|
763 |
return new Location('/', somePath);
|
|
764 |
}
|
|
765 |
}
|
|
766 |
|
|
767 |
if (isFile(path.join(base, somePath))) {
|
|
768 |
if (module) {
|
|
769 |
console.log('- Found several matches for ' + somePath +
|
|
770 |
' (ignoring 2nd)');
|
|
771 |
console.log(' - ' + module.fullname);
|
|
772 |
console.log(' - ' + path.join(base, somePath));
|
|
773 |
}
|
|
774 |
else {
|
|
775 |
module = new Location(base, somePath);
|
|
776 |
}
|
|
777 |
}
|
|
778 |
|
|
779 |
return module;
|
|
780 |
}
|
|
781 |
|
|
782 |
function relative(pathA, pathB) {
|
|
783 |
pathA = pathA.split(/[\/\\]+/);
|
|
784 |
pathB = pathB.split(/[\/\\]+/);
|
|
785 |
var aLen = pathA.length;
|
|
786 |
var bLen = pathB.length;
|
|
787 |
|
|
788 |
// Increment i to the first place where the paths diverge.
|
|
789 |
for (var i = 0; i < aLen && i < bLen && pathA[i] === pathB[i]; i++) {
|
|
790 |
}
|
|
791 |
|
|
792 |
// Remove the redundant parts of the paths.
|
|
793 |
function isntEmptyString(s) {
|
|
794 |
return s !== '';
|
|
795 |
}
|
|
796 |
pathA = pathA.slice(i).filter(isntEmptyString);
|
|
797 |
pathB = pathB.slice(i).filter(isntEmptyString);
|
|
798 |
|
|
799 |
var result = [];
|
|
800 |
for (i = 0; i < pathA.length; i++) {
|
|
801 |
result.push('..');
|
|
802 |
}
|
|
803 |
return result.concat(pathB).join('/');
|
|
804 |
}
|
|
805 |
|
|
806 |
function normalizeRequire(module, moduleName) {
|
|
807 |
if (moduleName.indexOf("!") !== -1) {
|
|
808 |
var chunks = moduleName.split("!");
|
|
809 |
return normalizeRequire(module, chunks[0]) + "!" +
|
|
810 |
normalizeRequire(module, chunks[1]);
|
|
811 |
}
|
|
812 |
|
|
813 |
if (moduleName.charAt(0) == ".") {
|
|
814 |
var requirersDirectory = module.dirname;
|
|
815 |
var pathToRequiredModule = path.join(requirersDirectory, moduleName);
|
|
816 |
// The call to `define` which makes the module being
|
|
817 |
// relatively required isn't the full relative path,
|
|
818 |
// but the path relative from the base.
|
|
819 |
return relative(module.base, pathToRequiredModule);
|
|
820 |
}
|
|
821 |
else {
|
|
822 |
return moduleName;
|
|
823 |
}
|
|
824 |
}
|
|
825 |
|
|
826 |
function findRequires(module) {
|
|
827 |
var code = fs.readFileSync(module.fullname).toString();
|
|
828 |
var ast;
|
|
829 |
try {
|
|
830 |
ast = ujs.parser.parse(code, false);
|
|
831 |
}
|
|
832 |
catch (ex) {
|
|
833 |
console.error('- Failed to compile ' + module.path + ': ' + ex);
|
|
834 |
return;
|
|
835 |
}
|
|
836 |
|
|
837 |
var reply = [];
|
|
838 |
var walkers = {
|
|
839 |
call: function(expr, args) {
|
|
840 |
// If anyone redefines 'require' we won't notice. We could maintain a
|
|
841 |
// list of declared variables in the current scope so we can detect this.
|
|
842 |
// A similar system could have us tracking calls to require via a
|
|
843 |
// different name. that was a useful escape system, but now we detect
|
|
844 |
// computed requires, it's not needed.
|
|
845 |
if (expr[1] === 'define') {
|
|
846 |
var params = null;
|
|
847 |
if (args[0][0] === 'array') {
|
|
848 |
params = args[0][1];
|
|
849 |
}
|
|
850 |
else if (args[0][0] === 'string' && args[1][0] == 'array') {
|
|
851 |
params = args[1][1];
|
|
852 |
}
|
|
853 |
else {
|
|
854 |
console.log('- ' + module.path + ' has define(...) ' +
|
|
855 |
'with non-array parameter. Ignoring requirement.');
|
|
856 |
return;
|
|
857 |
}
|
|
858 |
|
|
859 |
if (params) {
|
|
860 |
for (var i = 0; i < params.length; i++) {
|
|
861 |
param = params[i];
|
|
862 |
if (param[0] === 'string') {
|
|
863 |
reply.push(normalizeRequire(module, param[1]));
|
|
864 |
}
|
|
865 |
else {
|
|
866 |
console.log('- ' + module.path + ' has define(...) ' +
|
|
867 |
'with non-string parameter. Ignoring requirement.');
|
|
868 |
}
|
|
869 |
}
|
|
870 |
}
|
|
871 |
}
|
|
872 |
if (expr[1] === 'require') {
|
|
873 |
if (args[0][0] === 'string') {
|
|
874 |
reply.push(normalizeRequire(module, args[0][1]));
|
|
875 |
}
|
|
876 |
else {
|
|
877 |
console.log('- ' + module.path + ' has require(...) ' +
|
|
878 |
'with non-string parameter. Ignoring requirement.');
|
|
879 |
}
|
|
880 |
}
|
|
881 |
}
|
|
882 |
};
|
|
883 |
|
|
884 |
var walker = ujs.uglify.ast_walker();
|
|
885 |
walker.with_walkers(walkers, function() {
|
|
886 |
return walker.walk(ast);
|
|
887 |
});
|
|
888 |
|
|
889 |
return reply;
|
|
890 |
}
|
|
891 |
|
|
892 |
CommonJsProject.prototype.require = function(moduleName) {
|
|
893 |
var module = this.currentModules[moduleName];
|
|
894 |
if (module) {
|
|
895 |
return module;
|
|
896 |
}
|
|
897 |
module = this.ignoredModules[moduleName];
|
|
898 |
if (module) {
|
|
899 |
return module;
|
|
900 |
}
|
|
901 |
|
|
902 |
// Apply aliases on module path.
|
|
903 |
if (this.aliases) {
|
|
904 |
var parts = moduleName.split("/");
|
|
905 |
var moduleName = parts.pop();
|
|
906 |
|
|
907 |
var self = this;
|
|
908 |
var resolved = parts.map(function(part) {
|
|
909 |
var alias = self.aliases[part];
|
|
910 |
return alias ? alias : part;
|
|
911 |
});
|
|
912 |
|
|
913 |
var moduleUrl = ensureTrailingSlash(resolved.join("/"));
|
|
914 |
moduleName = moduleUrl + moduleName;
|
|
915 |
}
|
|
916 |
|
|
917 |
// Find which of the packages it is in
|
|
918 |
this.roots.forEach(function(base) {
|
|
919 |
if (this.textPluginPattern.test(moduleName)) {
|
|
920 |
var modulePath = moduleName.replace(this.textPluginPattern, '');
|
|
921 |
module = findModuleAt(module, base, modulePath);
|
|
922 |
if (module) {
|
|
923 |
module.isText = true;
|
|
924 |
}
|
|
925 |
} else {
|
|
926 |
module = findModuleAt(module, base, moduleName + '.js');
|
|
927 |
if (!module) {
|
|
928 |
module = findModuleAt(module, base, moduleName + '/index.js');
|
|
929 |
}
|
|
930 |
}
|
|
931 |
}, this);
|
|
932 |
|
|
933 |
if (!module) {
|
|
934 |
console.error('Failed to find module: ' + moduleName);
|
|
935 |
return;
|
|
936 |
}
|
|
937 |
|
|
938 |
module.deps = {};
|
|
939 |
this.currentModules[moduleName] = module;
|
|
940 |
|
|
941 |
if (!module.isText) {
|
|
942 |
// require() all this modules requirements
|
|
943 |
findRequires(module).forEach(function(moduleName) {
|
|
944 |
module.deps[moduleName] = 1;
|
|
945 |
this.require(moduleName);
|
|
946 |
}, this);
|
|
947 |
}
|
|
948 |
};
|
|
949 |
|
|
950 |
CommonJsProject.prototype.getCurrentModules = function() {
|
|
951 |
return Object.keys(this.currentModules).map(function(moduleName) {
|
|
952 |
return this.currentModules[moduleName];
|
|
953 |
}, this);
|
|
954 |
};
|
|
955 |
|
|
956 |
/**
|
|
957 |
*
|
|
958 |
*/
|
|
959 |
copy.createCommonJsProject = function(opts) {
|
|
960 |
return new CommonJsProject(opts);
|
|
961 |
};
|
|
962 |
|
|
963 |
/**
|
|
964 |
* Different types of source
|
|
965 |
*/
|
|
966 |
copy.source = {};
|
|
967 |
|
|
968 |
/**
|
|
969 |
* @deprecated
|
|
970 |
*/
|
|
971 |
copy.source.commonjs = function(obj) {
|
|
972 |
console.log('copy.source.commonjs is deprecated, ' +
|
|
973 |
'pass { project:... includes:...} directly as a source');
|
|
974 |
return obj;
|
|
975 |
};
|
|
976 |
|
|
977 |
/**
|
|
978 |
* File filters
|
|
979 |
*/
|
|
980 |
copy.filter = {};
|
|
981 |
|
|
982 |
copy.filter.debug = function(input, source) {
|
|
983 |
source = source || 'unknown';
|
|
984 |
module = source.path ? source.path : source;
|
|
985 |
return input;
|
|
986 |
};
|
|
987 |
copy.filter.debug.onRead = true;
|
|
988 |
|
|
989 |
/**
|
|
990 |
* Compress the given input code using UglifyJS.
|
|
991 |
*
|
|
992 |
* @param string input
|
|
993 |
* @return string output
|
|
994 |
*/
|
|
995 |
copy.filter.uglifyjs = function(input) {
|
|
996 |
if (typeof input !== 'string') {
|
|
997 |
input = input.toString();
|
|
998 |
}
|
|
999 |
|
|
1000 |
var opt = copy.filter.uglifyjs.options;
|
|
1001 |
var ast;
|
|
1002 |
try {
|
|
1003 |
ast = ujs.parser.parse(input, opt.parse_strict_semicolons);
|
|
1004 |
}
|
|
1005 |
catch (ex) {
|
|
1006 |
console.error('- Failed to compile code: ' + ex);
|
|
1007 |
return input;
|
|
1008 |
}
|
|
1009 |
|
|
1010 |
if (opt.mangle) {
|
|
1011 |
ast = ujs.uglify.ast_mangle(ast, opt.mangle_toplevel);
|
|
1012 |
}
|
|
1013 |
|
|
1014 |
if (opt.squeeze) {
|
|
1015 |
ast = ujs.uglify.ast_squeeze(ast, opt.squeeze_options);
|
|
1016 |
if (opt.squeeze_more) {
|
|
1017 |
ast = ujs.uglify.ast_squeeze_more(ast);
|
|
1018 |
}
|
|
1019 |
}
|
|
1020 |
|
|
1021 |
return ujs.uglify.gen_code(ast, opt.beautify);
|
|
1022 |
};
|
|
1023 |
copy.filter.uglifyjs.onRead = false;
|
|
1024 |
/**
|
|
1025 |
* UglifyJS filter options.
|
|
1026 |
*/
|
|
1027 |
copy.filter.uglifyjs.options = {
|
|
1028 |
parse_strict_semicolons: false,
|
|
1029 |
|
|
1030 |
/**
|
|
1031 |
* The beautify argument used for process.gen_code(). See the UglifyJS
|
|
1032 |
* documentation.
|
|
1033 |
*/
|
|
1034 |
beautify: false,
|
|
1035 |
mangle: true,
|
|
1036 |
mangle_toplevel: false,
|
|
1037 |
squeeze: true,
|
|
1038 |
|
|
1039 |
/**
|
|
1040 |
* The options argument used for process.ast_squeeze(). See the UglifyJS
|
|
1041 |
* documentation.
|
|
1042 |
*/
|
|
1043 |
squeeze_options: {},
|
|
1044 |
|
|
1045 |
/**
|
|
1046 |
* Tells if you want to perform potentially unsafe compression.
|
|
1047 |
*/
|
|
1048 |
squeeze_more: false
|
|
1049 |
};
|
|
1050 |
|
|
1051 |
/**
|
|
1052 |
* A filter to munge CommonJS headers
|
|
1053 |
*/
|
|
1054 |
copy.filter.addDefines = function(input, source) {
|
|
1055 |
if (typeof input !== 'string') {
|
|
1056 |
input = input.toString();
|
|
1057 |
}
|
|
1058 |
|
|
1059 |
if (!source) {
|
|
1060 |
throw new Error('Missing filename for moduleDefines');
|
|
1061 |
}
|
|
1062 |
|
|
1063 |
var module = source.isLocation ? source.path : source;
|
|
1064 |
|
|
1065 |
input = input.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1066 |
input = '"' + input.replace(/\n/g, '\\n" +\n "') + '"';
|
|
1067 |
|
|
1068 |
return 'define("text!' + module + '", [], ' + input + ');\n\n';
|
|
1069 |
};
|
|
1070 |
copy.filter.addDefines.onRead = true;
|
|
1071 |
|
|
1072 |
/**
|
|
1073 |
* Like addDefines, but adds base64 encoding
|
|
1074 |
*/
|
|
1075 |
copy.filter.base64 = function(input, source) {
|
|
1076 |
if (typeof input === 'string') {
|
|
1077 |
throw new Error('base64 filter needs to be the first in a filter set');
|
|
1078 |
}
|
|
1079 |
|
|
1080 |
if (!source) {
|
|
1081 |
throw new Error('Missing filename for moduleDefines');
|
|
1082 |
}
|
|
1083 |
|
|
1084 |
var module = source.isLocation ? source.path : source;
|
|
1085 |
|
|
1086 |
if (module.substr(-4) === '.png') {
|
|
1087 |
input = 'data:image/png;base64,' + input.toString('base64');
|
|
1088 |
}
|
|
1089 |
else if (module.substr(-4) === '.gif') {
|
|
1090 |
input = 'data:image/gif;base64,' + input.toString('base64');
|
|
1091 |
}
|
|
1092 |
else {
|
|
1093 |
throw new Error('Only gif/png supported by base64 filter: ' + source);
|
|
1094 |
}
|
|
1095 |
|
|
1096 |
return 'define("text!' + module + '", [], "' + input + '");\n\n';
|
|
1097 |
};
|
|
1098 |
copy.filter.base64.onRead = true;
|
|
1099 |
|
|
1100 |
/**
|
|
1101 |
* Munge define lines to add module names
|
|
1102 |
*/
|
|
1103 |
copy.filter.moduleDefines = function(input, source) {
|
|
1104 |
if (!source) {
|
|
1105 |
console.log('- Source without filename passed to moduleDefines().' +
|
|
1106 |
' Skipping addition of define(...) wrapper.');
|
|
1107 |
return input;
|
|
1108 |
}
|
|
1109 |
|
|
1110 |
if (source.isText) {
|
|
1111 |
return copy.filter.addDefines(input, source);
|
|
1112 |
}
|
|
1113 |
|
|
1114 |
if (typeof input !== 'string') {
|
|
1115 |
input = input.toString();
|
|
1116 |
}
|
|
1117 |
|
|
1118 |
var deps = source.deps ? Object.keys(source.deps) : [];
|
|
1119 |
deps = deps.length ? (", '" + deps.join("', '") + "'") : "";
|
|
1120 |
|
|
1121 |
var module = source.isLocation ? source.path : source;
|
|
1122 |
module = module.replace(/\.js$/, '');
|
|
1123 |
|
|
1124 |
return input.replace(/\bdefine\s*\(\s*function\s*\(require,\s*exports,\s*module\)\s*\{/,
|
|
1125 |
"define('" + module + "', ['require', 'exports', 'module' " + deps + "], function(require, exports, module) {");
|
|
1126 |
};
|
|
1127 |
copy.filter.moduleDefines.onRead = true;
|
|
1128 |
|
|
1129 |
/**
|
|
1130 |
* Why does node throw an exception for statSync(), especially when it has no
|
|
1131 |
* exists()?
|
|
1132 |
*/
|
|
1133 |
function isFile(fullPath) {
|
|
1134 |
return fs.existsSync(fullPath) && fs.statSync(fullPath).isFile();
|
|
1135 |
}
|
|
1136 |
|
|
1137 |
/**
|
|
1138 |
* Add a trailing slash to s directory path if needed
|
|
1139 |
*/
|
|
1140 |
function ensureTrailingSlash(filename) {
|
|
1141 |
if (filename.length > 0 && filename.substr(-1) !== '/') {
|
|
1142 |
filename += '/';
|
|
1143 |
}
|
|
1144 |
return filename;
|
|
1145 |
}
|
|
1146 |
|
|
1147 |
|
|
1148 |
exports.copy = copy;
|