import 'dart:io'; enum GoCoreLaunchSource { buildArtifact } class GoCoreLaunch { const GoCoreLaunch({ required this.executable, required this.source, this.arguments = const [], this.workingDirectory, }); final String executable; final GoCoreLaunchSource source; final List arguments; final String? workingDirectory; } typedef GoCoreBinaryExistsResolver = Future Function(String command); class GoCoreLocator { GoCoreLocator({ GoCoreBinaryExistsResolver? binaryExistsResolver, String? workspaceRoot, String Function()? resolvedExecutableResolver, }) : _binaryExistsResolver = binaryExistsResolver, _workspaceRoot = workspaceRoot, _resolvedExecutableResolver = resolvedExecutableResolver; final GoCoreBinaryExistsResolver? _binaryExistsResolver; final String? _workspaceRoot; final String Function()? _resolvedExecutableResolver; Future locate() async { for (final root in _candidateRoots()) { final path = '$root/build/bin/xworkmate-go-core'; if (await _binaryExists(path)) { return GoCoreLaunch( executable: path, source: GoCoreLaunchSource.buildArtifact, ); } } return null; } Future isAvailable() async => await locate() != null; List _candidateRoots() { final roots = {}; final explicitRoot = _workspaceRoot?.trim() ?? ''; if (explicitRoot.isNotEmpty) { roots.add(explicitRoot); roots.addAll(_ancestorPaths(Directory(explicitRoot))); } final currentPath = Directory.current.path.trim(); if (currentPath.isNotEmpty) { roots.add(currentPath); roots.addAll(_ancestorPaths(Directory(currentPath))); } final resolvedExecutable = (_resolvedExecutableResolver?.call() ?? Platform.resolvedExecutable) .trim(); if (resolvedExecutable.isNotEmpty) { final executableDirectory = File(resolvedExecutable).parent; roots.add(executableDirectory.path); roots.addAll(_ancestorPaths(executableDirectory)); } return roots .where((path) => path.trim().isNotEmpty) .toList(growable: false); } List _ancestorPaths(Directory start) { final ancestors = []; var current = start.absolute; while (true) { final parent = current.parent; if (parent.path == current.path) { break; } ancestors.add(parent.path); current = parent; } return ancestors; } Future _binaryExists(String command) async => (_binaryExistsResolver?.call(command)) ?? File(command).exists(); }