From 34393bd08f2f1391c593803f1dcf580fd627d302 Mon Sep 17 00:00:00 2001 From: Tony Blank Date: Fri, 13 Jun 2025 12:04:06 -0600 Subject: [PATCH 1/3] Update license configuration and package inclusion in pyproject.toml - Changed the license format from a file reference to a string for clarity and added a separate license-files entry. - Removed the OSI Approved classifier for the MIT License to simplify the classifiers section. - Updated the package inclusion settings to explicitly include all relevant packages under the 'toady' namespace. These changes aim to improve the clarity of the project configuration and ensure proper package inclusion during distribution. --- MANIFEST.in | 42 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 5 +++-- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f8d9f79 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,42 @@ +# Include license and documentation +include LICENSE +include README.md +include CHANGELOG.md + +# Include configuration for packaging +include pyproject.toml + +# Include type hints marker +include src/toady/py.typed + +# Exclude development and configuration files +exclude .env.example +exclude .pre-commit-config.yaml +exclude Makefile +exclude mypy.ini +exclude pytest.ini +exclude requirements.txt +exclude uv.lock + +# Exclude development directories +recursive-exclude .cursor * +recursive-exclude .roo * +recursive-exclude .taskmaster * +recursive-exclude tests * +recursive-exclude scripts * +recursive-exclude docs * +recursive-exclude htmlcov * +recursive-exclude build * +recursive-exclude dist * + +# Exclude cache and temporary files +global-exclude *.pyc +global-exclude *.pyo +global-exclude *.orig +global-exclude *.rej +global-exclude *~ +global-exclude __pycache__ +global-exclude .pytest_cache +global-exclude .coverage +global-exclude coverage.xml +global-exclude coverage.json diff --git a/pyproject.toml b/pyproject.toml index 6af5f4a..ac7307d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,8 @@ name = "toady-cli" version = "0.1.0" description = "A CLI tool for managing GitHub PR code reviews efficiently" readme = "README.md" -license = {file = "LICENSE"} +license = "MIT" +license-files = ["LICENSE"] authors = [ {name = "Tony Blank", email = "guillotine@lawnfucker.com"}, ] @@ -18,7 +19,6 @@ keywords = ["github", "cli", "code-review", "pull-request", "automation"] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", @@ -97,6 +97,7 @@ toady = "toady.cli:main" [tool.setuptools.packages.find] where = ["src"] +include = ["toady*"] [tool.setuptools.package-data] toady = ["py.typed"] From 2467116fb46a4d1277fe628a94e1f0a4f7a873f1 Mon Sep 17 00:00:00 2001 From: Tony Blank Date: Fri, 13 Jun 2025 12:57:47 -0600 Subject: [PATCH 2/3] Add publishing commands and documentation for PyPI distribution --- .github/workflows/publish.yml | 88 ++++++++++++++ Makefile | 62 ++++++++++ PUBLISHING.md | 222 ++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 PUBLISHING.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..383757c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,88 @@ +name: Publish to PyPI + +on: + release: + types: [published] + workflow_dispatch: # Allow manual triggering + inputs: + target: + description: 'Target repository' + required: true + default: 'testpypi' + type: choice + options: + - testpypi + - pypi + +jobs: + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + environment: + name: ${{ github.event.inputs.target || 'pypi' }} + url: ${{ github.event.inputs.target == 'testpypi' && 'https://test.pypi.org/project/toady-cli/' || 'https://pypi.org/project/toady-cli/' }} + + permissions: + id-token: write # For trusted publishing + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: Install dependencies + run: | + uv sync --all-extras + + - name: Run tests + run: | + make test-fast + + - name: Build package + run: | + make build + + - name: Check package + run: | + uv run twine check dist/* + + - name: Publish to TestPyPI + if: github.event.inputs.target == 'testpypi' || github.event_name == 'workflow_dispatch' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + + - name: Publish to PyPI + if: github.event_name == 'release' && github.event.action == 'published' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + + - name: Create release comment + if: github.event_name == 'release' + uses: actions/github-script@v7 + with: + script: | + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: context.ref.replace('refs/tags/', '') + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: release.id, + body: `๐ŸŽ‰ Package published to PyPI!\n\n๐Ÿ“ฆ Install: \`pip install toady-cli\`\n๐Ÿ”— View: https://pypi.org/project/toady-cli/` + }); diff --git a/Makefile b/Makefile index d4d14b0..34474fd 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: help install install-dev test test-fast test-integration test-performance test-analysis .PHONY: lint format format-check type-check pre-commit check check-fast fix-check clean build .PHONY: sync lock update add remove deps-check shell run +.PHONY: publish-test publish check-publish setup-publish # Single source of truth: ALL commands use uv export PATH := $(HOME)/.local/bin:$(PATH) @@ -44,6 +45,12 @@ help: @echo " make shell Open shell with project dependencies loaded" @echo " make run ARGS='...' Run toady CLI in development mode" @echo "" + @echo "๐Ÿ“ฆ Publishing:" + @echo " make setup-publish Set up PyPI credentials (one-time setup)" + @echo " make check-publish Check package for PyPI compliance" + @echo " make publish-test Publish to TestPyPI for testing" + @echo " make publish Publish to production PyPI" + @echo "" @echo "๐Ÿงน Maintenance:" @echo " make clean Remove build artifacts and cache files" @echo " make build Build distribution packages" @@ -180,3 +187,58 @@ clean: build: clean @echo "๐Ÿ“ฆ Building distribution packages..." uv build + +## Publishing (PyPI Distribution) +setup-publish: + @echo "๐Ÿ” Setting up PyPI publishing credentials..." + @echo "" + @echo "1. Register accounts (if you haven't already):" + @echo " โ€ข TestPyPI: https://test.pypi.org/account/register/" + @echo " โ€ข PyPI: https://pypi.org/account/register/" + @echo "" + @echo "2. Create API tokens:" + @echo " โ€ข TestPyPI: https://test.pypi.org/manage/account/token/" + @echo " โ€ข PyPI: https://pypi.org/manage/account/token/" + @echo "" + @echo "3. Configure credentials in ~/.pypirc:" + @echo " [distutils]" + @echo " index-servers = pypi testpypi" + @echo "" + @echo " [pypi]" + @echo " username = __token__" + @echo " password = pypi-YOUR_PRODUCTION_TOKEN_HERE" + @echo "" + @echo " [testpypi]" + @echo " repository = https://test.pypi.org/legacy/" + @echo " username = __token__" + @echo " password = pypi-YOUR_TEST_TOKEN_HERE" + @echo "" + @echo "4. Alternatively, set environment variables:" + @echo " export TWINE_USERNAME=__token__" + @echo " export TWINE_PASSWORD=pypi-YOUR_TOKEN_HERE" + +check-publish: build + @echo "๐Ÿ” Checking package for PyPI compliance..." + uv run twine check dist/* + @echo "โœ… Package passed PyPI compliance checks!" + +publish-test: check-publish + @echo "๐Ÿงช Publishing to TestPyPI..." + @echo "โš ๏ธ This will upload version $(shell grep '^version = ' pyproject.toml | cut -d'"' -f2) to TestPyPI" + @read -p "Continue? (y/N) " confirm && [ "$$confirm" = "y" ] || exit 1 + uv run twine upload --repository testpypi dist/* + @echo "" + @echo "โœ… Published to TestPyPI!" + @echo "๐Ÿ”— View at: https://test.pypi.org/project/toady-cli/" + @echo "๐Ÿ“ฆ Test install: pip install --index-url https://test.pypi.org/simple/ toady-cli" + +publish: check-publish + @echo "๐Ÿš€ Publishing to production PyPI..." + @echo "โš ๏ธ WARNING: This will publish version $(shell grep '^version = ' pyproject.toml | cut -d'"' -f2) to PRODUCTION PyPI" + @echo "โš ๏ธ This action CANNOT be undone!" + @read -p "Are you absolutely sure? (y/N) " confirm && [ "$$confirm" = "y" ] || exit 1 + uv run twine upload dist/* + @echo "" + @echo "๐ŸŽ‰ Published to PyPI!" + @echo "๐Ÿ”— View at: https://pypi.org/project/toady-cli/" + @echo "๐Ÿ“ฆ Install: pip install toady-cli" diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 index 0000000..dc27eba --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,222 @@ +# Publishing Guide for Toady CLI + +This guide covers how to publish Toady CLI to PyPI using different methods. + +## ๐Ÿš€ Quick Start + +### Option 1: Using Make Commands (Recommended) + +```bash +# 1. Set up credentials (one-time) +make setup-publish + +# 2. Test on TestPyPI first +make publish-test + +# 3. Publish to production PyPI +make publish +``` + +### Option 2: Manual Commands + +```bash +# Build and check +make build +twine check dist/* + +# Upload to TestPyPI +twine upload --repository testpypi dist/* + +# Upload to PyPI +twine upload dist/* +``` + +## ๐Ÿ” Credential Setup + +### Method 1: API Tokens (Recommended) + +1. **Create accounts:** + - [TestPyPI](https://test.pypi.org/account/register/) + - [PyPI](https://pypi.org/account/register/) + +2. **Generate API tokens:** + - [TestPyPI tokens](https://test.pypi.org/manage/account/token/) + - [PyPI tokens](https://pypi.org/manage/account/token/) + +3. **Configure `~/.pypirc`:** + ```ini + [distutils] + index-servers = pypi testpypi + + [pypi] + username = __token__ + password = pypi-YOUR_PRODUCTION_TOKEN_HERE + + [testpypi] + repository = https://test.pypi.org/legacy/ + username = __token__ + password = pypi-YOUR_TEST_TOKEN_HERE + ``` + +### Method 2: Environment Variables + +```bash +export TWINE_USERNAME=__token__ +export TWINE_PASSWORD=pypi-YOUR_TOKEN_HERE +``` + +## ๐Ÿค– Automated Publishing (CI/CD) + +### GitHub Actions + +The project includes automated publishing via GitHub Actions: + +- **Automatic:** Publishes to PyPI when you create a GitHub release +- **Manual:** Use workflow dispatch to publish to TestPyPI + +#### Setup for GitHub Actions: + +1. **Add secrets to your repository:** + - Go to: Settings โ†’ Secrets and variables โ†’ Actions + - Add secrets: + - `PYPI_API_TOKEN`: Your PyPI API token + - `TEST_PYPI_API_TOKEN`: Your TestPyPI API token + +2. **Create a release:** + ```bash + git tag v0.1.0 + git push origin v0.1.0 + # Then create release on GitHub + ``` + +3. **Manual testing:** + - Go to Actions โ†’ Publish to PyPI โ†’ Run workflow + - Select "testpypi" target + +## ๐Ÿ“‹ Publishing Checklist + +### Before Publishing: + +- [ ] All tests pass: `make test` +- [ ] Version updated in `pyproject.toml` +- [ ] `CHANGELOG.md` updated +- [ ] Git tags created +- [ ] Package builds successfully: `make build` +- [ ] Package passes checks: `make check-publish` + +### Publishing Process: + +1. **Test on TestPyPI:** + ```bash + make publish-test + ``` + +2. **Verify TestPyPI installation:** + ```bash + pip install --index-url https://test.pypi.org/simple/ toady-cli + toady --version + ``` + +3. **Publish to production:** + ```bash + make publish + ``` + +4. **Verify production installation:** + ```bash + pip install toady-cli + toady --version + ``` + +## ๐Ÿ”„ Version Management + +### Semantic Versioning + +Follow [SemVer](https://semver.org/): +- `0.1.0` โ†’ `0.1.1` (patch: bug fixes) +- `0.1.0` โ†’ `0.2.0` (minor: new features) +- `0.1.0` โ†’ `1.0.0` (major: breaking changes) + +### Update Version + +1. **Update `pyproject.toml`:** + ```toml + version = "0.1.1" + ``` + +2. **Update `src/toady/__init__.py`:** + ```python + __version__ = "0.1.1" + ``` + +3. **Create git tag:** + ```bash + git add . + git commit -m "Bump version to 0.1.1" + git tag v0.1.1 + git push origin main --tags + ``` + +## ๐Ÿ“ฆ Package Maintenance + +### Regular Updates + +- Update dependencies: `make update` +- Run security audit: `uv pip audit` +- Update classifiers in `pyproject.toml` +- Keep README and documentation current + +### Yanking Releases + +If you need to remove a broken release: + +```bash +# Via web interface (recommended) +# Go to PyPI project page โ†’ Manage โ†’ Yank release + +# Via command line +twine upload --repository pypi --yank "Broken release" dist/old-version/* +``` + +## ๐Ÿ› ๏ธ Troubleshooting + +### Common Issues + +1. **"File already exists"** + - You can't re-upload the same version + - Increment version number + +2. **"Invalid authentication"** + - Check API token format: `pypi-...` + - Verify token has upload permissions + +3. **"Package name taken"** + - Choose a different name in `pyproject.toml` + - Check availability on PyPI first + +4. **"Metadata validation failed"** + - Run `make check-publish` to see issues + - Fix `pyproject.toml` metadata + +### Getting Help + +- [PyPI Help](https://pypi.org/help/) +- [Packaging User Guide](https://packaging.python.org/) +- [Twine Documentation](https://twine.readthedocs.io/) + +## ๐ŸŒŸ Best Practices + +1. **Always test on TestPyPI first** +2. **Use API tokens, not passwords** +3. **Keep credentials secure and rotated** +4. **Tag releases in git** +5. **Update changelog and documentation** +6. **Run full test suite before publishing** +7. **Use automated publishing for consistency** + +## ๐Ÿ“š Additional Resources + +- [Python Packaging Authority](https://www.pypa.io/) +- [Python Packaging User Guide](https://packaging.python.org/) +- [PEP 517 - Build System Interface](https://peps.python.org/pep-0517/) +- [PEP 518 - Build System Requirements](https://peps.python.org/pep-0518/) From 5884d8f5e1174a6104ade4cbefd87a3347bb5dbf Mon Sep 17 00:00:00 2001 From: Tony Blank Date: Fri, 13 Jun 2025 13:19:24 -0600 Subject: [PATCH 3/3] Update TestPyPI publishing condition in CI workflow - Modified the conditional check for publishing to TestPyPI to ensure it only triggers on workflow dispatch events when the target is set to 'testpypi'. - This change aims to improve the clarity and reliability of the publishing process in the CI workflow. --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 383757c..43df589 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -57,7 +57,7 @@ jobs: uv run twine check dist/* - name: Publish to TestPyPI - if: github.event.inputs.target == 'testpypi' || github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi' uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/