fz-manager-plus 0.1.2__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.
- fz_manager_plus-0.1.2/.github/workflows/ci.yml +47 -0
- fz_manager_plus-0.1.2/.github/workflows/release.yml +54 -0
- fz_manager_plus-0.1.2/.gitignore +12 -0
- fz_manager_plus-0.1.2/.python-version +1 -0
- fz_manager_plus-0.1.2/LICENSE +21 -0
- fz_manager_plus-0.1.2/PKG-INFO +16 -0
- fz_manager_plus-0.1.2/README.md +108 -0
- fz_manager_plus-0.1.2/assets/img.png +0 -0
- fz_manager_plus-0.1.2/fz_manager_plus/__init__.py +7 -0
- fz_manager_plus-0.1.2/fz_manager_plus/application/__init__.py +1 -0
- fz_manager_plus-0.1.2/fz_manager_plus/application/ports.py +35 -0
- fz_manager_plus-0.1.2/fz_manager_plus/application/session.py +455 -0
- fz_manager_plus-0.1.2/fz_manager_plus/application/transfers.py +56 -0
- fz_manager_plus-0.1.2/fz_manager_plus/cli.py +5 -0
- fz_manager_plus-0.1.2/fz_manager_plus/config.py +134 -0
- fz_manager_plus-0.1.2/fz_manager_plus/domain/__init__.py +1 -0
- fz_manager_plus-0.1.2/fz_manager_plus/domain/errors.py +24 -0
- fz_manager_plus-0.1.2/fz_manager_plus/domain/messages.py +116 -0
- fz_manager_plus-0.1.2/fz_manager_plus/domain/state.py +83 -0
- fz_manager_plus-0.1.2/fz_manager_plus/infrastructure/__init__.py +0 -0
- fz_manager_plus-0.1.2/fz_manager_plus/infrastructure/factorio_zone/__init__.py +0 -0
- fz_manager_plus-0.1.2/fz_manager_plus/infrastructure/factorio_zone/client.py +194 -0
- fz_manager_plus-0.1.2/fz_manager_plus/infrastructure/factorio_zone/mods.py +43 -0
- fz_manager_plus-0.1.2/fz_manager_plus/infrastructure/factorio_zone/session.py +4 -0
- fz_manager_plus-0.1.2/fz_manager_plus/infrastructure/factorio_zone/socket.py +34 -0
- fz_manager_plus-0.1.2/fz_manager_plus/runtime.py +16 -0
- fz_manager_plus-0.1.2/fz_manager_plus/terminal.py +45 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/__init__.py +0 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/app.py +409 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/__init__.py +35 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/choice_screen.py +59 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/confirm_screen.py +52 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/log_pane.py +43 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/menu_pane.py +36 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/mods_pane.py +100 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/mods_upload_screen.py +155 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/multi_choice_screen.py +83 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/saves_pane.py +72 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/selectable_list.py +51 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/status_bar.py +25 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/components/token_screen.py +40 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/flows/__init__.py +13 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/flows/host.py +26 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/flows/mod_flows.py +29 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/flows/save_flows.py +57 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/flows/server_flows.py +68 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/flows/sync_flows.py +45 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/progress.py +26 -0
- fz_manager_plus-0.1.2/fz_manager_plus/tui/run.py +4 -0
- fz_manager_plus-0.1.2/fz_manager_plus/utils/__init__.py +0 -0
- fz_manager_plus-0.1.2/fz_manager_plus/utils/api_router/__init__.py +0 -0
- fz_manager_plus-0.1.2/fz_manager_plus/utils/api_router/http.py +86 -0
- fz_manager_plus-0.1.2/fz_manager_plus/utils/api_router/ws.py +67 -0
- fz_manager_plus-0.1.2/fz_manager_plus/utils/async_io.py +18 -0
- fz_manager_plus-0.1.2/fz_manager_plus/utils/concurrency.py +17 -0
- fz_manager_plus-0.1.2/fz_manager_plus/utils/files.py +34 -0
- fz_manager_plus-0.1.2/pyproject.toml +57 -0
- fz_manager_plus-0.1.2/tests/__init__.py +0 -0
- fz_manager_plus-0.1.2/tests/fakes.py +110 -0
- fz_manager_plus-0.1.2/tests/test_flows.py +72 -0
- fz_manager_plus-0.1.2/tests/test_fz_manager.py +9 -0
- fz_manager_plus-0.1.2/tests/test_session.py +224 -0
- fz_manager_plus-0.1.2/tests/test_settings_and_files.py +147 -0
- fz_manager_plus-0.1.2/tests/test_transfers.py +116 -0
- fz_manager_plus-0.1.2/tests/test_transport.py +264 -0
- fz_manager_plus-0.1.2/tests/test_tui.py +194 -0
- fz_manager_plus-0.1.2/tests/test_utils_http.py +58 -0
- fz_manager_plus-0.1.2/uv.lock +964 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
9
|
+
cancel-in-progress: true
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
name: Lint (ruff check)
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: astral-sh/setup-uv@v5
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
- run: uv sync --group dev --locked
|
|
24
|
+
- run: uv run ruff check fz_manager_plus tests
|
|
25
|
+
|
|
26
|
+
format:
|
|
27
|
+
name: Format check (ruff format, isort)
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: astral-sh/setup-uv@v5
|
|
32
|
+
with:
|
|
33
|
+
enable-cache: true
|
|
34
|
+
- run: uv sync --group dev --locked
|
|
35
|
+
- run: uv run ruff format --check fz_manager_plus tests
|
|
36
|
+
- run: uv run isort --check-only fz_manager_plus tests
|
|
37
|
+
|
|
38
|
+
test:
|
|
39
|
+
name: Tests (pytest)
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
- uses: astral-sh/setup-uv@v5
|
|
44
|
+
with:
|
|
45
|
+
enable-cache: true
|
|
46
|
+
- run: uv sync --group dev --locked
|
|
47
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build distribution
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
|
|
21
|
+
- name: Check tag matches pyproject.toml version
|
|
22
|
+
run: |
|
|
23
|
+
tag_version="${GITHUB_REF_NAME#v}"
|
|
24
|
+
project_version="$(uv version --short)"
|
|
25
|
+
if [[ "$tag_version" != "$project_version" ]]; then
|
|
26
|
+
echo "::error::tag v$tag_version does not match pyproject.toml version $project_version"
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
- run: uv build
|
|
31
|
+
|
|
32
|
+
- uses: actions/upload-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
|
|
37
|
+
publish:
|
|
38
|
+
name: Publish to PyPI
|
|
39
|
+
needs: build
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
# Configure this environment (and a PyPI "Trusted Publisher" for it) in
|
|
42
|
+
# the repository settings before the first release; see README.md.
|
|
43
|
+
environment:
|
|
44
|
+
name: pypi
|
|
45
|
+
url: https://pypi.org/project/fz-manager-plus/
|
|
46
|
+
permissions:
|
|
47
|
+
id-token: write
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/download-artifact@v4
|
|
50
|
+
with:
|
|
51
|
+
name: dist
|
|
52
|
+
path: dist/
|
|
53
|
+
- uses: astral-sh/setup-uv@v5
|
|
54
|
+
- run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Michel Sciortino
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fz-manager-plus
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Factorio Zone Server Manager
|
|
5
|
+
Project-URL: repository, https://github.com/Mesheryakof/FZ-Manager-Plus
|
|
6
|
+
Author-email: michelsciortino <michel.sciortino@outlook.com>, Mesheryakof <danil.spb.it@gmail.com>
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.14
|
|
9
|
+
Requires-Dist: certifi>=2021.10.8
|
|
10
|
+
Requires-Dist: httpx<0.28.0,>=0.27.0
|
|
11
|
+
Requires-Dist: pydantic-settings<3.0,>=2.5
|
|
12
|
+
Requires-Dist: pydantic<3.0,>=2.7
|
|
13
|
+
Requires-Dist: rich<15.0.0,>=14.2.0
|
|
14
|
+
Requires-Dist: textual-fspicker<2.0,>=1.0.1
|
|
15
|
+
Requires-Dist: textual<9.0.0,>=8.2.8
|
|
16
|
+
Requires-Dist: websockets<11.0,>=10.1
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# FZ-Manager Plus
|
|
2
|
+
|
|
3
|
+
Terminal UI for managing a Factorio server hosted by [Factorio Zone](https://factorio.zone/), based on [FZ-Manager](https://github.com/michelsciortino/FZ-Manager).
|
|
4
|
+
|
|
5
|
+
Requires Python 3.14 or newer.
|
|
6
|
+
|
|
7
|
+
## Installation and launch
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install fz-manager-plus
|
|
11
|
+
fzmp
|
|
12
|
+
# Equivalent:
|
|
13
|
+
fz-manager-plus
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Run `fzmp --help` to see settings and command-line options. Enter an existing Factorio Zone token, or submit an empty token to create a new account.
|
|
17
|
+
|
|
18
|
+
## Available in the TUI
|
|
19
|
+
|
|
20
|
+
- Start a server by choosing a region, version and save slot; stop a running server.
|
|
21
|
+
- Send console commands from the log pane.
|
|
22
|
+
- Upload missing ZIP mods from a folder, including nested folders, with per-file progress.
|
|
23
|
+
- Enable, disable or delete mods; delete all uploaded mods after confirmation.
|
|
24
|
+
- Download and delete remote save slots. Replacing an existing local archive requires confirmation.
|
|
25
|
+
- Quit with `Ctrl+Q`. Background operations are cancelled and network clients are closed.
|
|
26
|
+
|
|
27
|
+
Mod sync matches **filenames**, not archive contents. It uploads missing names; it does not delete remote mods or compare versions/checksums. Duplicate ZIP filenames in different local folders are reported as a conflict.
|
|
28
|
+
|
|
29
|
+
The upload dialog can be cancelled before upload starts. After starting, wait for the results or quit the application. The API still supports save uploads and the project includes a mod-settings archive utility; neither has a menu action in this TUI.
|
|
30
|
+
|
|
31
|
+
## Connection and operation status
|
|
32
|
+
|
|
33
|
+
WebSocket connections recover automatically with delays of 1, 2, 4, 8, 16, then 30 seconds. The session logs in with its current token and refreshes server state before enabling dependent actions. Authentication failures ask for a token again.
|
|
34
|
+
|
|
35
|
+
Console commands, deletions and transfers are **not replayed** on reconnect. A server may have accepted a request before the connection failed; inspect the refreshed state before repeating it.
|
|
36
|
+
|
|
37
|
+
Start succeeds only after a running state and server address arrive. Default synchronization/start/stop timeouts are 60/600/3600 seconds. Adjust them with `--sync-timeout`, `--start-timeout`, `--stop-timeout` or the corresponding `FZM_*` environment variables. `--sync-batch-size` defaults to 3.
|
|
38
|
+
|
|
39
|
+
The log pane and log deduplication history are limited to 10,000 entries each.
|
|
40
|
+
|
|
41
|
+
## Settings
|
|
42
|
+
|
|
43
|
+
Settings and the crash log are stored in:
|
|
44
|
+
|
|
45
|
+
| Platform | Default directory |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| macOS | `~/Library/Application Support/fz-manager-plus` |
|
|
48
|
+
| Linux | `$XDG_CONFIG_HOME/fz-manager-plus`, or `~/.config/fz-manager-plus` |
|
|
49
|
+
| Windows | `%LOCALAPPDATA%/fz-manager-plus` |
|
|
50
|
+
|
|
51
|
+
Use `--storage-dir` or `FZM_STORAGE_DIR` to override the directory. The settings file is `settings.json`; the crash log is `crash.log`.
|
|
52
|
+
|
|
53
|
+
On first use of a directory without settings, the previous temporary-directory `.fzmp/settings.json` is imported if present. Its source is retained. Settings are validated before migration and written atomically; tokens are saved as soon as authentication succeeds. On POSIX systems the new settings file has mode `0600`.
|
|
54
|
+
|
|
55
|
+
CLI options override environment variables; environment variables override `.env`; `.env` overrides stored settings. Existing option names, numeric save-slot preferences and `FZM_*` variables remain supported. Importing modules or assigning a settings field performs no persistence or CLI parsing.
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
uv sync --group dev
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Checkers
|
|
64
|
+
|
|
65
|
+
Each command only reports; none of them rewrite files. Run before pushing — this is the same set of checks GitHub Actions runs on a pull request, so a green run locally means a green PR:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
uv run ruff check fz_manager_plus tests
|
|
69
|
+
uv run ruff format --check fz_manager_plus tests
|
|
70
|
+
uv run isort --check-only fz_manager_plus tests
|
|
71
|
+
uv run pytest -q
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Tests use fake transports and temporary directories; they do not call Factorio Zone.
|
|
75
|
+
|
|
76
|
+
The composition root is `fz_manager_plus/runtime.py`. `domain` defines state and messages, `application` owns session/transfer workflows, `infrastructure` implements transports and archive utilities, and `tui` contains Textual views and user interaction. Rich styling stays at the UI boundary in `terminal.py`.
|
|
77
|
+
|
|
78
|
+
### CI
|
|
79
|
+
|
|
80
|
+
`.github/workflows/ci.yml` runs `lint`, `format` and `test` as separate jobs on every pull request targeting `main`. Every job fails the check instead of auto-fixing, and `uv sync --group dev --locked` also fails the job if `uv.lock` is out of date.
|
|
81
|
+
|
|
82
|
+
### Releasing a new version
|
|
83
|
+
|
|
84
|
+
Versions are bumped in place with [`uv version --bump`](https://docs.astral.sh/uv/concepts/projects/config/#project-version), which updates `pyproject.toml` (and re-locks `uv.lock`):
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
uv version --bump patch # 0.1.1 -> 0.1.2
|
|
88
|
+
uv version --bump minor # 0.1.1 -> 0.2.0
|
|
89
|
+
uv version --bump major # 0.1.1 -> 1.0.0
|
|
90
|
+
uv version --bump patch --bump dev # 0.1.1 -> 0.1.2.dev1 (pre-release/dev build)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Commit the result and tag it to trigger a release:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
git add pyproject.toml uv.lock
|
|
97
|
+
git commit -m "chore(release): bump version to v$(uv version --short)"
|
|
98
|
+
git tag "v$(uv version --short)"
|
|
99
|
+
git push origin main --tags
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Pushing a `v*` tag triggers `.github/workflows/release.yml`, which builds the sdist/wheel with `uv build`, verifies the tag matches `pyproject.toml`, and publishes to PyPI with `uv publish` using [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (no stored token). `dev` pre-releases go through the same workflow; PyPI treats them as pre-releases, installable with `pip install --pre`.
|
|
103
|
+
|
|
104
|
+
One-time setup: create a `pypi` environment in the repository's GitHub settings, and register this repository/workflow/environment as a Trusted Publisher for `fz-manager-plus` on PyPI.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## Screenshot
|
|
108
|
+

|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Application services, independent of the terminal UI."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from collections.abc import AsyncIterator, Callable
|
|
2
|
+
from typing import BinaryIO, Protocol
|
|
3
|
+
|
|
4
|
+
from fz_manager_plus.domain.messages import BlankMessage, FzMessage, LoginResponse
|
|
5
|
+
|
|
6
|
+
type Progress = Callable[[int], None] | None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ZoneAPI(Protocol):
|
|
10
|
+
visit_secret: str | None
|
|
11
|
+
user_token: str | None
|
|
12
|
+
|
|
13
|
+
async def login(self, reconnected: bool = False) -> LoginResponse: ...
|
|
14
|
+
async def start_instance(self, region: str, version: str, save: str) -> None: ...
|
|
15
|
+
async def stop_instance(self, launch_id: int) -> None: ...
|
|
16
|
+
async def send_command(self, launch_id: int, command: str) -> None: ...
|
|
17
|
+
async def toggle_mod(self, mod_id: int, enabled: bool) -> None: ...
|
|
18
|
+
async def delete_mod(self, mod_id: int) -> None: ...
|
|
19
|
+
async def upload_mod(
|
|
20
|
+
self, name: str, file: BinaryIO, size: int, progress: Progress = None
|
|
21
|
+
) -> None: ...
|
|
22
|
+
async def upload_save(
|
|
23
|
+
self, name: str, file: BinaryIO, size: int, slot: str, progress: Progress = None
|
|
24
|
+
) -> None: ...
|
|
25
|
+
async def delete_save_slot(self, slot: str) -> None: ...
|
|
26
|
+
async def download_save_slot(
|
|
27
|
+
self, slot: str, file_path: str, progress: Progress = None
|
|
28
|
+
) -> None: ...
|
|
29
|
+
async def aclose(self) -> None: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ZoneSocket(Protocol):
|
|
33
|
+
async def connect(self) -> None: ...
|
|
34
|
+
def messages(self) -> AsyncIterator[FzMessage | BlankMessage]: ...
|
|
35
|
+
async def close(self) -> None: ...
|