diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f11ab7..513465b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: inputs: tag: - description: "Tag to release (e.g., v0.1.6)" + description: "Tag to release (e.g., v0.1.13)" required: true type: string diff --git a/src/helpers/printVersions.ts b/src/helpers/printVersions.ts index b6e7225..75555d0 100644 --- a/src/helpers/printVersions.ts +++ b/src/helpers/printVersions.ts @@ -1,187 +1,83 @@ import { exec } from "node:child_process"; -import { readFile } from "node:fs"; +import { readFile } from "node:fs/promises"; import { version } from "../../package.json"; console.log(`ConvertX v${version}`); +// 帶 timeout 的 exec 包裝函數,防止 CI 環境卡住 +const execWithTimeout = ( + cmd: string, + timeoutMs = 5000, +): Promise<{ stdout: string; stderr: string; error: Error | null }> => { + return new Promise((resolve) => { + const child = exec(cmd, { timeout: timeoutMs }, (error, stdout, stderr) => { + resolve({ stdout: stdout || "", stderr: stderr || "", error }); + }); + // 設定額外保險 timeout + const timer = setTimeout(() => { + child.kill("SIGTERM"); + resolve({ stdout: "", stderr: "", error: new Error(`Command timeout: ${cmd}`) }); + }, timeoutMs + 1000); + // 正常完成時清除 timer + child.on("close", () => clearTimeout(timer)); + }); +}; + +// 定義所有要檢查的工具 +const tools = [ + { cmd: "pandoc -v", name: "Pandoc", errorMsg: "Pandoc is not installed.", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "ffmpeg -version", name: "FFmpeg", errorMsg: "FFmpeg is not installed.", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "vips -v", name: "Vips", errorMsg: "Vips is not installed.", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "magick --version", name: "ImageMagick", errorMsg: "ImageMagick is not installed.", formatter: (s: string) => s.split("\n")[0]?.replace("Version: ", "") }, + { cmd: "gm version", name: "GraphicsMagick", errorMsg: "GraphicsMagick is not installed.", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "inkscape --version", name: "Inkscape", errorMsg: "Inkscape is not installed.", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "djxl --version", name: "libjxl", errorMsg: "libjxl-tools is not installed.", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "dasel --version", name: "dasel", errorMsg: "dasel is not installed.", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "xelatex -version", name: "XeTeX", errorMsg: "Tex Live with XeTeX is not installed.", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "resvg -V", name: "resvg", errorMsg: "resvg is not installed", formatter: (s: string) => `resvg v${s.split("\n")[0]}` }, + { cmd: "assimp version", name: "assimp", errorMsg: "assimp is not installed", formatter: (s: string) => `assimp ${s.split("\n")[5]}` }, + { cmd: "ebook-convert --version", name: "calibre", errorMsg: "ebook-convert (calibre) is not installed", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "heif-info -v", name: "libheif", errorMsg: "libheif is not installed", formatter: (s: string) => `libheif v${s.split("\n")[0]}` }, + { cmd: "potrace -v", name: "potrace", errorMsg: "potrace is not installed", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "soffice --version", name: "LibreOffice", errorMsg: "libreoffice is not installed", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "msgconvert --version", name: "msgconvert", errorMsg: "msgconvert (libemail-outlook-message-perl) is not installed", formatter: (s: string) => s.split("\n")[0] }, + { cmd: "bun -v", name: "Bun", errorMsg: "Bun is not installed. wait what", formatter: (s: string) => `Bun v${s.split("\n")[0]}` }, +]; + if (process.env.NODE_ENV === "production") { - readFile("/etc/os-release", "utf8", (error, stdout) => { - if (error) { + (async () => { + // 讀取 OS 資訊 + try { + const osRelease = await readFile("/etc/os-release", "utf8"); + const prettyName = osRelease.split('PRETTY_NAME="')[1]?.split('"')[0]; + if (prettyName) console.log(prettyName); + } catch { console.error("Not running on docker, this is not supported."); } - if (stdout) { - console.log(stdout.split('PRETTY_NAME="')[1]?.split('"')[0]); - } - }); + // 並行執行所有工具版本檢查,每個都有 timeout + const results = await Promise.allSettled( + tools.map(async (tool) => { + const { stdout, stderr, error } = await execWithTimeout(tool.cmd, 5000); + return { tool, stdout, stderr, error }; + }), + ); - exec("pandoc -v", (error, stdout) => { - if (error) { - console.error("Pandoc is not installed."); + // 輸出結果 + for (const result of results) { + if (result.status === "fulfilled") { + const { tool, stdout, stderr, error } = result.value; + if (error) { + console.error(tool.errorMsg); + } else { + // 有些工具把版本資訊輸出到 stderr(例如 djxl) + const output = stdout || stderr; + if (output) { + console.log(tool.formatter(output)); + } + } + } } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("ffmpeg -version", (error, stdout) => { - if (error) { - console.error("FFmpeg is not installed."); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("vips -v", (error, stdout) => { - if (error) { - console.error("Vips is not installed."); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("magick --version", (error, stdout) => { - if (error) { - console.error("ImageMagick is not installed."); - } - - if (stdout) { - console.log(stdout.split("\n")[0]?.replace("Version: ", "")); - } - }); - - exec("gm version", (error, stdout) => { - if (error) { - console.error("GraphicsMagick is not installed."); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("inkscape --version", (error, stdout) => { - if (error) { - console.error("Inkscape is not installed."); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("djxl --version", (error, stdout) => { - if (error) { - console.error("libjxl-tools is not installed."); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("dasel --version", (error, stdout) => { - if (error) { - console.error("dasel is not installed."); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("xelatex -version", (error, stdout) => { - if (error) { - console.error("Tex Live with XeTeX is not installed."); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("resvg -V", (error, stdout) => { - if (error) { - console.error("resvg is not installed"); - } - - if (stdout) { - console.log(`resvg v${stdout.split("\n")[0]}`); - } - }); - - exec("assimp version", (error, stdout) => { - if (error) { - console.error("assimp is not installed"); - } - - if (stdout) { - console.log(`assimp ${stdout.split("\n")[5]}`); - } - }); - - exec("ebook-convert --version", (error, stdout) => { - if (error) { - console.error("ebook-convert (calibre) is not installed"); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("heif-info -v", (error, stdout) => { - if (error) { - console.error("libheif is not installed"); - } - - if (stdout) { - console.log(`libheif v${stdout.split("\n")[0]}`); - } - }); - - exec("potrace -v", (error, stdout) => { - if (error) { - console.error("potrace is not installed"); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("soffice --version", (error, stdout) => { - if (error) { - console.error("libreoffice is not installed"); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("msgconvert --version", (error, stdout) => { - if (error) { - console.error("msgconvert (libemail-outlook-message-perl) is not installed"); - } - - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); - - exec("bun -v", (error, stdout) => { - if (error) { - console.error("Bun is not installed. wait what"); - } - - if (stdout) { - console.log(`Bun v${stdout.split("\n")[0]}`); - } - }); + })(); } +