pypi-doppler

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

PyPI Publishing with Doppler (Local-Only)

基于Doppler的PyPI发布(仅本地使用)

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
当你需要以下操作时使用本技能:
  • 从本地机器向PyPI发布Python包
  • 配置Doppler以管理PyPI令牌
  • 创建带有CI检测防护的本地发布脚本
  • 在发布前验证仓库所有权

⚠️ WORKSPACE-WIDE POLICY: LOCAL-ONLY PUBLISHING

⚠️ 工作区全局策略:仅本地发布

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 configuration
Building packages in CI (
uv build
in prepareCmd) ❌ Storing PyPI tokens in GitHub secrets
从GitHub Actions发布从任何CI/CD流水线发布(GitHub Actions、GitLab CI、Jenkins、CircleCI) ❌ 在semantic-release配置中使用
publishCmd
在CI中构建包(在prepareCmd中使用
uv build
) ❌ 在GitHub Secrets中存储PyPI令牌

REQUIRED

要求操作

Use
scripts/publish-to-pypi.sh
on local machine
CI detection guards in publish scriptManual approval before each releaseDoppler credential management (no plaintext tokens) ✅ Repository verification (prevents fork abuse)
在本地机器上使用
scripts/publish-to-pypi.sh
发布脚本中包含CI检测防护每次发布前需手动批准使用Doppler管理凭证(禁止明文令牌) ✅ 仓库验证(防止分支滥用)

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

  • 安全性:不在GitHub Secrets中存储长期有效的PyPI令牌
  • 速度:本地发布仅需30秒,而CI中需要3-5分钟
  • 可控性:生产发布前需手动批准
  • 灵活性:通过Doppler集中管理凭证
参考: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
本技能提供仅本地使用的PyPI发布功能,通过Doppler实现安全的凭证管理。它与工作区全局发布流程集成,流程如下:
  1. GitHub Actions:仅负责自动版本管理(标签、发布、CHANGELOG)
  2. 本地机器:使用Doppler凭证手动完成PyPI发布

Bundled Scripts

内置脚本

ScriptPurpose
scripts/publish-to-pypi.sh
Local PyPI publishing with CI detection guards
Usage: Copy to your project's
scripts/
directory:
bash
/usr/bin/env bash << 'DOPPLER_EOF'
脚本用途
scripts/publish-to-pypi.sh
带有CI检测防护的本地PyPI发布脚本
使用方法:复制到你的项目
scripts/
目录:
bash
/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

---
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:
    bash
    brew install dopplerhq/cli/doppler
  2. Authenticate with Doppler:
    bash
    doppler login
  3. Verify access to
    claude-config
    project
    :
    bash
    doppler whoami
    doppler projects
  1. 安装Doppler CLI
    bash
    brew install dopplerhq/cli/doppler
  2. 通过Doppler认证
    bash
    doppler login
  3. 验证对
    claude-config
    项目的访问权限
    bash
    doppler whoami
    doppler projects

PyPI Token Setup

PyPI令牌设置

  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:
    bash
    doppler secrets set PYPI_TOKEN='pypi-AgEIcHlwaS5vcmc...' \
      --project claude-config \
      --config prd
  3. Verify token stored:
    bash
    doppler secrets get PYPI_TOKEN \
      --project claude-config \
      --config prd \
      --plain

  1. 创建PyPI API令牌
    • 访问:https://pypi.org/manage/account/token/
    • 若未启用2FA则需启用(2024年起为必填项)
    • 创建令牌,权限范围选择"Entire account"或特定项目
    • 复制令牌(格式为
      pypi-AgEIcHlwaS5vcmc...
      ,约180字符)
  2. 在Doppler中存储令牌
    bash
    doppler secrets set PYPI_TOKEN='pypi-AgEIcHlwaS5vcmc...' \
      --project claude-config \
      --config prd
  3. 验证令牌已存储
    bash
    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."
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.
发布前验证:在发布至PyPI前,需验证本地
pyproject.toml
中的版本号是否比上一个版本高。未递增版本号的发布是无效的,会浪费资源。
自动检查流程
  1. 比较本地
    pyproject.toml
    中的版本号与PyPI上的最新版本
  2. 若版本号相同 → 停止操作,请勿继续发布
  3. 告知用户:"版本号未递增。请先运行semantic-release,或确认提交记录包含
    feat:
    fix:
    类型的提交。"
重要性:PyPI会拒绝重复版本号,但更重要的是,用户和包管理器依赖版本号递增来检测更新。不递增版本号的发布流程是有问题的。

Complete Release Workflow

完整发布流程

Step 1: Development & Commit (Conventional Commits):
bash
undefined
步骤1:开发与提交(Conventional Commits):
bash
undefined

Make your changes

进行代码修改

git add .
git add .

Commit with conventional format (determines version bump)

以规范格式提交(决定版本号升级类型)

git commit -m "feat: add new feature" # MINOR bump
git commit -m "feat: add new feature" # 次要版本(MINOR)升级

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:

```bash
git push origin main

**步骤2:自动版本管理**(GitHub Actions - 40-60秒):

GitHub Actions工作流会自动完成以下操作:

- ✅ 使用`@semantic-release/commit-analyzer`分析提交记录
- ✅ 确定下一个版本号(例如`v7.1.0`)
- ✅ 更新`pyproject.toml`、`package.json`中的版本号
- ✅ 生成并更新`CHANGELOG.md`
- ✅ 创建Git标签(`v7.1.0`)
- ✅ 创建带有发布说明的GitHub Release
- ✅ 将更改提交回仓库,并添加`[skip ci]`标记

**⚠️ 注意:此步骤不进行PyPI发布**(为设计如此,参考ADR-0027)

**步骤3:本地PyPI发布**(30秒):

**在GitHub Actions完成后**,在本地执行PyPI发布:

```bash

Pull the latest release commit

拉取最新的发布提交

git pull origin main
git pull origin main

Publish to PyPI (uses pypi-doppler skill)

发布至PyPI(使用pypi-doppler技能)

./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

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

**预期输出**:

🚀 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.
关键提示:此命令仅能在本地机器运行,禁止在CI/CD中使用。

Using Bundled Script (Recommended)

使用内置脚本(推荐)

bash
/usr/bin/env bash << 'GIT_EOF'
bash
/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
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

在semantic-release创建GitHub Release后

git pull origin main
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
./scripts/publish-to-pypi.sh GIT_EOF

**内置脚本特性**:

- ✅ CI检测防护(当CI=true时阻止执行)
- ✅ 仓库验证(防止分支滥用)
- ✅ Doppler集成(获取PYPI_TOKEN)
- ✅ 构建+发布+验证全流程
- ✅ 清晰的错误提示

Manual Publishing (Advanced)

手动发布(高级用法)

For manual publishing without the canonical script:
bash
/usr/bin/env bash << 'CONFIG_EOF'
若不使用标准脚本,可手动发布:
bash
/usr/bin/env bash << 'CONFIG_EOF'

Retrieve token from Doppler

从Doppler获取令牌

PYPI_TOKEN=$(doppler secrets get PYPI_TOKEN
--project claude-config
--config prd
--plain)
PYPI_TOKEN=$(doppler secrets get PYPI_TOKEN
--project claude-config
--config prd
--plain)

Build package

构建包

uv build
uv build

Publish to PyPI

发布至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.

---
UV_PUBLISH_TOKEN="${PYPI_TOKEN}" uv publish CONFIG_EOF

**⚠️ 警告**:手动发布会绕过CI检测防护和仓库验证。除非有特殊需求,否则请使用标准脚本。

---

CI Detection Enforcement

CI检测强制机制

The canonical publish script (
scripts/publish-to-pypi.sh
) includes CI detection guards to prevent accidental execution in CI/CD pipelines.
标准发布脚本(
scripts/publish-to-pypi.sh
)包含CI检测防护,防止在CI/CD流水线中意外执行。

Environment Variables Checked

检测的环境变量

  • $CI
    - Generic CI indicator
  • $GITHUB_ACTIONS
    - GitHub Actions
  • $GITLAB_CI
    - GitLab CI
  • $JENKINS_URL
    - Jenkins
  • $CIRCLECI
    - CircleCI
  • $CI
    - 通用CI标识
  • $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)
若检测到任何CI变量,脚本会报错退出:
❌ 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

测试CI检测

bash
undefined
bash
undefined

This should FAIL with error message

此命令应报错

CI=true ./scripts/publish-to-pypi.sh
CI=true ./scripts/publish-to-pypi.sh

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

预期结果:❌ ERROR: This script must ONLY be run on your LOCAL machine


---

---

Credential Management

凭证管理

Doppler Configuration

Doppler配置

Project:
claude-config
Configs:
prd
(production),
dev
(development) Secret Name:
PYPI_TOKEN
项目
claude-config
配置环境
prd
(生产)、
dev
(开发) 密钥名称
PYPI_TOKEN

Token Format

令牌格式

Valid PyPI token format:
  • Starts with:
    pypi-AgEIcHlwaS5vcmc
  • Length: ~180 characters
  • Example:
    pypi-AgEIcHlwaS5vcmcCJGI4YmNhMDA5LTg...
有效的PyPI令牌格式:
  • 前缀:
    pypi-AgEIcHlwaS5vcmc
  • 长度:约180字符
  • 示例:
    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

令牌轮换

bash
undefined
bash
undefined

1. Create new token on PyPI

1. 在PyPI上创建新令牌

2. Update Doppler

2. 更新Doppler中的令牌

doppler secrets set PYPI_TOKEN='new-token'
--project claude-config
--config prd
doppler secrets set PYPI_TOKEN='new-token'
--project claude-config
--config prd

3. Verify new token works

3. 验证新令牌是否有效

doppler secrets get PYPI_TOKEN
--project claude-config
--config prd
--plain
doppler secrets get PYPI_TOKEN
--project claude-config
--config prd
--plain

4. Test publish (dry-run not available, use TestPyPI)

4. 测试发布(无试运行模式,可使用TestPyPI)

See: Troubleshooting → TestPyPI Testing

参考:故障排除 → TestPyPI测试


---

---

Troubleshooting

故障排除

Issue: "PYPI_TOKEN not found in Doppler"

问题:"PYPI_TOKEN not found in Doppler"

Symptom: Script fails at Step 0
Fix:
bash
undefined
症状:脚本在步骤0失败
解决方法
bash
undefined

Verify token exists

验证令牌是否存在

doppler secrets --project claude-config --config prd | grep PYPI_TOKEN
doppler secrets --project claude-config --config prd | grep PYPI_TOKEN

If missing, get new token from PyPI

若不存在,从PyPI获取新令牌

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

创建令牌,权限范围选择"Entire account"或特定项目

Store in Doppler

存储到Doppler中

doppler secrets set PYPI_TOKEN='your-token'
--project claude-config
--config prd
undefined
doppler secrets set PYPI_TOKEN='your-token'
--project claude-config
--config prd
undefined

Issue: "403 Forbidden from PyPI"

问题:"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
症状:脚本在步骤4失败,出现认证错误
根本原因:令牌过期或无效(PyPI自2024年起要求启用2FA)
解决方法
  1. 验证PyPI账户已启用2FA
  2. 创建新令牌:https://pypi.org/manage/account/token/
  3. 更新Doppler中的令牌:
    doppler secrets set PYPI_TOKEN='new-token' --project claude-config --config prd
  4. 重新尝试发布

Issue: "Script blocked with CI detection error"

问题:"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:
bash
undefined
症状
❌ ERROR: This script must ONLY be run on your LOCAL machine
Detected CI environment variables:
- CI: true
根本原因:在CI环境中运行,或本地设置了
CI
变量
解决方法
bash
undefined

Check if CI variable set in your shell

检查shell中是否设置了CI变量

env | grep CI
env | grep CI

If set, unset it

若已设置,取消设置

unset CI unset GITHUB_ACTIONS
unset CI unset GITHUB_ACTIONS

Retry publish

重新尝试发布

./scripts/publish-to-pypi.sh

**Expected behavior**: This is INTENTIONAL - script should ONLY run locally.
./scripts/publish-to-pypi.sh

**预期行为**:这是有意设计的——脚本仅能在本地运行。

Issue: "Version not updated in pyproject.toml"

问题:"Version not updated in pyproject.toml"

Symptom: Local publish uses old version number
Root Cause: Didn't pull latest release commit from GitHub
Fix:
bash
undefined
症状:本地使用旧版本号发布
根本原因:未从GitHub拉取最新的发布提交
解决方法
bash
undefined

Always pull before publishing

发布前务必拉取最新代码

git pull origin main
git pull origin main

Verify version updated

验证版本号已更新

grep '^version = ' pyproject.toml
grep '^version = ' pyproject.toml

Retry publish

重新尝试发布

./scripts/publish-to-pypi.sh
undefined
./scripts/publish-to-pypi.sh
undefined

Issue: "uv package manager not found"

问题:"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:
bash
undefined
症状:脚本启动时失败
根本原因:未安装uv,或uv不在PATH中
脚本查找uv的优先级
  1. 已在PATH中(Homebrew安装、直接安装、已配置shell)
  2. 常见直接安装路径(
    ~/.local/bin/uv
    ~/.cargo/bin/uv
    /opt/homebrew/bin/uv
  3. 版本管理器作为备选(mise、asdf)
解决方法:通过任意方式安装uv:
bash
undefined

Official installer (recommended)

官方安装脚本(推荐)

Homebrew

Homebrew

brew install uv
brew install uv

Cargo

Cargo

cargo install uv
cargo install uv

mise (if you use it)

mise(若使用)

mise use uv@latest

The script doesn't force any particular installation method.
mise use uv@latest

脚本不强制特定的安装方式。

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:
bash
/usr/bin/env bash << 'MISE_EOF'
症状:脚本启动后无输出,最终超时
根本原因:脚本加载了
~/.zshrc
~/.bashrc
,而这些文件需要交互式输入
解决方法:不要在脚本中加载shell配置文件。内置脚本使用以下方式:
bash
/usr/bin/env bash << 'MISE_EOF'

CORRECT - safe for non-interactive shells

正确方式:适用于非交互式shell

eval "$(mise activate bash 2>/dev/null)" || true
eval "$(mise activate bash 2>/dev/null)" || true

WRONG - hangs in non-interactive shells

错误方式:在非交互式shell中会挂起

source ~/.zshrc MISE_EOF

---
source ~/.zshrc MISE_EOF

---

TestPyPI Testing

TestPyPI测试

To test publishing workflow without affecting production:
  1. Get TestPyPI token:
  2. Store in Doppler (separate key):
    bash
    doppler secrets set TESTPYPI_TOKEN='your-test-token' \
      --project claude-config \
      --config prd
  3. Modify publish script temporarily:
    bash
    undefined
/usr/bin/env bash << 'DOPPLER_EOF_2'
若要测试发布流程而不影响生产环境:
  1. 获取TestPyPI令牌
  2. 存储到Doppler中(使用单独的密钥):
    bash
    doppler secrets set TESTPYPI_TOKEN='your-test-token' \
      --project claude-config \
      --config prd
  3. 临时修改发布脚本
    bash
    undefined
/usr/bin/env bash << 'DOPPLER_EOF_2'

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

在scripts/publish-to-pypi.sh中,将

uv publish --token "${PYPI_TOKEN}"
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
  1. Verify on TestPyPI:
  2. Restore script to production configuration

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

4. **测试发布**:

   ```bash
   ./scripts/publish-to-pypi.sh
  1. 在TestPyPI上验证
  2. 将脚本恢复为生产配置

mise Task Integration

mise任务集成

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.
toml
undefined
当使用mise任务编排发布流程时,发布任务必须依赖于构建任务。若没有此依赖,在构建前运行
mise run release:pypi
会失败,因为没有生成wheel包。
toml
undefined

.mise.toml — CORRECT: publish depends on build

.mise.toml — 正确配置:发布依赖于构建

[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
[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/

合并产物到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](../mise-tasks/references/release-workflow-patterns.md) for the complete DAG pattern and audit checklist.

---
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"] # 关键:强制先构建再发布 run = "./scripts/publish-to-pypi.sh"
[tasks."release:full"] description = "Full release workflow" depends = ["release:postflight", "release:pypi"] # 包含所有阶段 run = "echo 'Released and published!'"

**反模式**:定义`release:pypi`时不依赖`release:build-all`。发布脚本会检测到"未找到wheel包"并失败,但失败发生在后期,而非被任务DAG提前阻止。

参考[发布工作流模式](../mise-tasks/references/release-workflow-patterns.md)获取完整的DAG模式和审核清单。

---

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
  • semantic-release Skill:
    semantic-release
    - Versioning automation (NO publishing)
  • 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

  • ADR-0027
    docs/architecture/decisions/0027-local-only-pypi-publishing.md
    - 仅本地PyPI发布的架构决策
  • ADR-0028
    docs/architecture/decisions/0028-skills-documentation-alignment.md
    - 技能与ADR-0027的一致性
  • PUBLISHING.md
    docs/development/PUBLISHING.md
    - 完整发布流程指南
  • semantic-release Skill
    semantic-release
    - 版本管理自动化(不包含发布)
  • mise-tasks Skill
    mise-tasks
    - 带有依赖管理的任务编排
  • Release Workflow Patterns
    release-workflow-patterns.md
    - DAG模式与反模式
  • 内置脚本
    scripts/publish-to-pypi.sh
    - 带有CI防护的参考实现

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)
  • 2025-12-03:重构为优先发现、环境无关的方式
    • discover_uv()
      按PATH → 直接安装路径 → 版本管理器的顺序查找uv
    • 支持:curl安装、Homebrew、cargo、mise、asdf - 不强制特定方式
    • 在工作流步骤启动前提前发现
    • 新增非交互式shell问题的故障排除
  • 2025-11-22:与ADR-0027对齐创建(工作区全局仅本地发布策略)
  • 验证:CI检测防护已测试,Doppler集成已验证

最后更新:2025-12-03 策略:工作区全局仅本地PyPI发布(ADR-0027) 替代:无(从创建起即符合ADR-0027)