import { execFile } from "child_process"; import { promisify } from "util"; import { BLOG_ROOT } from "./config"; const execFileAsync = promisify(execFile); type GitExecError = Error & { stdout?: string; stderr?: string; code?: number; }; export interface DeployResult { success: boolean; output?: string; error?: string; conflict?: boolean; } interface QueueItem { message: string; resolve: (result: DeployResult) => void; reject: (err: Error) => void; } class GitDeployQueue { private queue: QueueItem[] = []; private running = false; async enqueue(commitMessage: string): Promise { return new Promise((resolve, reject) => { this.queue.push({ message: commitMessage, resolve, reject }); if (!this.running) this.next(); }); } async sync(): Promise { try { const result = await this.runGit(["pull", "--rebase", "--autostash", "origin", "main"]); return { success: true, output: result.stdout + result.stderr }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); if (this.isRebaseConflict(msg)) { await this.runGit(["rebase", "--abort"], true); return { success: false, conflict: true, error: "远程冲突,已放弃合并" }; } return { success: false, error: msg }; } } private async next(): Promise { const item = this.queue.shift(); if (!item) { this.running = false; return; } this.running = true; try { const result = await this.execute(item.message); item.resolve(result); } catch (e) { item.reject(e instanceof Error ? e : new Error(String(e))); } this.next(); } private async runGit(args: string[], allowFailure = false) { try { const { stdout, stderr } = await execFileAsync("git", args, { cwd: BLOG_ROOT, windowsHide: true, maxBuffer: 1024 * 1024, }); return { stdout, stderr, code: 0 }; } catch (error) { const err = error as GitExecError; const result = { stdout: err.stdout || "", stderr: err.stderr || err.message, code: typeof err.code === "number" ? err.code : 1, }; if (allowFailure) return result; throw new Error((result.stderr || result.stdout || err.message).trim()); } } private isRebaseConflict(message: string): boolean { return /conflict|CONFLICT|Resolve all conflicts|Automatic merge failed|could not apply|rebase/i.test(message); } private async execute(message: string): Promise { let output = ""; // git add const addResult = await this.runGit(["add", "content"]); output += addResult.stdout + addResult.stderr; // git diff --cached --quiet (exit 1 = staged changes exist) const diffResult = await this.runGit(["diff", "--cached", "--quiet"], true); if (diffResult.code === 1) { const commitResult = await this.runGit(["commit", "-m", message]); output += commitResult.stdout + commitResult.stderr; } else if (diffResult.code !== 0) { return { success: false, error: (diffResult.stderr || diffResult.stdout || "检查暂存区失败").trim() }; } // git pull --rebase --autostash try { const pullResult = await this.runGit(["pull", "--rebase", "--autostash", "origin", "main"]); output += pullResult.stdout + pullResult.stderr; } catch (error) { const msg = error instanceof Error ? error.message : String(error); if (this.isRebaseConflict(msg)) { await this.runGit(["rebase", "--abort"], true); return { success: false, conflict: true, error: "远程有冲突,已自动放弃合并。请先 /sync 同步,手动解决后再 deploy。", output: msg.slice(0, 1000), }; } return { success: false, error: msg }; } // git push try { const pushResult = await this.runGit(["push"]); output += pushResult.stdout + pushResult.stderr; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) }; } return { success: true, output: output.slice(0, 1000) }; } } export const deployQueue = new GitDeployQueue();