-
Notifications
You must be signed in to change notification settings - Fork 19
fix: use XDG-compliant paths for OpenCode user scope installations #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
|
|
||
| from pathlib import Path | ||
| import os | ||
| from platformdirs import PlatformDirs | ||
|
|
||
| # Base lola directory | ||
| LOLA_HOME = Path(os.environ.get("LOLA_HOME", Path.home() / ".lola")) | ||
|
|
@@ -24,3 +25,17 @@ | |
|
|
||
| # MCP servers definition filename | ||
| MCPS_FILE = "mcps.json" | ||
|
|
||
| # Platform-specific directories for user-scope installations | ||
| _PLATFORM_DIRS = PlatformDirs("opencode", appauthor=False) | ||
|
|
||
|
|
||
| def get_user_config_dir() -> Path: | ||
| """Get user configuration directory using platform conventions. | ||
|
|
||
| Returns platform-appropriate config directory: | ||
| - Linux/Unix: ~/.config | ||
| - macOS: ~/Library/Application Support | ||
| - Windows: %APPDATA% | ||
|
Comment on lines
+34
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct the return-path description in this docstring. The function returns an app-specific directory (includes Suggested docstring fix def get_user_config_dir() -> Path:
"""Get user configuration directory using platform conventions.
-
- Returns platform-appropriate config directory:
- - Linux/Unix: ~/.config
- - macOS: ~/Library/Application Support
- - Windows: %APPDATA%
+
+ Returns platform-appropriate app config directory for OpenCode:
+ - Linux/Unix: ~/.config/opencode
+ - macOS: ~/Library/Application Support/opencode
+ - Windows: %APPDATA%\\opencode
"""
return Path(_PLATFORM_DIRS.user_config_dir)🤖 Prompt for AI Agents |
||
| """ | ||
| return Path(_PLATFORM_DIRS.user_config_dir) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,35 +1,173 @@ | ||
| """Tests for OpenCodeTarget scope-aware path resolution.""" | ||
|
|
||
| from pathlib import Path | ||
| import pytest | ||
| from unittest.mock import Mock, patch | ||
|
|
||
| from lola.targets.opencode import OpenCodeTarget | ||
| from lola.config import get_user_config_dir | ||
|
|
||
|
|
||
| # --- User scope tests --- | ||
| # --- User config directory tests --- | ||
|
|
||
|
|
||
| def test_opencode_command_path_user_scope(): | ||
| def test_get_user_config_dir_with_xdg_env_set(monkeypatch): | ||
| """Test get_user_config_dir when XDG_CONFIG_HOME is set.""" | ||
| monkeypatch.setenv("XDG_CONFIG_HOME", "/custom/config") | ||
| # Force reload the platformdirs instance | ||
| import importlib | ||
| import lola.config | ||
|
|
||
| importlib.reload(lola.config) | ||
| assert get_user_config_dir() == Path("/custom/config/opencode") | ||
|
|
||
|
|
||
| def test_get_user_config_dir_without_env(monkeypatch): | ||
| """Test get_user_config_dir falls back to platform defaults.""" | ||
| monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) | ||
| # Force reload the platformdirs instance | ||
| import importlib | ||
| import lola.config | ||
|
|
||
| importlib.reload(lola.config) | ||
| result = get_user_config_dir() | ||
| # On Unix systems, this should be ~/.config | ||
| # On other platforms, platformdirs will return appropriate paths | ||
| assert result.is_absolute() | ||
|
|
||
|
|
||
| # --- User scope tests with platformdirs --- | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def reload_config(): | ||
| """Fixture to reload config module after environment changes.""" | ||
|
|
||
| def _reload(): | ||
| import importlib | ||
| import lola.config | ||
|
|
||
| importlib.reload(lola.config) | ||
|
|
||
| return _reload | ||
|
|
||
|
|
||
| def test_opencode_command_path_user_scope_custom_config(monkeypatch, reload_config): | ||
| """Test command path uses custom XDG_CONFIG_HOME when set.""" | ||
| monkeypatch.setenv("XDG_CONFIG_HOME", "/custom/config") | ||
| reload_config() | ||
| target = OpenCodeTarget() | ||
| path = target.get_command_path("/home/user/project", "user") | ||
| assert path == Path("/custom/config/opencode/commands") | ||
|
|
||
|
|
||
| def test_opencode_agent_path_user_scope_custom_config(monkeypatch, reload_config): | ||
| """Test agent path uses custom XDG_CONFIG_HOME when set.""" | ||
| monkeypatch.setenv("XDG_CONFIG_HOME", "/custom/config") | ||
| reload_config() | ||
| target = OpenCodeTarget() | ||
| path = target.get_agent_path("/home/user/project", "user") | ||
| assert path == Path("/custom/config/opencode/agents") | ||
|
|
||
|
|
||
| def test_opencode_instructions_path_user_scope_custom_config( | ||
| monkeypatch, reload_config | ||
| ): | ||
| """Test instructions path uses custom XDG_CONFIG_HOME when set.""" | ||
| monkeypatch.setenv("XDG_CONFIG_HOME", "/custom/config") | ||
| reload_config() | ||
| target = OpenCodeTarget() | ||
| path = target.get_instructions_path("/home/user/project", "user") | ||
| assert path == Path("/custom/config/opencode/AGENTS.md") | ||
|
|
||
|
|
||
| def test_opencode_mcp_path_user_scope_custom_config(monkeypatch, reload_config): | ||
| """Test MCP path uses custom XDG_CONFIG_HOME when set.""" | ||
| monkeypatch.setenv("XDG_CONFIG_HOME", "/custom/config") | ||
| reload_config() | ||
| target = OpenCodeTarget() | ||
| path = target.get_mcp_path("/home/user/project", "user") | ||
| assert path == Path("/custom/config/opencode/opencode.json") | ||
|
|
||
|
|
||
| def test_opencode_command_path_user_scope_platform_default(monkeypatch, reload_config): | ||
| """Test command path uses platform defaults when XDG_CONFIG_HOME unset.""" | ||
| monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) | ||
| reload_config() | ||
| target = OpenCodeTarget() | ||
| path = target.get_command_path("/home/user/project", "user") | ||
| assert path == Path.home() / ".opencode" / "commands" | ||
| # Should use platformdirs default - ends with opencode/commands | ||
| assert str(path).endswith("opencode/commands") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make platform-default path assertions separator-agnostic. Using Suggested test-safe assertion update- assert str(path).endswith("opencode/commands")
+ assert path.parts[-2:] == ("opencode", "commands")
@@
- assert str(path).endswith("opencode/agents")
+ assert path.parts[-2:] == ("opencode", "agents")
@@
- assert str(path).endswith("opencode/AGENTS.md")
+ assert path.parts[-2:] == ("opencode", "AGENTS.md")
@@
- assert str(path).endswith("opencode/opencode.json")
+ assert path.parts[-2:] == ("opencode", "opencode.json")Also applies to: 111-111, 124-124, 135-135 🤖 Prompt for AI Agents |
||
| assert path.is_absolute() | ||
|
|
||
|
|
||
| def test_opencode_agent_path_user_scope(): | ||
| def test_opencode_agent_path_user_scope_platform_default(monkeypatch, reload_config): | ||
| """Test agent path uses platform defaults when XDG_CONFIG_HOME unset.""" | ||
| monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) | ||
| reload_config() | ||
| target = OpenCodeTarget() | ||
| path = target.get_agent_path("/home/user/project", "user") | ||
| assert path == Path.home() / ".opencode" / "agents" | ||
| # Should use platformdirs default - ends with opencode/agents | ||
| assert str(path).endswith("opencode/agents") | ||
| assert path.is_absolute() | ||
|
|
||
|
|
||
| def test_opencode_instructions_path_user_scope(): | ||
| def test_opencode_instructions_path_user_scope_platform_default( | ||
| monkeypatch, reload_config | ||
| ): | ||
| """Test instructions path uses platform defaults when XDG_CONFIG_HOME unset.""" | ||
| monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) | ||
| reload_config() | ||
| target = OpenCodeTarget() | ||
| path = target.get_instructions_path("/home/user/project", "user") | ||
| assert path == Path.home() / "AGENTS.md" | ||
| # Should use platformdirs default - ends with opencode/AGENTS.md | ||
| assert str(path).endswith("opencode/AGENTS.md") | ||
| assert path.is_absolute() | ||
|
|
||
|
|
||
| def test_opencode_mcp_path_user_scope(): | ||
| def test_opencode_mcp_path_user_scope_platform_default(monkeypatch, reload_config): | ||
| """Test MCP path uses platform defaults when XDG_CONFIG_HOME unset.""" | ||
| monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) | ||
| reload_config() | ||
| target = OpenCodeTarget() | ||
| path = target.get_mcp_path("/home/user/project", "user") | ||
| assert path == Path.home() / "opencode.json" | ||
| # Should use platformdirs default - ends with opencode/opencode.json | ||
| assert str(path).endswith("opencode/opencode.json") | ||
| assert path.is_absolute() | ||
|
|
||
|
|
||
| # --- Cross-platform path tests with mocked platformdirs --- | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "platform_config_dir,platform_name", | ||
| [ | ||
| ("/home/user/.config/opencode", "Linux/Unix"), | ||
| ("/Users/user/Library/Application Support/opencode", "macOS"), | ||
| (r"C:\Users\user\AppData\Roaming\opencode", "Windows"), | ||
| ], | ||
| ) | ||
| def test_opencode_paths_cross_platform(platform_config_dir, platform_name): | ||
| """Test OpenCode paths work correctly on different platforms.""" | ||
| mock_platform_dirs = Mock() | ||
| mock_platform_dirs.user_config_dir = platform_config_dir | ||
|
|
||
| with patch("lola.config._PLATFORM_DIRS", mock_platform_dirs): | ||
| target = OpenCodeTarget() | ||
|
|
||
| # Test all path types | ||
| command_path = target.get_command_path("/project", "user") | ||
| agent_path = target.get_agent_path("/project", "user") | ||
| instructions_path = target.get_instructions_path("/project", "user") | ||
| mcp_path = target.get_mcp_path("/project", "user") | ||
|
|
||
| # Expected base path (platformdirs already includes opencode) | ||
| base = Path(platform_config_dir) | ||
|
|
||
| assert command_path == base / "commands" | ||
| assert agent_path == base / "agents" | ||
| assert instructions_path == base / "AGENTS.md" | ||
| assert mcp_path == base / "opencode.json" | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| def test_opencode_skill_path_user_scope(): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.