Publish to GitHub Pages
Upload an HTML file to a GitHub repository for GitHub Pages hosting
Source Code
import fs from "fs";
const [projectName, htmlPath, commitMessage = "Deploy web app", trackingPath] =
process.argv.slice(2);
// Validate inputs
if (!projectName) {
console.error("Error: projectName is required");
process.exit(1);
}
if (!htmlPath) {
console.error("Error: htmlPath is required");
process.exit(1);
}
if (!fs.existsSync(htmlPath)) {
console.error(`Error: File not found: ${htmlPath}`);
process.exit(1);
}
// Sanitize project name
const sanitizedName = projectName
.toLowerCase()
.replace(/[^a-z0-9-]/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
if (!sanitizedName) {
console.error("Error: Invalid project name after sanitization");
process.exit(1);
}
// Read the HTML file
const htmlContent = fs.readFileSync(htmlPath, "utf-8");
const contentBase64 = Buffer.from(htmlContent).toString("base64");
console.log(`Publishing "${sanitizedName}" to GitHub Pages...`);
console.log(` Source: ${htmlPath}`);
console.log(` Size: ${htmlContent.length} bytes`);
try {
// Step 1: Get authenticated user
const userResponse = await fetch("https://api.github.com/user", {
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
Accept: "application/vnd.github.v3+json",
"User-Agent": "Sauna-Agent",
},
});
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 repoName = sanitizedName;
const repoCheckResponse = await fetch(
`https://api.github.com/repos/${username}/${repoName}`,
{
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
Accept: "application/vnd.github.v3+json",
"User-Agent": "Sauna-Agent",
},
}
);
let repoExists = repoCheckResponse.ok;
// Step 3: Create repo if it doesn't exist
if (!repoExists) {
console.log(` Repository ${repoName} does not exist. Creating...`);
const createRepoResponse = await fetch(
"https://api.github.com/user/repos",
{
method: "POST",
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
Accept: "application/vnd.github.v3+json",
"Content-Type": "application/json",
"User-Agent": "Sauna-Agent",
},
body: JSON.stringify({
name: repoName,
description: `Web app: ${projectName}`,
homepage: `https://${username}.github.io/${repoName}`,
private: false,
auto_init: true,
has_pages: 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");
// Wait for repo initialization
await new Promise((resolve) => setTimeout(resolve, 2000));
} else {
console.log(` Repository ${repoName} exists`);
}
// Step 4: Check for existing index.html to get SHA
const getFileResponse = await fetch(
`https://api.github.com/repos/${username}/${repoName}/contents/index.html`,
{
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
Accept: "application/vnd.github.v3+json",
"User-Agent": "Sauna-Agent",
},
}
);
let sha = null;
if (getFileResponse.ok) {
const fileData = await getFileResponse.json();
sha = fileData.sha;
console.log(" Updating existing index.html");
} else {
console.log(" Creating new index.html");
}
// Step 5: Upload/update index.html
const uploadBody = {
message: commitMessage,
content: contentBase64,
};
if (sha) {
uploadBody.sha = sha;
}
const uploadResponse = await fetch(
`https://api.github.com/repos/${username}/${repoName}/contents/index.html`,
{
method: "PUT",
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
Accept: "application/vnd.github.v3+json",
"Content-Type": "application/json",
"User-Agent": "Sauna-Agent",
},
body: JSON.stringify(uploadBody),
}
);
if (!uploadResponse.ok) {
const error = await uploadResponse.text();
console.error("Failed to upload file");
console.error(error);
throw new Error("File upload failed");
}
const result = await uploadResponse.json();
// Step 6: Enable GitHub Pages if new repo
if (!repoExists) {
console.log(" Enabling GitHub Pages...");
// Try to enable Pages - may fail if already enabled or needs time
try {
await fetch(
`https://api.github.com/repos/${username}/${repoName}/pages`,
{
method: "POST",
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
Accept: "application/vnd.github.v3+json",
"Content-Type": "application/json",
"User-Agent": "Sauna-Agent",
},
body: JSON.stringify({
source: {
branch: "main",
path: "/",
},
}),
}
);
} catch {
// Pages may already be enabled or need manual setup
console.log(" Note: GitHub Pages may need manual activation");
}
}
// Output results
const publishedUrl = `https://${username}.github.io/${repoName}`;
const repoUrl = `https://github.com/${username}/${repoName}`;
console.log("\nā
Published successfully!");
console.log(`\nš Live URL: ${publishedUrl}`);
console.log(`š Repository: ${repoUrl}`);
console.log(`š Commit: ${result.commit.sha.substring(0, 7)}`);
console.log(
"\nNote: GitHub Pages may take 1-2 minutes to deploy after the first push."
);
// Output structured result for downstream use
const output = {
success: true,
url: publishedUrl,
repository: repoUrl,
project: sanitizedName,
username: username,
commit: result.commit.sha,
timestamp: new Date().toISOString(),
};
console.log("\n--- RESULT ---");
console.log(JSON.stringify(output, null, 2));
// Step 7: Update tracking file if provided
if (trackingPath) {
let sites = [];
if (fs.existsSync(trackingPath)) {
sites = JSON.parse(fs.readFileSync(trackingPath, "utf-8"));
}
const existing = sites.findIndex((s) => s.project === sanitizedName);
const entry = {
project: sanitizedName,
url: publishedUrl,
repository: repoUrl,
description: commitMessage.replace(/^(Deploy|Update):\s*/i, ""),
deployedAt:
existing === -1 ? output.timestamp : sites[existing].deployedAt,
lastUpdated: output.timestamp,
};
if (existing === -1) {
sites.push(entry);
} else {
sites[existing] = entry;
}
fs.writeFileSync(trackingPath, JSON.stringify(sites, null, 2));
console.log(`\nš Updated tracking: ${trackingPath}`);
}
} catch (error) {
console.error(`\nā Publishing failed: ${error.message}`);
process.exit(1);
}