Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ internal AbstractPluginEnvironment(List<PluginMetadata> pluginMetadataList, Plug
PluginSettings = pluginSettings;
}

/// <summary>
/// Resolves the configured plugin settings file path to an absolute path.
/// Supports both absolute paths and relative paths (relative to ProgramDirectory).
/// </summary>
private string ResolvedPluginsSettingsFilePath => DataLocation.ResolveAbsolutePath(PluginsSettingsFilePath);

internal IEnumerable<PluginPair> Setup()
{
// If no plugin is using the language, return empty list
Expand All @@ -48,13 +54,14 @@ internal IEnumerable<PluginPair> Setup()
return new List<PluginPair>();
}

if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
var resolvedPath = ResolvedPluginsSettingsFilePath;
if (!string.IsNullOrEmpty(resolvedPath) && FilesFolders.FileExists(resolvedPath))
{
// Ensure latest only if user is using Flow's environment setup.
if (PluginsSettingsFilePath.StartsWith(EnvPath, StringComparison.OrdinalIgnoreCase))
EnsureLatestInstalled(ExecutablePath, PluginsSettingsFilePath, EnvPath);
if (resolvedPath.StartsWith(EnvPath, StringComparison.OrdinalIgnoreCase))
EnsureLatestInstalled(ExecutablePath, resolvedPath, EnvPath);

return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
return SetPathForPluginPairs(resolvedPath, Language);
}

var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine);
Expand Down Expand Up @@ -103,9 +110,10 @@ internal IEnumerable<PluginPair> Setup()
InstallEnvironment();
}

if (FilesFolders.FileExists(PluginsSettingsFilePath))
resolvedPath = ResolvedPluginsSettingsFilePath;
if (FilesFolders.FileExists(resolvedPath))
{
return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
return SetPathForPluginPairs(resolvedPath, Language);
}
else
{
Expand Down
20 changes: 20 additions & 0 deletions Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,25 @@ public static bool PortableDataLocationInUse()
public const string PluginEnvironments = "Environments";
public const string PluginDeleteFile = "NeedDelete.txt";
public static readonly string PluginEnvironmentsPath = Path.Combine(DataDirectory(), PluginEnvironments);

/// <summary>
/// Resolves a path that may be relative to an absolute path.
/// If the path is already absolute, returns it as-is.
/// If the path is relative (starts with . or doesn't contain a drive), resolves it relative to ProgramDirectory.
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation comment states the method handles paths that "start with . or doesn't contain a drive", but the implementation only checks if the path is rooted using Path.IsPathRooted. This check returns false for relative paths (including those starting with ".") but may have unexpected behavior with UNC paths or paths on Unix-like systems. Consider clarifying the documentation to match the actual implementation, which relies on Path.IsPathRooted for the determination.

Suggested change
/// If the path is relative (starts with . or doesn't contain a drive), resolves it relative to ProgramDirectory.
/// If the path is not rooted (as determined by <see cref="Path.IsPathRooted(string)"/>), resolves it relative to ProgramDirectory.

Copilot uses AI. Check for mistakes.
/// </summary>
/// <param name="path">The path to resolve</param>
/// <returns>An absolute path</returns>
public static string ResolveAbsolutePath(string path)
{
if (string.IsNullOrEmpty(path))
return path;

// If already absolute, return as-is
if (Path.IsPathRooted(path))
return path;

// Resolve relative to ProgramDirectory
return Path.GetFullPath(Path.Combine(Constant.ProgramDirectory, path));
Comment on lines +62 to +63
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ResolveAbsolutePath method doesn't handle exceptions that can be thrown by Path.GetFullPath, such as ArgumentException for invalid path characters, NotSupportedException for invalid path formats, or PathTooLongException. If a user configures an invalid relative path, this could crash the application during plugin initialization. Consider adding try-catch error handling to gracefully handle invalid paths, either by logging an error and returning the original path, or by providing a meaningful error message to the user.

Suggested change
// Resolve relative to ProgramDirectory
return Path.GetFullPath(Path.Combine(Constant.ProgramDirectory, path));
// Resolve relative to ProgramDirectory, handling invalid path formats gracefully
try
{
return Path.GetFullPath(Path.Combine(Constant.ProgramDirectory, path));
}
catch (Exception ex) when (ex is ArgumentException ||
ex is NotSupportedException ||
ex is PathTooLongException)
{
// If the path cannot be resolved (invalid characters, format, or too long),
// return the original path to avoid crashing the application.
return path;
}

Copilot uses AI. Check for mistakes.
}
Comment on lines +53 to +64
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new ResolveAbsolutePath method lacks comprehensive test coverage. Given that this is a critical feature for portability and involves path resolution logic with edge cases (relative paths starting with ".", paths without drives, UNC paths, etc.), it should have unit tests to verify correct behavior across different scenarios. Consider adding tests in Flow.Launcher.Test to verify: 1) absolute paths are returned unchanged, 2) relative paths like ".\runtime\python.exe" resolve correctly, 3) paths without leading "." also resolve correctly, and 4) edge cases like null/empty strings are handled properly.

Copilot uses AI. Check for mistakes.
}
}
Loading