Implement dynamic converter path resolution and binary format detection for export functionality

This commit is contained in:
sudipnext 2026-03-16 14:54:19 +05:45
parent c8faab97d9
commit 6c72fe2e0e
3 changed files with 192 additions and 17 deletions

View file

@ -8,6 +8,8 @@ import { v4 as uuidv4 } from 'uuid';
import { spawn } from "child_process";
import { getPuppeteerExecutablePath } from "../utils/puppeteer-check";
type BinaryFormat = "elf" | "mach-o" | "pe" | "unknown";
export function setupExportHandlers() {
ipcMain.handle("file-downloaded", async (_, filePath: string): Promise<IPCStatus> => {
const fileName = path.basename(filePath);
@ -38,7 +40,7 @@ export function setupExportHandlers() {
await fs.promises.writeFile(exportTaskPath, JSON.stringify(exportTask));
const exportScriptPath = path.join(baseDir, "resources", "export", "index.js");
const pythonModulePath = path.join(baseDir, "resources", "export", "py", "convert");
const pythonModulePath = await resolveConverterPath(baseDir);
const puppeteerExecutablePath = await getPuppeteerExecutablePath();
console.log("[Export] Spawning export task with config:", {
exportAs,
@ -125,6 +127,94 @@ export function setupExportHandlers() {
}
async function resolveConverterPath(currentBaseDir: string): Promise<string> {
const pyDir = path.join(currentBaseDir, "resources", "export", "py");
const extension = process.platform === "win32" ? ".exe" : "";
const converterCandidates = [
path.join(pyDir, `convert-${process.platform}-${process.arch}${extension}`),
path.join(pyDir, `convert-${process.platform}${extension}`),
...(process.platform === "win32"
? [path.join(pyDir, "convert.exe"), path.join(pyDir, "convert")]
: [path.join(pyDir, "convert")]),
];
const converterPath = await findFirstExistingPath(converterCandidates);
if (!converterPath) {
throw new Error(
[
"No converter binary found for export.",
"Expected one of:",
...converterCandidates.map((candidate) => ` - ${candidate}`),
].join("\n")
);
}
const format = await detectBinaryFormat(converterPath);
if (!isBinaryFormatCompatible(format)) {
throw new Error(
[
`Converter binary is not valid for ${process.platform}/${process.arch}.`,
`Selected converter: ${converterPath}`,
`Detected format: ${format}`,
"Please bundle a platform-correct converter binary (for example convert-darwin-arm64 or convert-darwin-x64).",
].join("\n")
);
}
return converterPath;
}
async function findFirstExistingPath(paths: string[]): Promise<string | null> {
for (const candidate of paths) {
try {
await fs.promises.access(candidate, fs.constants.F_OK);
return candidate;
} catch {
continue;
}
}
return null;
}
async function detectBinaryFormat(binaryPath: string): Promise<BinaryFormat> {
const fd = await fs.promises.open(binaryPath, "r");
try {
const header = Buffer.alloc(4);
await fd.read(header, 0, 4, 0);
if (header[0] === 0x7f && header[1] === 0x45 && header[2] === 0x4c && header[3] === 0x46) {
return "elf";
}
if (header[0] === 0x4d && header[1] === 0x5a) {
return "pe";
}
const magic = header.readUInt32BE(0);
if (
magic === 0xfeedface ||
magic === 0xcefaedfe ||
magic === 0xfeedfacf ||
magic === 0xcffaedfe ||
magic === 0xcafebabe ||
magic === 0xbebafeca
) {
return "mach-o";
}
return "unknown";
} finally {
await fd.close();
}
}
function isBinaryFormatCompatible(format: BinaryFormat): boolean {
if (process.platform === "darwin") return format === "mach-o";
if (process.platform === "linux") return format === "elf";
if (process.platform === "win32") return format === "pe";
return true;
}
function resolveExportedFilePath(responseData: any): string | null {
if (responseData?.path && typeof responseData.path === "string") {
return path.isAbsolute(responseData.path)

View file

@ -16,7 +16,12 @@ const afterPack = async (context) => {
"resources"
)
const fastapiPath = path.join(resourcesRoot, "fastapi", "fastapi")
const convertPath = path.join(resourcesRoot, "export", "py", "convert")
const exportPyDir = path.join(resourcesRoot, "export", "py")
const converterCandidates = [
`convert-${process.platform}-${process.arch}`,
`convert-${process.platform}`,
"convert",
]
console.log("Setting executable permissions for FastAPI binary...")
console.log("FastAPI path:", fastapiPath)
@ -29,13 +34,17 @@ const afterPack = async (context) => {
}
console.log("Setting executable permissions for export converter binary...")
console.log("Converter path:", convertPath)
if (fs.existsSync(convertPath)) {
fs.chmodSync(convertPath, 0o755)
console.log("✓ Execute permissions set for converter")
} else {
console.warn("⚠ Converter binary not found at:", convertPath)
let converterFound = false
for (const candidate of converterCandidates) {
const candidatePath = path.join(exportPyDir, candidate)
if (fs.existsSync(candidatePath)) {
fs.chmodSync(candidatePath, 0o755)
console.log("✓ Execute permissions set for converter:", candidatePath)
converterFound = true
}
}
if (!converterFound) {
console.warn("⚠ No converter binary found in:", exportPyDir)
}
const fastapiDir = path.join(resourcesRoot, "fastapi")
@ -43,7 +52,6 @@ const afterPack = async (context) => {
console.log("FastAPI directory contents:", fs.readdirSync(fastapiDir))
}
const exportPyDir = path.join(resourcesRoot, "export", "py")
if (fs.existsSync(exportPyDir)) {
console.log("Export py directory contents:", fs.readdirSync(exportPyDir))
}

View file

@ -3,7 +3,23 @@ const path = require("path");
const targetRoot = path.join(__dirname, "resources", "export");
const targetPyDir = path.join(targetRoot, "py");
const targetIndex = path.join(targetRoot, "index.js");
const targetConvert = path.join(targetPyDir, "convert");
const targetConvertDefault = path.join(targetPyDir, "convert");
function getConverterCandidates() {
const tagged = path.join(
targetPyDir,
`convert-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`
);
const platformOnly = path.join(
targetPyDir,
`convert-${process.platform}${process.platform === "win32" ? ".exe" : ""}`
);
const legacy = process.platform === "win32"
? [path.join(targetPyDir, "convert.exe"), targetConvertDefault]
: [targetConvertDefault];
return [tagged, platformOnly, ...legacy];
}
function ensureExists(filePath, label) {
if (!fs.existsSync(filePath)) {
@ -17,20 +33,81 @@ function chmodIfPossible(filePath) {
}
}
function detectBinaryFormat(filePath) {
const fd = fs.openSync(filePath, "r");
try {
const header = Buffer.alloc(4);
fs.readSync(fd, header, 0, 4, 0);
if (header[0] === 0x7f && header[1] === 0x45 && header[2] === 0x4c && header[3] === 0x46) {
return "elf";
}
if (header[0] === 0x4d && header[1] === 0x5a) {
return "pe";
}
const magic = header.readUInt32BE(0);
if (
magic === 0xfeedface ||
magic === 0xcefaedfe ||
magic === 0xfeedfacf ||
magic === 0xcffaedfe ||
magic === 0xcafebabe ||
magic === 0xbebafeca
) {
return "mach-o";
}
return "unknown";
} finally {
fs.closeSync(fd);
}
}
function isFormatCompatible(format) {
if (process.platform === "darwin") return format === "mach-o";
if (process.platform === "linux") return format === "elf";
if (process.platform === "win32") return format === "pe";
return true;
}
function main() {
ensureExists(
targetIndex,
"Committed runtime JS bundle (electron/resources/export/index.js)"
);
ensureExists(
targetConvert,
"Committed runtime converter binary (electron/resources/export/py/convert)"
);
chmodIfPossible(targetConvert);
const converterCandidates = getConverterCandidates();
const converterPath = converterCandidates.find((candidate) => fs.existsSync(candidate));
if (!converterPath) {
throw new Error(
[
"No converter binary found in electron/resources/export/py.",
"Expected one of:",
...converterCandidates.map((candidate) => ` - ${candidate}`),
].join("\n")
);
}
const binaryFormat = detectBinaryFormat(converterPath);
if (!isFormatCompatible(binaryFormat)) {
throw new Error(
[
`Converter binary is not valid for ${process.platform}/${process.arch}.`,
`Selected converter: ${converterPath}`,
`Detected format: ${binaryFormat}`,
"Bundle a platform-correct converter binary (e.g. convert-darwin-arm64, convert-darwin-x64, convert-linux-x64, convert.exe).",
].join("\n")
);
}
chmodIfPossible(converterPath);
console.log("[export-runtime] Using committed runtime artifacts:");
console.log(` - ${targetIndex}`);
console.log(` - ${targetConvert}`);
console.log(` - ${converterPath}`);
}
main();