0.1.6Updated a month ago
import { BlobWriter, Uint8ArrayReader, Uint8ArrayWriter, ZipWriter, ZipReader, BlobReader } from "https://deno.land/x/zipjs@v2.7.57/index.js";
import { existsSync } from "jsr:@std/fs";
import { join, relative } from "jsr:@std/path";

interface ZipOptions {
  destination_file_path?: string
}

export interface I_File {
  actual_path: string
  path_in_zip: string
}

export class Zip {
  private files: I_File[]
  private destination_file_path?: string
  private _blob!: Blob;
  private _length: number = 0;

  private constructor(files: I_File[], { destination_file_path }: ZipOptions = {}) {
    this.files = files;
    this.destination_file_path = destination_file_path;
  }

  private async getZipFileBlob() {
    const zipWriter = new ZipWriter(new BlobWriter('application/zip'));

    await Promise.all(this.files.map(file => {
      zipWriter.add(file.path_in_zip, new Uint8ArrayReader(Deno.readFileSync(file.actual_path)));
    }))

    return zipWriter.close();
  }

  async process() {
    this._blob = await this.getZipFileBlob();
    return this;
  }

  get length() {
    return this.blob?.size ?? 0;
  }

  get blob() {
    return this._blob;
  }

  async save() {
    if(!this.destination_file_path) throw new Error('No destination file path provided!');

    Deno.writeFileSync(this.destination_file_path, await this.blob.bytes());
  }

  get stream() {
    return this.blob.stream();
  }

  static Process(files: I_File[], options?: ZipOptions) {
    return new Zip(files, options).process();
  }

  static async Unzip(body: ReadableStream<Uint8Array<ArrayBufferLike>> | Blob, root_path: string) {
    const path = this.EnsurePathExists(root_path);

    let reader;
    if(body instanceof Blob) {
      reader = new ZipReader(new BlobReader(body));
    } else {
      reader = new ZipReader(body);
    }

    for(const entry of (await reader.getEntries())) {
      const entry_path_depths = entry.filename.split(/[\\\/]+/g);
      const entry_file_name = entry_path_depths.pop()!;

      const entry_path_full = join(path, entry_path_depths.join('/'));

      const entry_dir = this.EnsurePathExists(entry_path_full, path);

      const data = await entry.getData?.(new Uint8ArrayWriter());

      if(data) Deno.writeFileSync(join(entry_dir, entry_file_name), data);
    };

    return true;
  }

  private static EnsurePathExists(target_path: string, start_path?: string) {
    start_path ||= Deno.cwd();
    
    const directory_depths = relative(start_path, target_path).split(/[\\\/]+/g);
    
    let current_path = start_path;

    while(directory_depths.length) {
      const next_dir = join(current_path, directory_depths.shift()!);
      
      if(!existsSync(next_dir, { isDirectory: true })) {
        Deno.mkdirSync(next_dir);
      }
      
      current_path = next_dir;
    }

    return current_path;
  }
}