feat(publish): add PyPI publish workflow, CLI command, parser integration, and tests
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / linter-shell (push) Has been cancelled
Mark stable commit / linter-python (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled

* Introduce publish action with PyPI target detection via MIRRORS
* Resolve version from SemVer git tags on HEAD
* Support preview mode and non-interactive CI usage
* Build and upload artifacts using build + twine with token resolution
* Add CLI wiring (dispatch, command handler, parser)
* Add E2E publish help tests for pkgmgr and nix run
* Add integration tests for publish preview and mirror handling
* Add unit tests for git tag parsing, PyPI URL parsing, workflow preview, and CLI handler
* Clean up dispatch and parser structure while integrating publish

https://chatgpt.com/share/693f0f00-af68-800f-8846-193dca69bd2e
This commit is contained in:
Kevin Veen-Birkenbach
2025-12-14 20:24:01 +01:00
parent 3d7d7e9c09
commit 9456ad4475
17 changed files with 557 additions and 94 deletions

View File

@@ -0,0 +1,20 @@
import unittest
from unittest.mock import patch
from pkgmgr.actions.publish.git_tags import head_semver_tags
class TestHeadSemverTags(unittest.TestCase):
@patch("pkgmgr.actions.publish.git_tags.run_git")
def test_no_tags(self, mock_run_git):
mock_run_git.return_value = ""
self.assertEqual(head_semver_tags(), [])
@patch("pkgmgr.actions.publish.git_tags.run_git")
def test_filters_and_sorts_semver(self, mock_run_git):
mock_run_git.return_value = "v1.0.0\nv2.0.0\nfoo\n"
self.assertEqual(
head_semver_tags(),
["v1.0.0", "v2.0.0"],
)

View File

@@ -0,0 +1,13 @@
import unittest
from pkgmgr.actions.publish.pypi_url import parse_pypi_project_url
class TestParsePyPIUrl(unittest.TestCase):
def test_valid_pypi_url(self):
t = parse_pypi_project_url("https://pypi.org/project/example/")
self.assertIsNotNone(t)
self.assertEqual(t.project, "example")
def test_invalid_url(self):
self.assertIsNone(parse_pypi_project_url("https://example.com/foo"))

View File

@@ -0,0 +1,21 @@
import unittest
from unittest.mock import patch
from pkgmgr.actions.publish.workflow import publish
class TestPublishWorkflowPreview(unittest.TestCase):
@patch("pkgmgr.actions.publish.workflow.read_mirrors_file")
@patch("pkgmgr.actions.publish.workflow.head_semver_tags")
def test_preview_does_not_build(self, mock_tags, mock_mirrors):
mock_mirrors.return_value = {
"pypi": "https://pypi.org/project/example/"
}
mock_tags.return_value = ["v1.0.0"]
publish(
repo={},
repo_dir=".",
preview=True,
)

View File

@@ -0,0 +1,12 @@
import unittest
from unittest.mock import patch
from pkgmgr.cli.commands.publish import handle_publish
class TestHandlePublish(unittest.TestCase):
@patch("pkgmgr.cli.commands.publish.publish")
def test_no_selected_repos(self, mock_publish):
handle_publish(args=object(), ctx=None, selected=[])
mock_publish.assert_not_called()