Files
clawhub/src/lib/uploadUtils.ts
T
Patrick Erichsen 3097319ef6 fix: publish complete skill artifacts (#3196)
* fix: preserve complete skill artifacts

* test: align artifact metadata expectations

* fix: harden complete skill artifact handling

* fix: close complete artifact review gaps

* fix: preserve legacy skill file metadata hints

* fix: close artifact presentation review gaps

* fix(cli): preserve legacy skill file collector export

* refactor: centralize artifact upload helpers

* fix: preserve artifact scan and publish bounds

* fix: scan complete published text artifacts

* fix: harden artifact download presentation

* test: avoid secret-like fixture text

* refactor: preview artifacts by content

* chore(deps): patch transitive audit advisories
2026-07-20 15:54:47 -07:00

68 lines
2.4 KiB
TypeScript

import { normalizeContentType } from "clawhub-schema/textFiles";
import { getUserFacingConvexError } from "./convexError";
export async function uploadFile(uploadUrl: string, file: File) {
const contentType = normalizeContentType(file.type) || file.type || "application/octet-stream";
const response = await fetch(uploadUrl, {
method: "POST",
headers: { "Content-Type": contentType },
body: file,
});
if (!response.ok) {
throw new Error(`Upload failed: ${await response.text()}`);
}
const payload = (await response.json()) as { storageId: string };
return payload.storageId;
}
export async function hashFile(file: File) {
const buffer =
typeof file.arrayBuffer === "function"
? await file.arrayBuffer()
: await new Response(file).arrayBuffer();
const hash = await crypto.subtle.digest("SHA-256", new Uint8Array(buffer));
const bytes = new Uint8Array(hash);
return Array.from(bytes)
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
}
export function formatBytes(bytes: number) {
if (!Number.isFinite(bytes)) return "0 B";
const units = ["B", "KB", "MB", "GB"];
let size = bytes;
let unit = 0;
while (size >= 1024 && unit < units.length - 1) {
size /= 1024;
unit += 1;
}
return `${size.toFixed(size < 10 && unit > 0 ? 1 : 0)} ${units[unit]}`;
}
export function formatPublishError(error: unknown) {
return getUserFacingConvexError(error, "Publish failed. Please try again.");
}
export async function readText(blob: Blob) {
if (typeof (blob as Blob & { text?: unknown }).text === "function") {
return (blob as Blob & { text: () => Promise<string> }).text();
}
if (typeof (blob as Blob & { arrayBuffer?: unknown }).arrayBuffer === "function") {
const buffer = await (blob as Blob & { arrayBuffer: () => Promise<ArrayBuffer> }).arrayBuffer();
return new TextDecoder().decode(new Uint8Array(buffer));
}
if (typeof FileReader !== "undefined" && blob instanceof Blob) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener("error", () => {
reject(reader.error ?? new Error("Could not read blob."));
});
reader.addEventListener("load", () => {
resolve(typeof reader.result === "string" ? reader.result : "");
});
reader.readAsText(blob);
});
}
return new Response(blob as BodyInit).text();
}