const { existsSync, readdirSync, statSync } = require('fs'); const { join } = require('path'); const linuxArchivePath = ['.cache', 'chromium', 'chrome-linux64', 'chrome']; const windowsArchivePath = ['.cache', 'chromium', 'chrome-win64', 'chrome.exe']; const macArchivePath = [ '.cache', 'chromium', 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium' ]; function sortChromiumDirectories(entries) { return [...entries].sort((left, right) => { const leftMatch = /chromium-?(\d+)/i.exec(left); const rightMatch = /chromium-?(\d+)/i.exec(right); const leftValue = leftMatch ? Number.parseInt(leftMatch[1], 10) : Number.NEGATIVE_INFINITY; const rightValue = rightMatch ? Number.parseInt(rightMatch[1], 10) : Number.NEGATIVE_INFINITY; return rightValue - leftValue; }); } function expandVersionedArchives(rootDir = join(__dirname, '..')) { const base = join(rootDir, '.cache', 'chromium'); if (!existsSync(base)) { return []; } const nestedCandidates = []; for (const entry of readdirSync(base)) { const nestedRoot = join(base, entry); try { if (!statSync(nestedRoot).isDirectory()) { continue; } } catch { continue; } nestedCandidates.push( join(nestedRoot, 'chrome-linux64', 'chrome'), join(nestedRoot, 'chrome-win64', 'chrome.exe'), join(nestedRoot, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium') ); } return nestedCandidates; } function expandNestedArchives(rootDir = join(__dirname, '..')) { const base = join(rootDir, '.cache', 'chromium'); if (!existsSync(base)) { return []; } const maxDepth = 4; const queue = [{ dir: base, depth: 0 }]; const candidates = []; while (queue.length) { const { dir, depth } = queue.shift(); let entries; try { entries = readdirSync(dir); } catch { continue; } for (const entry of entries) { const nested = join(dir, entry); let stats; try { stats = statSync(nested); } catch { continue; } if (!stats.isDirectory()) { continue; } candidates.push( join(nested, 'chrome-linux64', 'chrome'), join(nested, 'chrome-win64', 'chrome.exe'), join(nested, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium') ); if (depth + 1 <= maxDepth) { queue.push({ dir: nested, depth: depth + 1 }); } } } return candidates; } function candidatePaths(rootDir = join(__dirname, '..')) { const { env } = process; const playwrightBase = join(rootDir, 'node_modules', 'playwright-core', '.local-browsers'); const homePlaywrightBase = env.HOME ? join(env.HOME, '.cache', 'ms-playwright') : null; const windowsLocalPlaywrightBase = env.LOCALAPPDATA ? join(env.LOCALAPPDATA, 'ms-playwright') : null; const windowsProfilePlaywrightBase = env.USERPROFILE ? join(env.USERPROFILE, 'AppData', 'Local', 'ms-playwright') : null; const playwrightCacheBases = [homePlaywrightBase, windowsLocalPlaywrightBase, windowsProfilePlaywrightBase].filter(Boolean); let playwrightChromium = []; try { if (existsSync(playwrightBase)) { const chromiumEntries = sortChromiumDirectories( readdirSync(playwrightBase).filter((d) => d.startsWith('chromium-')) ); playwrightChromium = chromiumEntries .map((d) => join(playwrightBase, d, 'chrome-linux', 'chrome')) .concat( chromiumEntries.map((d) => join(playwrightBase, d, 'chrome-win', 'chrome.exe')), chromiumEntries.map((d) => join(playwrightBase, d, 'chrome-win64', 'chrome.exe')), chromiumEntries.map((d) => join(playwrightBase, d, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium')) ); } } catch { playwrightChromium = []; } let homeChromium = []; try { for (const cacheBase of playwrightCacheBases) { if (!existsSync(cacheBase)) { continue; } const chromiumEntries = sortChromiumDirectories( readdirSync(cacheBase).filter((d) => d.startsWith('chromium')) ); homeChromium.push( ...chromiumEntries.flatMap((d) => [ join(cacheBase, d, 'chrome-linux', 'chrome'), join(cacheBase, d, 'chrome-win', 'chrome.exe'), join(cacheBase, d, 'chrome-win64', 'chrome.exe'), join(cacheBase, d, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'), ]) ); } } catch { homeChromium = []; } const baseCandidates = [ env.STELLAOPS_CHROMIUM_BIN, env.CHROME_BIN, env.PUPPETEER_EXECUTABLE_PATH, ...playwrightChromium, ...homeChromium, '/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', join(rootDir, ...linuxArchivePath), join(rootDir, ...windowsArchivePath), join(rootDir, ...macArchivePath), ...expandVersionedArchives(rootDir), ...expandNestedArchives(rootDir) ]; const seen = new Set(); return baseCandidates .filter(Boolean) .filter((candidate) => { if (seen.has(candidate)) { return false; } seen.add(candidate); return true; }); } function resolveChromeBinary(rootDir = join(__dirname, '..')) { for (const candidate of candidatePaths(rootDir)) { if (existsSync(candidate)) { return candidate; } } return null; } module.exports = { candidatePaths, resolveChromeBinary };