UNPKG

10 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.walk = exports.Walker = exports.compilePaths = exports.tmpfilepath = exports.findBaseDirectory = exports.isExecutableFile = exports.pathExecutable = exports.pathWritable = exports.pathReadable = exports.pathExists = exports.pathAccessible = exports.writeStreamToFile = exports.cacheFileChecksum = exports.getFileChecksums = exports.getFileChecksum = exports.fileToString = exports.getFileTree = exports.readdirp = exports.readdirSafe = exports.statSafe = void 0;
4const tslib_1 = require("tslib");
5const fs = require("fs-extra");
6const os = require("os");
7const path = require("path");
8const stream = require("stream");
9const safe = require("./safe");
10tslib_1.__exportStar(require("fs-extra"), exports);
11var safe_1 = require("./safe");
12Object.defineProperty(exports, "statSafe", { enumerable: true, get: function () { return safe_1.stat; } });
13Object.defineProperty(exports, "readdirSafe", { enumerable: true, get: function () { return safe_1.readdir; } });
14async function readdirp(dir, { filter, onError, walkerOptions } = {}) {
15 return new Promise((resolve, reject) => {
16 const items = [];
17 let rs = walk(dir, walkerOptions);
18 if (filter) {
19 rs = rs.pipe(new stream.Transform({
20 objectMode: true,
21 transform(obj, enc, cb) {
22 if (!filter || filter(obj)) {
23 this.push(obj);
24 }
25 cb();
26 },
27 }));
28 }
29 rs
30 .on('error', (err) => onError ? onError(err) : reject(err))
31 .on('data', (item) => items.push(item.path))
32 .on('end', () => resolve(items));
33 });
34}
35exports.readdirp = readdirp;
36/**
37 * Compile and return a file tree structure.
38 *
39 * This function walks a directory structure recursively, building a nested
40 * object structure in memory that represents it. When finished, the root
41 * directory node is returned.
42 *
43 * @param dir The root directory from which to compile the file tree
44 */
45async function getFileTree(dir, { onError, onFileNode = n => n, onDirectoryNode = n => n, walkerOptions } = {}) {
46 const fileMap = new Map([]);
47 const getOrCreateParent = (item) => {
48 const parentPath = path.dirname(item.path);
49 const parent = fileMap.get(parentPath);
50 if (parent && parent.type === "directory" /* FileType.DIRECTORY */) {
51 return parent;
52 }
53 return onDirectoryNode({ path: parentPath, type: "directory" /* FileType.DIRECTORY */, children: [] });
54 };
55 const createFileNode = (item, parent) => {
56 const node = { path: item.path, parent };
57 return item.stats.isDirectory() ?
58 onDirectoryNode({ ...node, type: "directory" /* FileType.DIRECTORY */, children: [] }) :
59 onFileNode({ ...node, type: "file" /* FileType.FILE */ });
60 };
61 return new Promise((resolve, reject) => {
62 dir = path.resolve(dir);
63 const rs = walk(dir, walkerOptions);
64 rs
65 .on('error', err => onError ? onError(err) : reject(err))
66 .on('data', item => {
67 const parent = getOrCreateParent(item);
68 const node = createFileNode(item, parent);
69 parent.children.push(node);
70 fileMap.set(item.path, node);
71 fileMap.set(parent.path, parent);
72 })
73 .on('end', () => {
74 const root = fileMap.get(dir);
75 if (!root) {
76 return reject(new Error('No root node found after walking directory structure.'));
77 }
78 delete root.parent;
79 resolve(root);
80 });
81 });
82}
83exports.getFileTree = getFileTree;
84async function fileToString(filePath) {
85 try {
86 return await fs.readFile(filePath, { encoding: 'utf8' });
87 }
88 catch (e) {
89 if (e.code === 'ENOENT' || e.code === 'ENOTDIR') {
90 return '';
91 }
92 throw e;
93 }
94}
95exports.fileToString = fileToString;
96async function getFileChecksum(filePath) {
97 const crypto = await Promise.resolve().then(() => require('crypto'));
98 return new Promise((resolve, reject) => {
99 const hash = crypto.createHash('md5');
100 const input = fs.createReadStream(filePath);
101 input.on('error', (err) => {
102 reject(err);
103 });
104 hash.once('readable', () => {
105 const fullChecksum = hash.read().toString('hex');
106 resolve(fullChecksum);
107 });
108 input.pipe(hash);
109 });
110}
111exports.getFileChecksum = getFileChecksum;
112/**
113 * Return true and cached checksums for a file by its path.
114 *
115 * Cached checksums are stored as `.md5` files next to the original file. If
116 * the cache file is missing, the cached checksum is undefined.
117 *
118 * @param p The file path
119 * @return Promise<[true checksum, cached checksum or undefined if cache file missing]>
120 */
121async function getFileChecksums(p) {
122 return Promise.all([
123 getFileChecksum(p),
124 (async () => {
125 try {
126 const md5 = await fs.readFile(`${p}.md5`, { encoding: 'utf8' });
127 return md5.trim();
128 }
129 catch (e) {
130 if (e.code !== 'ENOENT') {
131 throw e;
132 }
133 }
134 })(),
135 ]);
136}
137exports.getFileChecksums = getFileChecksums;
138/**
139 * Store a cache file containing the source file's md5 checksum hash.
140 *
141 * @param p The file path
142 * @param checksum The checksum. If excluded, the checksum is computed
143 */
144async function cacheFileChecksum(p, checksum) {
145 const md5 = await getFileChecksum(p);
146 await fs.writeFile(`${p}.md5`, md5, { encoding: 'utf8' });
147}
148exports.cacheFileChecksum = cacheFileChecksum;
149function writeStreamToFile(stream, destination) {
150 return new Promise((resolve, reject) => {
151 const dest = fs.createWriteStream(destination);
152 stream.pipe(dest);
153 dest.on('error', reject);
154 dest.on('finish', resolve);
155 });
156}
157exports.writeStreamToFile = writeStreamToFile;
158async function pathAccessible(filePath, mode) {
159 try {
160 await fs.access(filePath, mode);
161 }
162 catch (e) {
163 return false;
164 }
165 return true;
166}
167exports.pathAccessible = pathAccessible;
168async function pathExists(filePath) {
169 return pathAccessible(filePath, fs.constants.F_OK);
170}
171exports.pathExists = pathExists;
172async function pathReadable(filePath) {
173 return pathAccessible(filePath, fs.constants.R_OK);
174}
175exports.pathReadable = pathReadable;
176async function pathWritable(filePath) {
177 return pathAccessible(filePath, fs.constants.W_OK);
178}
179exports.pathWritable = pathWritable;
180async function pathExecutable(filePath) {
181 return pathAccessible(filePath, fs.constants.X_OK);
182}
183exports.pathExecutable = pathExecutable;
184async function isExecutableFile(filePath) {
185 const [stats, executable] = await (Promise.all([
186 safe.stat(filePath),
187 pathExecutable(filePath),
188 ]));
189 return !!stats && (stats.isFile() || stats.isSymbolicLink()) && executable;
190}
191exports.isExecutableFile = isExecutableFile;
192/**
193 * Find the base directory based on the path given and a marker file to look for.
194 */
195async function findBaseDirectory(dir, file) {
196 if (!dir || !file) {
197 return;
198 }
199 for (const d of compilePaths(dir)) {
200 const results = await safe.readdir(d);
201 if (results.includes(file)) {
202 return d;
203 }
204 }
205}
206exports.findBaseDirectory = findBaseDirectory;
207/**
208 * Generate a random file path within the computer's temporary directory.
209 *
210 * @param prefix Optionally provide a filename prefix.
211 */
212function tmpfilepath(prefix) {
213 const rn = Math.random().toString(16).substring(2, 8);
214 const p = path.resolve(os.tmpdir(), prefix ? `${prefix}-${rn}` : rn);
215 return p;
216}
217exports.tmpfilepath = tmpfilepath;
218/**
219 * Given an absolute system path, compile an array of paths working backwards
220 * one directory at a time, always ending in the root directory.
221 *
222 * For example, `'/some/dir'` => `['/some/dir', '/some', '/']`
223 *
224 * @param filePath Absolute system base path.
225 */
226function compilePaths(filePath) {
227 filePath = path.normalize(filePath);
228 if (!path.isAbsolute(filePath)) {
229 throw new Error(`${filePath} is not an absolute path`);
230 }
231 const parsed = path.parse(filePath);
232 if (filePath === parsed.root) {
233 return [filePath];
234 }
235 return filePath
236 .slice(parsed.root.length)
237 .split(path.sep)
238 .map((segment, i, array) => parsed.root + path.join(...array.slice(0, array.length - i)))
239 .concat(parsed.root);
240}
241exports.compilePaths = compilePaths;
242class Walker extends stream.Readable {
243 constructor(p, options = {}) {
244 super({ objectMode: true });
245 this.p = p;
246 this.options = options;
247 this.paths = [this.p];
248 }
249 _read() {
250 const p = this.paths.shift();
251 const { pathFilter } = this.options;
252 if (!p) {
253 this.push(null);
254 return;
255 }
256 fs.lstat(p, (err, stats) => {
257 if (err) {
258 this.emit('error', err);
259 return;
260 }
261 const item = { path: p, stats };
262 if (stats.isDirectory()) {
263 fs.readdir(p, (err, contents) => {
264 if (err) {
265 this.emit('error', err);
266 return;
267 }
268 let paths = contents.map(file => path.join(p, file));
269 if (pathFilter) {
270 paths = paths.filter(p => pathFilter(p.substring(this.p.length + 1)));
271 }
272 this.paths.push(...paths);
273 this.push(item);
274 });
275 }
276 else {
277 this.push(item);
278 }
279 });
280 }
281}
282exports.Walker = Walker;
283function walk(p, options = {}) {
284 return new Walker(p, options);
285}
286exports.walk = walk;