UNPKG

12.5 kBJavaScriptView Raw
1import _defineProperty from "@babel/runtime/helpers/defineProperty";
2import { Readable, pipeline } from 'stream';
3import { join as pathJoin, dirname, basename, resolve } from 'path';
4import { promisify } from 'util';
5import fse from 'fs-extra';
6import { fsLchmodSupported, fsLchmod, fsLutimesSupported, fsLutimes, fsWalk, fsLstatExists } from '@shockpkg/archive-files';
7import { once } from "./util.mjs";
8import { Queue } from "./queue.mjs";
9const pipelineP = promisify(pipeline);
10const userExec = 0b001000000;
11/**
12 * Options for adding resources.
13 */
14
15/**
16 * Bundle constructor.
17 *
18 * @param path Output path for the main executable.
19 */
20export class Bundle extends Object {
21 /**
22 * File and directory names to exclude when adding a directory.
23 */
24
25 /**
26 * Bundle main executable path.
27 */
28
29 /**
30 * Projector instance.
31 */
32
33 /**
34 * Open flag.
35 */
36
37 /**
38 * Close callbacks priority queue.
39 */
40 constructor(path) {
41 super();
42
43 _defineProperty(this, "excludes", [/^\./, /^ehthumbs\.db$/, /^Thumbs\.db$/]);
44
45 _defineProperty(this, "path", void 0);
46
47 _defineProperty(this, "projector", void 0);
48
49 _defineProperty(this, "_isOpen", false);
50
51 _defineProperty(this, "_closeQueue", new Queue());
52
53 this.path = path;
54 }
55 /**
56 * Check if output open.
57 *
58 * @returns Returns true if open, else false.
59 */
60
61
62 get isOpen() {
63 return this._isOpen;
64 }
65 /**
66 * Check if name is excluded file.
67 *
68 * @param name File name.
69 * @returns Returns true if excluded, else false.
70 */
71
72
73 isExcludedFile(name) {
74 for (const exclude of this.excludes) {
75 if (exclude.test(name)) {
76 return true;
77 }
78 }
79
80 return false;
81 }
82 /**
83 * Open output with file.
84 *
85 * @param player Player path.
86 * @param movieFile Movie file.
87 */
88
89
90 async openFile(player, movieFile) {
91 const movieData = movieFile ? await fse.readFile(movieFile) : null;
92 await this.openData(player, movieData);
93 }
94 /**
95 * Open output with data.
96 *
97 * @param player Player path.
98 * @param movieData Movie data.
99 */
100
101
102 async openData(player, movieData) {
103 if (this._isOpen) {
104 throw new Error('Already open');
105 }
106
107 await this._checkOutput();
108
109 this._closeQueue.clear();
110
111 await this._openData(player, movieData);
112 this._isOpen = true;
113 }
114 /**
115 * Close output.
116 */
117
118
119 async close() {
120 this._assertIsOpen();
121
122 try {
123 await this._close();
124 } finally {
125 this._closeQueue.clear();
126 }
127
128 this._isOpen = false;
129 }
130 /**
131 * Write out projector with player and file.
132 * Has a callback to write out the resources.
133 *
134 * @param player Player path.
135 * @param movieFile Movie file.
136 * @param func Async function.
137 * @returns Return value of the async function.
138 */
139
140
141 async withFile(player, movieFile, func = null) {
142 const movieData = movieFile ? await fse.readFile(movieFile) : null;
143 return this.withData(player, movieData, func);
144 }
145 /**
146 * Write out projector with player and data.
147 * Has a callback to write out the resources.
148 *
149 * @param player Player path.
150 * @param movieData Movie data.
151 * @param func Async function.
152 * @returns Return value of the async function.
153 */
154
155
156 async withData(player, movieData, func = null) {
157 await this.openData(player, movieData);
158
159 try {
160 return func ? await func.call(this, this) : null;
161 } finally {
162 await this.close();
163 }
164 }
165 /**
166 * Get path for resource.
167 *
168 * @param destination Resource destination.
169 * @returns Destination path.
170 */
171
172
173 resourcePath(destination) {
174 return pathJoin(dirname(this.projector.path), destination);
175 }
176 /**
177 * Check if path for resource exists.
178 *
179 * @param destination Resource destination.
180 * @returns True if destination exists, else false.
181 */
182
183
184 async resourceExists(destination) {
185 return !!fsLstatExists(this.resourcePath(destination));
186 }
187 /**
188 * Copy resource, detecting source type automatically.
189 *
190 * @param destination Resource destination.
191 * @param source Source directory.
192 * @param options Resource options.
193 */
194
195
196 async copyResource(destination, source, options = null) {
197 this._assertIsOpen();
198
199 const stat = await fse.lstat(source);
200
201 switch (true) {
202 case stat.isSymbolicLink():
203 {
204 await this.copyResourceSymlink(destination, source, options);
205 break;
206 }
207
208 case stat.isFile():
209 {
210 await this.copyResourceFile(destination, source, options);
211 break;
212 }
213
214 case stat.isDirectory():
215 {
216 await this.copyResourceDirectory(destination, source, options);
217 break;
218 }
219
220 default:
221 {
222 throw new Error(`Unsupported resource type: ${source}`);
223 }
224 }
225 }
226 /**
227 * Copy directory as resource, recursive copy.
228 *
229 * @param destination Resource destination.
230 * @param source Source directory.
231 * @param options Resource options.
232 */
233
234
235 async copyResourceDirectory(destination, source, options = null) {
236 this._assertIsOpen(); // Create directory.
237
238
239 await this.createResourceDirectory(destination, options ? await this._expandResourceOptionsCopy(options, async () => fse.stat(source)) : options); // If not recursive do not walk contents.
240
241 if (options && options.noRecurse) {
242 return;
243 } // Any directories we add should not be recursive.
244
245
246 const opts = { ...(options || {}),
247 noRecurse: true
248 };
249 await fsWalk(source, async (path, stat) => {
250 // If this name is excluded, skip without descending.
251 if (this.isExcludedFile(basename(path))) {
252 return false;
253 }
254
255 await this.copyResource(pathJoin(destination, path), pathJoin(source, path), opts);
256 return true;
257 });
258 }
259 /**
260 * Copy file as resource.
261 *
262 * @param destination Resource destination.
263 * @param source Source file.
264 * @param options Resource options.
265 */
266
267
268 async copyResourceFile(destination, source, options = null) {
269 this._assertIsOpen();
270
271 await this.streamResourceFile(destination, fse.createReadStream(source), options ? await this._expandResourceOptionsCopy(options, async () => fse.stat(source)) : options);
272 }
273 /**
274 * Copy symlink as resource.
275 *
276 * @param destination Resource destination.
277 * @param source Source symlink.
278 * @param options Resource options.
279 */
280
281
282 async copyResourceSymlink(destination, source, options = null) {
283 this._assertIsOpen();
284
285 await this.createResourceSymlink(destination, await fse.readlink(source), options ? await this._expandResourceOptionsCopy(options, async () => fse.lstat(source)) : options);
286 }
287 /**
288 * Create a resource directory.
289 *
290 * @param destination Resource destination.
291 * @param options Resource options.
292 */
293
294
295 async createResourceDirectory(destination, options = null) {
296 this._assertIsOpen();
297
298 const dest = await this._assertNotResourceExists(destination, !!(options && options.merge));
299 await fse.ensureDir(dest); // If either is set, queue up change times when closing.
300
301 if (options && (options.atime || options.mtime)) {
302 // Get absolute path, use length for the priority.
303 // Also copy the options object which the owner could change.
304 const abs = resolve(dest);
305
306 this._closeQueue.push(this._setResourceAttributes.bind(this, abs, { ...options
307 }), abs.length);
308 }
309 }
310 /**
311 * Create a resource file.
312 *
313 * @param destination Resource destination.
314 * @param data Resource data.
315 * @param options Resource options.
316 */
317
318
319 async createResourceFile(destination, data, options = null) {
320 this._assertIsOpen();
321
322 await this.streamResourceFile(destination, new Readable({
323 read() {
324 this.push(data);
325 this.push(null);
326 }
327
328 }), options);
329 }
330 /**
331 * Create a resource symlink.
332 *
333 * @param destination Resource destination.
334 * @param target Symlink target.
335 * @param options Resource options.
336 */
337
338
339 async createResourceSymlink(destination, target, options = null) {
340 this._assertIsOpen();
341
342 const dest = await this._assertNotResourceExists(destination);
343 await fse.ensureDir(dirname(dest));
344 await fse.symlink(target, dest);
345
346 if (options) {
347 await this._setResourceAttributes(dest, options);
348 }
349 }
350 /**
351 * Stream readable source to resource file.
352 *
353 * @param destination Resource destination.
354 * @param data Resource stream.
355 * @param options Resource options.
356 */
357
358
359 async streamResourceFile(destination, data, options = null) {
360 this._assertIsOpen();
361
362 const dest = await this._assertNotResourceExists(destination);
363 await fse.ensureDir(dirname(dest));
364 await pipelineP(data, fse.createWriteStream(dest));
365
366 if (options) {
367 await this._setResourceAttributes(dest, options);
368 }
369 }
370 /**
371 * Check that output path is valid, else throws.
372 */
373
374
375 async _checkOutput() {
376 for (const p of [this.path, this.resourcePath('')]) {
377 // eslint-disable-next-line no-await-in-loop
378 if (await fse.pathExists(p)) {
379 throw new Error(`Output path already exists: ${p}`);
380 }
381 }
382 }
383 /**
384 * Expand resource options copy properties with stat object from source.
385 *
386 * @param options Options object.
387 * @param stat Stat function.
388 * @returns Options copy with any values populated.
389 */
390
391
392 async _expandResourceOptionsCopy(options, stat) {
393 const r = { ...options
394 };
395 const st = once(stat);
396
397 if (!r.atime && r.atimeCopy) {
398 r.atime = (await st()).atime;
399 }
400
401 if (!r.mtime && r.mtimeCopy) {
402 r.mtime = (await st()).mtime;
403 }
404
405 if (typeof r.executable !== 'boolean' && r.executableCopy) {
406 r.executable = this._getResourceModeExecutable((await st()).mode);
407 }
408
409 return r;
410 }
411 /**
412 * Set resource attributes from options object.
413 *
414 * @param path File path.
415 * @param options Options object.
416 */
417
418
419 async _setResourceAttributes(path, options) {
420 const {
421 atime,
422 mtime,
423 executable
424 } = options;
425 const st = await fse.lstat(path); // Maybe set executable if not a directory and supported.
426
427 if (typeof executable === 'boolean' && !st.isDirectory()) {
428 if (!st.isSymbolicLink()) {
429 await fse.chmod(path, this._setResourceModeExecutable(st.mode, executable));
430 } else if (fsLchmodSupported) {
431 await fsLchmod(path, this._setResourceModeExecutable( // Workaround for a legacy Node issue.
432 // eslint-disable-next-line no-bitwise
433 st.mode & 0b111111111, executable));
434 }
435 } // Maybe change times if either is set and supported.
436
437
438 if (atime || mtime) {
439 if (!st.isSymbolicLink()) {
440 await fse.utimes(path, atime || st.atime, mtime || st.mtime);
441 } else if (fsLutimesSupported) {
442 await fsLutimes(path, atime || st.atime, mtime || st.mtime);
443 }
444 }
445 }
446 /**
447 * Get file mode executable.
448 *
449 * @param mode Current mode.
450 * @returns Is executable.
451 */
452
453
454 _getResourceModeExecutable(mode) {
455 // eslint-disable-next-line no-bitwise
456 return !!(mode & userExec);
457 }
458 /**
459 * Set file mode executable.
460 *
461 * @param mode Current mode.
462 * @param executable Is executable.
463 * @returns File mode.
464 */
465
466
467 _setResourceModeExecutable(mode, executable) {
468 // eslint-disable-next-line no-bitwise
469 return (executable ? mode | userExec : mode & ~userExec) >>> 0;
470 }
471 /**
472 * Open output with data.
473 *
474 * @param player Player path.
475 * @param movieData Movie data.
476 */
477
478
479 async _openData(player, movieData) {
480 await this.projector.withData(player, movieData);
481 }
482 /**
483 * Close output.
484 */
485
486
487 async _close() {
488 await this._writeLauncher();
489 await this._closeQueue.run();
490 }
491 /**
492 * Assert bundle is open.
493 */
494
495
496 _assertIsOpen() {
497 if (!this._isOpen) {
498 throw new Error('Not open');
499 }
500 }
501 /**
502 * Assert resource does not exist, returning destination path.
503 *
504 * @param destination Resource destination.
505 * @param ignoreDirectory Ignore directories.
506 * @returns Destination path.
507 */
508
509
510 async _assertNotResourceExists(destination, ignoreDirectory = false) {
511 const dest = this.resourcePath(destination);
512 const st = await fsLstatExists(dest);
513
514 if (st && (!ignoreDirectory || !st.isDirectory())) {
515 throw new Error(`Resource path exists: ${dest}`);
516 }
517
518 return dest;
519 }
520 /**
521 * Main application file extension.
522 *
523 * @returns File extension.
524 */
525
526
527}
528//# sourceMappingURL=bundle.mjs.map