Files
5chan/scripts/find-forge-executable.js
plebeius 16012b4239 fix(find-forge-executable): make appName preference effective
Added fallback variable to track valid candidates that don't match appName. Now returns immediately on exact match, otherwise continues searching and falls back to any valid executable. Applied fix consistently across Windows, Darwin, and Linux branches including .app directories and .AppImage files.
2026-01-30 12:57:52 +08:00

140 lines
5.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Find the packaged Electron executable built by Electron Forge.
* This script locates the executable in the out/ directory structure.
*/
import { readdirSync, statSync, existsSync, readFileSync } from 'fs';
import { isAbsolute, join, resolve } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = resolve(__filename, '..');
const platform = process.platform;
const repoRoot = resolve(__dirname, '..');
const packageJson = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf-8'));
const appName = (packageJson.build?.productName || packageJson.name || '5chan').toLowerCase();
const resolveOutDir = (dir) => (isAbsolute(dir) ? dir : join(repoRoot, dir));
const envOutDir = process.env.ELECTRON_FORGE_OUT_DIR;
const candidateRoots = [
envOutDir ? resolveOutDir(envOutDir) : null,
join(repoRoot, 'out'),
join(repoRoot, 'out', 'make'),
join(repoRoot, '..', 'out'),
join(repoRoot, '..', '..', 'out'),
join(repoRoot, 'electron', 'out'),
].filter(Boolean);
// Skip directories that contain helper binaries/app code, not the main executable
// 'resources' contains the app bundle with IPFS binaries in bin/ - don't recurse there
const skipDirs = new Set(['node_modules', '.git', 'bin', 'app', 'resources']);
function findExecutable(dir, platform) {
const entries = readdirSync(dir, { withFileTypes: true });
let fallback = null; // Track a valid candidate that doesn't match appName
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
if (skipDirs.has(entry.name)) continue;
if (platform === 'darwin' && entry.name.endsWith('.app')) {
const appPath = fullPath;
const exePath = join(appPath, 'Contents', 'MacOS', entry.name.replace('.app', ''));
if (existsSync(exePath)) {
const lowerAppName = entry.name.toLowerCase();
if (lowerAppName.includes(appName)) {
return exePath; // Exact match, return immediately
}
fallback = fallback || exePath; // Store as fallback, continue searching
}
}
const result = findExecutable(fullPath, platform);
if (result) {
const resultName = result.split('/').pop().toLowerCase();
if (resultName.includes(appName)) {
return result; // Exact match from recursion
}
fallback = fallback || result; // Store as fallback
}
} else if (entry.isFile()) {
// Check if it's an executable
if (platform === 'win32') {
const lowerName = entry.name.toLowerCase();
if (lowerName.endsWith('.exe') && !lowerName.includes('electron') && !lowerName.includes('crashpad')) {
if (lowerName.includes(appName)) {
return fullPath; // Exact match, return immediately
}
fallback = fallback || fullPath; // Store as fallback, continue searching
}
} else if (platform === 'darwin') {
const stat = statSync(fullPath);
if (stat.isFile() && stat.mode & parseInt('111', 8)) {
const lowerName = entry.name.toLowerCase();
if (!lowerName.includes('helper') && !lowerName.includes('crashpad')) {
if (lowerName.includes(appName)) {
return fullPath; // Exact match, return immediately
}
fallback = fallback || fullPath; // Store as fallback, continue searching
}
}
} else if (platform === 'linux') {
// Linux executables (AppImage or unpacked)
if (entry.name.endsWith('.AppImage')) {
const lowerName = entry.name.toLowerCase();
if (lowerName.includes(appName)) {
return fullPath; // Exact match, return immediately
}
fallback = fallback || fullPath; // Store as fallback, continue searching
}
// Check for executable files (not .so libraries)
const stat = statSync(fullPath);
if (stat.isFile() && stat.mode & parseInt('111', 8) && !entry.name.includes('.so')) {
// Skip helper binaries
const lowerName = entry.name.toLowerCase();
if (!lowerName.includes('chrome') && !lowerName.includes('crashpad')) {
if (lowerName.includes(appName)) {
return fullPath; // Exact match, return immediately
}
fallback = fallback || fullPath; // Store as fallback, continue searching
}
}
}
}
}
return fallback; // Return fallback candidate if no exact appName match found
}
let executable = null;
const checkedDirs = [];
for (const root of candidateRoots) {
if (!existsSync(root)) {
checkedDirs.push(`${root} (missing)`);
continue;
}
const result = findExecutable(root, platform);
checkedDirs.push(root);
if (result) {
executable = result;
break;
}
}
if (!executable) {
console.error('Error: Could not find packaged executable.');
console.error('Platform:', platform);
console.error('Checked directories:', checkedDirs.join(', '));
process.exit(1);
}
console.log(executable);