NodeJs's Git Auto Update Daemon
2026-08-26
分享一个NodeJs的基于git的自动升级守护进程
import { fork, exec, ExecException, ChildProcess } from "child_process";
import path from "path";
import fs from "fs";
import getLogger from "./utils/logger";
class AppDeamon {
private readonly logger = getLogger("AppDeamon");
private appPath: string;
private appEntry: string;
private shell: string;
private appProcess: ChildProcess | null = null;
private isDeploying = false;
private checkInterval = 60 * 1000;
private currentBranch = "";
constructor() {
this.logger.info("🛡️ 守护进程 v1.0.0");
this.shell = this.detectShell(); // 自动检测
this.appPath = path.join(process.cwd(), "dist");
this.appEntry = path.join(this.appPath, "app.js");
this.logger.info(`📂 应用路径: ${this.appPath}`);
this.logger.info(`📂 应用入口: ${this.appEntry}`);
this.logger.info(`💻 平台: ${process.platform}`);
this.logger.info(`💻 Shell: ${this.shell}`);
this.logger.info(`⏱️ 检查间隔: ${this.checkInterval / 1000}秒`);
}
private detectShell(): string {
const platform = process.platform;
if (platform === "win32") {
// Windows: 检查是否在Git Bash或WSL环境中
if (process.env.SHELL?.includes("bash") || process.env.WSL_DISTRO_NAME) {
this.logger.debug("检测到 WSL 环境, Shell: 使用 WSL 的 bash");
return "/bin/bash";
}
this.logger.debug("检测到 Windows 环境, Shell: 使用 Cmd");
// 默认使用cmd
return "cmd.exe";
} else if (platform === "darwin") {
// macOS
this.logger.debug("检测到 macOS 环境, Shell: 使用 zsh");
return "/bin/zsh";
} else {
this.logger.debug("检测到 linux 环境, Shell: 使用 bash");
// Linux
return "/bin/bash";
}
}
private async executeCommand(
command: string,
options?: {
cwd?: string;
timeout?: number;
},
): Promise<{ stdout: string; stderr: string }> {
return new Promise((resolve, reject) => {
let finalCommand = command;
if (this.shell === "cmd.exe") {
finalCommand = this.convertToWindowsCommand(command);
}
const execOptions = {
cwd: options?.cwd || process.cwd(),
shell: this.shell,
encoding: "utf-8" as BufferEncoding,
timeout: options?.timeout || 30000,
maxBuffer: 1024 * 1024 * 10,
// Windows
...(this.shell === "cmd.exe" && {
env: {
...process.env,
PATH: process.env.PATH || "",
},
}),
};
exec(finalCommand, execOptions, (error: ExecException | null, stdout: string, stderr: string) => {
if (error) {
reject(new Error(`命令执行失败: ${command}\n` + `Shell: ${this.shell}\n` + `错误: ${error.message}\n` + `stderr: ${stderr}`));
} else {
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
});
}
});
});
}
private async getCurrentBranch(): Promise<string> {
try {
const result = await this.executeCommand("git branch --show-current");
if (result.stdout) {
return result.stdout;
}
const result2 = await this.executeCommand("git rev-parse --abbrev-ref HEAD");
return result2.stdout;
} catch (error) {
this.logger.error("获取分支失败:", error);
throw error;
}
}
private async hasUpdate(branch: string): Promise<boolean> {
try {
// 先fetch
await this.executeCommand(`git fetch origin ${branch}`);
// 获取本地和远程hash
const localResult = await this.executeCommand("git rev-parse HEAD");
const remoteResult = await this.executeCommand(`git rev-parse origin/${branch}`);
return localResult.stdout !== remoteResult.stdout;
} catch (error) {
this.logger.error("检查更新失败:", error);
return false;
}
}
private convertToWindowsCommand(command: string): string {
// Git命令在cmd下需要特殊处理
if (command.startsWith("git ")) {
// 确保git在PATH中
return command;
}
// 处理&&和||这类shell操作符
if (command.includes("&&") || command.includes("||")) {
// cmd使用 & 而不是 &&
return command.replace(/&&/g, "&");
}
// 处理引号问题
if (command.includes("'")) {
return command.replace(/'/g, '"');
}
return command;
}
async init() {
this.logger.info("守护进程启动");
if (!process.env.NODE_ENV) {
this.logger.warn("未检测到当前环境类型, 已采用默认值: 生产环境");
process.env.NODE_ENV = "production";
}
this.logger.info("当前环境: " + process.env.NODE_ENV);
// fork(this.appPath, [], {
// execArgv: ["--enable-source-maps"],
// });
this.logger.info("正在安装 Pnpm⏳");
try {
await this.executeCommand("npm install -g pnpm --ignore-scripts");
} catch (e) {
this.logger.error("安装 Pnpm 失败: ", e);
process.exit(0);
}
this.logger.info("正在检查 Pnpm⏳");
try {
const pnpmVersion = (await this.executeCommand("pnpm -v")).stdout;
if (!pnpmVersion) throw new Error("版本无输出");
this.logger.info("Pnpm 版本: " + pnpmVersion);
} catch (e) {
this.logger.error("检查 Pnpm 时出错: ", e);
process.exit(0);
}
this.logger.info("正在安装依赖⏳");
try {
const result = (await this.executeCommand("pnpm install")).stdout;
if (!result.includes("Done")) throw new Error(result);
} catch (e) {
this.logger.error("Pnpm 安装依赖时出错: ", e);
process.exit(0);
}
this.logger.info("正在检查 git⏳");
try {
const gitVersion = (await this.executeCommand("git -v")).stdout;
if (!gitVersion) throw new Error("版本无输出");
this.logger.info("git 版本: " + gitVersion);
const branch = await this.getCurrentBranch();
if (!branch) throw new Error("获取当前分支失败");
this.currentBranch = branch;
this.logger.info("当前分支: " + branch);
} catch (e) {
this.logger.error("检查 git 时出错: ", e);
process.exit(0);
}
this.logger.info("正在构建当前版本⏳");
try {
const result = (await this.executeCommand("pnpm build")).stdout;
if (!result.includes("Build success")) throw new Error(result);
} catch (e) {
this.logger.error("构建失败: ", e);
process.exit(0);
}
this.logger.info("准备App⏳");
try {
if (!fs.existsSync(this.appEntry)) throw new Error("入口文件不存在");
} catch (e) {
this.logger.error("准备App时出错: ", e);
process.exit(0);
}
this.checkAndDeploy().catch(this.logger.error);
// 定时轮询
setInterval(() => {
this.checkAndDeploy().catch(this.logger.error);
}, this.checkInterval);
}
private async performDeploy(branch: string): Promise<void> {
this.logger.info(`🔄 开始部署分支 ${branch}...`);
this.logger.info(`🖥️ 系统: ${process.platform}, Shell: ${this.shell}`);
try {
// 1. 停止应用
this.logger.info("🛑 停止应用...");
this.appProcess?.kill();
// 2. 拉取代码
this.logger.info("📥 拉取最新代码...");
await this.executeCommand(`git reset --hard origin/${branch}`);
// 3. 安装依赖
this.logger.info("📦 安装依赖...");
await this.executeCommand("pnpm install");
// 4. 构建(检查平台)
this.logger.info("🏗️ 构建项目...");
try {
await this.executeCommand("pnpm build");
} catch (error) {
this.logger.info("⚠️ 无build脚本或构建失败,跳过");
}
// 5. 启动应用
this.logger.info("🚀 启动应用...");
this.start();
this.logger.info("✅ 部署完成!");
} catch (error) {
this.logger.error("❌ 部署失败:", error);
}
}
async checkAndDeploy(): Promise<void> {
if (this.isDeploying) {
this.logger.info("⏳ 部署进行中,跳过本次检查");
return;
}
this.isDeploying = true;
try {
const branch = await this.getCurrentBranch();
if (branch !== this.currentBranch) {
this.logger.warn("检测到分支切换: " + `${this.currentBranch} -> ${branch}`);
this.currentBranch = branch;
}
if (await this.hasUpdate(branch)) {
await this.performDeploy(branch);
// 部署
} else {
this.logger.info("✅ 代码已是最新");
}
} catch (error) {
this.logger.error("❌ 检查失败:", error);
} finally {
this.isDeploying = false;
}
}
async start() {
this.logger.info("启动App⏳");
this.appProcess = fork(this.appEntry, [], {
cwd: this.appPath,
execArgv: ["--enable-source-maps"],
});
this.appProcess.on("exit", () => {
this.logger.warn("进程退出");
});
}
}使用
const daemon = new AppDeamon();
(async () => {
await daemon.init();
await daemon.start();
})();
代办
闲置时进行
NodeJs's Git Auto Update Daemon
/archives/nodejs-s-git-auto-update-daemon