import { existsSync, mkdirSync, readdirSync, symlinkSync } from 'node:fs'; import { homedir } from 'node:os'; import path from 'node:path'; import { getOpenClawConfigDir } from './paths'; const PLAYWRIGHT_BROWSERS_DIR = 'ms-playwright'; const PLAYWRIGHT_LOCKS_DIR = 'locks'; const PLAYWRIGHT_COMPLETE_MARKER = 'INSTALLATION_COMPLETE'; function resolveDefaultPlaywrightBrowsersPath(): string | null { if (process.platform === 'darwin') { return path.join(homedir(), 'Library', 'Caches', 'ms-playwright'); } if (process.platform === 'win32') { const localAppData = process.env.LOCALAPPDATA?.trim(); return localAppData ? path.join(localAppData, 'ms-playwright') : null; } return path.join(homedir(), '.cache', 'ms-playwright'); } export function resolveYinianPlaywrightBrowsersPath(): string { return path.join(getOpenClawConfigDir(), 'runtime', PLAYWRIGHT_BROWSERS_DIR); } export function resolveYinianPlaywrightInstallLockPath(): string { return path.join(getOpenClawConfigDir(), 'runtime', PLAYWRIGHT_LOCKS_DIR, 'playwright-chromium-install.lock'); } function mirrorCompletedDefaultBrowsers(targetRoot: string): void { const defaultRoot = resolveDefaultPlaywrightBrowsersPath(); if (!defaultRoot || defaultRoot === targetRoot || !existsSync(defaultRoot)) return; let entries; try { entries = readdirSync(defaultRoot, { withFileTypes: true }); } catch { return; } for (const entry of entries) { if (!entry.isDirectory() || !entry.name.startsWith('chromium')) continue; const sourceDir = path.join(defaultRoot, entry.name); if (!existsSync(path.join(sourceDir, PLAYWRIGHT_COMPLETE_MARKER))) continue; const targetDir = path.join(targetRoot, entry.name); if (existsSync(targetDir)) continue; try { symlinkSync(sourceDir, targetDir, process.platform === 'win32' ? 'junction' : 'dir'); } catch { // Best effort. A missing browser remains a warning-level diagnostic. } } } export function ensureYinianPlaywrightRuntimeDirs(): void { const browsersPath = resolveYinianPlaywrightBrowsersPath(); mkdirSync(browsersPath, { recursive: true }); mkdirSync(path.dirname(resolveYinianPlaywrightInstallLockPath()), { recursive: true }); mirrorCompletedDefaultBrowsers(browsersPath); } export function buildPlaywrightRuntimeEnv( baseEnv: Record = process.env as Record, ): Record { const explicitBrowsersPath = baseEnv.PLAYWRIGHT_BROWSERS_PATH?.trim(); return { PLAYWRIGHT_BROWSERS_PATH: explicitBrowsersPath || resolveYinianPlaywrightBrowsersPath(), PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: baseEnv.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD ?? '1', YINIAN_PLAYWRIGHT_INSTALL_LOCK_PATH: baseEnv.YINIAN_PLAYWRIGHT_INSTALL_LOCK_PATH ?? resolveYinianPlaywrightInstallLockPath(), }; }