/** Minimal ustar writing shared by repository archives and action archives. */ export const TAR_BLOCK = 512; const TAR_NAME_BYTES = 100; const TAR_PREFIX_BYTES = 155; const encoder = new TextEncoder(); function octal(value: number, length: number): string { return value.toString(8).padStart(length - 1, "0") + "\0"; } function writeString(target: Uint8Array, offset: number, length: number, value: string): void { target.set(encoder.encode(value).subarray(0, length), offset); } /** A ustar header, or null when the path can't be represented. `mtimeSeconds` defaults to now. */ export function tarHeader( path: string, size: number, mode: number, type: "0" | "2" | "5", linkTarget = "", mtimeSeconds = Math.floor(Date.now() / 1000), ): Uint8Array | null { let name = path; let prefix = ""; if (encoder.encode(name).byteLength > TAR_NAME_BYTES) { const split = path.lastIndexOf("/", TAR_PREFIX_BYTES); if (split <= 0) return null; prefix = path.slice(0, split); name = path.slice(split + 1); if (encoder.encode(name).byteLength > TAR_NAME_BYTES || encoder.encode(prefix).byteLength > TAR_PREFIX_BYTES) return null; } const header = new Uint8Array(TAR_BLOCK); writeString(header, 0, 100, name); writeString(header, 100, 8, octal(mode, 8)); writeString(header, 108, 8, octal(0, 8)); writeString(header, 116, 8, octal(0, 8)); writeString(header, 124, 12, octal(size, 12)); writeString(header, 136, 12, octal(mtimeSeconds, 12)); writeString(header, 148, 8, " "); writeString(header, 156, 1, type); writeString(header, 157, 100, linkTarget); writeString(header, 257, 6, "ustar\0"); writeString(header, 263, 2, "00"); writeString(header, 345, 155, prefix); const checksum = header.reduce((sum, byte) => sum + byte, 0); writeString(header, 148, 8, `${checksum.toString(8).padStart(6, "0")}\0 `); return header; } export function tarPadding(size: number): number { return (TAR_BLOCK - (size % TAR_BLOCK)) % TAR_BLOCK; }