nw
Version:
An installer for NW.js
96 lines (84 loc) • 3.11 kB
JavaScript
import fs from "node:fs";
import path from "node:path";
import stream from "node:stream";
import * as tar from "tar";
import yauzl from "yauzl-promise";
import util from "./util.js";
/**
* Decompresses a file at `filePath` to `cacheDir` directory.
*
* @param {string} filePath - file path to compressed binary
* @param {string} cacheDir - directory to decompress into
*/
export default async function decompress(filePath, cacheDir) {
if (filePath.endsWith(".zip")) {
await unzip(filePath, cacheDir);
} else {
await tar.extract({
file: filePath,
C: cacheDir
});
}
}
/**
* Get file mode from entry. Reference implementation is [here](https://github.com/fpsqdb/zip-lib/blob/ac447d269218d396e05cd7072d0e9cd82b5ec52c/src/unzip.ts#L380).
*
* @param {yauzl.Entry} entry - Yauzl entry
* @return {number} - entry's file mode
*/
function modeFromEntry(entry) {
const attr = entry.externalFileAttributes >> 16 || 33188;
return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */]
.map(mask => attr & mask)
.reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
}
/**
* Unzip `zippedFile` to `cacheDir`.
*
* @async
* @function
* @param {string} zippedFile - file path to .zip file
* @param {string} cacheDir - directory to unzip in
* @return {Promise<void>}
*/
async function unzip(zippedFile, cacheDir) {
const zip = await yauzl.open(zippedFile);
let entry = await zip.readEntry();
const symlinks = []; // Array to hold symbolic link entries
while (entry !== null) {
let entryPathAbs = path.join(cacheDir, entry.filename);
/* Check if entry is a symbolic link */
const isSymlink = ((modeFromEntry(entry) & 0o170000) === 0o120000);
if (isSymlink) {
/* Store symlink entries to process later */
symlinks.push(entry);
} else {
/* Handle regular files and directories */
await fs.promises.mkdir(path.dirname(entryPathAbs), {recursive: true});
if (!entry.filename.endsWith('/')) { // Skip directories
const readStream = await entry.openReadStream();
const writeStream = fs.createWriteStream(entryPathAbs);
await stream.promises.pipeline(readStream, writeStream);
/* Set file permissions after the file has been written */
const mode = modeFromEntry(entry);
await fs.promises.chmod(entryPathAbs, mode);
}
}
/* Read next entry */
entry = await zip.readEntry();
}
/* Process symbolic links after all other files have been extracted */
for (const symlinkEntry of symlinks) {
let entryPathAbs = path.join(cacheDir, symlinkEntry.filename);
const readStream = await symlinkEntry.openReadStream();
const chunks = [];
readStream.on("data", (chunk) => chunks.push(chunk));
await new Promise(resolve => readStream.on("end", resolve));
const linkTarget = Buffer.concat(chunks).toString('utf8').trim();
const fileExists = await util.fileExists(entryPathAbs);
if (!fileExists) {
await fs.promises.symlink(linkTarget, entryPathAbs);
}
}
await zip.close();
}