# PassFX PyPI Publishing Guide

Everything that could be done locally is done. Here's what's next.

---

## Current Status

**Local Setup: COMPLETE**

All code-level packaging configuration has been verified and tested:

| Component | Status | Verification |
|-----------|--------|--------------|
| `pyproject.toml` metadata | Complete | Full metadata including readme, license, classifiers |
| Version consistency | Verified | `1.0.0` in both `pyproject.toml` and `passfx/__init__.py` |
| CLI entry point | Verified | `passfx = "passfx.cli:main"` creates working command |
| Build system | Verified | Hatchling builds wheel and sdist successfully |
| TCSS stylesheet | Included | `passfx/styles/passfx.tcss` (78,932 bytes) in wheel |
| README rendering | Verified | Included as `long_description`, passes twine check |
| Sdist exclusions | Configured | `.claude/` excluded from source distribution |
| Installation test | Passed | Installs and imports correctly in clean venv |
| Twine validation | Passed | Both wheel and sdist pass `twine check` |
| Security audit | Passed | Bandit reports no issues |

**External Setup: NOT YET DONE**

The following require manual action outside this repository:

- PyPI account creation and 2FA setup
- TestPyPI account creation and 2FA setup
- Trusted Publisher configuration on PyPI
- GitHub environment setup
- First release creation

---

## Table of Contents

1. [What's Already Done](#whats-already-done)
2. [Build and Test Locally](#build-and-test-locally)
3. [External Setup Required](#external-setup-required)
4. [Release Process](#release-process)
5. [Troubleshooting](#troubleshooting)
6. [Next Actions Checklist](#next-actions-checklist)

---

## What's Already Done

### Packaging Configuration

The `pyproject.toml` is fully configured:

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "passfx"
version = "1.0.0"
description = "A secure, stylish terminal password manager. Your secrets are safe with us. Probably."
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
authors = [{ name = "PassFX Team", email = "secrets@/dev/null" }]
keywords = ["password", "manager", "cli", "security", "encryption"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Environment :: Console",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Topic :: Security",
]
dependencies = [
    "rich>=14.0.0,<15.0.0",
    "cryptography>=46.0.0,<47.0.0",
    "pyperclip>=1.11.0,<2.0.0",
    "zxcvbn>=4.5.0,<5.0.0",
    "textual>=6.11.0,<7.0.0",
    "setproctitle>=1.3.7,<2.0.0",
    "simple-term-menu>=1.6.6,<2.0.0",
]

[project.scripts]
passfx = "passfx.cli:main"

[tool.hatch.build.targets.wheel]
packages = ["passfx"]

[tool.hatch.build.targets.sdist]
exclude = [".claude/"]
```

### GitHub Actions Workflow

The publish workflow exists at `.github/workflows/publish.yml`:

```yaml
name: Publish to PyPI

on:
  release:
    types: [published]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install build
      - run: python -m build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  publish:
    needs: build
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write  # OIDC for Trusted Publishing
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - uses: pypa/gh-action-pypi-publish@release/v1
```

This workflow uses Trusted Publishing (OIDC) - no API tokens needed.

### Package Contents

The wheel contains exactly what's needed:

```
passfx-1.0.0-py3-none-any.whl (39 files, 546KB)
├── passfx/                    # All Python modules
│   ├── __init__.py            # __version__ = "1.0.0"
│   ├── __main__.py            # python -m passfx support
│   ├── cli.py                 # main() entry point
│   ├── core/                  # crypto.py, vault.py, models.py
│   ├── screens/               # 11 Textual screens
│   ├── styles/
│   │   └── passfx.tcss        # 78,932 bytes - INCLUDED
│   ├── ui/                    # logo, menu, styles
│   ├── utils/                 # clipboard, generator, io, strength
│   └── widgets/               # terminal, id_card_modal
└── passfx-1.0.0.dist-info/
    ├── METADATA               # Full project metadata
    ├── WHEEL                  # Wheel metadata
    ├── entry_points.txt       # passfx = passfx.cli:main
    └── licenses/LICENSE       # MIT
```

### Verified Build Output

```
$ python -m build
Successfully built passfx-1.0.0.tar.gz and passfx-1.0.0-py3-none-any.whl

$ twine check dist/*
Checking dist/passfx-1.0.0-py3-none-any.whl: PASSED
Checking dist/passfx-1.0.0.tar.gz: PASSED
```

### Verified Installation

```
$ pip install dist/passfx-1.0.0-py3-none-any.whl
Successfully installed passfx-1.0.0

$ which passfx
/tmp/passfx-test-install/bin/passfx

$ python -c "import passfx; print(passfx.__version__)"
1.0.0

$ python -c "from passfx.cli import main; print('Entry point:', main)"
Entry point: <function main at 0x...>
```

---

## Build and Test Locally

To rebuild and verify locally:

```bash
# Create fresh venv
rm -rf .venv
python3 -m venv .venv
source .venv/bin/activate

# Install build tools
pip install --upgrade pip build twine

# Build
rm -rf dist/
python -m build

# Verify
twine check dist/*
unzip -l dist/passfx-1.0.0-py3-none-any.whl | grep tcss  # Should show passfx.tcss

# Test installation in separate venv
python3 -m venv /tmp/test-install
source /tmp/test-install/bin/activate
pip install dist/passfx-1.0.0-py3-none-any.whl
python -c "import passfx; print(passfx.__version__)"
which passfx
```

---

## External Setup Required

### 1. Create PyPI Account

1. Go to https://pypi.org/account/register/
2. Create account with a valid email
3. Verify your email address

### 2. Enable 2FA on PyPI (Mandatory)

PyPI enforces 2FA for all publishers.

1. Log in to https://pypi.org/manage/account/
2. Navigate to "Two factor authentication"
3. Enable TOTP (recommended) or WebAuthn
4. **Store recovery codes offline in an encrypted location**

### 3. Create TestPyPI Account

TestPyPI is separate from PyPI. You need both.

1. Go to https://test.pypi.org/account/register/
2. Create account (can use same email)
3. Enable 2FA at https://test.pypi.org/manage/account/

### 4. Configure Trusted Publishing on PyPI

Before automated releases work, PyPI must trust your GitHub repo.

1. Log in to https://pypi.org
2. Go to "Your projects" > "Manage" > "Publishing"
   - If first upload: https://pypi.org/manage/account/publishing/
   - After first upload: https://pypi.org/manage/project/passfx/settings/publishing/
3. Add a new publisher with these exact values:

| Field | Value |
|-------|-------|
| Owner | `dinesh-git17` |
| Repository | `passfx` |
| Workflow name | `publish.yml` |
| Environment | `pypi` |

4. Click "Add"

For TestPyPI, repeat at:
https://test.pypi.org/manage/account/publishing/

### 5. Create GitHub Environment

1. Go to https://github.com/dinesh-git17/passfx/settings/environments
2. Click "New environment"
3. Name: `pypi`
4. Optional: Add required reviewers for manual approval
5. Optional: Limit deployment to `main` branch

---

## Release Process

### First Release (Manual Upload to Claim Name)

For the first-ever release, upload manually to claim the package name:

```bash
# Build fresh
rm -rf dist/
python -m build

# Upload to TestPyPI first
twine upload --repository testpypi dist/*
# Username: __token__
# Password: your TestPyPI API token

# Test installation from TestPyPI
pip install --index-url https://test.pypi.org/simple/ \
    --extra-index-url https://pypi.org/simple/ passfx

# If TestPyPI works, upload to PyPI
twine upload dist/*
# Username: __token__
# Password: your PyPI API token
```

### Subsequent Releases (Automated)

After the first release, use GitHub Releases:

1. **Update version numbers:**
   - `pyproject.toml`: `version = "1.1.0"`
   - `passfx/__init__.py`: `__version__ = "1.1.0"`
   - `docs/CHANGELOG.md`: Add release notes

2. **Commit and push:**
   ```bash
   git checkout -b release/v1.1.0
   git add pyproject.toml passfx/__init__.py docs/CHANGELOG.md
   git commit -m "chore(release): bump version to 1.1.0"
   git push -u origin release/v1.1.0
   ```

3. **Create PR, get review, merge to main**

4. **Create release:**
   ```bash
   git checkout main
   git pull origin main
   git tag -a v1.1.0 -m "Release v1.1.0"
   git push origin v1.1.0
   ```

5. **Publish via GitHub UI:**
   - Go to https://github.com/dinesh-git17/passfx/releases
   - Click "Draft a new release"
   - Select tag `v1.1.0`
   - Title: `v1.1.0`
   - Description: Copy from CHANGELOG.md
   - Click "Publish release"

6. **Monitor workflow:**
   - Go to https://github.com/dinesh-git17/passfx/actions
   - Watch "Publish to PyPI" workflow complete

---

## Troubleshooting

### Build Failures

| Issue | Cause | Fix |
|-------|-------|-----|
| `ModuleNotFoundError: hatchling` | Missing build deps | `pip install build` |
| Wheel missing TCSS | File not in package dir | Verify `passfx/styles/passfx.tcss` exists |
| `twine check` warnings | Missing readme | Already fixed with `readme = "README.md"` |

### Upload Failures

| Issue | Cause | Fix |
|-------|-------|-----|
| `403 Forbidden` | Wrong token | Use token starting with `pypi-` |
| `400 File already exists` | Version uploaded | Bump version number |
| `Invalid token` | TestPyPI vs PyPI mismatch | Use correct token for each index |

### Installation Failures

| Issue | Cause | Fix |
|-------|-------|-----|
| `passfx: command not found` | Venv not activated | `source .venv/bin/activate` |
| `ModuleNotFoundError` | Wrong pip | Use `python -m pip install` |
| Missing TCSS at runtime | Broken wheel | Rebuild with `python -m build` |

### Trusted Publishing Failures

| Issue | Cause | Fix |
|-------|-------|-----|
| OIDC token rejected | Publisher misconfigured | Verify owner/repo/workflow/environment match exactly |
| Environment not found | Missing GitHub environment | Create `pypi` environment in repo settings |
| Workflow not triggered | Wrong release event | Must be `published`, not `created` |

---

## Next Actions Checklist

Complete these steps in order to publish PassFX to PyPI:

### One-Time Setup (Do Once)

- [ ] **Create PyPI account** at https://pypi.org/account/register/
- [ ] **Enable 2FA on PyPI** at https://pypi.org/manage/account/
- [ ] **Create TestPyPI account** at https://test.pypi.org/account/register/
- [ ] **Enable 2FA on TestPyPI** at https://test.pypi.org/manage/account/
- [ ] **Create API token on TestPyPI** at https://test.pypi.org/manage/account/token/
- [ ] **Create API token on PyPI** at https://pypi.org/manage/account/token/
- [ ] **Create GitHub environment** `pypi` at https://github.com/dinesh-git17/passfx/settings/environments

### First Release

- [ ] **Build locally**: `python -m build`
- [ ] **Upload to TestPyPI**: `twine upload --repository testpypi dist/*`
- [ ] **Test from TestPyPI**: Install in clean venv, verify TUI launches
- [ ] **Upload to PyPI**: `twine upload dist/*`
- [ ] **Configure Trusted Publishing** on PyPI (after first upload creates project)
- [ ] **Configure Trusted Publishing** on TestPyPI
- [ ] **Verify on PyPI**: https://pypi.org/project/passfx/
- [ ] **Test public install**: `pip install passfx` in clean venv

### Subsequent Releases

- [ ] Update version in `pyproject.toml` and `passfx/__init__.py`
- [ ] Update `docs/CHANGELOG.md`
- [ ] Create PR, merge to main
- [ ] Create git tag: `git tag -a vX.Y.Z -m "Release vX.Y.Z"`
- [ ] Push tag: `git push origin vX.Y.Z`
- [ ] Create GitHub Release (triggers automated publish)
- [ ] Verify on PyPI

---

## Quick Reference

### Version Locations

| File | Format |
|------|--------|
| `pyproject.toml` | `version = "X.Y.Z"` |
| `passfx/__init__.py` | `__version__ = "X.Y.Z"` |
| Git tag | `vX.Y.Z` |

### Commands

```bash
# Build
python -m build

# Check
twine check dist/*

# Upload to TestPyPI
twine upload --repository testpypi dist/*

# Upload to PyPI
twine upload dist/*

# Install from TestPyPI
pip install --index-url https://test.pypi.org/simple/ \
    --extra-index-url https://pypi.org/simple/ passfx

# Install from PyPI
pip install passfx
```

### URLs

| Resource | URL |
|----------|-----|
| PyPI Project | https://pypi.org/project/passfx/ |
| TestPyPI Project | https://test.pypi.org/project/passfx/ |
| PyPI Account | https://pypi.org/manage/account/ |
| GitHub Actions | https://github.com/dinesh-git17/passfx/actions |
| GitHub Releases | https://github.com/dinesh-git17/passfx/releases |

---

*Local packaging setup completed: December 2025*
*PassFX version: 1.0.0*
*Build system: Hatchling*
*Status: Ready for first PyPI upload*
