1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.Bundle = void 0;
|
7 | var _nodeFs = require("node:fs");
|
8 | var _promises = require("node:fs/promises");
|
9 | var _nodeStream = require("node:stream");
|
10 | var _promises2 = require("node:stream/promises");
|
11 | var _nodePath = require("node:path");
|
12 | var _archiveFiles = require("@shockpkg/archive-files");
|
13 | var _queue = require("./queue.js");
|
14 | const userExec = 0b001000000;
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | class Bundle {
|
24 | |
25 |
|
26 |
|
27 | excludes = [/^\./, /^ehthumbs\.db$/i, /^Thumbs\.db$/i];
|
28 |
|
29 | |
30 |
|
31 |
|
32 |
|
33 | |
34 |
|
35 |
|
36 |
|
37 | |
38 |
|
39 |
|
40 |
|
41 | |
42 |
|
43 |
|
44 | _isOpen = false;
|
45 |
|
46 | |
47 |
|
48 |
|
49 | _closeQueue = new _queue.Queue();
|
50 |
|
51 | |
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 | constructor(path, flat = false) {
|
58 | this.path = path;
|
59 | this.flat = flat;
|
60 | }
|
61 |
|
62 | |
63 |
|
64 |
|
65 |
|
66 |
|
67 | get isOpen() {
|
68 | return this._isOpen;
|
69 | }
|
70 |
|
71 | |
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | isExcludedFile(name) {
|
78 | for (const exclude of this.excludes) {
|
79 | if (exclude.test(name)) {
|
80 | return true;
|
81 | }
|
82 | }
|
83 | return false;
|
84 | }
|
85 |
|
86 | |
87 |
|
88 |
|
89 | async open() {
|
90 | if (this._isOpen) {
|
91 | throw new Error('Already open');
|
92 | }
|
93 | await this._checkOutput();
|
94 | this._closeQueue.clear();
|
95 | await this._open();
|
96 | this._isOpen = true;
|
97 | }
|
98 |
|
99 | |
100 |
|
101 |
|
102 | async close() {
|
103 | this._assertIsOpen();
|
104 | try {
|
105 | await this._close();
|
106 | } finally {
|
107 | this._closeQueue.clear();
|
108 | }
|
109 | this._isOpen = false;
|
110 | }
|
111 |
|
112 | |
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | async write(func = null) {
|
120 | await this.open();
|
121 | try {
|
122 | return func ? await func.call(this, this) : null;
|
123 | } finally {
|
124 | await this.close();
|
125 | }
|
126 | }
|
127 |
|
128 | |
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | resourcePath(destination) {
|
135 | return (0, _nodePath.join)((0, _nodePath.dirname)(this.projector.path), destination);
|
136 | }
|
137 |
|
138 | |
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | async resourceExists(destination) {
|
145 | return !!(await (0, _archiveFiles.fsLstatExists)(this.resourcePath(destination)));
|
146 | }
|
147 |
|
148 | |
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | async copyResource(destination, source, options = null) {
|
156 | this._assertIsOpen();
|
157 | const stat = await (0, _promises.lstat)(source);
|
158 | switch (true) {
|
159 | case stat.isSymbolicLink():
|
160 | {
|
161 | await this.copyResourceSymlink(destination, source, options);
|
162 | break;
|
163 | }
|
164 | case stat.isFile():
|
165 | {
|
166 | await this.copyResourceFile(destination, source, options);
|
167 | break;
|
168 | }
|
169 | case stat.isDirectory():
|
170 | {
|
171 | await this.copyResourceDirectory(destination, source, options);
|
172 | break;
|
173 | }
|
174 | default:
|
175 | {
|
176 | throw new Error(`Unsupported resource type: ${source}`);
|
177 | }
|
178 | }
|
179 | }
|
180 |
|
181 | |
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 | async copyResourceDirectory(destination, source, options = null) {
|
189 | this._assertIsOpen();
|
190 |
|
191 |
|
192 | await this.createResourceDirectory(destination, options ? await this._expandResourceOptionsCopy(options, async () => (0, _promises.stat)(source)) : options);
|
193 |
|
194 |
|
195 | if (options && options.noRecurse) {
|
196 | return;
|
197 | }
|
198 |
|
199 |
|
200 | const opts = {
|
201 | ...(options || {}),
|
202 | noRecurse: true
|
203 | };
|
204 | await (0, _archiveFiles.fsWalk)(source, async (path, stat) => {
|
205 |
|
206 | if (this.isExcludedFile((0, _nodePath.basename)(path))) {
|
207 | return false;
|
208 | }
|
209 | await this.copyResource((0, _nodePath.join)(destination, path), (0, _nodePath.join)(source, path), opts);
|
210 | return true;
|
211 | });
|
212 | }
|
213 |
|
214 | |
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | async copyResourceFile(destination, source, options = null) {
|
222 | this._assertIsOpen();
|
223 | await this.streamResourceFile(destination, (0, _nodeFs.createReadStream)(source), options ? await this._expandResourceOptionsCopy(options, async () => (0, _promises.stat)(source)) : options);
|
224 | }
|
225 |
|
226 | |
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | async copyResourceSymlink(destination, source, options = null) {
|
234 | this._assertIsOpen();
|
235 | await this.createResourceSymlink(destination, await (0, _promises.readlink)(source), options ? await this._expandResourceOptionsCopy(options, async () => (0, _promises.lstat)(source)) : options);
|
236 | }
|
237 |
|
238 | |
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 | async createResourceDirectory(destination, options = null) {
|
245 | this._assertIsOpen();
|
246 | const dest = await this._assertNotResourceExists(destination, !!(options && options.merge));
|
247 | await (0, _promises.mkdir)(dest, {
|
248 | recursive: true
|
249 | });
|
250 |
|
251 |
|
252 | if (options && (options.atime || options.mtime)) {
|
253 |
|
254 |
|
255 | const abs = (0, _nodePath.resolve)(dest);
|
256 | this._closeQueue.push(this._setResourceAttributes.bind(this, abs, {
|
257 | ...options
|
258 | }), abs.length);
|
259 | }
|
260 | }
|
261 |
|
262 | |
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | async createResourceFile(destination, data, options = null) {
|
270 | this._assertIsOpen();
|
271 | await this.streamResourceFile(destination, new _nodeStream.Readable({
|
272 | |
273 |
|
274 |
|
275 | read() {
|
276 | this.push(data);
|
277 | this.push(null);
|
278 | }
|
279 | }), options);
|
280 | }
|
281 |
|
282 | |
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 | async createResourceSymlink(destination, target, options = null) {
|
290 | this._assertIsOpen();
|
291 | const dest = await this._assertNotResourceExists(destination);
|
292 | await (0, _promises.mkdir)((0, _nodePath.dirname)(dest), {
|
293 | recursive: true
|
294 | });
|
295 | const t = typeof target === 'string' ? target : Buffer.from(target.buffer, target.byteOffset, target.byteLength);
|
296 | await (0, _promises.symlink)(t, dest);
|
297 | if (options) {
|
298 | await this._setResourceAttributes(dest, options);
|
299 | }
|
300 | }
|
301 |
|
302 | |
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | async streamResourceFile(destination, data, options = null) {
|
310 | this._assertIsOpen();
|
311 | const dest = await this._assertNotResourceExists(destination);
|
312 | await (0, _promises.mkdir)((0, _nodePath.dirname)(dest), {
|
313 | recursive: true
|
314 | });
|
315 | await (0, _promises2.pipeline)(data, (0, _nodeFs.createWriteStream)(dest));
|
316 | if (options) {
|
317 | await this._setResourceAttributes(dest, options);
|
318 | }
|
319 | }
|
320 |
|
321 | |
322 |
|
323 |
|
324 | async _checkOutput() {
|
325 | if (this.flat) {
|
326 | const p = (0, _nodePath.dirname)(this.path);
|
327 | if (await (0, _archiveFiles.fsLstatExists)(p)) {
|
328 | for (const n of await (0, _promises.readdir)(p)) {
|
329 | if (!this.isExcludedFile(n)) {
|
330 | throw new Error(`Output path not empty: ${p}`);
|
331 | }
|
332 | }
|
333 | }
|
334 | return;
|
335 | }
|
336 | await Promise.all([this.path, this.resourcePath('')].map(async p => {
|
337 | if (await (0, _archiveFiles.fsLstatExists)(p)) {
|
338 | throw new Error(`Output path already exists: ${p}`);
|
339 | }
|
340 | }));
|
341 | }
|
342 |
|
343 | |
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 | async _expandResourceOptionsCopy(options, stat) {
|
351 | const r = {
|
352 | ...options
|
353 | };
|
354 | let st;
|
355 | if (!r.atime && r.atimeCopy) {
|
356 | st = await stat();
|
357 | r.atime = st.atime;
|
358 | }
|
359 | if (!r.mtime && r.mtimeCopy) {
|
360 | st ??= await stat();
|
361 | r.mtime = st.mtime;
|
362 | }
|
363 | if (typeof r.executable !== 'boolean' && r.executableCopy) {
|
364 | st ??= await stat();
|
365 | r.executable = this._getResourceModeExecutable(st.mode);
|
366 | }
|
367 | return r;
|
368 | }
|
369 |
|
370 | |
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 | async _setResourceAttributes(path, options) {
|
377 | const {
|
378 | atime,
|
379 | mtime,
|
380 | executable
|
381 | } = options;
|
382 | const st = await (0, _promises.lstat)(path);
|
383 |
|
384 |
|
385 | if (typeof executable === 'boolean' && !st.isDirectory()) {
|
386 | if (st.isSymbolicLink()) {
|
387 | await (0, _archiveFiles.fsLchmod)(path, this._setResourceModeExecutable(
|
388 |
|
389 |
|
390 | st.mode & 0b111111111, executable));
|
391 | } else {
|
392 | await (0, _promises.chmod)(path, this._setResourceModeExecutable(st.mode, executable));
|
393 | }
|
394 | }
|
395 |
|
396 |
|
397 | if (atime || mtime) {
|
398 | if (st.isSymbolicLink()) {
|
399 | await (0, _archiveFiles.fsLutimes)(path, atime || st.atime, mtime || st.mtime);
|
400 | } else {
|
401 | await (0, _promises.utimes)(path, atime || st.atime, mtime || st.mtime);
|
402 | }
|
403 | }
|
404 | }
|
405 |
|
406 | |
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 | _getResourceModeExecutable(mode) {
|
413 |
|
414 | return !!(mode & userExec);
|
415 | }
|
416 |
|
417 | |
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 | _setResourceModeExecutable(mode, executable) {
|
425 |
|
426 | return (executable ? mode | userExec : mode & ~userExec) >>> 0;
|
427 | }
|
428 |
|
429 | |
430 |
|
431 |
|
432 | async _open() {
|
433 | await this.projector.write();
|
434 | }
|
435 |
|
436 | |
437 |
|
438 |
|
439 | async _close() {
|
440 | await this._closeQueue.run();
|
441 | }
|
442 |
|
443 | |
444 |
|
445 |
|
446 | _assertIsOpen() {
|
447 | if (!this._isOpen) {
|
448 | throw new Error('Not open');
|
449 | }
|
450 | }
|
451 |
|
452 | |
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 | async _assertNotResourceExists(destination, ignoreDirectory = false) {
|
460 | const dest = this.resourcePath(destination);
|
461 | const st = await (0, _archiveFiles.fsLstatExists)(dest);
|
462 | if (st && (!ignoreDirectory || !st.isDirectory())) {
|
463 | throw new Error(`Resource path exists: ${dest}`);
|
464 | }
|
465 | return dest;
|
466 | }
|
467 |
|
468 | |
469 |
|
470 |
|
471 |
|
472 |
|
473 | _getProjectorPath() {
|
474 | return this.flat ? this.path : this._getProjectorPathNested();
|
475 | }
|
476 |
|
477 | |
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 | |
484 |
|
485 |
|
486 |
|
487 |
|
488 | }
|
489 | exports.Bundle = Bundle;
|
490 |
|
\ | No newline at end of file |