forgexa-cli 1.27.2__tar.gz → 1.27.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/PKG-INFO +1 -1
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli/daemon.py +9 -8
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli/main.py +13 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/pyproject.toml +1 -1
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_local_bind_commands.py +38 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/README.md +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli/agent_core.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/setup.cfg +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.27.2 → forgexa_cli-1.27.3}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.27.
|
|
2
|
+
__version__ = "1.27.3"
|
|
@@ -909,7 +909,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
909
909
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
910
910
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
911
911
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
912
|
-
DAEMON_VERSION = "1.27.
|
|
912
|
+
DAEMON_VERSION = "1.27.3"
|
|
913
913
|
|
|
914
914
|
|
|
915
915
|
def _detect_client_type() -> str:
|
|
@@ -1925,13 +1925,14 @@ async def _validate_local_workspace_binding(binding: dict[str, Any], runtime_id:
|
|
|
1925
1925
|
entry = _load_local_workspaces_mapping().get(binding_id)
|
|
1926
1926
|
if not binding_id or not isinstance(entry, dict):
|
|
1927
1927
|
return "mapping_missing"
|
|
1928
|
-
if (
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1928
|
+
if str(entry.get("project_id") or "") != str(binding.get("project_id") or ""):
|
|
1929
|
+
return "mapping_project_mismatch"
|
|
1930
|
+
if str(entry.get("user_id") or "") != str(binding.get("user_id") or ""):
|
|
1931
|
+
return "mapping_user_mismatch"
|
|
1932
|
+
if str(entry.get("runtime_id") or "") != str(runtime_id):
|
|
1933
|
+
return "mapping_runtime_mismatch"
|
|
1934
|
+
if str(entry.get("repo_identity") or "") != str(binding.get("repo_identity") or ""):
|
|
1935
|
+
return "mapping_repository_mismatch"
|
|
1935
1936
|
|
|
1936
1937
|
local_path = Path(str(entry.get("local_path") or "")).expanduser()
|
|
1937
1938
|
try:
|
|
@@ -2857,6 +2857,19 @@ def cmd_project_bind_local(args: argparse.Namespace) -> None:
|
|
|
2857
2857
|
if not binding_id:
|
|
2858
2858
|
print("Error: failed to create local workspace binding", file=sys.stderr)
|
|
2859
2859
|
sys.exit(1)
|
|
2860
|
+
expected_identity = binding.get("repo_identity")
|
|
2861
|
+
if isinstance(expected_identity, str) and expected_identity != identity:
|
|
2862
|
+
try:
|
|
2863
|
+
_delete(f"/projects/{args.project}/local-workspace-bindings/{binding_id}")
|
|
2864
|
+
except Exception:
|
|
2865
|
+
pass
|
|
2866
|
+
print(
|
|
2867
|
+
"Error: this folder's Git remote does not match the selected Forgexa project's repository "
|
|
2868
|
+
f"(project: {expected_identity}, this folder: {identity}). "
|
|
2869
|
+
"Open the matching folder or update the project's repository URL.",
|
|
2870
|
+
file=sys.stderr,
|
|
2871
|
+
)
|
|
2872
|
+
sys.exit(1)
|
|
2860
2873
|
|
|
2861
2874
|
mapping = load_local_workspaces_mapping()
|
|
2862
2875
|
local_path_str = str(local_path)
|
|
@@ -152,6 +152,44 @@ def test_bind_local_rejects_unrecognized_remote_url(tmp_path: Path, monkeypatch:
|
|
|
152
152
|
main.cmd_project_bind_local(args)
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
def test_bind_local_rejects_project_repository_mismatch_before_writing_mapping(
|
|
156
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
157
|
+
) -> None:
|
|
158
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
159
|
+
repo = tmp_path / "other-project"
|
|
160
|
+
_init_git_repo_with_remote(repo, "https://github.com/acme/other.git")
|
|
161
|
+
|
|
162
|
+
monkeypatch.setattr(main, "_get_runtimes", lambda: [
|
|
163
|
+
{"id": "runtime-1", "pool_type": "personal", "daemon_id": "d1", "device_name": "laptop"},
|
|
164
|
+
])
|
|
165
|
+
monkeypatch.setattr(main, "_get", lambda path: {"id": "user-1"} if path == "/auth/me" else None)
|
|
166
|
+
|
|
167
|
+
posts: list[tuple[str, dict]] = []
|
|
168
|
+
deletes: list[str] = []
|
|
169
|
+
|
|
170
|
+
def fake_post(path: str, data=None):
|
|
171
|
+
posts.append((path, data or {}))
|
|
172
|
+
assert path == "/projects/project-1/local-workspace-bindings"
|
|
173
|
+
return {
|
|
174
|
+
"id": "binding-1",
|
|
175
|
+
"status": "pending",
|
|
176
|
+
"repo_identity": "github.com/acme/widgets",
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
monkeypatch.setattr(main, "_post", fake_post)
|
|
180
|
+
monkeypatch.setattr(main, "_delete", lambda path: deletes.append(path))
|
|
181
|
+
|
|
182
|
+
args = SimpleNamespace(project="project-1", path=str(repo), runtime=None)
|
|
183
|
+
with pytest.raises(SystemExit):
|
|
184
|
+
main.cmd_project_bind_local(args)
|
|
185
|
+
|
|
186
|
+
assert posts == [
|
|
187
|
+
("/projects/project-1/local-workspace-bindings", {"runtime_id": "runtime-1"}),
|
|
188
|
+
]
|
|
189
|
+
assert deletes == ["/projects/project-1/local-workspace-bindings/binding-1"]
|
|
190
|
+
assert not (tmp_path / ".forgexa" / "local-workspaces.json").exists()
|
|
191
|
+
|
|
192
|
+
|
|
155
193
|
def test_bind_local_requires_disambiguation_with_multiple_personal_runtimes(
|
|
156
194
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
|
157
195
|
) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|