Files
openwork/.opencode/skills/upload-photo/scripts/upload.mjs
T

121 lines
3.3 KiB
JavaScript

import { basename } from "node:path";
import { existsSync, readFileSync } from "node:fs";
const usage = "Usage: node .opencode/skills/upload-photo/scripts/upload.mjs <file.png> [more files...] [--prefix <path/prefix>] [--stable]";
function printUsage(message = null) {
if (message) console.error(message);
console.error(usage);
}
function parseArgs(argv) {
const files = [];
let prefix = `uploads/${new Date().toISOString().slice(0, 10)}`;
let stable = false;
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--stable") {
stable = true;
continue;
}
if (arg === "--prefix") {
const value = argv[index + 1];
if (!value || value.startsWith("--")) return null;
prefix = value;
index += 1;
continue;
}
if (arg.startsWith("--")) return null;
files.push(arg);
}
if (files.length === 0) return null;
return { files, prefix, stable };
}
function contentTypeFor(file) {
const lower = file.toLowerCase();
if (lower.endsWith(".png")) return "image/png";
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
if (lower.endsWith(".gif")) return "image/gif";
if (lower.endsWith(".webp")) return "image/webp";
if (lower.endsWith(".svg")) return "image/svg+xml";
if (lower.endsWith(".avif")) return "image/avif";
return "application/octet-stream";
}
function encodePathname(pathname) {
return pathname.split("/").map((segment) => encodeURIComponent(segment)).join("/");
}
function buildPathname(prefix, file) {
const cleanPrefix = prefix.replace(/^\/+|\/+$/g, "");
return encodePathname([cleanPrefix, basename(file)].filter(Boolean).join("/"));
}
async function uploadFile({ file, prefix, stable, token }) {
if (!existsSync(file)) {
console.error(`File does not exist: ${file}`);
return 1;
}
const pathname = buildPathname(prefix, file);
const headers = {
authorization: `Bearer ${token}`,
"x-content-type": contentTypeFor(file),
};
if (stable) headers["x-add-random-suffix"] = "0";
const response = await fetch(`https://blob.vercel-storage.com/${pathname}`, {
method: "PUT",
headers,
body: readFileSync(file),
});
if (!response.ok) {
const detail = (await response.text()).slice(0, 300);
console.error(`Upload failed (${response.status}) for ${file}: ${detail}`);
return 1;
}
let payload;
try {
payload = await response.json();
} catch {
console.error(`Upload failed for ${file}: response was not JSON`);
return 1;
}
if (!payload || typeof payload.url !== "string" || payload.url.length === 0) {
console.error(`Upload failed for ${file}: response did not include url`);
return 1;
}
console.log(payload.url);
return 0;
}
async function main() {
const args = parseArgs(process.argv.slice(2));
if (!args) {
printUsage();
return 1;
}
const token = process.env.BLOB_READ_WRITE_TOKEN;
if (!token) {
console.error("BLOB_READ_WRITE_TOKEN is not set — fetch it with the get-env-var skill: export BLOB_READ_WRITE_TOKEN=\"$(infisical secrets get BLOB_READ_WRITE_TOKEN --plain --silent)\"");
return 1;
}
for (const file of args.files) {
const status = await uploadFile({ file, prefix: args.prefix, stable: args.stable, token });
if (status !== 0) return status;
}
return 0;
}
process.exitCode = await main();