feat: 添加更新功能控制台日志 (console.log/error),标记每步执行与错误输出

This commit is contained in:
2026-05-24 23:40:20 +08:00
parent ee02326d1e
commit f3fcffa9ee
3 changed files with 123 additions and 25 deletions
+16
View File
@@ -1,15 +1,31 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { checkForUpdate } from "@/lib/update"; import { checkForUpdate } from "@/lib/update";
function apiLog(msg: string) {
const t = new Date().toISOString().slice(0, 19).replace("T", " ");
console.log(`[${t}] [更新检查] ${msg}`);
}
function apiErr(msg: string) {
const t = new Date().toISOString().slice(0, 19).replace("T", " ");
console.error(`[${t}] [更新检查] ${msg}`);
}
// GET /api/update/check - 检查是否有可用更新 // GET /api/update/check - 检查是否有可用更新
export async function GET() { export async function GET() {
apiLog("收到版本检查请求");
try { try {
const versionInfo = await checkForUpdate(); const versionInfo = await checkForUpdate();
const hasUpdate = versionInfo.hasUpdate;
const latestVer = versionInfo.latest?.version || "无";
apiLog(`检查完成: ${hasUpdate ? "有新版本 " + latestVer : "已是最新"}`);
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
data: versionInfo, data: versionInfo,
}); });
} catch (error) { } catch (error) {
const msg = error instanceof Error ? error.message : "未知错误";
apiErr(`检查异常: ${msg}`);
return NextResponse.json( return NextResponse.json(
{ success: false, error: "检查更新失败" }, { success: false, error: "检查更新失败" },
{ status: 500 } { status: 500 }
+17
View File
@@ -1,9 +1,21 @@
const API_TAG = "SSE-API";
function apiLog(msg: string) {
const t = new Date().toISOString().slice(0, 19).replace("T", " ");
console.log(`[${t}] [${API_TAG}] ${msg}`);
}
function apiErr(msg: string) {
const t = new Date().toISOString().slice(0, 19).replace("T", " ");
console.error(`[${t}] [${API_TAG}] ${msg}`);
}
import { executeUpdate } from "@/lib/update"; import { executeUpdate } from "@/lib/update";
// POST /api/update/execute - 执行容器更新(SSE 流式输出日志) // POST /api/update/execute - 执行容器更新(SSE 流式输出日志)
export async function POST() { export async function POST() {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
apiLog("收到更新请求,创建 SSE 流");
const stream = new ReadableStream({ const stream = new ReadableStream({
async start(controller) { async start(controller) {
// 发送日志的辅助函数 // 发送日志的辅助函数
@@ -13,8 +25,11 @@ export async function POST() {
}; };
try { try {
apiLog("开始执行 executeUpdate...");
const result = await executeUpdate(sendLog); const result = await executeUpdate(sendLog);
apiLog(`executeUpdate 完成: success=${result.success}, message="${result.message}"`);
// 发送最终结果 // 发送最终结果
const finalData = JSON.stringify({ const finalData = JSON.stringify({
done: true, done: true,
@@ -25,12 +40,14 @@ export async function POST() {
} catch (error) { } catch (error) {
const errMsg = const errMsg =
error instanceof Error ? error.message : "未知错误"; error instanceof Error ? error.message : "未知错误";
apiErr(`SSE 流异常: ${errMsg}`);
controller.enqueue( controller.enqueue(
encoder.encode( encoder.encode(
`data: ${JSON.stringify({ done: true, success: false, message: errMsg })}\n\n` `data: ${JSON.stringify({ done: true, success: false, message: errMsg })}\n\n`
) )
); );
} finally { } finally {
apiLog("关闭 SSE 流");
controller.close(); controller.close();
} }
}, },
+90 -25
View File
@@ -44,6 +44,53 @@ export type LogCallback = (message: string) => void;
/** 空回调 */ /** 空回调 */
const noopLog: LogCallback = () => {}; const noopLog: LogCallback = () => {};
/** 带时间戳的控制台日志 */
function consoleLog(tag: string, msg: string): void {
const t = new Date().toISOString().slice(0, 19).replace("T", " ");
console.log(`[${t}] [更新:${tag}] ${msg}`);
}
/** 带时间戳的控制台错误 */
function consoleErr(tag: string, msg: string): void {
const t = new Date().toISOString().slice(0, 19).replace("T", " ");
console.error(`[${t}] [更新:${tag}] ${msg}`);
}
/**
* 执行命令并记录到控制台
*/
function execSyncAndLog(
command: string,
tag: string,
options: Parameters<typeof execSync>[1] & { encoding?: "utf-8" } = {}
): string {
consoleLog(tag, `执行: ${command}`);
try {
const stdout = execSync(command, {
encoding: "utf-8",
windowsHide: true,
...options,
});
const out = stdout?.trim();
if (out) {
const lines = out.split("\n").filter(Boolean);
// 最多打印 5 行,避免刷屏
lines.slice(0, 5).forEach((l) => consoleLog(tag, `${l}`));
if (lines.length > 5) consoleLog(tag, ` │ ... 共 ${lines.length}`);
}
consoleLog(tag, "✓ 完成");
return stdout || "";
} catch (err) {
const e = err as Error & { stderr?: Buffer };
const stderr = e.stderr?.toString().trim();
if (stderr) {
stderr.split("\n").filter(Boolean).forEach((l) => consoleErr(tag, `${l}`));
}
consoleErr(tag, `失败: ${e.message}`);
throw err;
}
}
/** /**
* 读取并解析本地的 .version 文件 * 读取并解析本地的 .version 文件
*/ */
@@ -182,10 +229,15 @@ export function compareVersions(v1: string, v2: string): number {
* 检查是否有可用更新 * 检查是否有可用更新
*/ */
export async function checkForUpdate(): Promise<VersionInfo> { export async function checkForUpdate(): Promise<VersionInfo> {
consoleLog("检查", "开始检查版本更新");
const current = readLocalVersionFile(); const current = readLocalVersionFile();
consoleLog("检查", `当前版本: v${current.version} (${current.release_date || "无日期"})`);
const remoteCommit = getRemoteCommitHash(); const remoteCommit = getRemoteCommitHash();
if (!remoteCommit) { if (!remoteCommit) {
consoleErr("检查", "无法连接到远程仓库");
return { return {
current, current,
latest: null, latest: null,
@@ -194,10 +246,15 @@ export async function checkForUpdate(): Promise<VersionInfo> {
}; };
} }
consoleLog("检查", `远程 HEAD: ${remoteCommit}`);
const remoteVersionFile = getRemoteVersionFile(); const remoteVersionFile = getRemoteVersionFile();
if (remoteVersionFile) { if (remoteVersionFile) {
const cmp = compareVersions(remoteVersionFile.version, current.version); const cmp = compareVersions(remoteVersionFile.version, current.version);
consoleLog(
"检查",
`远程版本: v${remoteVersionFile.version}${cmp > 0 ? "有新版本" : "已是最新"}`
);
return { return {
current, current,
latest: remoteVersionFile, latest: remoteVersionFile,
@@ -206,7 +263,7 @@ export async function checkForUpdate(): Promise<VersionInfo> {
}; };
} }
// 远程能连上但 .version 文件不存在或解析失败 consoleErr("检查", "远程仓库中未找到 .version 文件");
return { return {
current, current,
latest: null, latest: null,
@@ -223,15 +280,13 @@ export async function checkForUpdate(): Promise<VersionInfo> {
* 检查 Docker 是否可用 * 检查 Docker 是否可用
*/ */
function checkDockerAvailable(): { ok: boolean; message: string } { function checkDockerAvailable(): { ok: boolean; message: string } {
consoleLog("Docker", "检查 Docker 守护进程...");
try { try {
execSync("docker info", { execSyncAndLog("docker info", "Docker");
encoding: "utf-8", consoleLog("Docker", "✓ Docker 正常");
timeout: 10000,
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});
return { ok: true, message: "" }; return { ok: true, message: "" };
} catch { } catch {
consoleErr("Docker", "✗ Docker 不可用");
return { return {
ok: false, ok: false,
message: message:
@@ -256,14 +311,19 @@ export async function executeUpdate(
`recipe_tool_update_${Date.now()}` `recipe_tool_update_${Date.now()}`
); );
consoleLog("执行", "=== 开始执行更新 ===");
consoleLog("执行", `临时目录: ${tmpDir}`);
try { try {
onLog("开始更新流程"); onLog("开始更新流程");
// 0. 检查 Docker 是否可用 // 0. 检查 Docker 是否可用
onLog("[0/4] 检查 Docker 环境"); onLog("[0/4] 检查 Docker 环境");
consoleLog("执行", "步骤 0/4 — 检查 Docker 环境");
const dockerCheck = checkDockerAvailable(); const dockerCheck = checkDockerAvailable();
if (!dockerCheck.ok) { if (!dockerCheck.ok) {
onLog(`${dockerCheck.message}`); onLog(`${dockerCheck.message}`);
consoleErr("执行", "Docker 检查未通过,更新终止");
return { success: false, message: dockerCheck.message }; return { success: false, message: dockerCheck.message };
} }
onLog("✓ Docker 环境正常"); onLog("✓ Docker 环境正常");
@@ -271,16 +331,17 @@ export async function executeUpdate(
// 1. 拉取最新代码 // 1. 拉取最新代码
const url = getAuthenticatedRemoteUrl(); const url = getAuthenticatedRemoteUrl();
onLog("[1/4] 克隆最新代码"); onLog("[1/4] 克隆最新代码");
execSync(`git clone --depth 1 "${url}" "${tmpDir}"`, { consoleLog("执行", "步骤 1/4 — 克隆代码");
encoding: "utf-8", execSyncAndLog(
timeout: 120000, `git clone --depth 1 "${url}" "${tmpDir}"`,
windowsHide: true, "git-clone",
stdio: ["ignore", "pipe", "pipe"], { timeout: 120000, stdio: ["ignore", "pipe", "pipe"] }
}); );
onLog("✓ 代码拉取完成"); onLog("✓ 代码拉取完成");
// 2. 生成 .env 文件 // 2. 生成 .env 文件
onLog("[2/4] 准备环境变量"); onLog("[2/4] 准备环境变量");
consoleLog("执行", "步骤 2/4 — 准备环境变量");
const envContent = [ const envContent = [
`DB_HOST=${process.env.DB_HOST || "127.0.0.1"}`, `DB_HOST=${process.env.DB_HOST || "127.0.0.1"}`,
`DB_PORT=${process.env.DB_PORT || "3306"}`, `DB_PORT=${process.env.DB_PORT || "3306"}`,
@@ -293,36 +354,40 @@ export async function executeUpdate(
`ONEDEV_ACCESS_TOKEN=${process.env.ONEDEV_ACCESS_TOKEN || ""}`, `ONEDEV_ACCESS_TOKEN=${process.env.ONEDEV_ACCESS_TOKEN || ""}`,
].join("\n"); ].join("\n");
fs.writeFileSync(path.join(tmpDir, ".env"), envContent, "utf-8"); fs.writeFileSync(path.join(tmpDir, ".env"), envContent, "utf-8");
consoleLog("执行", `.env 已写入 ${envContent.split("\n").length}`);
onLog("✓ 环境变量已准备"); onLog("✓ 环境变量已准备");
// 3. 构建新镜像 // 3. 构建新镜像
onLog("[3/4] 构建新 Docker 镜像"); onLog("[3/4] 构建新 Docker 镜像");
execSync(`cd "${tmpDir}" && docker-compose -p recipe_tool build`, { consoleLog("执行", "步骤 3/4 — 构建镜像(可能耗时较长)");
encoding: "utf-8", execSyncAndLog(
timeout: 600000, `cd "${tmpDir}" && docker-compose -p recipe_tool build`,
windowsHide: true, "docker-build",
stdio: ["ignore", "pipe", "pipe"], { timeout: 600000, stdio: ["ignore", "pipe", "pipe"] }
}); );
onLog("✓ 镜像构建完成"); onLog("✓ 镜像构建完成");
// 4. 重启服务 // 4. 重启服务
onLog("[4/4] 重启服务"); onLog("[4/4] 重启服务");
execSync(`cd "${tmpDir}" && docker-compose -p recipe_tool up -d`, { consoleLog("执行", "步骤 4/4 — 重启服务");
encoding: "utf-8", execSyncAndLog(
timeout: 60000, `cd "${tmpDir}" && docker-compose -p recipe_tool up -d`,
windowsHide: true, "docker-up",
stdio: ["ignore", "pipe", "pipe"], { timeout: 60000, stdio: ["ignore", "pipe", "pipe"] }
}); );
onLog("✓ 服务已重启"); onLog("✓ 服务已重启");
consoleLog("执行", "=== 更新成功 ===");
return { success: true, message: "更新完成" }; return { success: true, message: "更新完成" };
} catch (error) { } catch (error) {
const errMsg = error instanceof Error ? error.message : "未知错误"; const errMsg = error instanceof Error ? error.message : "未知错误";
consoleErr("执行", `=== 更新失败: ${errMsg} ===`);
onLog(`✗ 更新失败: ${errMsg}`); onLog(`✗ 更新失败: ${errMsg}`);
return { success: false, message: errMsg }; return { success: false, message: errMsg };
} finally { } finally {
try { try {
fs.rmSync(tmpDir, { recursive: true, force: true }); fs.rmSync(tmpDir, { recursive: true, force: true });
consoleLog("执行", `临时目录已清理: ${tmpDir}`);
} catch { } catch {
// ignore cleanup errors // ignore cleanup errors
} }