Upload Archive to GitHub Releases
Upload an existing archive file to GitHub Releases for download
Source Code
import fs from "fs";
const [repoName = "sauna-exports", archivePath] = process.argv.slice(2);
// Validate inputs
if (!archivePath) {
console.error("Error: archivePath is required");
process.exit(1);
}
if (!fs.existsSync(archivePath)) {
console.error(`Error: Archive not found: ${archivePath}`);
process.exit(1);
}
// Sanitize repo name
const sanitizedName = repoName
.toLowerCase()
.replace(/[^a-z0-9-]/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
if (!sanitizedName) {
console.error("Error: Invalid repository name after sanitization");
process.exit(1);
}
// Get archive info
const archiveStats = fs.statSync(archivePath);
const archiveSizeMB = (archiveStats.size / (1024 * 1024)).toFixed(1);
const archiveFilename = archivePath.split("/").pop();
// Generate release tag with timestamp
const now = new Date();
const dateStr = now.toISOString().split("T")[0];
const timeStr = now.toISOString().split("T")[1].split(".")[0].replace(/:/g, "");
const releaseTag = `export-${dateStr}-${timeStr}`;
console.log(`Uploading archive to GitHub Releases...`);
console.log(` Archive: ${archivePath}`);
console.log(` Size: ${archiveSizeMB} MB`);
console.log(` Repository: ${sanitizedName}`);
// GitHub Releases API supports up to 2GB
if (archiveStats.size > 2 * 1024 * 1024 * 1024) {
console.error(`Error: Archive exceeds GitHub's 2GB release asset limit`);
process.exit(1);
}
// GitHub API helper
async function githubFetch(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
Accept: "application/vnd.github.v3+json",
"User-Agent": "Sauna-Agent",
...options.headers,
},
});
return response;
}
try {
// Step 1: Get authenticated user
console.log("\nConnecting to GitHub...");
const userResponse = await githubFetch("https://api.github.com/user");
if (!userResponse.ok) {
const error = await userResponse.text();
console.error("Failed to authenticate with GitHub");
console.error(error);
throw new Error("GitHub authentication failed");
}
const user = await userResponse.json();
const username = user.login;
console.log(` Authenticated as: ${username}`);
// Step 2: Check if repo exists
const repoCheckResponse = await githubFetch(
`https://api.github.com/repos/${username}/${sanitizedName}`
);
let repoExists = repoCheckResponse.ok;
// Step 3: Create repo if it doesn't exist
if (!repoExists) {
console.log(` Repository ${sanitizedName} does not exist. Creating...`);
const createRepoResponse = await githubFetch(
"https://api.github.com/user/repos",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: sanitizedName,
description: "Workspace exports from Sauna",
private: true,
auto_init: true,
}),
}
);
if (!createRepoResponse.ok) {
const error = await createRepoResponse.text();
console.error("Failed to create repository");
console.error(error);
throw new Error("Repository creation failed");
}
console.log(" Repository created successfully (private)");
await new Promise((resolve) => setTimeout(resolve, 2000));
} else {
console.log(` Repository ${sanitizedName} exists`);
}
// Step 4: Create a release
console.log(`\nCreating release ${releaseTag}...`);
const createReleaseResponse = await githubFetch(
`https://api.github.com/repos/${username}/${sanitizedName}/releases`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
tag_name: releaseTag,
name: `Documents Export - ${dateStr}`,
body: `Workspace documents export created on ${now.toISOString()}`,
draft: false,
prerelease: false,
}),
}
);
if (!createReleaseResponse.ok) {
const error = await createReleaseResponse.text();
console.error("Failed to create release");
console.error(error);
throw new Error("Release creation failed");
}
const release = await createReleaseResponse.json();
console.log(` Release created: ${release.html_url}`);
// Step 5: Upload archive as release asset
console.log(`\nUploading archive (${archiveSizeMB} MB)...`);
const archiveBuffer = fs.readFileSync(archivePath);
const uploadUrl = release.upload_url.replace(
"{?name,label}",
`?name=${encodeURIComponent(archiveFilename)}`
);
const uploadResponse = await fetch(uploadUrl, {
method: "POST",
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
Accept: "application/vnd.github.v3+json",
"Content-Type": "application/gzip",
"Content-Length": archiveBuffer.length.toString(),
"User-Agent": "Sauna-Agent",
},
body: archiveBuffer,
});
if (!uploadResponse.ok) {
const error = await uploadResponse.text();
console.error("Failed to upload archive");
console.error(error);
throw new Error("Archive upload failed");
}
const asset = await uploadResponse.json();
console.log(` Upload complete!`);
// Output results
const repoUrl = `https://github.com/${username}/${sanitizedName}`;
const downloadUrl = asset.browser_download_url;
console.log("\nโ
Export successful!");
console.log(`\n๐ฆ DOWNLOAD LINK: ${downloadUrl}`);
console.log(`๐ Repository: ${repoUrl}`);
console.log(`๐ท๏ธ Release: ${release.html_url}`);
console.log(`๐ Archive size: ${archiveSizeMB} MB`);
const output = {
success: true,
downloadUrl: downloadUrl,
repository: repoUrl,
releaseUrl: release.html_url,
releaseTag: releaseTag,
repoName: sanitizedName,
username: username,
archiveFilename: archiveFilename,
archiveSize: archiveStats.size,
archiveSizeMB: parseFloat(archiveSizeMB),
timestamp: new Date().toISOString(),
isPrivate: true,
};
console.log("\n--- RESULT ---");
console.log(JSON.stringify(output, null, 2));
} catch (error) {
console.error(`\nโ Export failed: ${error.message}`);
process.exit(1);
}