keycloak_theme/src/bin/tools/extractArchive.ts

126 lines
3.4 KiB
TypeScript
Raw Normal View History

2024-05-24 17:26:38 +02:00
import fs from "fs/promises";
import fsSync from "fs";
import yauzl from "yauzl";
import stream from "stream";
import { Deferred } from "evt/tools/Deferred";
import { dirname as pathDirname, sep as pathSep } from "path";
export async function extractArchive(params: {
archiveFilePath: string;
onArchiveFile: (params: {
relativeFilePathInArchive: string;
readFile: () => Promise<Buffer>;
writeFile: (params: { filePath: string; modifiedData?: Buffer }) => Promise<void>;
earlyExit: () => void;
2024-05-24 17:26:38 +02:00
}) => Promise<void>;
}) {
const { archiveFilePath, onArchiveFile } = params;
const zipFile = await new Promise<yauzl.ZipFile>((resolve, reject) => {
yauzl.open(archiveFilePath, { lazyEntries: true }, async (error, zipFile) => {
2024-05-25 11:02:22 +02:00
if (error) {
2024-05-24 17:26:38 +02:00
reject(error);
return;
}
resolve(zipFile);
});
});
const dDone = new Deferred<void>();
zipFile.once("end", () => {
zipFile.close();
dDone.resolve();
});
2024-05-25 12:30:21 +02:00
const writeFile = async (
entry: yauzl.Entry,
params: {
2024-05-24 17:26:38 +02:00
filePath: string;
modifiedData?: Buffer;
2024-05-25 12:30:21 +02:00
}
): Promise<void> => {
const { filePath, modifiedData } = params;
await fs.mkdir(pathDirname(filePath), { recursive: true });
if (modifiedData !== undefined) {
await fs.writeFile(filePath, modifiedData);
return;
}
const readStream = await new Promise<stream.Readable>(resolve =>
zipFile.openReadStream(entry, async (error, readStream) => {
if (error) {
dDone.reject(error);
return;
}
2024-05-24 17:26:38 +02:00
2024-05-25 12:30:21 +02:00
resolve(readStream);
})
);
2024-05-24 17:26:38 +02:00
2024-05-25 12:30:21 +02:00
const dDoneWithFile = new Deferred<void>();
stream.pipeline(readStream, fsSync.createWriteStream(filePath), error => {
if (error) {
dDone.reject(error);
2024-05-24 17:26:38 +02:00
return;
}
2024-05-25 12:30:21 +02:00
dDoneWithFile.resolve();
});
2024-05-24 17:26:38 +02:00
2024-05-25 12:30:21 +02:00
await dDoneWithFile.pr;
};
2024-05-24 17:26:38 +02:00
2024-05-25 12:30:21 +02:00
const readFile = (entry: yauzl.Entry) =>
new Promise<Buffer>(resolve =>
zipFile.openReadStream(entry, async (error, readStream) => {
2024-05-25 11:02:22 +02:00
if (error) {
2024-05-24 17:26:38 +02:00
dDone.reject(error);
return;
}
2024-05-25 12:30:21 +02:00
const chunks: Buffer[] = [];
2024-05-24 17:26:38 +02:00
2024-05-25 12:30:21 +02:00
readStream.on("data", chunk => {
chunks.push(chunk);
});
2024-05-24 17:26:38 +02:00
2024-05-25 12:30:21 +02:00
readStream.on("end", () => {
resolve(Buffer.concat(chunks));
});
readStream.on("error", error => {
dDone.reject(error);
});
})
);
2024-05-24 17:26:38 +02:00
zipFile.on("entry", async (entry: yauzl.Entry) => {
handle_file: {
// NOTE: Skip directories
if (entry.fileName.endsWith(pathSep)) {
break handle_file;
}
await onArchiveFile({
relativeFilePathInArchive: entry.fileName.split("/").join(pathSep),
2024-05-25 12:30:21 +02:00
readFile: () => readFile(entry),
writeFile: params => writeFile(entry, params),
earlyExit: () => {
zipFile.close();
dDone.resolve();
}
2024-05-24 17:26:38 +02:00
});
}
zipFile.readEntry();
});
zipFile.readEntry();
await dDone.pr;
}