mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-21 16:00:59 -05:00
Added utility methods for reading zipped files
This commit is contained in:
parent
e407797735
commit
cf10a77ae0
2 changed files with 127 additions and 18 deletions
|
@ -1,7 +1,8 @@
|
||||||
import { $i } from "@/utils/collections";
|
import { $i } from "@/utils/collections";
|
||||||
import { FileNotFoundError } from "@/utils/errors";
|
import { FileNotFoundError } from "@/utils/errors";
|
||||||
import glob from "fast-glob";
|
import glob from "fast-glob";
|
||||||
import { ReadStream, createReadStream, existsSync, readFileSync as readFileNodeSync, statSync } from "node:fs";
|
import StreamZip from "node-stream-zip";
|
||||||
|
import { PathLike, ReadStream, createReadStream, existsSync, readFileSync as readFileNodeSync, statSync } from "node:fs";
|
||||||
import { readFile as readFileNode } from "node:fs/promises";
|
import { readFile as readFileNode } from "node:fs/promises";
|
||||||
import { basename, dirname } from "node:path";
|
import { basename, dirname } from "node:path";
|
||||||
|
|
||||||
|
@ -195,13 +196,9 @@ export function findFilesSync(pattern: string | string[]): FileInfo[] {
|
||||||
*
|
*
|
||||||
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
||||||
*/
|
*/
|
||||||
export async function readFile(pattern: string): Promise<Buffer> {
|
export async function readFile(pattern: PathLike): Promise<Buffer> {
|
||||||
const files = await glob(pattern);
|
const file = await getFileName(pattern);
|
||||||
if (!files?.length) {
|
return await readFileNode(file);
|
||||||
throw new FileNotFoundError(pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await readFileNode(files[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,10 +211,45 @@ export async function readFile(pattern: string): Promise<Buffer> {
|
||||||
*
|
*
|
||||||
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
||||||
*/
|
*/
|
||||||
export async function readAllText(pattern: string, encoding?: BufferEncoding): Promise<string> {
|
export async function readAllText(pattern: PathLike, encoding?: BufferEncoding): Promise<string> {
|
||||||
return (await readFile(pattern)).toString(encoding);
|
return (await readFile(pattern)).toString(encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a zipped file and returns its content as a Buffer.
|
||||||
|
*
|
||||||
|
* @param pattern - The glob pattern used to locate the zip archive.
|
||||||
|
* @param entry - The entry name of the file within the zip archive.
|
||||||
|
*
|
||||||
|
* @returns A promise that resolves to a Buffer containing the file contents.
|
||||||
|
*/
|
||||||
|
export async function readZippedFile(pattern: PathLike, entry: string) : Promise<Buffer> {
|
||||||
|
const file = await getFileName(pattern);
|
||||||
|
|
||||||
|
let zip = undefined as StreamZip.StreamZipAsync;
|
||||||
|
try {
|
||||||
|
// Dude, it's not my constructor, calm down.
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
zip = new StreamZip.async({ file });
|
||||||
|
return await zip.entryData(entry);
|
||||||
|
} finally {
|
||||||
|
await zip?.close().catch(() => undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a zipped file and returns its content as a string.
|
||||||
|
*
|
||||||
|
* @param pattern - The glob pattern used to locate the zip archive.
|
||||||
|
* @param entry - The entry name of the file within the zip archive.
|
||||||
|
* @param encoding - The optional encoding to use for reading the file. Defaults to `utf8`.
|
||||||
|
*
|
||||||
|
* @returns A promise that resolves to a string containing the file contents.
|
||||||
|
*/
|
||||||
|
export async function readAllZippedText(pattern: PathLike, entry: string, encoding?: BufferEncoding) : Promise<string> {
|
||||||
|
return (await readZippedFile(pattern, entry)).toString(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the contents of the first file matching the specified glob pattern synchronously.
|
* Reads the contents of the first file matching the specified glob pattern synchronously.
|
||||||
*
|
*
|
||||||
|
@ -227,13 +259,9 @@ export async function readAllText(pattern: string, encoding?: BufferEncoding): P
|
||||||
*
|
*
|
||||||
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
||||||
*/
|
*/
|
||||||
export function readFileSync(pattern: string): Buffer {
|
export function readFileSync(pattern: PathLike): Buffer {
|
||||||
const files = glob.sync(pattern);
|
const file = getFileNameSync(pattern);
|
||||||
if (!files?.length) {
|
return readFileNodeSync(file);
|
||||||
throw new FileNotFoundError(pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
return readFileNodeSync(files[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -246,6 +274,50 @@ export function readFileSync(pattern: string): Buffer {
|
||||||
*
|
*
|
||||||
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
||||||
*/
|
*/
|
||||||
export function readAllTextSync(pattern: string, encoding?: BufferEncoding): string {
|
export function readAllTextSync(pattern: PathLike, encoding?: BufferEncoding): string {
|
||||||
return readFileSync(pattern).toString(encoding);
|
return readFileSync(pattern).toString(encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the name of the first file that matches the specified glob pattern.
|
||||||
|
*
|
||||||
|
* @param pattern - The file path or glob pattern.
|
||||||
|
*
|
||||||
|
* @returns The name of the first matching file.
|
||||||
|
*
|
||||||
|
* @throws {FileNotFoundError} - If no matching file is found.
|
||||||
|
*/
|
||||||
|
async function getFileName(pattern: PathLike): Promise<string> {
|
||||||
|
if (existsSync(pattern)) {
|
||||||
|
return pattern.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await glob(pattern.toString());
|
||||||
|
if (files?.[0]) {
|
||||||
|
return files[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundError(pattern.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronously retrieves the name of the first file that matches the specified glob pattern.
|
||||||
|
*
|
||||||
|
* @param pattern - The file path or glob pattern.
|
||||||
|
*
|
||||||
|
* @returns The name of the first matching file.
|
||||||
|
*
|
||||||
|
* @throws {FileNotFoundError} - If no matching file is found.
|
||||||
|
*/
|
||||||
|
function getFileNameSync(pattern: PathLike): string {
|
||||||
|
if (existsSync(pattern)) {
|
||||||
|
return pattern.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = glob.sync(pattern.toString());
|
||||||
|
if (files?.[0]) {
|
||||||
|
return files[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundError(pattern.toString());
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { statSync } from "node:fs";
|
import { statSync } from "node:fs";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
|
import { zipContent } from "../../../utils/zip-utils";
|
||||||
import {
|
import {
|
||||||
FileInfo,
|
FileInfo,
|
||||||
fileEquals,
|
fileEquals,
|
||||||
|
@ -7,15 +8,18 @@ import {
|
||||||
findFilesSync,
|
findFilesSync,
|
||||||
readAllText,
|
readAllText,
|
||||||
readAllTextSync,
|
readAllTextSync,
|
||||||
|
readAllZippedText,
|
||||||
readFile,
|
readFile,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
|
readZippedFile,
|
||||||
} from "@/utils/io/file-info";
|
} from "@/utils/io/file-info";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
mockFs({
|
mockFs({
|
||||||
"path/to": {
|
"path/to": {
|
||||||
"test.txt": "test",
|
"test.txt": "test",
|
||||||
"test.json": JSON.stringify({ foo: 42 }),
|
"test.json": JSON.stringify({ foo: 42 }),
|
||||||
|
"test.zip": await zipContent("test", "test.txt"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -281,3 +285,36 @@ describe("readAllTextSync", () => {
|
||||||
expect(() => readAllTextSync("path/from/*.txt")).toThrow(/path\/from\/\*\.txt/);
|
expect(() => readAllTextSync("path/from/*.txt")).toThrow(/path\/from\/\*\.txt/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("readZippedFile", () => {
|
||||||
|
test("reads the contents of the first matching file", async () => {
|
||||||
|
const content = await readZippedFile("path/to/*.zip", "test.txt");
|
||||||
|
|
||||||
|
expect(Buffer.isBuffer(content)).toBe(true);
|
||||||
|
expect(content.toString()).toEqual("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws if no files were found", async () => {
|
||||||
|
await expect(readZippedFile("path/from/*.zip", "")).rejects.toThrow(/path\/from\/\*\.zip/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws if the entry does not exist within the zip", async () => {
|
||||||
|
await expect(readZippedFile("path/to/test.zip", "not-test.txt")).rejects.toThrow(/Entry not found/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("readAllZippedText", () => {
|
||||||
|
test("reads the contents of the first matching file", async () => {
|
||||||
|
const content = await readAllZippedText("path/to/*.zip", "test.txt");
|
||||||
|
|
||||||
|
expect(content).toEqual("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws if no files were found", async () => {
|
||||||
|
await expect(readAllZippedText("path/from/*.zip", "")).rejects.toThrow(/path\/from\/\*\.zip/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws if the entry does not exist within the zip", async () => {
|
||||||
|
await expect(readAllZippedText("path/to/test.zip", "not-test.txt")).rejects.toThrow(/Entry not found/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue