
Pypi Doppler
- 114 installs
- 62 repo stars
- Updated August 3, 2026
- terrylica/cc-skills
Use pypi-doppler for development tasks
About
pypi-doppler: A skill for development. This provides functionality for development workflows.
- pypi-doppler
Pypi Doppler by the numbers
- 114 all-time installs (skills.sh)
- +2 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,903 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/terrylica/cc-skills --skill pypi-dopplerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 114 |
|---|---|
| repo stars | ★ 62 |
| Last updated | August 3, 2026 |
| Repository | terrylica/cc-skills ↗ |
What it does
Use pypi-doppler for development tasks
Files
PyPI Publishing with Doppler (Local-Only)
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
When to Use This Skill
Use this skill when:
- Publishing Python packages to PyPI from local machine
- Setting up Doppler for PyPI token management
- Creating local publish scripts with CI detection guards
- Validating repository ownership before release
WORKSPACE-WIDE POLICY: LOCAL-ONLY PUBLISHING
This skill supports LOCAL machine publishing ONLY.
FORBIDDEN
- Publishing from GitHub Actions
- Publishing from any CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins, CircleCI)
publishCmdin semantic-release configuration- Building packages in CI (
uv buildin prepareCmd) - Storing PyPI tokens in GitHub secrets
REQUIRED
- Use
scripts/publish-to-pypi.shon local machine - CI detection guards in publish script
- Manual approval before each release
- Doppler 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:
1. GitHub Actions: Automated versioning ONLY (tags, releases, CHANGELOG) 2. Local Machine: Manual PyPI publishing with Doppler credentials
Bundled Scripts
| Script | Purpose |
|---|---|
| `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
1. Install Doppler CLI:
brew install dopplerhq/cli/doppler2. Authenticate with Doppler:
doppler login3. Verify access to `claude-config` project:
doppler whoami
doppler projectsPyPI Token Setup
1. 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)
2. Store token in Doppler:
doppler secrets set PYPI_TOKEN='pypi-AgEIcHlwaS5vcmc...' \
--project claude-config \
--config prd3. 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:
1. Compare local pyproject.toml version against latest PyPI version 2. If versions match -- STOP - do not proceed with publishing 3. Inform user: "Version not incremented. Run semantic-release first or verify commits include feat: or fix: types."
Complete Release Workflow
Step 1: Development & Commit (Conventional Commits):
git add .
git commit -m "feat: add new feature" # MINOR bump
git push origin mainStep 2: Automated Versioning (GitHub Actions - 40-60s):
GitHub Actions automatically: analyzes commits, determines next version, updates pyproject.toml/package.json, generates CHANGELOG, creates git tag, creates GitHub release.
PyPI publishing does NOT happen here (by design - see ADR-0027).
Step 3: Local PyPI Publishing (30 seconds):
git pull origin main
./scripts/publish-to-pypi.shUsing Bundled Script (Recommended)
/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_EOFBundled script features: CI detection guards, repository verification, Doppler integration, 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_EOFWARNING: Manual publishing bypasses CI detection guards and repository verification. Use canonical script unless you have a specific reason not to.
---
Reference Documentation
| Topic | Reference |
|---|---|
| CI Detection | CI Detection Enforcement |
| Credential Management | Doppler & Token Management |
| Troubleshooting | Troubleshooting Guide |
| TestPyPI Testing | TestPyPI Testing |
| mise Task Integration | mise Task Integration |
---
Related Documentation
- 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 - mise-tasks Skill: `mise-tasks` - Task orchestration with dependency management
- Release Workflow Patterns: `release-workflow-patterns.md` - DAG patterns and anti-patterns
- 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
- 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)
Post-Execution Reflection
After this skill completes, check before closing:
1. Did the command succeed? — If not, fix the instruction or error table that caused the failure. 2. Did parameters or output change? — If the underlying tool's interface drifted, update Usage examples and Parameters table to match. 3. Was a workaround needed? — If you had to improvise (different flags, extra steps), update this SKILL.md so the next invocation doesn't need the same workaround.
Only update if the issue is real and reproducible — not speculative.
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 machineCredential 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: TestPyPI Testing referenceEvolution Log
Convention: Reverse chronological order (newest on top, oldest at bottom). Prepend new entries.
---
2026-02-26: Initial Evolution Log
Status: Skill is in use and maintained. Track improvements here.
Purpose
This evolution log tracks updates to the skill. Each entry should note:
- What changed (content, structure, tooling)
- Why it changed (bug fix, feature request, best practice)
- Files affected
How to Use
1. When updating SKILL.md or references, add an entry here with the date 2. Keep entries reverse-chronological (newest first) 3. Link to ADRs or GitHub issues when relevant 4. Reference specific line changes when helpful
---
mise Task Integration
When using mise tasks to orchestrate the release workflow, the publish task must depend on the build task. Without this dependency, running mise run release:pypi before building will fail because no wheels exist.
# .mise.toml — CORRECT: publish depends on build
[tasks."release:build-all"]
description = "Build all platform wheels + sdist"
depends = ["release:version"]
run = """
mise run release:macos-arm64
mise run release:linux
mise run release:sdist
# Consolidate artifacts to dist/
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*= "\\(.*\\)"/\\1/')
cp -n target/wheels/*-${VERSION}-*.whl dist/ 2>/dev/null || true
cp -n target/wheels/*-${VERSION}.tar.gz dist/ 2>/dev/null || true
"""
[tasks."release:pypi"]
description = "Publish to PyPI using Doppler credentials (local-only, ADR-0027)"
depends = ["release:build-all"] # CRITICAL: enforces build-before-publish
run = "./scripts/publish-to-pypi.sh"
[tasks."release:full"]
description = "Full release workflow"
depends = ["release:postflight", "release:pypi"] # Include ALL phases
run = "echo 'Released and published!'"Anti-pattern: Defining release:pypi without depends on release:build-all. The publish script will detect "no wheels found" and fail, but the failure happens late instead of being prevented by the task DAG.
See Release Workflow Patterns for the complete DAG pattern and audit checklist.
TestPyPI Testing
To test the publishing workflow without affecting production:
1. Get TestPyPI token:
- Visit: <https://test.pypi.org/manage/account/token/>
- Create token
2. Store in Doppler (separate key):
doppler secrets set TESTPYPI_TOKEN='your-test-token' \
--project claude-config \
--config prd3. 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_24. Test publish:
./scripts/publish-to-pypi.sh5. Verify on TestPyPI:
- <https://test.pypi.org/project/your-package/>
6. Restore script to production configuration
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 prdIssue: "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:
1. Verify 2FA enabled on PyPI account 2. Create new token: <https://pypi.org/manage/account/token/> 3. Update Doppler: doppler secrets set PYPI_TOKEN='new-token' --project claude-config --config prd 4. 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: trueRoot 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.shExpected 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.shIssue: "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):
1. Already in PATH (Homebrew, direct install, shell configured) 2. Common direct install locations (~/.local/bin/uv, ~/.cargo/bin/uv, /opt/homebrew/bin/uv) 3. 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@latestThe 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:
# PROCESS-STORM-OK
/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#!/bin/bash
# PyPI Publishing with Doppler Secret Management (Local-Only)
#
# WORKSPACE-WIDE POLICY: This script must ONLY run on local machines.
# CI/CD publishing is forbidden - see ADR-0027 for rationale.
#
# Prerequisites:
# - Doppler CLI installed (brew install dopplerhq/cli/doppler)
# - uv package manager installed (curl -LsSf https://astral.sh/uv/install.sh | sh)
# - PYPI_TOKEN stored in Doppler (project: claude-config, config: prd)
# - pyproject.toml with name and version fields
#
# Usage:
# Copy this script to your project's scripts/ directory and run:
# git pull origin main
# ./scripts/publish-to-pypi.sh
#
# This script is project-agnostic and environment-agnostic.
set -euo pipefail
# ============================================================================
# CONFIGURATION (ADR: 2025-12-08-mise-env-centralized-config)
# ============================================================================
# Environment variables with defaults for backward compatibility.
# These can be pre-set via mise [env] or exported manually.
DOPPLER_PROJECT="${DOPPLER_PROJECT:-claude-config}"
DOPPLER_CONFIG="${DOPPLER_CONFIG:-prd}"
DOPPLER_PYPI_SECRET="${DOPPLER_PYPI_SECRET:-PYPI_TOKEN}"
PYPI_VERIFY_DELAY="${PYPI_VERIFY_DELAY:-3}"
# ============================================================================
# ENVIRONMENT DISCOVERY
# ============================================================================
# Discover how uv is installed before making assumptions.
# Supports: direct install, Homebrew, cargo, mise, asdf, or already in PATH.
#
# Non-interactive shells (like Claude Code) don't source shell configs,
# so we need to find tools explicitly.
discover_uv() {
# Priority 1: Already in PATH (native install, Homebrew, or shell already configured)
if command -v uv &> /dev/null; then
echo "uv"
return 0
fi
# Priority 2: Check common direct installation locations
local uv_locations=(
"$HOME/.local/bin/uv" # Official curl installer
"$HOME/.cargo/bin/uv" # Cargo install
"/opt/homebrew/bin/uv" # macOS Homebrew (ARM)
"/usr/local/bin/uv" # macOS Homebrew (Intel) / Linux package manager
)
for uv_path in "${uv_locations[@]}"; do
if [[ -x "$uv_path" ]]; then
echo "$uv_path"
return 0
fi
done
# Priority 3: Try version managers (mise, asdf) as fallback
# Only if uv not found directly - don't force any tool manager
# Try mise
local mise_locations=(
"$HOME/.local/bin/mise"
"/opt/homebrew/bin/mise"
"/usr/local/bin/mise"
)
for mise_path in "${mise_locations[@]}"; do
if [[ -x "$mise_path" ]]; then
# Check if mise has uv available
if "$mise_path" which uv &>/dev/null 2>&1; then
echo "$mise_path exec -- uv"
return 0
fi
fi
done
# Try asdf
if [[ -f "$HOME/.asdf/asdf.sh" ]]; then
# shellcheck source=/dev/null
source "$HOME/.asdf/asdf.sh" 2>/dev/null || true
if command -v uv &> /dev/null; then
echo "uv"
return 0
fi
fi
# Not found
return 1
}
# Discover uv installation method
UV_CMD=""
if UV_CMD=$(discover_uv); then
: # Found
else
echo ""
echo "==============================================================="
echo " ERROR: uv package manager not found"
echo "==============================================================="
echo ""
echo " Install uv using one of these methods:"
echo ""
echo " # Official installer (recommended)"
echo " curl -LsSf https://astral.sh/uv/install.sh | sh"
echo ""
echo " # Homebrew"
echo " brew install uv"
echo ""
echo " # Cargo"
echo " cargo install uv"
echo ""
echo " # mise"
echo " mise use uv@latest"
echo ""
exit 1
fi
# ============================================================================
# CI DETECTION GUARDS
# ============================================================================
# This script must ONLY run on local machines, NEVER in CI/CD pipelines.
# Rationale (ADR-0027):
# - 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
detect_ci_environment() {
local ci_detected=false
local detected_vars=""
if [[ -n "${CI:-}" ]]; then
ci_detected=true
detected_vars="${detected_vars}\n - CI: ${CI}"
fi
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
ci_detected=true
detected_vars="${detected_vars}\n - GITHUB_ACTIONS: ${GITHUB_ACTIONS}"
fi
if [[ -n "${GITLAB_CI:-}" ]]; then
ci_detected=true
detected_vars="${detected_vars}\n - GITLAB_CI: ${GITLAB_CI}"
fi
if [[ -n "${JENKINS_URL:-}" ]]; then
ci_detected=true
detected_vars="${detected_vars}\n - JENKINS_URL: ${JENKINS_URL}"
fi
if [[ -n "${CIRCLECI:-}" ]]; then
ci_detected=true
detected_vars="${detected_vars}\n - CIRCLECI: ${CIRCLECI}"
fi
if [[ -n "${TRAVIS:-}" ]]; then
ci_detected=true
detected_vars="${detected_vars}\n - TRAVIS: ${TRAVIS}"
fi
if [[ -n "${BUILDKITE:-}" ]]; then
ci_detected=true
detected_vars="${detected_vars}\n - BUILDKITE: ${BUILDKITE}"
fi
if [[ "${ci_detected}" == "true" ]]; then
echo ""
echo "==============================================================="
echo " ERROR: This script must ONLY be run on your LOCAL machine"
echo "==============================================================="
echo ""
echo " Detected CI environment variables:"
echo -e "${detected_vars}"
echo ""
echo " This project enforces LOCAL-ONLY PyPI publishing for:"
echo " - Security: No long-lived PyPI tokens in GitHub secrets"
echo " - Speed: 30 seconds locally vs 3-5 minutes in CI"
echo " - Control: Manual approval step before production release"
echo ""
echo " See: docs/development/PUBLISHING.md (ADR-0027)"
echo ""
exit 1
fi
}
# Run CI detection immediately
detect_ci_environment
# ============================================================================
# BRANCH VALIDATION GUARDS
# ============================================================================
# Publishing must only happen from main/master to ensure released code matches
# the version tagged by semantic-release.
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "${CURRENT_BRANCH}" != "main" && "${CURRENT_BRANCH}" != "master" ]]; then
echo ""
echo "==============================================================="
echo " ERROR: This script must ONLY be run on main/master branch"
echo "==============================================================="
echo ""
echo " Current branch: ${CURRENT_BRANCH}"
echo " Required branch: main or master"
echo ""
echo " To publish, switch to main and pull latest:"
echo " git checkout main"
echo " git pull origin main"
echo " ./scripts/publish-to-pypi.sh"
echo ""
exit 1
fi
# ============================================================================
# MAIN PUBLISHING WORKFLOW
# ============================================================================
echo ""
echo "Publishing to PyPI (Local Workflow)"
echo "======================================================"
# Step 0: Verify Doppler token is available
echo -e "\n Step 0: Verifying Doppler credentials..."
if ! command -v doppler &> /dev/null; then
echo " ERROR: Doppler CLI not installed"
echo " Install: brew install dopplerhq/cli/doppler"
exit 1
fi
# Try to get PYPI_TOKEN from Doppler (configurable via env vars)
if ! PYPI_TOKEN=$(doppler secrets get "$DOPPLER_PYPI_SECRET" --project "$DOPPLER_PROJECT" --config "$DOPPLER_CONFIG" --plain 2>/dev/null); then
echo " ERROR: $DOPPLER_PYPI_SECRET not found in Doppler"
echo ""
echo " To fix, run:"
echo " doppler secrets set $DOPPLER_PYPI_SECRET='your-token' --project $DOPPLER_PROJECT --config $DOPPLER_CONFIG"
echo ""
echo " Get token from: https://pypi.org/manage/account/token/"
exit 1
fi
echo " Doppler token verified"
# Step 1: Verify pyproject.toml exists
echo -e "\n Step 1: Reading package info from pyproject.toml..."
if [[ ! -f "pyproject.toml" ]]; then
echo " ERROR: pyproject.toml not found"
echo " This script must be run from the project root directory."
exit 1
fi
# Extract package name and version from pyproject.toml
PACKAGE_NAME=$(grep '^name = ' pyproject.toml | sed 's/name = "\(.*\)"/\1/' | head -1)
CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/' | head -1)
if [[ -z "${PACKAGE_NAME}" ]]; then
echo " ERROR: Could not extract package name from pyproject.toml"
exit 1
fi
if [[ -z "${CURRENT_VERSION}" ]]; then
echo " ERROR: Could not extract version from pyproject.toml"
exit 1
fi
echo " Package: ${PACKAGE_NAME}"
echo " Version: v${CURRENT_VERSION}"
# Step 2: Clean old builds - safe glob handling
# ADR: /docs/adr/2025-12-07-idempotency-backup-traceability.md
echo -e "\n Step 2: Cleaning old builds..."
rm -rf dist/ build/ 2>/dev/null || true
find . -maxdepth 1 -name "*.egg-info" -type d -exec rm -rf {} + 2>/dev/null || true
echo " Cleaned"
# Step 3: Build package
echo -e "\n Step 3: Building package..."
echo " Using: $UV_CMD"
$UV_CMD build 2>&1 | grep -E "(Building|Successfully built)" || $UV_CMD build
echo " Built: dist/${PACKAGE_NAME}-${CURRENT_VERSION}*"
# Step 4: Publish to PyPI using Doppler token
echo -e "\n Step 4: Publishing to PyPI..."
echo " Using $DOPPLER_PYPI_SECRET from Doppler ($DOPPLER_PROJECT/$DOPPLER_CONFIG)"
# Use UV_PUBLISH_TOKEN environment variable for security (no token in process list)
UV_PUBLISH_TOKEN="${PYPI_TOKEN}" $UV_CMD publish 2>&1 | grep -E "(Uploading|succeeded|Failed)" || \
UV_PUBLISH_TOKEN="${PYPI_TOKEN}" $UV_CMD publish
echo " Published to PyPI"
# Step 5: Verify publication on PyPI
echo -e "\n Step 5: Verifying on PyPI..."
sleep "$PYPI_VERIFY_DELAY"
# Check if package version is live on PyPI
if curl -s "https://pypi.org/pypi/${PACKAGE_NAME}/${CURRENT_VERSION}/json" | grep -q "\"version\":"; then
echo " Verified: https://pypi.org/project/${PACKAGE_NAME}/${CURRENT_VERSION}/"
else
echo " Still propagating (CDN caching)"
echo " Check manually in 30 seconds: https://pypi.org/project/${PACKAGE_NAME}/${CURRENT_VERSION}/"
fi
echo -e "\n Complete! Published ${PACKAGE_NAME} v${CURRENT_VERSION} to PyPI"
echo ""
echo "Next steps:"
echo " - Verify package is installable: pip install ${PACKAGE_NAME}==${CURRENT_VERSION}"
echo " - Check PyPI page: https://pypi.org/project/${PACKAGE_NAME}/"
echo " - Monitor downloads: https://pypistats.org/packages/${PACKAGE_NAME}"