All files / src/utils tar.ts

92.5% Statements 37/40
50% Branches 3/6
90.9% Functions 10/11
92.3% Lines 36/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 9133x   33x 6x 6x     10x     10x 10x 10x       10x 10x         10x   10x         10x   10x 10x         6x 6x 6x 6x               10x     10x     10x     10x 10x     10x     10x 10x     10x     10x     10x     10x 10x 5120x     10x   10x       6x    
import { PassThrough } from 'stream'
 
export class TarStream {
  output = new PassThrough()
  currentFileSize = 0
 
  beginFile(path: string, size: number) {
    Iif (path.length > 100) {
      throw new Error(`File name too long: ${path}`)
    }
    const header = createHeader(path, size)
    this.output.write(header)
    this.currentFileSize = 0
  }
 
  async appendFile(data: Uint8Array) {
    return new Promise<void>(resolve => {
      Iif (!this.output.write(data)) {
        this.output.once('drain', () => {
          resolve()
        })
      } else {
        resolve()
      }
      this.currentFileSize += data.length
    })
  }
 
  async endFile() {
    const padding = this.currentFileSize % 512 === 0 ? 0 : 512 - (this.currentFileSize % 512)
 
    if (padding > 0) {
      this.output.write(Buffer.alloc(padding, 0))
    }
  }
 
  async end() {
    return new Promise<void>(resolve => {
      this.output.write(createEndOfArchive())
      this.output.end(() => {
        resolve()
      })
    })
  }
}
 
function createHeader(path: string, size: number): Uint8Array {
  // Initialize header with zeros
  const header = Buffer.alloc(512, 0)
 
  // File name, truncated to 100 characters if necessary
  header.write(path.slice(0, 100).padEnd(100, '\0'), 0, 100)
 
  // File mode (octal) and null-terminated
  header.write('0000777\0', 100, 8)
 
  // UID and GID (octal) and null-terminated
  header.write('0001750\0', 108, 8) // UID
  header.write('0001750\0', 116, 8) // GID
 
  // File size in octal (11 chars) and null-terminated
  header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12)
 
  // Modification time in octal and null-terminated
  const modTime = Math.floor(new Date().getTime() / 1000)
  header.write(modTime.toString(8).padStart(11, '0') + '\0', 136, 12)
 
  // Checksum placeholder (8 spaces)
  header.write('        ', 148, 8)
 
  // Typeflag (normal file)
  header.write('0', 156, 1)
 
  // USTAR magic and version
  header.write('ustar\0\0', 257, 8)
 
  // Calculate checksum
  let checksum = 0
  for (let i = 0; i < 512; i++) {
    checksum += header[i]
  }
 
  header.write(checksum.toString(8).padStart(6, '0') + '\0 ', 148, 8)
 
  return header
}
 
function createEndOfArchive(): Uint8Array {
  return Buffer.alloc(1024, 0)
}