import { spawn } from 'node:child_process' import fs from 'node:fs' import path from 'node:path' import type { Shell } from 'electron' import type { Game } from '../../domain/models/Game' import type { GameLauncher } from '../../domain/ports/GameLauncher' /** * Launching what was installed. * * A hosted title is a URL, so it goes to the browser. A native one is whatever the * store recorded: on macOS the app bundle through `open`, elsewhere the executable * from its own directory — the same working directory the menu entry uses, because * games load their assets relative to it. */ export class ElectronGameLauncher implements GameLauncher { public constructor (private readonly shell: Shell) {} public async launchGame (game: Game): Promise { if (game.mode === 'web' && game.hostedUrl !== null) { await this.shell.openExternal(game.hostedUrl) return true } const target = game.menuEntryPath ?? game.executablePath if (target === null || !fs.existsSync(target)) return false if (process.platform === 'darwin' && target.endsWith('.app')) { this.spawnDetached('open', [target], path.dirname(target)) return true } if (process.platform === 'win32' || target.endsWith('.desktop')) { const failure = await this.shell.openPath(target) if (failure === '') return true } const executable = game.executablePath ?? target this.spawnDetached(executable, [], path.dirname(executable)) return true } public async openFolder (directory: string): Promise { if (directory.length === 0) return false const failure = await this.shell.openPath(directory) return failure === '' } public async openUrl (url: string): Promise { if (!url.startsWith('https://')) return false await this.shell.openExternal(url) return true } private spawnDetached (command: string, commandArguments: readonly string[], cwd: string): void { spawn(command, [...commandArguments], { cwd, detached: true, stdio: 'ignore' }).unref() } }