UNPKG

4.22 kBJavaScriptView Raw
1const path = require('path');
2const Git = require('./git.js');
3const async = require('async');
4const fs = require('fs-extra');
5
6/**
7 * Generate a list of unique directory paths given a list of file paths.
8 * @param {Array<string>} files List of file paths.
9 * @return {Array<string>} List of directory paths.
10 */
11function uniqueDirs(files) {
12 const dirs = new Set();
13 files.forEach((filepath) => {
14 const parts = path.dirname(filepath).split(path.sep);
15 let partial = parts[0] || '/';
16 dirs.add(partial);
17 for (let i = 1, ii = parts.length; i < ii; ++i) {
18 partial = path.join(partial, parts[i]);
19 dirs.add(partial);
20 }
21 });
22 return Array.from(dirs);
23}
24exports.uniqueDirs = uniqueDirs;
25
26/**
27 * Sort function for paths. Sorter paths come first. Paths of equal length are
28 * sorted alphanumerically in path segment order.
29 * @param {string} a First path.
30 * @param {string} b Second path.
31 * @return {number} Comparison.
32 */
33function byShortPath(a, b) {
34 const aParts = a.split(path.sep);
35 const bParts = b.split(path.sep);
36 const aLength = aParts.length;
37 const bLength = bParts.length;
38 let cmp = 0;
39 if (aLength < bLength) {
40 cmp = -1;
41 } else if (aLength > bLength) {
42 cmp = 1;
43 } else {
44 let aPart, bPart;
45 for (let i = 0; i < aLength; ++i) {
46 aPart = aParts[i];
47 bPart = bParts[i];
48 if (aPart < bPart) {
49 cmp = -1;
50 break;
51 } else if (aPart > bPart) {
52 cmp = 1;
53 break;
54 }
55 }
56 }
57 return cmp;
58}
59exports.byShortPath = byShortPath;
60
61/**
62 * Generate a list of directories to create given a list of file paths.
63 * @param {Array<string>} files List of file paths.
64 * @return {Array<string>} List of directory paths ordered by path length.
65 */
66function dirsToCreate(files) {
67 return uniqueDirs(files).sort(byShortPath);
68}
69exports.dirsToCreate = dirsToCreate;
70
71/**
72 * Copy a file.
73 * @param {object} obj Object with src and dest properties.
74 * @param {function(Error)} callback Callback
75 */
76function copyFile(obj, callback) {
77 let called = false;
78 function done(err) {
79 if (!called) {
80 called = true;
81 callback(err);
82 }
83 }
84
85 const read = fs.createReadStream(obj.src);
86 read.on('error', (err) => {
87 done(err);
88 });
89
90 const write = fs.createWriteStream(obj.dest);
91 write.on('error', (err) => {
92 done(err);
93 });
94 write.on('close', () => {
95 done();
96 });
97
98 read.pipe(write);
99}
100exports.copyFile = copyFile;
101
102/**
103 * Make directory, ignoring errors if directory already exists.
104 * @param {string} path Directory path.
105 * @param {function(Error)} callback Callback.
106 */
107function makeDir(path, callback) {
108 fs.mkdir(path, (err) => {
109 if (err) {
110 // check if directory exists
111 fs.stat(path, (err2, stat) => {
112 if (err2 || !stat.isDirectory()) {
113 callback(err);
114 } else {
115 callback();
116 }
117 });
118 } else {
119 callback();
120 }
121 });
122}
123
124/**
125 * Copy a list of files.
126 * @param {Array<string>} files Files to copy.
127 * @param {string} base Base directory.
128 * @param {string} dest Destination directory.
129 * @return {Promise} A promise.
130 */
131exports.copy = function (files, base, dest) {
132 return new Promise((resolve, reject) => {
133 const pairs = [];
134 const destFiles = [];
135 files.forEach((file) => {
136 const src = path.resolve(base, file);
137 const relative = path.relative(base, src);
138 const target = path.join(dest, relative);
139 pairs.push({
140 src: src,
141 dest: target,
142 });
143 destFiles.push(target);
144 });
145
146 async.eachSeries(dirsToCreate(destFiles), makeDir, (err) => {
147 if (err) {
148 return reject(err);
149 }
150 async.each(pairs, copyFile, (err) => {
151 if (err) {
152 return reject(err);
153 } else {
154 return resolve();
155 }
156 });
157 });
158 });
159};
160
161exports.getUser = function (cwd) {
162 return Promise.all([
163 new Git(cwd).exec('config', 'user.name'),
164 new Git(cwd).exec('config', 'user.email'),
165 ])
166 .then((results) => {
167 return {name: results[0].output.trim(), email: results[1].output.trim()};
168 })
169 .catch((err) => {
170 // git config exits with 1 if name or email is not set
171 return null;
172 });
173};