diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 939e704..844de13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,25 +144,48 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Get version + id: version + env: + PYTHONPATH: src + run: python -c "from version import VERSION; print(f'version={VERSION}')" >> "$GITHUB_OUTPUT" + + - name: Check tag + id: tag + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + if git ls-remote --exit-code --tags origin "refs/tags/${VERSION}" > /dev/null; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + status=$? + if [ "$status" -ne 2 ]; then + exit "$status" + fi + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + - name: Download artifacts + if: steps.tag.outputs.exists == 'false' uses: actions/download-artifact@v8 - - name: Get date - id: date - run: echo "date=$(date +%Y.%m.%d)" >> "$GITHUB_OUTPUT" - - name: Rename artifacts + if: steps.tag.outputs.exists == 'false' env: - VERSION: ${{ steps.date.outputs.date }}.${{ github.run_number }} + VERSION: ${{ steps.version.outputs.version }} run: | mv snapjaw-windows-x86_64/snapjaw-windows-x86_64.zip snapjaw-${VERSION}-windows-x86_64.zip mv snapjaw-linux-x86_64/snapjaw-linux-x86_64.tar.gz snapjaw-${VERSION}-linux-x86_64.tar.gz - name: Release + if: steps.tag.outputs.exists == 'false' uses: softprops/action-gh-release@v3 with: - tag_name: ${{ steps.date.outputs.date }}.${{ github.run_number }} - name: ${{ steps.date.outputs.date }}.${{ github.run_number }} + tag_name: ${{ steps.version.outputs.version }} + name: ${{ steps.version.outputs.version }} files: | - snapjaw-${{ steps.date.outputs.date }}.${{ github.run_number }}-windows-x86_64.zip - snapjaw-${{ steps.date.outputs.date }}.${{ github.run_number }}-linux-x86_64.tar.gz + snapjaw-${{ steps.version.outputs.version }}-windows-x86_64.zip + snapjaw-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz diff --git a/pyproject.toml b/pyproject.toml index 30bd7d3..f907a76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "tabulate>=0.9.0", ] name = "snapjaw" -version = "1.0" +dynamic = ["version"] description = "Vanilla WoW AddOn Manager" [tool.ruff] diff --git a/src/snapjaw.py b/src/snapjaw.py index c430643..86c514d 100644 --- a/src/snapjaw.py +++ b/src/snapjaw.py @@ -24,6 +24,7 @@ import mygit import signature import toc +import version class CliError(RuntimeError): @@ -79,6 +80,7 @@ def main(): def parse_args(): parser = argparse.ArgumentParser() + parser.add_argument("--version", action="version", version=f"snapjaw {version.VERSION}") parser.add_argument( "--addons-dir", diff --git a/src/version.py b/src/version.py new file mode 100644 index 0000000..af63e4a --- /dev/null +++ b/src/version.py @@ -0,0 +1 @@ +VERSION = "1.4.0" diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..014f605 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,60 @@ +"""Tests for the application version and CLI version flag.""" + +import re + +import pytest + +import version +from snapjaw import parse_args + + +class TestVersion: + """Tests for the single source of truth version constant.""" + + def test_has_three_numeric_components(self): + """VERSION uses semantic versioning's three-component numeric form.""" + assert re.fullmatch(r"\d+\.\d+\.\d+", version.VERSION) + + +class TestVersionFlag: + """Tests for the top-level --version flag.""" + + @staticmethod + def assert_version_output(monkeypatch, capsys, argv): + monkeypatch.setattr("sys.argv", argv) + + with pytest.raises(SystemExit) as error: + parse_args() + + assert error.value.code == 0 + captured = capsys.readouterr() + assert captured.out == f"snapjaw {version.VERSION}\n" + assert captured.err == "" + + def test_prints_exact_version(self, monkeypatch, capsys, tmp_path): + """--version prints the application name and VERSION exactly.""" + self.assert_version_output( + monkeypatch, + capsys, + [ + "snapjaw", + "--addons-dir", + str(tmp_path), + "--game-version", + "vanilla", + "--version", + "status", + ], + ) + + def test_works_without_subcommand(self, monkeypatch, capsys): + """--version exits before required subcommand validation.""" + self.assert_version_output(monkeypatch, capsys, ["snapjaw", "--version"]) + + def test_works_outside_game_directory(self, monkeypatch, capsys, tmp_path): + """--version exits before attempting to resolve an addons directory.""" + isolated_directory = tmp_path / "isolated" + isolated_directory.mkdir() + monkeypatch.chdir(isolated_directory) + + self.assert_version_output(monkeypatch, capsys, ["snapjaw", "--version"]) diff --git a/uv.lock b/uv.lock index 90e45d8..cfee933 100644 --- a/uv.lock +++ b/uv.lock @@ -309,7 +309,6 @@ wheels = [ [[package]] name = "snapjaw" -version = "1.0" source = { virtual = "." } dependencies = [ { name = "colorama" },