feat: update worker to use Gitea API for deployments and remove GitHub references
This commit is contained in:
171
worker.ts
171
worker.ts
@@ -1,11 +1,10 @@
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { Job, Worker } from "bullmq";
|
||||
import dotenv from "dotenv";
|
||||
import { redisConfig } from "./redis.js";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
console.log("👷 Deploy Worker Started...");
|
||||
console.log("👷 Gitea Deploy Worker Started...");
|
||||
|
||||
export interface DeployJobData {
|
||||
repoName: string;
|
||||
@@ -13,16 +12,42 @@ export interface DeployJobData {
|
||||
files: Record<string, string>;
|
||||
}
|
||||
|
||||
// Configuration
|
||||
const GITEA_API_URL = process.env.GITEA_API_URL || "https://git.mworld.cloud/api/v1";
|
||||
const GITEA_TOKEN = process.env.GITEA_TOKEN;
|
||||
const GITEA_USERNAME = process.env.GITEA_USERNAME;
|
||||
|
||||
if (!GITEA_TOKEN || !GITEA_USERNAME) {
|
||||
throw new Error("Missing GITEA_TOKEN or GITEA_USERNAME");
|
||||
}
|
||||
|
||||
// Helper to call Gitea API
|
||||
async function callGitea(method: string, path: string, body?: any) {
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `token ${GITEA_TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const response = await fetch(`${GITEA_API_URL}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
|
||||
if (!response.ok && response.status !== 404) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Gitea API Error ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
const worker = new Worker<DeployJobData>(
|
||||
"github-deployments",
|
||||
async (job: Job) => {
|
||||
const {
|
||||
repoName,
|
||||
chatId,
|
||||
files,
|
||||
} = job.data;
|
||||
const { repoName, chatId, files } = job.data;
|
||||
|
||||
// 1. Sanitize Repo Name (e.g. "Hono Vite Health Check" -> "hono-vite-health-check")
|
||||
// 1. Sanitize Repo Name
|
||||
const sanitizedRepoName = repoName
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
@@ -33,110 +58,70 @@ const worker = new Worker<DeployJobData>(
|
||||
`[Job ${job.id}] Processing deployment for ${sanitizedRepoName} (Chat: ${chatId})...`
|
||||
);
|
||||
|
||||
// 2. Auth
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const username = process.env.GITHUB_USERNAME;
|
||||
|
||||
if (!token || !username) {
|
||||
throw new Error("Missing GitHub Token or Username. Cannot deploy.");
|
||||
}
|
||||
|
||||
// Initialize GitHub Client
|
||||
const octokit = new Octokit({ auth: token });
|
||||
|
||||
try {
|
||||
await job.updateProgress(10);
|
||||
|
||||
// 3. Ensure Repo Exists or Create it
|
||||
let latestCommitSha: string;
|
||||
let repoDetails: any;
|
||||
|
||||
const checkRepo = await callGitea("GET", `/repos/${GITEA_USERNAME}/${sanitizedRepoName}`);
|
||||
|
||||
try {
|
||||
// Check if repo exists
|
||||
await octokit.repos.get({ owner: username, repo: sanitizedRepoName });
|
||||
|
||||
// If exists, get HEAD commit
|
||||
const ref = await octokit.git.getRef({
|
||||
owner: username,
|
||||
repo: sanitizedRepoName,
|
||||
ref: "heads/main",
|
||||
if (checkRepo.status === 404) {
|
||||
console.log(`[Job ${job.id}] Repo not found, creating new repo: ${sanitizedRepoName}`);
|
||||
|
||||
// Create Repo
|
||||
const createRes = await callGitea("POST", "/user/repos", {
|
||||
name: sanitizedRepoName,
|
||||
private: true,
|
||||
auto_init: true,
|
||||
description: `Generated by MWorld for chat ${chatId}`,
|
||||
});
|
||||
latestCommitSha = ref.data.object.sha;
|
||||
} catch (e: any) {
|
||||
if (e.status === 404) {
|
||||
console.log(
|
||||
`[Job ${job.id}] Repo not found, creating new repo: ${sanitizedRepoName}`
|
||||
);
|
||||
|
||||
// Create Repo
|
||||
await octokit.repos.createForAuthenticatedUser({
|
||||
name: sanitizedRepoName,
|
||||
private: true, // Set to true if you want private repos
|
||||
auto_init: true, // Creates an initial commit (README) so we have a HEAD
|
||||
description: `Generated by MWorld for chat ${chatId}`,
|
||||
});
|
||||
if (!createRes.ok) throw new Error("Failed to create repository");
|
||||
|
||||
repoDetails = await createRes.json();
|
||||
|
||||
// Wait a brief moment for GitHub propagation
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
|
||||
// Get the initial commit SHA
|
||||
const ref = await octokit.git.getRef({
|
||||
owner: username,
|
||||
repo: sanitizedRepoName,
|
||||
ref: "heads/main",
|
||||
});
|
||||
latestCommitSha = ref.data.object.sha;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
} else {
|
||||
repoDetails = await checkRepo.json();
|
||||
}
|
||||
|
||||
const defaultBranch = repoDetails.default_branch || "main"; //
|
||||
|
||||
await job.updateProgress(30);
|
||||
|
||||
// 4. Create Tree (Blobs)
|
||||
// The API expects an array of file objects.
|
||||
const treeData = Object.entries(files).map(([path, content]) => ({
|
||||
path, // e.g., "src/main.jsx" or "package.json"
|
||||
mode: "100644" as const,
|
||||
type: "blob" as const,
|
||||
content: content as string, // The actual string content from Redis
|
||||
// 3. Prepare Batch File Operations
|
||||
const fileOperations = Object.entries(files).map(([path, content]) => ({
|
||||
operation: "upload",
|
||||
path: path,
|
||||
content: Buffer.from(content as string).toString("base64"),
|
||||
}));
|
||||
|
||||
// Create a tree based on the latest commit
|
||||
const { data: tree } = await octokit.git.createTree({
|
||||
owner: username,
|
||||
repo: sanitizedRepoName,
|
||||
base_tree: latestCommitSha,
|
||||
tree: treeData,
|
||||
});
|
||||
|
||||
await job.updateProgress(60);
|
||||
|
||||
// 5. Create Commit
|
||||
const { data: commit } = await octokit.git.createCommit({
|
||||
owner: username,
|
||||
repo: sanitizedRepoName,
|
||||
message: `Deploy updates from MWorld ⚡️ (Chat ${chatId})`,
|
||||
tree: tree.sha,
|
||||
parents: [latestCommitSha],
|
||||
});
|
||||
// 4. Commit and Push (Atomic Batch Operation)
|
||||
const commitRes = await callGitea(
|
||||
"POST",
|
||||
`/repos/${GITEA_USERNAME}/${sanitizedRepoName}/contents`,
|
||||
{
|
||||
branch: defaultBranch,
|
||||
message: `Deploy updates from MWorld ⚡️ (Chat ${chatId})`,
|
||||
files: fileOperations,
|
||||
author: {
|
||||
name: "MWorld Bot",
|
||||
email: "bot@mworld.com",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await job.updateProgress(80);
|
||||
|
||||
// 6. Push (Update Reference)
|
||||
await octokit.git.updateRef({
|
||||
owner: username,
|
||||
repo: sanitizedRepoName,
|
||||
ref: "heads/main",
|
||||
sha: commit.sha,
|
||||
});
|
||||
if (!commitRes.ok) {
|
||||
throw new Error(`Failed to commit files: ${await commitRes.text()}`);
|
||||
}
|
||||
|
||||
await job.updateProgress(100);
|
||||
|
||||
const repoUrl = `https://github.com/${username}/${sanitizedRepoName}`;
|
||||
const repoUrl = `${GITEA_API_URL.replace("/api/v1", "")}/${GITEA_USERNAME}/${sanitizedRepoName}`;
|
||||
console.log(`[Job ${job.id}] Deployment Complete! 🚀 URL: ${repoUrl}`);
|
||||
|
||||
// TODO: USE SUPABASE TO UPDATE THE REPO URL
|
||||
|
||||
return { success: true, url: repoUrl };
|
||||
} catch (error: any) {
|
||||
console.error(`[Job ${job.id}] Failed:`, error.message);
|
||||
@@ -148,7 +133,7 @@ const worker = new Worker<DeployJobData>(
|
||||
concurrency: 5,
|
||||
limiter: {
|
||||
max: 10,
|
||||
duration: 1000, // Rate limit protection for GitHub API
|
||||
duration: 1000,
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -156,4 +141,4 @@ const worker = new Worker<DeployJobData>(
|
||||
// Listener for errors
|
||||
worker.on("failed", (job, err) => {
|
||||
console.error(`[Job ${job?.id}] Failed with error ${err.message}`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user