forgexa-cli 1.16.4__tar.gz → 1.16.6__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.16.4 → forgexa_cli-1.16.6}/PKG-INFO +4 -3
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/README.md +3 -2
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli/daemon.py +13 -5
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli/main.py +74 -6
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli.egg-info/PKG-INFO +4 -3
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/pyproject.toml +1 -1
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/tests/test_auth_and_runtime_commands.py +117 -0
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.16.4 → forgexa_cli-1.16.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forgexa-cli
|
|
3
|
-
Version: 1.16.
|
|
3
|
+
Version: 1.16.6
|
|
4
4
|
Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
|
|
5
5
|
Author-email: Jason Sun <dev.winds@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -127,10 +127,11 @@ The command detects how `forgexa-cli` was installed:
|
|
|
127
127
|
|
|
128
128
|
- `pipx` install: uses `pipx upgrade forgexa-cli`, or `pipx install --force forgexa-cli==<version>` for a pinned version
|
|
129
129
|
- `pip` install: uses `python -m pip install --upgrade forgexa-cli`
|
|
130
|
+
- non-editable local path install (`pip install /path/to/cli`): follows the same `pip` upgrade path and switches to the published PyPI release
|
|
130
131
|
|
|
131
|
-
For safety, `forgexa upgrade` stops any running local daemon before upgrading.
|
|
132
|
+
For safety, `forgexa upgrade` stops any running local daemon before upgrading. If the daemon was previously started in background mode, the CLI starts it again automatically after a successful upgrade; otherwise it prints a manual restart hint.
|
|
132
133
|
|
|
133
|
-
`forgexa upgrade`
|
|
134
|
+
`forgexa upgrade` supports standard PyPI / pipx installs and non-editable local path installs. Editable installs, VCS installs, and other direct URL installs are rejected with a manual upgrade hint.
|
|
134
135
|
|
|
135
136
|
## Configuration
|
|
136
137
|
|
|
@@ -95,10 +95,11 @@ The command detects how `forgexa-cli` was installed:
|
|
|
95
95
|
|
|
96
96
|
- `pipx` install: uses `pipx upgrade forgexa-cli`, or `pipx install --force forgexa-cli==<version>` for a pinned version
|
|
97
97
|
- `pip` install: uses `python -m pip install --upgrade forgexa-cli`
|
|
98
|
+
- non-editable local path install (`pip install /path/to/cli`): follows the same `pip` upgrade path and switches to the published PyPI release
|
|
98
99
|
|
|
99
|
-
For safety, `forgexa upgrade` stops any running local daemon before upgrading.
|
|
100
|
+
For safety, `forgexa upgrade` stops any running local daemon before upgrading. If the daemon was previously started in background mode, the CLI starts it again automatically after a successful upgrade; otherwise it prints a manual restart hint.
|
|
100
101
|
|
|
101
|
-
`forgexa upgrade`
|
|
102
|
+
`forgexa upgrade` supports standard PyPI / pipx installs and non-editable local path installs. Editable installs, VCS installs, and other direct URL installs are rejected with a manual upgrade hint.
|
|
102
103
|
|
|
103
104
|
## Configuration
|
|
104
105
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.16.
|
|
2
|
+
__version__ = "1.16.6"
|
|
@@ -523,7 +523,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
523
523
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
524
524
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
525
525
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
526
|
-
DAEMON_VERSION = "1.16.
|
|
526
|
+
DAEMON_VERSION = "1.16.6"
|
|
527
527
|
|
|
528
528
|
|
|
529
529
|
def _detect_client_type() -> str:
|
|
@@ -2617,7 +2617,9 @@ class WorkspaceManager:
|
|
|
2617
2617
|
try:
|
|
2618
2618
|
await self._git(
|
|
2619
2619
|
"push", "origin", "--delete", branch_name,
|
|
2620
|
-
cwd=ws_path,
|
|
2620
|
+
cwd=ws_path,
|
|
2621
|
+
timeout=settings.GIT_PUSH_TIMEOUT,
|
|
2622
|
+
project_key=project_key,
|
|
2621
2623
|
)
|
|
2622
2624
|
logger.info(
|
|
2623
2625
|
"Deleted remote branch %s for fresh analysis restart",
|
|
@@ -2930,7 +2932,9 @@ class WorkspaceManager:
|
|
|
2930
2932
|
await self._git(
|
|
2931
2933
|
"fetch", "origin",
|
|
2932
2934
|
f"{branch_name}:refs/remotes/origin/{branch_name}",
|
|
2933
|
-
cwd=main_repo,
|
|
2935
|
+
cwd=main_repo,
|
|
2936
|
+
timeout=settings.GIT_FETCH_TIMEOUT,
|
|
2937
|
+
project_key=project_key,
|
|
2934
2938
|
)
|
|
2935
2939
|
except RuntimeError:
|
|
2936
2940
|
pass # Branch may not exist on remote yet (first analysis)
|
|
@@ -2986,7 +2990,9 @@ class WorkspaceManager:
|
|
|
2986
2990
|
await self._git(
|
|
2987
2991
|
"fetch", "origin",
|
|
2988
2992
|
f"{branch_name}:refs/remotes/origin/{branch_name}",
|
|
2989
|
-
cwd=main_repo,
|
|
2993
|
+
cwd=main_repo,
|
|
2994
|
+
timeout=settings.GIT_FETCH_TIMEOUT,
|
|
2995
|
+
project_key=project_key,
|
|
2990
2996
|
)
|
|
2991
2997
|
except RuntimeError:
|
|
2992
2998
|
pass
|
|
@@ -3023,7 +3029,9 @@ class WorkspaceManager:
|
|
|
3023
3029
|
try:
|
|
3024
3030
|
await self._git(
|
|
3025
3031
|
"push", "origin", "--delete", branch_name,
|
|
3026
|
-
cwd=main_repo,
|
|
3032
|
+
cwd=main_repo,
|
|
3033
|
+
timeout=settings.GIT_PUSH_TIMEOUT,
|
|
3034
|
+
project_key=project_key,
|
|
3027
3035
|
)
|
|
3028
3036
|
logger.info(
|
|
3029
3037
|
"Deleted remote branch %s for fresh analysis restart (new worktree path)",
|
|
@@ -402,7 +402,7 @@ def _unsupported_upgrade_source(direct_url: dict | None) -> str | None:
|
|
|
402
402
|
return f"{vcs} checkout"
|
|
403
403
|
url = str(direct_url.get("url") or "").strip()
|
|
404
404
|
if url.startswith("file:"):
|
|
405
|
-
return
|
|
405
|
+
return None
|
|
406
406
|
if url:
|
|
407
407
|
return "direct URL install"
|
|
408
408
|
return "non-standard install source"
|
|
@@ -694,6 +694,62 @@ def _stop_daemon_if_running() -> tuple[bool, int | None]:
|
|
|
694
694
|
return True, pid
|
|
695
695
|
|
|
696
696
|
|
|
697
|
+
_DAEMON_START_CONTEXT_PATH: Path = Path.home() / ".forgexa" / "daemon" / "start-context.json"
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def _write_daemon_start_context(*, mode: str, server_url: str | None) -> None:
|
|
701
|
+
try:
|
|
702
|
+
_DAEMON_START_CONTEXT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
703
|
+
_DAEMON_START_CONTEXT_PATH.write_text(
|
|
704
|
+
json.dumps(
|
|
705
|
+
{
|
|
706
|
+
"mode": mode,
|
|
707
|
+
"server_url": (server_url or "").rstrip("/") or None,
|
|
708
|
+
}
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
except Exception:
|
|
712
|
+
pass
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _read_daemon_start_context() -> dict[str, str] | None:
|
|
716
|
+
try:
|
|
717
|
+
payload = json.loads(_DAEMON_START_CONTEXT_PATH.read_text())
|
|
718
|
+
except Exception:
|
|
719
|
+
return None
|
|
720
|
+
if not isinstance(payload, dict):
|
|
721
|
+
return None
|
|
722
|
+
mode = str(payload.get("mode") or "").strip()
|
|
723
|
+
server_url = str(payload.get("server_url") or "").strip().rstrip("/")
|
|
724
|
+
result: dict[str, str] = {}
|
|
725
|
+
if mode:
|
|
726
|
+
result["mode"] = mode
|
|
727
|
+
if server_url:
|
|
728
|
+
result["server_url"] = server_url
|
|
729
|
+
return result or None
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def _start_daemon_after_upgrade() -> tuple[bool, str | None]:
|
|
733
|
+
context = _read_daemon_start_context() or {}
|
|
734
|
+
if context.get("mode") != "background":
|
|
735
|
+
return False, None
|
|
736
|
+
|
|
737
|
+
server_url = context.get("server_url") or None
|
|
738
|
+
try:
|
|
739
|
+
cmd_daemon_start(
|
|
740
|
+
argparse.Namespace(
|
|
741
|
+
detach=True,
|
|
742
|
+
server_url=server_url,
|
|
743
|
+
)
|
|
744
|
+
)
|
|
745
|
+
except SystemExit as exc:
|
|
746
|
+
return False, f"Auto-restart failed with exit code {exc.code}."
|
|
747
|
+
except Exception as exc:
|
|
748
|
+
return False, str(exc)
|
|
749
|
+
|
|
750
|
+
return True, server_url
|
|
751
|
+
|
|
752
|
+
|
|
697
753
|
def _request_json(
|
|
698
754
|
path: str,
|
|
699
755
|
*,
|
|
@@ -1120,6 +1176,11 @@ def cmd_daemon_start(args: argparse.Namespace) -> None:
|
|
|
1120
1176
|
if token:
|
|
1121
1177
|
os.environ.setdefault("DAEMON_API_TOKEN", token)
|
|
1122
1178
|
|
|
1179
|
+
_write_daemon_start_context(
|
|
1180
|
+
mode="background" if getattr(args, "detach", False) else "foreground",
|
|
1181
|
+
server_url=server_url,
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1123
1184
|
if getattr(args, "detach", False):
|
|
1124
1185
|
# ── Background (detach) mode ─────────────────────────────────────────
|
|
1125
1186
|
import subprocess as sp
|
|
@@ -1500,7 +1561,13 @@ def cmd_upgrade(args: argparse.Namespace) -> None:
|
|
|
1500
1561
|
installed_version = _installed_cli_version() or "unknown"
|
|
1501
1562
|
print(f"Upgrade complete. Installed version: {installed_version}")
|
|
1502
1563
|
if daemon_stopped:
|
|
1503
|
-
|
|
1564
|
+
restarted, server_url = _start_daemon_after_upgrade()
|
|
1565
|
+
if restarted:
|
|
1566
|
+
print("Daemon restarted in background.")
|
|
1567
|
+
if server_url:
|
|
1568
|
+
print(f" Server : {server_url}")
|
|
1569
|
+
else:
|
|
1570
|
+
print("Restart the daemon with: forgexa daemon start")
|
|
1504
1571
|
|
|
1505
1572
|
|
|
1506
1573
|
# ── Argument parser ──
|
|
@@ -1751,10 +1818,6 @@ def main() -> None:
|
|
|
1751
1818
|
_output_format = args.format or "table"
|
|
1752
1819
|
|
|
1753
1820
|
cmd = args.command
|
|
1754
|
-
if not cmd:
|
|
1755
|
-
parser.print_help()
|
|
1756
|
-
return
|
|
1757
|
-
|
|
1758
1821
|
# Start background update-check now (writes cache if stale; zero latency).
|
|
1759
1822
|
# Skip for `upgrade` (user is already updating) and raw log tailing
|
|
1760
1823
|
# (`daemon logs -f` / `logs -f`) which never returns.
|
|
@@ -1765,6 +1828,11 @@ def main() -> None:
|
|
|
1765
1828
|
if not _skip_update_check:
|
|
1766
1829
|
_launch_update_check_process()
|
|
1767
1830
|
|
|
1831
|
+
if not cmd:
|
|
1832
|
+
parser.print_help()
|
|
1833
|
+
_print_update_notice_if_available()
|
|
1834
|
+
return
|
|
1835
|
+
|
|
1768
1836
|
dispatch: dict = {
|
|
1769
1837
|
"version": cmd_version,
|
|
1770
1838
|
"upgrade": cmd_upgrade,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forgexa-cli
|
|
3
|
-
Version: 1.16.
|
|
3
|
+
Version: 1.16.6
|
|
4
4
|
Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
|
|
5
5
|
Author-email: Jason Sun <dev.winds@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -127,10 +127,11 @@ The command detects how `forgexa-cli` was installed:
|
|
|
127
127
|
|
|
128
128
|
- `pipx` install: uses `pipx upgrade forgexa-cli`, or `pipx install --force forgexa-cli==<version>` for a pinned version
|
|
129
129
|
- `pip` install: uses `python -m pip install --upgrade forgexa-cli`
|
|
130
|
+
- non-editable local path install (`pip install /path/to/cli`): follows the same `pip` upgrade path and switches to the published PyPI release
|
|
130
131
|
|
|
131
|
-
For safety, `forgexa upgrade` stops any running local daemon before upgrading.
|
|
132
|
+
For safety, `forgexa upgrade` stops any running local daemon before upgrading. If the daemon was previously started in background mode, the CLI starts it again automatically after a successful upgrade; otherwise it prints a manual restart hint.
|
|
132
133
|
|
|
133
|
-
`forgexa upgrade`
|
|
134
|
+
`forgexa upgrade` supports standard PyPI / pipx installs and non-editable local path installs. Editable installs, VCS installs, and other direct URL installs are rejected with a manual upgrade hint.
|
|
134
135
|
|
|
135
136
|
## Configuration
|
|
136
137
|
|
|
@@ -266,6 +266,29 @@ def test_launch_update_check_process_spawns_background_python_when_cache_is_stal
|
|
|
266
266
|
assert kwargs["stderr"] is main.subprocess.DEVNULL
|
|
267
267
|
|
|
268
268
|
|
|
269
|
+
def test_main_without_command_still_runs_update_notice_flow(
|
|
270
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
271
|
+
) -> None:
|
|
272
|
+
calls: list[str] = []
|
|
273
|
+
|
|
274
|
+
monkeypatch.setattr(main.sys, "argv", ["forgexa"])
|
|
275
|
+
monkeypatch.setattr(
|
|
276
|
+
main,
|
|
277
|
+
"_launch_update_check_process",
|
|
278
|
+
lambda: calls.append("launch"),
|
|
279
|
+
)
|
|
280
|
+
monkeypatch.setattr(
|
|
281
|
+
main,
|
|
282
|
+
"_print_update_notice_if_available",
|
|
283
|
+
lambda: calls.append("notice"),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
287
|
+
main.main()
|
|
288
|
+
|
|
289
|
+
assert calls == ["launch", "notice"]
|
|
290
|
+
|
|
291
|
+
|
|
269
292
|
def test_build_upgrade_plan_for_pipx_latest(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
270
293
|
monkeypatch.setattr(main, "_cli_distribution", lambda: SimpleNamespace(version="1.12.3"))
|
|
271
294
|
monkeypatch.setattr(main, "_distribution_root", lambda dist: Path("/installed/site-packages"))
|
|
@@ -339,6 +362,41 @@ def test_build_upgrade_plan_adds_break_system_packages_on_pep668(monkeypatch: py
|
|
|
339
362
|
assert plan.command[-1] == "forgexa-cli"
|
|
340
363
|
|
|
341
364
|
|
|
365
|
+
def test_build_upgrade_plan_allows_non_editable_local_path_installs(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
366
|
+
monkeypatch.setattr(main, "_cli_distribution", lambda: SimpleNamespace(version="1.15.7"))
|
|
367
|
+
monkeypatch.setattr(
|
|
368
|
+
main,
|
|
369
|
+
"_distribution_root",
|
|
370
|
+
lambda dist: Path("/home/demo/.local/lib/python3.12/site-packages"),
|
|
371
|
+
)
|
|
372
|
+
monkeypatch.setattr(main, "_running_from_distribution_root", lambda root: True)
|
|
373
|
+
monkeypatch.setattr(
|
|
374
|
+
main,
|
|
375
|
+
"_direct_url_metadata",
|
|
376
|
+
lambda dist: {"dir_info": {}, "url": "file:///opt/forgexa/cli"},
|
|
377
|
+
)
|
|
378
|
+
monkeypatch.setattr(main, "_looks_like_pipx_environment", lambda python=None: False)
|
|
379
|
+
monkeypatch.setattr(main.importlib.util, "find_spec", lambda name: object() if name == "pip" else None)
|
|
380
|
+
monkeypatch.setattr(main, "_is_user_site_install", lambda root: True)
|
|
381
|
+
monkeypatch.setattr(main, "_is_virtualenv", lambda: False)
|
|
382
|
+
monkeypatch.setattr(main, "_is_pep668_environment", lambda: True)
|
|
383
|
+
monkeypatch.setattr(main.sys, "executable", "/usr/bin/python3")
|
|
384
|
+
|
|
385
|
+
plan = main._build_upgrade_plan(None)
|
|
386
|
+
|
|
387
|
+
assert plan.installer == "pip"
|
|
388
|
+
assert plan.command == [
|
|
389
|
+
"/usr/bin/python3",
|
|
390
|
+
"-m",
|
|
391
|
+
"pip",
|
|
392
|
+
"install",
|
|
393
|
+
"--upgrade",
|
|
394
|
+
"--user",
|
|
395
|
+
"--break-system-packages",
|
|
396
|
+
"forgexa-cli",
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
|
|
342
400
|
def test_pep668_detection_returns_false_in_normal_venv(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
343
401
|
monkeypatch.setattr(main.sys, "prefix", str(tmp_path))
|
|
344
402
|
import sysconfig as _sc
|
|
@@ -378,6 +436,7 @@ def test_cmd_upgrade_runs_planned_command(monkeypatch: pytest.MonkeyPatch) -> No
|
|
|
378
436
|
monkeypatch.setattr(main, "_normalize_target_version", lambda value: value)
|
|
379
437
|
monkeypatch.setattr(main, "_stop_daemon_if_running", lambda: (False, None))
|
|
380
438
|
monkeypatch.setattr(main, "_installed_cli_version", lambda: "1.12.3")
|
|
439
|
+
monkeypatch.setattr(main, "_start_daemon_after_upgrade", lambda: (False, None))
|
|
381
440
|
|
|
382
441
|
def fake_run(cmd):
|
|
383
442
|
calls.append(cmd)
|
|
@@ -394,6 +453,64 @@ def test_cmd_upgrade_runs_planned_command(monkeypatch: pytest.MonkeyPatch) -> No
|
|
|
394
453
|
assert "Upgrade complete. Installed version: 1.12.3" in output
|
|
395
454
|
|
|
396
455
|
|
|
456
|
+
def test_cmd_upgrade_restarts_background_daemon_after_success(
|
|
457
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
458
|
+
) -> None:
|
|
459
|
+
plan = main.UpgradePlan(
|
|
460
|
+
installer="pip",
|
|
461
|
+
command=["python3", "-m", "pip", "install", "--upgrade", "forgexa-cli"],
|
|
462
|
+
current_version="1.16.3",
|
|
463
|
+
target_version=None,
|
|
464
|
+
)
|
|
465
|
+
start_calls: list[bool] = []
|
|
466
|
+
|
|
467
|
+
monkeypatch.setattr(main, "_build_upgrade_plan", lambda target: plan)
|
|
468
|
+
monkeypatch.setattr(main, "_normalize_target_version", lambda value: value)
|
|
469
|
+
monkeypatch.setattr(main, "_stop_daemon_if_running", lambda: (True, 4242))
|
|
470
|
+
monkeypatch.setattr(main, "_installed_cli_version", lambda: "1.16.4")
|
|
471
|
+
monkeypatch.setattr(main.subprocess, "run", lambda cmd: SimpleNamespace(returncode=0))
|
|
472
|
+
monkeypatch.setattr(
|
|
473
|
+
main,
|
|
474
|
+
"_start_daemon_after_upgrade",
|
|
475
|
+
lambda: start_calls.append(True) or (True, "https://api.example.com"),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
buffer = io.StringIO()
|
|
479
|
+
with contextlib.redirect_stdout(buffer):
|
|
480
|
+
main.cmd_upgrade(SimpleNamespace(target_version=None))
|
|
481
|
+
|
|
482
|
+
output = buffer.getvalue()
|
|
483
|
+
assert start_calls == [True]
|
|
484
|
+
assert "Stopped local daemon (PID 4242) before upgrade." in output
|
|
485
|
+
assert "Daemon restarted in background." in output
|
|
486
|
+
assert "Server : https://api.example.com" in output
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def test_cmd_upgrade_keeps_manual_restart_hint_when_auto_restart_skips(
|
|
490
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
491
|
+
) -> None:
|
|
492
|
+
plan = main.UpgradePlan(
|
|
493
|
+
installer="pip",
|
|
494
|
+
command=["python3", "-m", "pip", "install", "--upgrade", "forgexa-cli"],
|
|
495
|
+
current_version="1.16.3",
|
|
496
|
+
target_version=None,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
monkeypatch.setattr(main, "_build_upgrade_plan", lambda target: plan)
|
|
500
|
+
monkeypatch.setattr(main, "_normalize_target_version", lambda value: value)
|
|
501
|
+
monkeypatch.setattr(main, "_stop_daemon_if_running", lambda: (True, 4242))
|
|
502
|
+
monkeypatch.setattr(main, "_installed_cli_version", lambda: "1.16.4")
|
|
503
|
+
monkeypatch.setattr(main.subprocess, "run", lambda cmd: SimpleNamespace(returncode=0))
|
|
504
|
+
monkeypatch.setattr(main, "_start_daemon_after_upgrade", lambda: (False, None))
|
|
505
|
+
|
|
506
|
+
buffer = io.StringIO()
|
|
507
|
+
with contextlib.redirect_stdout(buffer):
|
|
508
|
+
main.cmd_upgrade(SimpleNamespace(target_version=None))
|
|
509
|
+
|
|
510
|
+
output = buffer.getvalue()
|
|
511
|
+
assert "Restart the daemon with: forgexa daemon start" in output
|
|
512
|
+
|
|
513
|
+
|
|
397
514
|
def test_cmd_upgrade_noops_when_target_matches_current(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
398
515
|
plan = main.UpgradePlan(
|
|
399
516
|
installer="pip",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|