UNPKG

5.19 kBJavaScriptView Raw
1"use strict";
2
3const path = require('path');
4
5const {
6 createHash
7} = require('crypto');
8
9const errors = require('errno');
10
11const util = require('util');
12
13const {
14 ReadableStream,
15 WritableStream
16} = require('stream');
17
18const MemoryFileSystemError = require('memory-fs/lib/MemoryFileSystem');
19
20const debug = util.debuglog('astroturf:memory-fs');
21
22const returnsTrue = () => true;
23
24const returnsFalse = () => false;
25
26const md5 = input => {
27 const hash = createHash('md5');
28 hash.update(input);
29 return hash.digest('hex');
30};
31
32const read = (file, optsOrEncoding) => {
33 const encoding = (optsOrEncoding === null || optsOrEncoding === void 0 ? void 0 : optsOrEncoding.encoding) || optsOrEncoding;
34 file.atime = new Date();
35 return encoding ? file.contents.toString(encoding) : file.contents;
36};
37/**
38 * A simple in memorry ponyfill of the node fs that stores objects in memory.
39 * We don't use `memory-fs` because our use-case is much narrower (only need files not dirs)
40 * and it doesn't support stat timestamps
41 */
42
43
44class MemoryFs {
45 constructor() {
46 this.addFile = (p, data, updateMtime = false) => {
47 p = path.normalize(p);
48 const hash = md5(data);
49 const existing = this.paths.get(p);
50 const keepTime = !updateMtime && existing && existing.hash === hash;
51 if (!keepTime) debug(`${existing ? 'modifying' : 'writing'} file ${path.relative(process.cwd(), p)} [${hash}]`);
52 const mtime = keepTime ? existing.mtime : new Date();
53 this.paths.set(p, {
54 hash,
55 contents: Buffer.isBuffer(data) ? data : Buffer.from(data),
56 birthtime: existing ? existing.birthtime : new Date(),
57 ctime: existing ? existing.ctime : new Date(),
58 atime: existing ? existing.atime : new Date(),
59 mtime
60 });
61 return mtime;
62 };
63
64 this.getPaths = () => this.paths;
65
66 this.exists = (p, cb) => cb(this.existsSync(p));
67
68 this.existsSync = p => this.paths.has(path.normalize(p));
69
70 this.statSync = p => {
71 const file = this.paths.get(path.normalize(p));
72 if (file) return {
73 mtime: file.mtime,
74 atime: file.atime,
75 ctime: file.ctime,
76 birthtime: file.birthtime,
77 mtimeMs: file.mtime.getTime(),
78 atimeMs: file.atime.getTime(),
79 ctimeMs: file.ctime.getTime(),
80 birthtimeMs: file.birthtime.getTime(),
81 isFile: returnsTrue,
82 isDirectory: returnsFalse,
83 isBlockDevice: returnsFalse,
84 isCharacterDevice: returnsFalse,
85 isSymbolicLink: returnsFalse,
86 isFIFO: returnsFalse,
87 isSocket: returnsFalse
88 };
89 throw new MemoryFileSystemError(errors.code.ENOENT, p, 'stat');
90 };
91
92 this.readFileSync = (p, optsOrEncoding) => {
93 p = path.normalize(p);
94 if (!this.existsSync(p)) throw new MemoryFileSystemError(errors.code.ENOENT, p, 'readFile');
95 return read(this.paths.get(p), optsOrEncoding);
96 };
97
98 this.readdirSync = p => {
99 const results = [];
100 p = path.normalize(p);
101 this.paths.forEach((_, key) => {
102 if (key.startsWith(p)) results.push(key);
103 });
104 };
105
106 this.rmdirSync = p => {
107 p = path.normalize(p);
108 this.paths.forEach((_, key) => {
109 if (p.startsWith(key)) this.unlinkSync(key);
110 });
111 };
112
113 this.unlinkSync = p => this.paths.delete(p);
114
115 this.writeFileSync = (p, data) => {
116 this.addFile(p, data, true);
117 };
118
119 this.paths = new Map();
120 ['stat', 'readdir', 'rmdir', 'unlink', 'readFile', 'writeFile'].forEach(fn => {
121 this[fn] = (...args) => {
122 const cb = args.pop();
123 let result;
124
125 try {
126 result = this[`${fn}Sync`](...args);
127 } catch (err) {
128 setImmediate(() => cb(err));
129 return;
130 }
131
132 setImmediate(() => cb(null, result));
133 };
134 });
135 }
136
137 /** stream methods taken from memory-fs */
138 createReadStream(p, options) {
139 const stream = new ReadableStream();
140 let done = false;
141 let data;
142
143 try {
144 data = this.readFileSync(p);
145 } catch (e) {
146 // eslint-disable-next-line no-underscore-dangle
147 stream._read = function $read() {
148 if (done) return;
149 done = true;
150 this.emit('error', e);
151 this.push(null);
152 };
153
154 return stream;
155 }
156
157 options = options || {};
158 options.start = options.start || 0;
159 options.end = options.end || data.length; // eslint-disable-next-line no-underscore-dangle
160
161 stream._read = function $read() {
162 if (done) return;
163 done = true;
164 this.push(data.slice(options.start, options.end));
165 this.push(null);
166 };
167
168 return stream;
169 }
170
171 createWriteStream(p) {
172 const stream = new WritableStream();
173
174 try {
175 this.writeFileSync(p, Buffer.from(0));
176 } catch (e) {
177 stream.once('prefinish', () => {
178 stream.emit('error', e);
179 });
180 return stream;
181 }
182
183 const bl = [];
184 let len = 0; // eslint-disable-next-line no-underscore-dangle
185
186 stream._write = (chunk, encoding, callback) => {
187 bl.push(chunk);
188 len += chunk.length;
189 this.writeFile(p, Buffer.concat(bl, len), callback);
190 };
191
192 return stream;
193 }
194
195}
196
197module.exports = MemoryFs;
\No newline at end of file