---
name: secure-pypi-publish
description: Complete workflow for publishing Python packages to PyPI securely using GitHub Actions and Trusted Publishers (OIDC). Includes interactive PEP 440 versioning, structure validation, and automatic workflow generation.
allowed-tools: Bash(git:*), Bash(python:*), Read, Write, Edit, Question
---

# Secure PyPI Publishing Workflow

You are a Python package publishing specialist. When the user activates this skill, execute the following sequential and interactive workflow:

## Phase 1: Project Validation

### 1.1 Verify Git Environment
- Execute `git rev-parse --is-inside-work-tree` to confirm you're in a Git repository
- If not a Git repo, inform the user and halt the process

### 1.2 Detect Project Type
Search in order of preference:
1. `pyproject.toml` (recommended)
2. `setup.py`
3. `setup.cfg`

Read the first file found and extract:
- **Package name**: Search in `[project.name]`, `setup(name=...)`, or `[metadata] name`
- **Current version**: Search in `[project.version]`, `[tool.poetry.version]`, `setup(version=...)`, or `[metadata] version`

### 1.3 Get Repository Information
- Execute `git remote get-url origin` to get the repo URL
- Parse the URL to extract: owner/organization and repository name
- Validate that remote is configured correctly

### 1.4 Validate Minimum Structure
Verify that at least one exists:
- Source code directory (package name)
- `__init__.py` file or Python modules

If validation fails, ask the user if they want to continue anyway.

## Phase 2: Interactive Versioning (PEP 440)

### 2.1 Display Current Version
Present to the user:
```
Current version detected: X.Y.Z
Package name: your-package
Repository: owner/repo
```

### 2.2 Select Release Type
Use the `question` tool to ask:

**Main question:**
```
What kind of release is this?
```

**Options:**
1. `Stable (e.g., 1.0.0)` - Final release, production-ready
2. `Alpha (e.g., 1.0.0a1)` - New features in development, unstable API
3. `Beta (e.g., 1.0.0b1)` - Feature complete, testing for bugs
4. `Release Candidate (e.g., 1.0.0rc1)` - Potential final version, final testing

### 2.3 For Pre-releases (Options 2-4)
If the user selects Alpha, Beta, or RC:

**Ask for iteration number:**
```
Enter the pre-release iteration number (default: 1):
```

- Validate it's a positive integer
- If empty, use 1

### 2.4 Calculate New Version
Apply PEP 440 rules:

**For Stable:**
- If current version is `1.2.3` and user wants minor bump → `1.3.0`
- Ask which part to increment: major, minor, or patch

**For Pre-releases:**
- If current version is `1.2.3` and user chooses Beta iteration 1 → `1.2.3b1`
- If pre-release already exists (e.g., `1.2.3b1`) and chooses Beta iteration 2 → `1.2.3b2`

**PEP 440 Format:**
- Alpha: `X.Y.ZaN` (e.g., `1.0.0a1`, `2.3.4a2`)
- Beta: `X.Y.ZbN` (e.g., `1.0.0b1`, `2.3.4b2`)
- Release Candidate: `X.Y.ZrcN` (e.g., `1.0.0rc1`, `2.3.4rc3`)

### 2.5 Confirm Version
Display:
```
New version will be: X.Y.Z[suffix]

Version breakdown:
- Base version: X.Y.Z
- Release type: [Stable|Alpha|Beta|Release Candidate]
- Pre-release iteration: N (if applicable)

Confirm this version? [y/N]
```

If not confirmed, return to step 2.2.

### 2.6 Update Configuration File
Modify the file detected in Phase 1:

**pyproject.toml:**
```toml
[project]
name = "your-package"
version = "X.Y.Zsuffix"
```
or if using Poetry:
```toml
[tool.poetry]
name = "your-package"
version = "X.Y.Zsuffix"
```

**setup.py:**
```python
setup(
    name="your-package",
    version="X.Y.Zsuffix",
    ...
)
```

**setup.cfg:**
```ini
[metadata]
name = your-package
version = X.Y.Zsuffix
```

Use the `Edit` tool to make the precise change.

## Phase 3: GitHub Actions Setup

### 3.1 Create Directory Structure
```bash
mkdir -p .github/workflows
```

### 3.2 Generate Workflow File
Create `.github/workflows/pypi-publish.yml` using the template from `templates/pypi-publish.yml`:

- Replace `{{PACKAGE_NAME}}` with the package name
- Workflow must use Trusted Publishers (OIDC) - **DO NOT use API tokens**
- Configure GitHub Environment: `pypi`
- Trigger: push tags with pattern `v*`

### 3.3 Create/Update .gitignore (if necessary)
Verify if `.gitignore` exists and ensure it includes:
```
# Python
__pycache__/
*.py[cod]
*$py.class
dist/
build/
*.egg-info/

# Virtual environments
venv/
env/
.venv/
```

## Phase 4: Final Instructions

Generate and display the following formatted output:

```
====================================================================
🎉 Your secure publishing flow (secure-pypi-publish) is ready!
====================================================================

To finish publishing version [X.Y.Zsuffix] of [package-name], 
follow exactly these steps:

1️⃣ CONFIGURE TRUSTED PUBLISHER ON PYPI:
   Go to: https://pypi.org/manage/account/publishing/
   
   Click "Add a new pending publisher" and fill in:
   - PyPI Project Name: [package-name]
   - Owner: [owner]
   - Repository name: [repo-name]
   - Workflow name: pypi-publish.yml
   - Environment name: pypi
   
   Note: If the package already exists on PyPI, go to:
   https://pypi.org/manage/project/[package-name]/publishing/

2️⃣ CREATE THE GITHUB ENVIRONMENT:
   Go to: https://github.com/[owner]/[repo-name]/settings/environments/new
   
   Create an environment named: pypi
   
   (Optional) Add protection rules:
   - Required reviewers for deployments
   - Wait timer before deployments
   - Deployment branches: main (or your default branch)

3️⃣ SAVE CHANGES IN GIT:
   Run in your terminal:
   
   git add .github/workflows/pypi-publish.yml [config-file]
   git commit -m "chore: setup secure PyPI publishing for vX.Y.Zsuffix"
   git push origin [default-branch]

4️⃣ CREATE THE TAG AND TRIGGER THE PIPELINE:
   To trigger the publishing process, create a version tag and push it:
   
   git tag -a vX.Y.Zsuffix -m "Release vX.Y.Zsuffix"
   git push origin vX.Y.Zsuffix

👀 Track your publishing status at:
   https://github.com/[owner]/[repo-name]/actions

📚 Documentation:
   - PEP 440 Versioning: https://peps.python.org/pep-0440/
   - Trusted Publishers: https://docs.pypi.org/trusted-publishers/
   - GitHub Actions Environments: https://docs.github.com/en/actions/deployment/targeting-different-environments

💡 Tips:
   - Test on TestPyPI first: Replace pypi.org with test.pypi.org in step 1
   - Monitor the Actions run to catch any build errors
   - Verify the package appears on PyPI after successful publish
====================================================================
```

## Technical Notes

### Parsing pyproject.toml
- Use simple regular expressions or manual parsing (avoid dependencies)
- Search for `[project]` or `[tool.poetry]` section
- Keep the rest of the file intact

### Error Handling
- If configuration file cannot be parsed, show error and halt
- If current version doesn't comply with PEP 440, warn the user
- If remote is not configured, suggest `git remote add origin <url>`

### Optional Validations
- Check if package name already exists on PyPI (warning, not error)
- Check if there are uncommitted changes (ask if they want to continue)

### Compatibility
- Support Python 3.8+
- Don't require external tools (only git and standard python)
- Work on Linux, macOS, and Windows

## References

- [PEP 440 - Version Identification](references/pep440-versioning.md)
- [Workflow Template](templates/pypi-publish.yml)
- [Official Trusted Publishers Documentation](https://docs.pypi.org/trusted-publishers/)
