diff --git a/Dockerfile b/Dockerfile index 820ae9d..fecc0ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,6 +62,7 @@ RUN apt-get update && apt-get install -y \ libreoffice \ libva2 \ libvips-tools \ + libemail-address-perl \ libemail-outlook-message-perl \ lmodern \ mupdf-tools \ diff --git a/knip.json b/knip.json index dc571d6..73e2fa3 100644 --- a/knip.json +++ b/knip.json @@ -18,7 +18,7 @@ "heif-info", "potrace", "soffice", - "msgconvert" + "perl" ], "tailwind": { "entry": ["src/main.css"] diff --git a/src/helpers/printVersions.ts b/src/helpers/printVersions.ts index 8647dd5..6625aec 100644 --- a/src/helpers/printVersions.ts +++ b/src/helpers/printVersions.ts @@ -8,6 +8,7 @@ if (process.env.NODE_ENV === "production") { readFile("/etc/os-release", "utf8", (error, stdout) => { if (error) { console.error("Not running on docker, this is not supported."); + return; } if (stdout) { @@ -18,6 +19,7 @@ if (process.env.NODE_ENV === "production") { exec("pandoc -v", (error, stdout) => { if (error) { console.error("Pandoc is not installed."); + return; } if (stdout) { @@ -28,6 +30,7 @@ if (process.env.NODE_ENV === "production") { exec("ffmpeg -version", (error, stdout) => { if (error) { console.error("FFmpeg is not installed."); + return; } if (stdout) { @@ -38,6 +41,7 @@ if (process.env.NODE_ENV === "production") { exec("vips -v", (error, stdout) => { if (error) { console.error("Vips is not installed."); + return; } if (stdout) { @@ -48,6 +52,7 @@ if (process.env.NODE_ENV === "production") { exec("magick --version", (error, stdout) => { if (error) { console.error("ImageMagick is not installed."); + return; } if (stdout) { @@ -58,6 +63,7 @@ if (process.env.NODE_ENV === "production") { exec("gm version", (error, stdout) => { if (error) { console.error("GraphicsMagick is not installed."); + return; } if (stdout) { @@ -68,6 +74,7 @@ if (process.env.NODE_ENV === "production") { exec("inkscape --version", (error, stdout) => { if (error) { console.error("Inkscape is not installed."); + return; } if (stdout) { @@ -78,6 +85,7 @@ if (process.env.NODE_ENV === "production") { exec("djxl --version", (error, stdout) => { if (error) { console.error("libjxl-tools is not installed."); + return; } if (stdout) { @@ -85,19 +93,21 @@ if (process.env.NODE_ENV === "production") { } }); - exec("dasel --version", (error, stdout) => { + exec("dasel version", (error, stdout) => { if (error) { console.error("dasel is not installed."); + return; } if (stdout) { - console.log(stdout.split("\n")[0]); + console.log(`dasel ${stdout.split("\n")[0]}`); } }); exec("xelatex -version", (error, stdout) => { if (error) { console.error("Tex Live with XeTeX is not installed."); + return; } if (stdout) { @@ -108,36 +118,30 @@ if (process.env.NODE_ENV === "production") { exec("resvg -V", (error, stdout) => { if (error) { console.error("resvg is not installed"); + return; } if (stdout) { - // stdout may contain the command plus version (e.g. "resvg -V v1.0.0"). - // Extract the last token and print it as version to avoid duplication. - const firstLine = (stdout || "").split("\n")[0] || ""; - const lastToken = (firstLine.split(" ").filter(Boolean).pop() ?? "").toString(); - console.log(`resvg ${lastToken}`); + console.log(`resvg v${stdout.split("\n")[0]}`); } }); exec("assimp version", (error, stdout) => { if (error) { console.error("assimp is not installed"); + return; } if (stdout) { - // assimp prints its version on a specific line in real output; if the - // expected line isn't present (e.g. in tests/mocks), fall back to the - // first non-empty line. Then extract the last token as version. - const lines = (stdout || "").split("\n").filter(Boolean); - const candidate = (lines[5] ?? lines[0] ?? "").toString(); - const lastToken = (candidate.split(" ").filter(Boolean).pop() ?? "").toString(); - console.log(`assimp ${lastToken}`); + const firstLines = stdout.split("\n"); + console.log(`assimp ${firstLines[5] || firstLines[0] || ""}`); } }); exec("ebook-convert --version", (error, stdout) => { if (error) { console.error("ebook-convert (calibre) is not installed"); + return; } if (stdout) { @@ -148,6 +152,7 @@ if (process.env.NODE_ENV === "production") { exec("heif-info -v", (error, stdout) => { if (error) { console.error("libheif is not installed"); + return; } if (stdout) { @@ -158,6 +163,7 @@ if (process.env.NODE_ENV === "production") { exec("potrace -v", (error, stdout) => { if (error) { console.error("potrace is not installed"); + return; } if (stdout) { @@ -168,6 +174,7 @@ if (process.env.NODE_ENV === "production") { exec("soffice --version", (error, stdout) => { if (error) { console.error("libreoffice is not installed"); + return; } if (stdout) { @@ -175,28 +182,29 @@ if (process.env.NODE_ENV === "production") { } }); - exec("msgconvert --version", (error, stdout) => { - if (error) { - console.error("msgconvert (libemail-outlook-message-perl) is not installed"); - } + // msgconvert has no version flag, so read the version of the perl module providing it + exec( + "perl -MEmail::Outlook::Message -e 'print $Email::Outlook::Message::VERSION'", + (error, stdout) => { + if (error) { + console.error("msgconvert (libemail-outlook-message-perl) is not installed"); + return; + } - if (stdout) { - console.log(stdout.split("\n")[0]); - } - }); + if (stdout) { + console.log(`msgconvert v${stdout.split("\n")[0]}`); + } + }, + ); exec("bun -v", (error, stdout) => { if (error) { console.error("Bun is not installed. wait what"); + return; } if (stdout) { - // stdout may include the command itself (e.g. "bun -v v1.0.0"). Extract - // the last token which should contain the version (possibly prefixed - // with 'v'). - const firstLine = (stdout || "").split("\n")[0] || ""; - const lastToken = (firstLine.split(" ").filter(Boolean).pop() ?? "").toString(); - console.log(`Bun ${lastToken}`); + console.log(`Bun v${stdout.split("\n")[0]}`); } }); } diff --git a/tests/helpers/printVersions.test.ts b/tests/helpers/printVersions.test.ts index f00342c..307260b 100644 --- a/tests/helpers/printVersions.test.ts +++ b/tests/helpers/printVersions.test.ts @@ -1,4 +1,4 @@ -import { test, expect, mock, spyOn, afterEach } from "bun:test"; +import { afterEach, expect, mock, spyOn, test } from "bun:test"; import { exec } from "node:child_process"; import { readFile } from "node:fs"; @@ -12,11 +12,38 @@ mock.module("node:child_process", () => ({ const shouldError = (process.env.MOCK_EXEC_ERROR || "") .split(",") .some((p) => p && cmd.includes(p)); + if (shouldError) { cb(new Error(`${cmd} not found`), ""); - } else { - cb(null, `${cmd} v1.0.0\n`); + return; } + + // resvg, bun, heif-info, dasel, and the perl one-liner (msgconvert) print just + // the bare version number — the source code itself prepends the tool name as a label. + if ( + cmd.startsWith("resvg") || + cmd.startsWith("bun") || + cmd.startsWith("heif-info") || + cmd.startsWith("dasel") || + cmd.startsWith("perl") + ) { + cb(null, "1.0.0\n"); + return; + } + + // assimp's real output is multi-line; the source reads line index 5. + if (cmd.startsWith("assimp")) { + cb(null, "l1\nl2\nl3\nl4\nl5\nVersion 1.0.0 (GIT commit abc123)\n"); + return; + } + + // magick prefixes its version line with "Version: ". + if (cmd.startsWith("magick")) { + cb(null, "Version: ImageMagick v1.0.0\n"); + return; + } + + cb(null, `${cmd} v1.0.0\n`); }), })); @@ -79,8 +106,8 @@ test("prints system information and tool versions in production mode", async () test("logs error paths when tools are missing", async () => { process.env.NODE_ENV = "production"; - // Trigger a few error paths - process.env.MOCK_EXEC_ERROR = "pandoc,resvg,bun"; + // Trigger a few error paths ("perl" is the msgconvert version lookup) + process.env.MOCK_EXEC_ERROR = "pandoc,resvg,bun,perl"; consoleLogSpy = spyOn(console, "log"); consoleErrorSpy = spyOn(console, "error"); @@ -91,6 +118,9 @@ test("logs error paths when tools are missing", async () => { expect(consoleErrorSpy).toHaveBeenCalledWith("Pandoc is not installed."); expect(consoleErrorSpy).toHaveBeenCalledWith("resvg is not installed"); expect(consoleErrorSpy).toHaveBeenCalledWith("Bun is not installed. wait what"); + expect(consoleErrorSpy).toHaveBeenCalledWith( + "msgconvert (libemail-outlook-message-perl) is not installed", + ); }); test("processes output parsing correctly and logs no errors in the success case", async () => { @@ -108,6 +138,8 @@ test("processes output parsing correctly and logs no errors in the success case" expect(consoleLogSpy).toHaveBeenCalledWith("ffmpeg -version v1.0.0"); expect(consoleLogSpy).toHaveBeenCalledWith("resvg v1.0.0"); expect(consoleLogSpy).toHaveBeenCalledWith("Bun v1.0.0"); + expect(consoleLogSpy).toHaveBeenCalledWith("dasel 1.0.0"); + expect(consoleLogSpy).toHaveBeenCalledWith("msgconvert v1.0.0"); // make sure that error paths have not been triggered expect(consoleErrorSpy).not.toHaveBeenCalled();