Implement dynamic converter path resolution and binary format detection for export functionality
This commit is contained in:
parent
c8faab97d9
commit
6c72fe2e0e
3 changed files with 192 additions and 17 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue