claude-code-router/build/verify-packaged-app.cjs

233 lines
6.3 KiB
JavaScript

const fs = require("node:fs");
const path = require("node:path");
const betterSqliteNativeRelativePath = path.join(
"app.asar.unpacked",
"node_modules",
"better-sqlite3",
"build",
"Release",
"better_sqlite3.node"
);
const betterSqlitePackageRelativePath = path.join("app.asar.unpacked", "node_modules", "better-sqlite3");
const betterSqlitePrunablePaths = [
"deps",
"src",
"binding.gyp",
"README.md",
"docs",
"benchmark",
"benchmarks",
"test",
path.join("build", "Release", "obj"),
path.join("build", "Release", "obj.target")
];
module.exports = async function verifyPackagedApp(context) {
const platform = context?.electronPlatformName;
const arch = normalizeArch(context?.arch);
const appOutDir = context?.appOutDir;
if (!platform || !appOutDir) {
throw new Error("Packaged app verification did not receive electron-builder platform context.");
}
const resourcesDir = findResourcesDir(appOutDir, platform);
assertFile(path.join(resourcesDir, "app.asar"), "Packaged app archive");
cleanupBetterSqlitePackage(resourcesDir);
const nativeModule = path.join(resourcesDir, betterSqliteNativeRelativePath);
assertFile(nativeModule, "better-sqlite3 native module");
const nativeInfo = inspectNativeModule(nativeModule);
if (nativeInfo.platform !== platform) {
throw new Error(
`Packaged better-sqlite3 native module targets ${formatNativeInfo(nativeInfo)}, ` +
`but electron-builder is packaging ${platform}/${arch}. Rebuild native dependencies for the target platform before packaging.`
);
}
if (arch && !nativeArchMatches(nativeInfo, arch)) {
throw new Error(
`Packaged better-sqlite3 native module targets ${formatNativeInfo(nativeInfo)}, ` +
`but electron-builder is packaging ${platform}/${arch}. Run npm run rebuild:sqlite3 on the target platform before packaging.`
);
}
};
function cleanupBetterSqlitePackage(resourcesDir) {
const packageDir = path.join(resourcesDir, betterSqlitePackageRelativePath);
for (const relativePath of betterSqlitePrunablePaths) {
fs.rmSync(path.join(packageDir, relativePath), { force: true, recursive: true });
}
}
function findResourcesDir(appOutDir, platform) {
if (platform !== "darwin") {
return path.join(appOutDir, "resources");
}
const appBundle = fs.readdirSync(appOutDir)
.find((entry) => entry.endsWith(".app") && fs.statSync(path.join(appOutDir, entry)).isDirectory());
if (!appBundle) {
throw new Error(`Could not find a .app bundle in ${appOutDir}`);
}
return path.join(appOutDir, appBundle, "Contents", "Resources");
}
function assertFile(file, label) {
let stat;
try {
stat = fs.statSync(file);
} catch {
throw new Error(`${label} is missing: ${file}`);
}
if (!stat.isFile() || stat.size === 0) {
throw new Error(`${label} is empty or not a file: ${file}`);
}
}
function inspectNativeModule(file) {
const buffer = fs.readFileSync(file);
if (buffer.length < 32) {
throw new Error(`Native module is too small to identify: ${file}`);
}
if (buffer[0] === 0x4d && buffer[1] === 0x5a) {
return inspectPe(buffer, file);
}
if (buffer[0] === 0x7f && buffer[1] === 0x45 && buffer[2] === 0x4c && buffer[3] === 0x46) {
return inspectElf(buffer);
}
const magicLe = buffer.readUInt32LE(0);
const magicBe = buffer.readUInt32BE(0);
if (magicLe === 0xfeedface || magicLe === 0xfeedfacf || magicLe === 0xcefaedfe || magicLe === 0xcffaedfe) {
return inspectMachO(buffer, magicLe);
}
if (magicBe === 0xcafebabe || magicBe === 0xcafebabf) {
return inspectFatMachO(buffer, magicBe);
}
throw new Error(`Native module has an unknown binary format: ${file}`);
}
function inspectPe(buffer, file) {
const peOffset = buffer.readUInt32LE(0x3c);
if (peOffset + 6 >= buffer.length || buffer.toString("ascii", peOffset, peOffset + 4) !== "PE\0\0") {
throw new Error(`Native module has an invalid PE header: ${file}`);
}
const machine = buffer.readUInt16LE(peOffset + 4);
return {
arch: peMachineArch(machine),
platform: "win32"
};
}
function inspectElf(buffer) {
const machine = buffer.readUInt16LE(18);
return {
arch: elfMachineArch(machine),
platform: "linux"
};
}
function inspectMachO(buffer, magicLe) {
const bigEndian = magicLe === 0xcefaedfe || magicLe === 0xcffaedfe;
const cpuType = bigEndian ? buffer.readUInt32BE(4) : buffer.readUInt32LE(4);
return {
arch: machCpuArch(cpuType),
platform: "darwin"
};
}
function inspectFatMachO(buffer, magicBe) {
const archs = [];
const count = buffer.readUInt32BE(4);
const entrySize = magicBe === 0xcafebabf ? 32 : 20;
for (let index = 0; index < count; index += 1) {
const offset = 8 + index * entrySize;
if (offset + 8 > buffer.length) {
break;
}
archs.push(machCpuArch(buffer.readUInt32BE(offset)));
}
return {
arch: "universal",
archs,
platform: "darwin"
};
}
function peMachineArch(machine) {
if (machine === 0x8664) {
return "x64";
}
if (machine === 0xaa64) {
return "arm64";
}
if (machine === 0x014c) {
return "ia32";
}
return `unknown-pe-${machine.toString(16)}`;
}
function elfMachineArch(machine) {
if (machine === 0x3e) {
return "x64";
}
if (machine === 0xb7) {
return "arm64";
}
if (machine === 0x03) {
return "ia32";
}
if (machine === 0x28) {
return "armv7l";
}
return `unknown-elf-${machine.toString(16)}`;
}
function machCpuArch(cpuType) {
if (cpuType === 0x01000007) {
return "x64";
}
if (cpuType === 0x0100000c) {
return "arm64";
}
if (cpuType === 0x00000007) {
return "ia32";
}
return `unknown-macho-${cpuType.toString(16)}`;
}
function normalizeArch(arch) {
if (typeof arch === "string") {
return arch;
}
const electronBuilderArchNames = new Map([
[0, "ia32"],
[1, "x64"],
[2, "armv7l"],
[3, "arm64"],
[4, "universal"]
]);
return electronBuilderArchNames.get(arch);
}
function nativeArchMatches(info, arch) {
if (info.arch === arch) {
return true;
}
if (info.arch === "universal") {
return arch === "universal" || Boolean(info.archs?.includes(arch));
}
return false;
}
function formatNativeInfo(info) {
return info.arch === "universal" && info.archs?.length
? `${info.platform}/${info.arch} (${info.archs.join(", ")})`
: `${info.platform}/${info.arch}`;
}