import fs from 'fs'; import path from 'path'; const args = process.argv.slice(2); const normalizedArgs = args[0] === '--' ? args.slice(1) : args; const [platform, arch = ''] = normalizedArgs; if (!platform) { console.error( 'Usage: node tools/packaging/verify-electron-package-layout.mjs [arch]' ); process.exit(1); } const workspaceRoot = process.cwd(); const executablesRoot = path.join(workspaceRoot, 'dist', 'executables'); const workerRelativeDir = path.join( 'dist', 'apps', 'electron-backend', 'workers' ); const workerFiles = ['epg-parser.worker.js', 'database.worker.js']; const nativeModuleRelativeDirs = [ path.join('app.asar.unpacked', 'node_modules'), path.join('app.asar.unpacked', 'electron-backend', 'node_modules'), path.join( 'app.asar.unpacked', 'dist', 'apps', 'electron-backend', 'node_modules' ), ]; function directoryExists(directoryPath) { return fs.existsSync(directoryPath) && fs.statSync(directoryPath).isDirectory(); } function fileExists(filePath) { return fs.existsSync(filePath) && fs.statSync(filePath).isFile(); } function getMacResourceDirs() { const candidates = [ { arch: 'x64', directory: path.join( executablesRoot, 'mac', 'IPTVnator.app', 'Contents', 'Resources' ), }, { arch: 'arm64', directory: path.join( executablesRoot, 'mac-arm64', 'IPTVnator.app', 'Contents', 'Resources' ), }, ]; return candidates .filter((candidate) => !arch || candidate.arch === arch) .map((candidate) => candidate.directory) .filter(directoryExists); } function getUnpackedResourceDirs(prefix) { if (!directoryExists(executablesRoot)) { return []; } return fs .readdirSync(executablesRoot, { withFileTypes: true }) .filter( (entry) => entry.isDirectory() && entry.name.startsWith(prefix) && entry.name.endsWith('-unpacked') ) .map((entry) => path.join(executablesRoot, entry.name, 'resources')) .filter(directoryExists); } function getResourceDirs() { switch (platform) { case 'macos': return getMacResourceDirs(); case 'linux': return getUnpackedResourceDirs('linux'); case 'windows': return getUnpackedResourceDirs('win'); default: console.error(`Unsupported platform "${platform}"`); process.exit(1); } } function verifyResourceDir(resourceDir) { const missingWorkers = workerFiles.filter( (workerFile) => !fileExists(path.join(resourceDir, workerRelativeDir, workerFile)) ); const nativeModuleDirs = nativeModuleRelativeDirs.map((relativeDir) => path.join(resourceDir, relativeDir) ); const matchingNativeModuleDir = nativeModuleDirs.find((nativeDir) => fileExists(path.join(nativeDir, 'better-sqlite3', 'package.json')) ); const errors = []; if (missingWorkers.length > 0) { errors.push( `Missing worker artifacts in ${resourceDir}: ${missingWorkers.join(', ')}` ); } if (!matchingNativeModuleDir) { errors.push( [ `Unable to find unpacked better-sqlite3 in ${resourceDir}.`, 'Checked:', ...nativeModuleDirs.map((nativeDir) => `- ${nativeDir}`), ].join('\n') ); } return { resourceDir, errors, matchingNativeModuleDir, }; } const resourceDirs = getResourceDirs(); if (resourceDirs.length === 0) { console.error( `No packaged resource directories found for ${platform}${arch ? ` (${arch})` : ''}.` ); process.exit(1); } const results = resourceDirs.map(verifyResourceDir); const failures = results.filter((result) => result.errors.length > 0); for (const result of results) { console.log(`Verified packaged resources: ${result.resourceDir}`); if (result.matchingNativeModuleDir) { console.log( `Resolved unpacked better-sqlite3 at: ${result.matchingNativeModuleDir}` ); } } if (failures.length > 0) { for (const failure of failures) { console.error(failure.errors.join('\n')); } process.exit(1); }