import { useRef, useState } from "react"; import { downloadBlob } from "../../archive/downloads"; import { errorMessage } from "../../archive/errors"; import { ARCHIVE_LIMITS, formatBytes } from "../../archive/limits"; import { createArchive } from "../../archive/service"; import type { CreateFormat } from "../../archive/types"; import { StatusMessage } from "./StatusMessage"; export function CreateWorkspace() { const [files, setFiles] = useState([]); const [format, setFormat] = useState("zip"); const [name, setName] = useState("archive"); const [encryption, setEncryption] = useState< "none" | "aes-256" | "zipcrypto" >("none"); const [password, setPassword] = useState(""); const [busy, setBusy] = useState(false); const [status, setStatus] = useState(); const [error, setError] = useState(); const controller = useRef(undefined); const total = files.reduce((sum, file) => sum + file.size, 0); async function build() { controller.current?.abort(); const next = new AbortController(); controller.current = next; setBusy(true); setError(undefined); try { const blob = await createArchive( files, format, next.signal, (progress) => setStatus( `${progress.stage}: ${progress.completed} / ${progress.total}`, ), format === "zip" && encryption !== "none" ? { password, method: encryption } : undefined, ); const extension = format === "tar.gz" ? "tar.gz" : format; downloadBlob(blob, `${name.trim() || "archive"}.${extension}`); setStatus( encryption === "none" || format !== "zip" ? `Created deterministic ${format.toLocaleUpperCase("en-US")} locally.` : `Created encrypted ${format.toLocaleUpperCase("en-US")} locally; random salt makes encrypted bytes intentionally non-deterministic.`, ); if (encryption !== "none") setPassword(""); } catch (reason) { if (!next.signal.aborted) setError(errorMessage(reason)); } finally { if (controller.current === next) setBusy(false); } } return (

Reproducible output

Create an archive

Files are sorted by normalized path. Timestamps, owner IDs and permissions are normalized; ZIP compression uses a fixed implementation and level.

{format === "zip" ? (
{encryption !== "none" ? ( ) : null}
) : null} {format === "zip" && encryption === "zipcrypto" ? (

ZipCrypto is offered only for compatibility and is not secure against modern password attacks. Prefer AES-256.

) : null}
{busy ? ( ) : null}
); }