import { debug7z, exec, exists, getPath7za, log, statOrNull, unlinkIfExists } from "builder-util"
import { move } from "fs-extra"
import * as path from "path"
import { create, CreateOptions, FileOptions } from "tar"
import { TmpDir } from "temp-file"
import { CompressionLevel } from "../core"
import { getLinuxToolsPath } from "./tools"
export async function tar(compression: CompressionLevel | any, format: string, outFile: string, dirToArchive: string, isMacApp: boolean, tempDirManager: TmpDir): Promise<void> {
const tarFile = await tempDirManager.getTempFile({ suffix: ".tar" })
const tarArgs: CreateOptions & FileOptions = {
file: tarFile,
portable: true,
cwd: dirToArchive,
prefix: path.basename(outFile, `.${format}`),
}
let tarDirectory = "."
if (isMacApp) {
delete tarArgs.prefix
tarArgs.cwd = path.dirname(dirToArchive)
tarDirectory = path.basename(dirToArchive)
}
await Promise.all([
create(tarArgs, [tarDirectory]),
unlinkIfExists(outFile),
])
if (format === "tar.lz") {
let lzipPath = "lzip"
if (process.platform === "darwin") {
lzipPath = path.join(await getLinuxToolsPath(), "bin", lzipPath)
}
await exec(lzipPath, [compression === "store" ? "-1" : "-9", "--keep" , tarFile])
await move(`${tarFile}.lz`, outFile)
return
}
const args = compute7zCompressArgs(format === "tar.xz" ? "xz" : format === "tar.bz2" ? "bzip2" : "gzip", {
isRegularFile: true,
method: "DEFAULT",
compression,
})
args.push(outFile, tarFile)
await exec(
await getPath7za(),
args,
{
cwd: path.dirname(dirToArchive),
},
debug7z.enabled
)
}
export interface ArchiveOptions {
compression?: CompressionLevel | null
* @default false
*/
withoutDir?: boolean
* @default true
*/
solid?: boolean
* @default true
*/
isArchiveHeaderCompressed?: boolean
dictSize?: number
excluded?: Array<string> | null
method?: "Copy" | "LZMA" | "Deflate" | "DEFAULT"
isRegularFile?: boolean
}
export function compute7zCompressArgs(format: string, options: ArchiveOptions = {}) {
let storeOnly = options.compression === "store"
const args = debug7zArgs("a")
let isLevelSet = false
if (process.env.ELECTRON_BUILDER_COMPRESSION_LEVEL != null) {
storeOnly = false
args.push(`-mx=${process.env.ELECTRON_BUILDER_COMPRESSION_LEVEL}`)
isLevelSet = true
}
const isZip = format === "zip"
if (!storeOnly) {
if (isZip && options.compression === "maximum") {
args.push("-mfb=258", "-mpass=15")
}
if (!isLevelSet) {
args.push("-mx=" + (!isZip || options.compression === "maximum" ? "9" : "7"))
}
}
if (options.dictSize != null) {
args.push(`-md=${options.dictSize}m`)
}
if (!options.isRegularFile) {
args.push("-mtc=off")
}
if (format === "7z" || format.endsWith(".7z")) {
if (options.solid === false) {
args.push("-ms=off")
}
if (options.isArchiveHeaderCompressed === false) {
args.push("-mhc=off")
}
if (process.env.ELECTRON_BUILDER_7Z_FILTER) {
args.push(`-mf=${process.env.ELECTRON_BUILDER_7Z_FILTER}`)
}
args.push("-mtm=off", "-mta=off")
}
if (options.method != null) {
if (options.method !== "DEFAULT") {
args.push(`-mm=${options.method}`)
}
} else if (isZip || storeOnly) {
args.push(`-mm=${storeOnly ? "Copy" : "Deflate"}`)
}
if (isZip) {
args.push("-mcu")
}
return args
}
export function computeZipCompressArgs(options: ArchiveOptions = {}) {
let storeOnly = options.compression === "store"
const args = ["-q", "-r", "-y"]
if (debug7z.enabled) {
args.push("-v")
}
if (process.env.ELECTRON_BUILDER_COMPRESSION_LEVEL != null) {
storeOnly = false
args.push(`-${process.env.ELECTRON_BUILDER_COMPRESSION_LEVEL}`)
} else if (!storeOnly) {
args.push("-" + (options.compression === "maximum" ? "9" : "7"))
}
if (options.dictSize != null) {
log.warn({ distSize: options.dictSize }, `ignoring unsupported option`)
}
if (!options.isRegularFile) {
args.push("-X")
}
if (options.method != null) {
if (options.method !== "DEFAULT") {
log.warn({ method: options.method }, `ignoring unsupported option`)
}
} else {
args.push("-Z", storeOnly ? "store" : "deflate")
}
return args
}
export async function archive(format: string, outFile: string, dirToArchive: string, options: ArchiveOptions = {}): Promise<string> {
const outFileStat = await statOrNull(outFile)
const dirStat = await statOrNull(dirToArchive)
if (outFileStat && dirStat && outFileStat.mtime > dirStat.mtime) {
log.info({ reason: "Archive file is up to date", outFile }, `skipped archiving`)
return outFile
}
let use7z = true
if (process.platform === "darwin" && format === "zip" && dirToArchive.normalize("NFC") !== dirToArchive) {
log.warn({ reason: "7z doesn't support NFD-normalized filenames" }, `using zip`)
use7z = false
}
const args = use7z ? compute7zCompressArgs(format, options) : computeZipCompressArgs(options)
await unlinkIfExists(outFile)
args.push(outFile, options.withoutDir ? "." : path.basename(dirToArchive))
if (options.excluded != null) {
for (const mask of options.excluded) {
args.push(use7z ? `-xr!${mask}` : `-x${mask}`)
}
}
try {
const binary = use7z ? await getPath7za() : "zip"
await exec(
binary,
args,
{
cwd: options.withoutDir ? dirToArchive : path.dirname(dirToArchive),
},
debug7z.enabled
)
} catch (e: any) {
if (e.code === "ENOENT" && !(await exists(dirToArchive))) {
throw new Error(`Cannot create archive: "${dirToArchive}" doesn't exist`)
} else {
throw e
}
}
return outFile
}
function debug7zArgs(command: "a" | "x"): Array<string> {
const args = [command, "-bd"]
if (debug7z.enabled) {
args.push("-bb")
}
return args
}