209 lines
6.5 KiB
TypeScript
209 lines
6.5 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import type { IArchiveEntry, TCompressionLevel } from './interfaces.js';
|
|
import { GzipTools } from './classes.gziptools.js';
|
|
|
|
/**
|
|
* TAR archive creation and extraction utilities
|
|
*/
|
|
export class TarTools {
|
|
/**
|
|
* Add a file to a TAR pack stream
|
|
*/
|
|
public async addFileToPack(
|
|
pack: plugins.tarStream.Pack,
|
|
optionsArg: {
|
|
fileName?: string;
|
|
content?:
|
|
| string
|
|
| Buffer
|
|
| plugins.stream.Readable
|
|
| plugins.smartfile.SmartFile
|
|
| plugins.smartfile.StreamFile;
|
|
byteLength?: number;
|
|
filePath?: string;
|
|
}
|
|
): Promise<void> {
|
|
return new Promise<void>(async (resolve, reject) => {
|
|
let fileName: string | null = null;
|
|
|
|
if (optionsArg.fileName) {
|
|
fileName = optionsArg.fileName;
|
|
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
|
|
fileName = optionsArg.content.relative;
|
|
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
|
|
fileName = optionsArg.content.relativeFilePath;
|
|
} else if (optionsArg.filePath) {
|
|
fileName = optionsArg.filePath;
|
|
}
|
|
|
|
if (!fileName) {
|
|
reject(new Error('No filename specified for TAR entry'));
|
|
return;
|
|
}
|
|
|
|
// Determine content byte length
|
|
let contentByteLength: number | undefined;
|
|
if (optionsArg.byteLength) {
|
|
contentByteLength = optionsArg.byteLength;
|
|
} else if (typeof optionsArg.content === 'string') {
|
|
contentByteLength = Buffer.byteLength(optionsArg.content, 'utf8');
|
|
} else if (Buffer.isBuffer(optionsArg.content)) {
|
|
contentByteLength = optionsArg.content.length;
|
|
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
|
|
contentByteLength = await optionsArg.content.getSize();
|
|
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
|
|
contentByteLength = await optionsArg.content.getSize();
|
|
} else if (optionsArg.filePath) {
|
|
const fileStat = await plugins.fsPromises.stat(optionsArg.filePath);
|
|
contentByteLength = fileStat.size;
|
|
}
|
|
|
|
// Convert all content types to Readable stream
|
|
let content: plugins.stream.Readable;
|
|
if (Buffer.isBuffer(optionsArg.content)) {
|
|
content = plugins.stream.Readable.from(optionsArg.content);
|
|
} else if (typeof optionsArg.content === 'string') {
|
|
content = plugins.stream.Readable.from(Buffer.from(optionsArg.content));
|
|
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
|
|
content = plugins.stream.Readable.from(optionsArg.content.contents);
|
|
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
|
|
content = await optionsArg.content.createReadStream();
|
|
} else if (optionsArg.content instanceof plugins.stream.Readable) {
|
|
content = optionsArg.content;
|
|
} else if (optionsArg.filePath) {
|
|
content = plugins.fs.createReadStream(optionsArg.filePath);
|
|
} else {
|
|
reject(new Error('No content or filePath specified for TAR entry'));
|
|
return;
|
|
}
|
|
|
|
const entry = pack.entry(
|
|
{
|
|
name: fileName,
|
|
...(contentByteLength !== undefined ? { size: contentByteLength } : {}),
|
|
},
|
|
(err: Error | null) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
}
|
|
);
|
|
|
|
content.pipe(entry);
|
|
// Note: resolve() is called in the callback above when pipe completes
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pack a directory into a TAR stream
|
|
*/
|
|
public async packDirectory(directoryPath: string): Promise<plugins.tarStream.Pack> {
|
|
const fileTree = await plugins.listFileTree(directoryPath, '**/*');
|
|
const pack = await this.getPackStream();
|
|
|
|
for (const filePath of fileTree) {
|
|
const absolutePath = plugins.path.join(directoryPath, filePath);
|
|
const fileStat = await plugins.fsPromises.stat(absolutePath);
|
|
await this.addFileToPack(pack, {
|
|
byteLength: fileStat.size,
|
|
filePath: absolutePath,
|
|
fileName: filePath,
|
|
content: plugins.fs.createReadStream(absolutePath),
|
|
});
|
|
}
|
|
|
|
return pack;
|
|
}
|
|
|
|
/**
|
|
* Get a new TAR pack stream
|
|
*/
|
|
public async getPackStream(): Promise<plugins.tarStream.Pack> {
|
|
return plugins.tarStream.pack();
|
|
}
|
|
|
|
/**
|
|
* Get a TAR extraction stream
|
|
*/
|
|
public getDecompressionStream(): plugins.tarStream.Extract {
|
|
return plugins.tarStream.extract();
|
|
}
|
|
|
|
/**
|
|
* Pack files into a TAR buffer
|
|
*/
|
|
public async packFiles(files: IArchiveEntry[]): Promise<Buffer> {
|
|
const pack = await this.getPackStream();
|
|
|
|
for (const file of files) {
|
|
await this.addFileToPack(pack, {
|
|
fileName: file.archivePath,
|
|
content: file.content as string | Buffer | plugins.stream.Readable | plugins.smartfile.SmartFile | plugins.smartfile.StreamFile,
|
|
byteLength: file.size,
|
|
});
|
|
}
|
|
|
|
pack.finalize();
|
|
|
|
const chunks: Buffer[] = [];
|
|
return new Promise((resolve, reject) => {
|
|
pack.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
pack.on('end', () => resolve(Buffer.concat(chunks)));
|
|
pack.on('error', reject);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pack a directory into a TAR.GZ buffer
|
|
*/
|
|
public async packDirectoryToTarGz(
|
|
directoryPath: string,
|
|
compressionLevel?: TCompressionLevel
|
|
): Promise<Buffer> {
|
|
const pack = await this.packDirectory(directoryPath);
|
|
pack.finalize();
|
|
|
|
const gzipTools = new GzipTools();
|
|
const gzipStream = gzipTools.getCompressionStream(compressionLevel);
|
|
|
|
const chunks: Buffer[] = [];
|
|
return new Promise((resolve, reject) => {
|
|
pack
|
|
.pipe(gzipStream)
|
|
.on('data', (chunk: Buffer) => chunks.push(chunk))
|
|
.on('end', () => resolve(Buffer.concat(chunks)))
|
|
.on('error', reject);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pack a directory into a TAR.GZ stream
|
|
*/
|
|
public async packDirectoryToTarGzStream(
|
|
directoryPath: string,
|
|
compressionLevel?: TCompressionLevel
|
|
): Promise<plugins.stream.Readable> {
|
|
const pack = await this.packDirectory(directoryPath);
|
|
pack.finalize();
|
|
|
|
const gzipTools = new GzipTools();
|
|
const gzipStream = gzipTools.getCompressionStream(compressionLevel);
|
|
|
|
return pack.pipe(gzipStream);
|
|
}
|
|
|
|
/**
|
|
* Pack files into a TAR.GZ buffer
|
|
*/
|
|
public async packFilesToTarGz(
|
|
files: IArchiveEntry[],
|
|
compressionLevel?: TCompressionLevel
|
|
): Promise<Buffer> {
|
|
const tarBuffer = await this.packFiles(files);
|
|
const gzipTools = new GzipTools();
|
|
return gzipTools.compress(tarBuffer, compressionLevel);
|
|
}
|
|
}
|