UNPKG

3.08 kBJavaScriptView Raw
1import fs from "node:fs";
2import path from "node:path";
3import stream from "node:stream";
4
5import tar from "tar";
6import yauzl from "yauzl-promise";
7
8import util from "./util.js";
9
10/**
11 * Decompresses a file at `filePath` to `cacheDir` directory.
12 *
13 * @param {string} filePath - file path to compressed binary
14 * @param {string} cacheDir - directory to decompress into
15 */
16export default async function decompress(filePath, cacheDir) {
17 if (filePath.endsWith(".zip")) {
18 await unzip(filePath, cacheDir);
19 } else {
20 await tar.extract({
21 file: filePath,
22 C: cacheDir
23 });
24 }
25}
26
27/**
28 * Get file mode from entry. Reference implementation is [here](https://github.com/fpsqdb/zip-lib/blob/ac447d269218d396e05cd7072d0e9cd82b5ec52c/src/unzip.ts#L380).
29 *
30 * @param {yauzl.Entry} entry - Yauzl entry
31 * @return {number} - entry's file mode
32 */
33function modeFromEntry(entry) {
34 const attr = entry.externalFileAttributes >> 16 || 33188;
35
36 return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */]
37 .map(mask => attr & mask)
38 .reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
39}
40
41/**
42 * Unzip `zippedFile` to `cacheDir`.
43 *
44 * @async
45 * @function
46 * @param {string} zippedFile - file path to .zip file
47 * @param {string} cacheDir - directory to unzip in
48 * @return {Promise<void>}
49 */
50async function unzip(zippedFile, cacheDir) {
51 const zip = await yauzl.open(zippedFile);
52 let entry = await zip.readEntry();
53 const symlinks = []; // Array to hold symbolic link entries
54
55 while (entry !== null) {
56 let entryPathAbs = path.join(cacheDir, entry.filename);
57 /* Check if entry is a symbolic link */
58 const isSymlink = ((modeFromEntry(entry) & 0o170000) === 0o120000);
59
60 if (isSymlink) {
61 /* Store symlink entries to process later */
62 symlinks.push(entry);
63 } else {
64 /* Handle regular files and directories */
65 await fs.promises.mkdir(path.dirname(entryPathAbs), {recursive: true});
66 if (!entry.filename.endsWith('/')) { // Skip directories
67 const readStream = await entry.openReadStream();
68 const writeStream = fs.createWriteStream(entryPathAbs);
69 await stream.promises.pipeline(readStream, writeStream);
70
71 /* Set file permissions after the file has been written */
72 const mode = modeFromEntry(entry);
73 await fs.promises.chmod(entryPathAbs, mode);
74 }
75 }
76
77 /* Read next entry */
78 entry = await zip.readEntry();
79 }
80
81 /* Process symbolic links after all other files have been extracted */
82 for (const symlinkEntry of symlinks) {
83 let entryPathAbs = path.join(cacheDir, symlinkEntry.filename);
84 const readStream = await symlinkEntry.openReadStream();
85 const chunks = [];
86 readStream.on("data", (chunk) => chunks.push(chunk));
87 await new Promise(resolve => readStream.on("end", resolve));
88 const linkTarget = Buffer.concat(chunks).toString('utf8').trim();
89
90 const fileExists = await util.fileExists(entryPathAbs);
91 if (!fileExists) {
92 await fs.promises.symlink(linkTarget, entryPathAbs);
93 }
94 }
95}
\No newline at end of file