pypi-doppler

安装量: 55
排名: #13579

安装

npx skills add https://github.com/terrylica/cc-skills --skill pypi-doppler

This skill supports LOCAL machine publishing ONLY.

FORBIDDEN

Publishing from GitHub ActionsPublishing from any CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins, CircleCI) ❌ publishCmd in semantic-release configurationBuilding packages in CI (uv build in prepareCmd) ❌ Storing PyPI tokens in GitHub secrets

REQUIRED

Use scripts/publish-to-pypi.sh on local machineCI detection guards in publish scriptManual approval before each releaseDoppler credential management (no plaintext tokens) ✅ Repository verification (prevents fork abuse)

Rationale

  • Security: No long-lived PyPI tokens in GitHub secrets

  • Speed: 30 seconds locally vs 3-5 minutes in CI

  • Control: Manual approval step before production release

  • Flexibility: Centralized credential management via Doppler

See: ADR-0027, docs/development/PUBLISHING.md

Overview

This skill provides local-only PyPI publishing using Doppler for secure credential management. It integrates with the workspace-wide release workflow where:

  • GitHub Actions: Automated versioning ONLY (tags, releases, CHANGELOG)

  • Local Machine: Manual PyPI publishing with Doppler credentials

Bundled Scripts

| scripts/publish-to-pypi.sh | Local PyPI publishing with CI detection guards

Usage: Copy to your project's scripts/ directory:

/usr/bin/env bash << 'DOPPLER_EOF'
# Environment-agnostic path
PLUGIN_DIR="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/itp}"
cp "$PLUGIN_DIR/skills/pypi-doppler/scripts/publish-to-pypi.sh" scripts/
chmod +x scripts/publish-to-pypi.sh
DOPPLER_EOF

Prerequisites

One-Time Setup

  • Install Doppler CLI:
brew install dopplerhq/cli/doppler
  • Authenticate with Doppler:
doppler login
  • Verify access to claude-config project:
doppler whoami
doppler projects

PyPI Token Setup

  • Create PyPI API token:

Visit: https://pypi.org/manage/account/token/

  • Enable 2FA if not already enabled (required since 2024)

  • Create token with scope: "Entire account" or specific project

  • Copy token (starts with pypi-AgEIcHlwaS5vcmc..., ~180 characters)

  • Store token in Doppler:

doppler secrets set PYPI_TOKEN='pypi-AgEIcHlwaS5vcmc...' \
  --project claude-config \
  --config prd
  • Verify token stored:
doppler secrets get PYPI_TOKEN \
  --project claude-config \
  --config prd \
  --plain

Publishing Workflow

MANDATORY: Verify Version Increment Before Publishing

Pre-publish validation: Before publishing to PyPI, verify that the version has incremented from the previous release. Publishing without a version increment is invalid and wastes resources.

Autonomous check sequence:

  • Compare local pyproject.toml version against latest PyPI version

  • If versions match → STOP - do not proceed with publishing

  • Inform user: "Version not incremented. Run semantic-release first or verify commits include feat: or fix: types."

Why this matters: PyPI rejects duplicate versions, but more importantly, users and package managers rely on version increments to detect updates. A release workflow that doesn't increment version is broken.

Complete Release Workflow

Step 1: Development & Commit (Conventional Commits):

# Make your changes
git add .

# Commit with conventional format (determines version bump)
git commit -m "feat: add new feature"  # MINOR bump

# Push to main
git push origin main

Step 2: Automated Versioning (GitHub Actions - 40-60s):

GitHub Actions workflow automatically:

  • ✅ Analyzes commits using @semantic-release/commit-analyzer

  • ✅ Determines next version (e.g., v7.1.0)

  • ✅ Updates pyproject.toml, package.json versions

  • ✅ Generates and updates CHANGELOG.md

  • ✅ Creates git tag (v7.1.0)

  • ✅ Creates GitHub release with release notes

  • ✅ Commits changes back to repo with [skip ci] message

⚠️ PyPI publishing does NOT happen here (by design - see ADR-0027)

Step 3: Local PyPI Publishing (30 seconds):

After GitHub Actions completes, publish to PyPI locally:

# Pull the latest release commit
git pull origin main

# Publish to PyPI (uses pypi-doppler skill)
./scripts/publish-to-pypi.sh

Expected output:

🚀 Publishing to PyPI (Local Workflow)
======================================

🔐 Step 0: Verifying Doppler credentials...
   ✅ Doppler token verified

📥 Step 1: Pulling latest release commit...
   Current version: v7.1.0

🧹 Step 2: Cleaning old builds...
   ✅ Cleaned

📦 Step 3: Building package...
   ✅ Built: dist/gapless_crypto_clickhouse-7.1.0-py3-none-any.whl

📤 Step 4: Publishing to PyPI...
   Using PYPI_TOKEN from Doppler
   ✅ Published to PyPI

🔍 Step 5: Verifying on PyPI...
   ✅ Verified: https://pypi.org/project/gapless-crypto-clickhouse/7.1.0/

✅ Complete! Published v7.1.0 to PyPI in 28 seconds

Publishing Command (Local Machine Only)

CRITICAL: This command must ONLY run on your local machine, NEVER in CI/CD.

/usr/bin/env bash << 'GIT_EOF'
# First time: copy script from skill to your project (environment-agnostic)
PLUGIN_DIR="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/itp}"
cp "$PLUGIN_DIR/skills/pypi-doppler/scripts/publish-to-pypi.sh" scripts/
chmod +x scripts/publish-to-pypi.sh

# After semantic-release creates GitHub release
git pull origin main

# Publish using local copy of bundled script
./scripts/publish-to-pypi.sh
GIT_EOF

Bundled script features:

  • ✅ CI detection guards (blocks if CI=true)

  • ✅ Repository verification (prevents fork abuse)

  • ✅ Doppler integration (PYPI_TOKEN retrieval)

  • ✅ Build + publish + verify workflow

  • ✅ Clear error messages

Manual Publishing (Advanced)

For manual publishing without the canonical script:

/usr/bin/env bash << 'CONFIG_EOF'
# Retrieve token from Doppler
PYPI_TOKEN=$(doppler secrets get PYPI_TOKEN \
  --project claude-config \
  --config prd \
  --plain)

# Build package
uv build

# Publish to PyPI
UV_PUBLISH_TOKEN="${PYPI_TOKEN}" uv publish
CONFIG_EOF

⚠️ WARNING: Manual publishing bypasses CI detection guards and repository verification. Use canonical script unless you have a specific reason not to.

CI Detection Enforcement

The canonical publish script (scripts/publish-to-pypi.sh) includes CI detection guards to prevent accidental execution in CI/CD pipelines.

Environment Variables Checked

  • $CI - Generic CI indicator

  • $GITHUB_ACTIONS - GitHub Actions

  • $GITLAB_CI - GitLab CI

  • $JENKINS_URL - Jenkins

  • $CIRCLECI - CircleCI

Behavior

If any CI variable detected, script exits with error:

❌ ERROR: This script must ONLY be run on your LOCAL machine

   Detected CI environment variables:
   - CI: true
   - GITHUB_ACTIONS: <not set>
   ...

   This project enforces LOCAL-ONLY PyPI publishing for:
   - Security: No long-lived PyPI tokens in GitHub secrets
   - Speed: 30 seconds locally vs 3-5 minutes in CI
   - Control: Manual approval step before production release

   See: docs/development/PUBLISHING.md (ADR-0027)

Testing CI Detection

# This should FAIL with error message
CI=true ./scripts/publish-to-pypi.sh

# Expected: ❌ ERROR: This script must ONLY be run on your LOCAL machine

Credential Management

Doppler Configuration

Project: claude-config Configs: prd (production), dev (development) Secret Name: PYPI_TOKEN

Token Format

Valid PyPI token format:

  • Starts with: pypi-AgEIcHlwaS5vcmc

  • Length: ~180 characters

  • Example: pypi-AgEIcHlwaS5vcmcCJGI4YmNhMDA5LTg...

Token Permissions

Account-wide token (recommended):

  • Can publish to all projects under your account

  • Simpler management

  • One token for all repositories

Project-scoped token:

  • Can only publish to specific project

  • More restrictive

  • Separate token per project needed

Token Rotation

# 1. Create new token on PyPI
# Visit: https://pypi.org/manage/account/token/

# 2. Update Doppler
doppler secrets set PYPI_TOKEN='new-token' \
  --project claude-config \
  --config prd

# 3. Verify new token works
doppler secrets get PYPI_TOKEN \
  --project claude-config \
  --config prd \
  --plain

# 4. Test publish (dry-run not available, use TestPyPI)
# See: Troubleshooting → TestPyPI Testing

Troubleshooting

Issue: "PYPI_TOKEN not found in Doppler"

Symptom: Script fails at Step 0

Fix:

# Verify token exists
doppler secrets --project claude-config --config prd | grep PYPI_TOKEN

# If missing, get new token from PyPI
# Visit: https://pypi.org/manage/account/token/
# Create token with scope: "Entire account" or specific project

# Store in Doppler
doppler secrets set PYPI_TOKEN='your-token' \
  --project claude-config \
  --config prd

Issue: "403 Forbidden from PyPI"

Symptom: Script fails at Step 4 with authentication error

Root Cause: Token expired or invalid (PyPI requires 2FA since 2024)

Fix:

  • Verify 2FA enabled on PyPI account

  • Create new token: https://pypi.org/manage/account/token/

  • Update Doppler: doppler secrets set PYPI_TOKEN='new-token' --project claude-config --config prd

  • Retry publish

Issue: "Script blocked with CI detection error"

Symptom:

❌ ERROR: This script must ONLY be run on your LOCAL machine
Detected CI environment variables:
- CI: true

Root Cause: Running in CI environment OR CI variable set locally

Fix:

# Check if CI variable set in your shell
env | grep CI

# If set, unset it
unset CI
unset GITHUB_ACTIONS

# Retry publish
./scripts/publish-to-pypi.sh

Expected behavior: This is INTENTIONAL - script should ONLY run locally.

Issue: "Version not updated in pyproject.toml"

Symptom: Local publish uses old version number

Root Cause: Didn't pull latest release commit from GitHub

Fix:

# Always pull before publishing
git pull origin main

# Verify version updated
grep '^version = ' pyproject.toml

# Retry publish
./scripts/publish-to-pypi.sh

Issue: "uv package manager not found"

Symptom: Script fails at startup before any steps

Root Cause: uv not installed or not discoverable

How the script discovers uv (in priority order):

  • Already in PATH (Homebrew, direct install, shell configured)

  • Common direct install locations (~/.local/bin/uv, ~/.cargo/bin/uv, /opt/homebrew/bin/uv)

  • Version managers as fallback (mise, asdf)

Fix: Install uv using any method:

# Official installer (recommended)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Homebrew
brew install uv

# Cargo
cargo install uv

# mise (if you use it)
mise use uv@latest

The script doesn't force any particular installation method.

Issue: Script Hangs with No Output

Symptom: Script starts but produces no output, eventually times out

Root Cause: Script sources ~/.zshrc or ~/.bashrc which waits for interactive input

Fix: Never source shell config files in scripts. The bundled script uses:

/usr/bin/env bash << 'MISE_EOF'
# CORRECT - safe for non-interactive shells
eval "$(mise activate bash 2>/dev/null)" || true

# WRONG - hangs in non-interactive shells
source ~/.zshrc
MISE_EOF

TestPyPI Testing

To test publishing workflow without affecting production:

  • Get TestPyPI token:

Visit: https://test.pypi.org/manage/account/token/

  • Create token

  • Store in Doppler (separate key):

doppler secrets set TESTPYPI_TOKEN='your-test-token' \
  --project claude-config \
  --config prd
  • Modify publish script temporarily:

/usr/bin/env bash << 'DOPPLER_EOF_2'

In scripts/publish-to-pypi.sh, change:

uv publish --token "${PYPI_TOKEN}"

To:

TESTPYPI_TOKEN=$(doppler secrets get TESTPYPI_TOKEN --plain) uv publish --repository testpypi --token "${TESTPYPI_TOKEN}"

DOPPLER_EOF_2

4. **Test publish**:

   ```bash
   ./scripts/publish-to-pypi.sh
  • Verify on TestPyPI:

https://test.pypi.org/project/your-package/

  • Restore script to production configuration
  • ADR-0027: docs/architecture/decisions/0027-local-only-pypi-publishing.md - Architectural decision for local-only publishing

  • ADR-0028: docs/architecture/decisions/0028-skills-documentation-alignment.md - Skills alignment with ADR-0027

  • PUBLISHING.md: docs/development/PUBLISHING.md - Complete release workflow guide

  • semantic-release Skill: semantic-release - Versioning automation (NO publishing)

  • Bundled Script: scripts/publish-to-pypi.sh - Reference implementation with CI guards

Validation History

  • 2025-12-03: Refactored to discovery-first, environment-agnostic approach

discover_uv() checks PATH → direct installs → version managers (priority order)

  • Supports: curl install, Homebrew, cargo, mise, asdf - doesn't force any method

  • Early discovery at startup before any workflow steps

  • Troubleshooting for non-interactive shell issues

  • 2025-11-22: Created with ADR-0027 alignment (workspace-wide local-only policy)

  • Validation: CI detection guards tested, Doppler integration verified

Last Updated: 2025-12-03 Policy: Workspace-wide local-only PyPI publishing (ADR-0027) Supersedes: None (created with ADR-0027 compliance from start)

返回排行榜