UNPKG

6.52 kBJavaScriptView Raw
1'use strict';
2
3function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
5var path = _interopDefault(require('path'));
6var events = require('events');
7var fs = _interopDefault(require('fs-extra'));
8var glob = _interopDefault(require('fast-glob'));
9
10function _asyncToGenerator(fn) {
11 return function () {
12 var self = this,
13 args = arguments;
14 return new Promise(function (resolve, reject) {
15 var gen = fn.apply(self, args);
16
17 function step(key, arg) {
18 try {
19 var info = gen[key](arg);
20 var value = info.value;
21 } catch (error) {
22 reject(error);
23 return;
24 }
25
26 if (info.done) {
27 resolve(value);
28 } else {
29 Promise.resolve(value).then(_next, _throw);
30 }
31 }
32
33 function _next(value) {
34 step("next", value);
35 }
36
37 function _throw(err) {
38 step("throw", err);
39 }
40
41 _next();
42 });
43 };
44}
45
46class Wares {
47 constructor() {
48 this.middlewares = [];
49 }
50
51 use(middleware) {
52 this.middlewares = this.middlewares.concat(middleware);
53 return this;
54 }
55
56 run(context) {
57 return this.middlewares.reduce((current, next) => {
58 return current.then(() => Promise.resolve(next(context)));
59 }, Promise.resolve());
60 }
61
62}
63
64class Majo extends events.EventEmitter {
65 constructor() {
66 super();
67 /**
68 * @typedef {(ctx: Majo) => Promise<void> | void} Middleware
69 * @type {Middleware[]} */
70
71 this.middlewares = [];
72 /**
73 * An object you can use across middleware to share states
74 * @type {{[k: string]: any}}
75 */
76
77 this.meta = {};
78 }
79 /**
80 * Find files from specific directory
81 * @param {string | string[]} source Glob patterns
82 * @param {{baseDir?: string, dotFiles?: boolean}} opts
83 * @param opts.baseDir The base directory to find files
84 * @param opts.dotFiles Including dot files
85 */
86
87
88 source(patterns, {
89 baseDir = '.',
90 dotFiles = true
91 } = {}) {
92 this.baseDir = path.resolve(baseDir);
93 this.sourcePatterns = patterns;
94 this.dotFiles = dotFiles;
95 return this;
96 }
97 /**
98 * Use a middleware
99 * @param {(ctx: Majo) => Promise<void> | void} middleware
100 */
101
102
103 use(middleware) {
104 this.middlewares.push(middleware);
105 return this;
106 }
107 /**
108 * Process middlewares against files
109 */
110
111
112 process() {
113 var _this = this;
114
115 return _asyncToGenerator(function* () {
116 const allStats = yield glob(_this.sourcePatterns, {
117 cwd: _this.baseDir,
118 dot: _this.dotFiles,
119 stats: true
120 });
121 /**
122 * @typedef {{path: string, stats: fs.Stats, contents: Buffer}} File
123 * @type {{[relativePath: string]: File}}
124 */
125
126 _this.files = {};
127 yield Promise.all(allStats.map(stats => {
128 const absolutePath = path.resolve(_this.baseDir, stats.path);
129 return fs.readFile(absolutePath).then(contents => {
130 const file = {
131 contents,
132 stats,
133 path: absolutePath
134 };
135 _this.files[stats.path] = file;
136 });
137 }));
138 yield new Wares().use(_this.middlewares).run(_this);
139 return _this;
140 })();
141 }
142 /**
143 * Filter files
144 * @param {(relativePath: string, file: File) => boolean} fn Filter handler
145 */
146
147
148 filter(fn) {
149 return this.use(context => {
150 for (const relativePath in context.files) {
151 if (!fn(relativePath, context.files[relativePath])) {
152 delete context.files[relativePath];
153 }
154 }
155 });
156 }
157 /**
158 * Transform file at given path
159 * @param {string} relativePath Relative path
160 * @param {(contents: string) => string} fn Transform handler
161 */
162
163
164 transform(relativePath, fn) {
165 const contents = this.files[relativePath].contents.toString();
166 const result = fn(contents);
167
168 if (!result.then) {
169 this.files[relativePath].contents = Buffer.from(result);
170 return;
171 }
172
173 return result.then(newContents => {
174 this.files[relativePath].contents = Buffer.from(newContents);
175 });
176 }
177 /**
178 * Run middlewares and write processed files to disk
179 * @param {string} dest Target directory
180 * @param {{baseDir?: string, clean?: boolean}} opts
181 * @param opts.baseDir Base directory to resolve target directory
182 * @param opts.clean Clean directory before writing
183 */
184
185
186 dest(dest, {
187 baseDir = '.',
188 clean = false
189 } = {}) {
190 var _this2 = this;
191
192 return _asyncToGenerator(function* () {
193 const destPath = path.resolve(baseDir, dest);
194 yield _this2.process();
195
196 if (clean) {
197 yield fs.remove(destPath);
198 }
199
200 yield Promise.all(Object.keys(_this2.files).map(filename => {
201 const contents = _this2.files[filename].contents;
202 const target = path.join(destPath, filename);
203
204 _this2.emit('write', filename, target);
205
206 return fs.ensureDir(path.dirname(target)).then(() => fs.writeFile(target, contents));
207 }));
208 return _this2;
209 })();
210 }
211 /**
212 * Get file contents as a UTF-8 string
213 * @param {string} relativePath Relative path
214 * @return {string}
215 */
216
217
218 fileContents(relativePath) {
219 return this.file(relativePath).contents.toString();
220 }
221 /**
222 * Write contents to specific file
223 * @param {string} relativePath Relative path
224 * @param {string} string File content as a UTF-8 string
225 */
226
227
228 writeContents(relativePath, string) {
229 this.files[relativePath].contents = Buffer.from(string);
230 return this;
231 }
232 /**
233 * Get the fs.Stats object of specified file
234 * @param {string} relativePath Relative path
235 * @return {fs.Stats}
236 */
237
238
239 fileStats(relativePath) {
240 return this.file(relativePath).stats;
241 }
242 /**
243 * Get a file by relativePath path
244 * @param {string} relativePath Relative path
245 * @return {File}
246 */
247
248
249 file(relativePath) {
250 return this.files[relativePath];
251 }
252 /**
253 * Delete a file
254 * @param {string} relativePath Relative path
255 */
256
257
258 deleteFile(relativePath) {
259 delete this.files[relativePath];
260 return this;
261 }
262 /**
263 * Create a new file
264 * @param {string} relativePath Relative path
265 * @param {File} file
266 */
267
268
269 createFile(relativePath, file) {
270 this.files[relativePath] = file;
271 return this;
272 }
273 /**
274 * Get an array of sorted file paths
275 * @return {string[]}
276 */
277
278
279 get fileList() {
280 return Object.keys(this.files).sort();
281 }
282
283}
284
285const majo = () => new Majo();
286
287majo.glob = glob;
288majo.fs = fs;
289
290module.exports = majo;