pypi-doppler
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePyPI 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 Actions
❌ Publishing from any CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins, CircleCI)
❌ in semantic-release configuration
❌ Building packages in CI ( in prepareCmd)
❌ Storing PyPI tokens in GitHub secrets
publishCmduv build❌ 从GitHub Actions发布
❌ 从任何CI/CD流水线发布(GitHub Actions、GitLab CI、Jenkins、CircleCI)
❌ 在semantic-release配置中使用
❌ 在CI中构建包(在prepareCmd中使用)
❌ 在GitHub Secrets中存储PyPI令牌
publishCmduv buildREQUIRED
要求操作
✅ Use on local machine
✅ CI detection guards in publish script
✅ Manual approval before each release
✅ Doppler credential management (no plaintext tokens)
✅ Repository verification (prevents fork abuse)
scripts/publish-to-pypi.sh✅ 在本地机器上使用
✅ 发布脚本中包含CI检测防护
✅ 每次发布前需手动批准
✅ 使用Doppler管理凭证(禁止明文令牌)
✅ 仓库验证(防止分支滥用)
scripts/publish-to-pypi.shRationale
策略依据
- 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.mdOverview
概述
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
本技能提供仅本地使用的PyPI发布功能,通过Doppler实现安全的凭证管理。它与工作区全局发布流程集成,流程如下:
- GitHub Actions:仅负责自动版本管理(标签、发布、CHANGELOG)
- 本地机器:使用Doppler凭证手动完成PyPI发布
Bundled Scripts
内置脚本
| Script | Purpose |
|---|---|
| Local PyPI publishing with CI detection guards |
Usage: Copy to your project's directory:
scripts/bash
/usr/bin/env bash << 'DOPPLER_EOF'| 脚本 | 用途 |
|---|---|
| 带有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
一次性设置
-
Install Doppler CLI:bash
brew install dopplerhq/cli/doppler -
Authenticate with Doppler:bash
doppler login -
Verify access toproject:
claude-configbashdoppler whoami doppler projects
-
安装Doppler CLI:bash
brew install dopplerhq/cli/doppler -
通过Doppler认证:bash
doppler login -
验证对项目的访问权限:
claude-configbashdoppler whoami doppler projects
PyPI Token Setup
PyPI令牌设置
-
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 , ~180 characters)
pypi-AgEIcHlwaS5vcmc...
-
Store token in Doppler:bash
doppler secrets set PYPI_TOKEN='pypi-AgEIcHlwaS5vcmc...' \ --project claude-config \ --config prd -
Verify token stored:bash
doppler secrets get PYPI_TOKEN \ --project claude-config \ --config prd \ --plain
-
创建PyPI API令牌:
- 访问:https://pypi.org/manage/account/token/
- 若未启用2FA则需启用(2024年起为必填项)
- 创建令牌,权限范围选择"Entire account"或特定项目
- 复制令牌(格式为,约180字符)
pypi-AgEIcHlwaS5vcmc...
-
在Doppler中存储令牌:bash
doppler secrets set PYPI_TOKEN='pypi-AgEIcHlwaS5vcmc...' \ --project claude-config \ --config prd -
验证令牌已存储: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:
- Compare local version against latest PyPI version
pyproject.toml - If versions match → STOP - do not proceed with publishing
- Inform user: "Version not incremented. Run semantic-release first or verify commits include or
feat:types."fix:
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自动检查流程:
- 比较本地中的版本号与PyPI上的最新版本
pyproject.toml - 若版本号相同 → 停止操作,请勿继续发布
- 告知用户:"版本号未递增。请先运行semantic-release,或确认提交记录包含或
feat:类型的提交。"fix:
重要性:PyPI会拒绝重复版本号,但更重要的是,用户和包管理器依赖版本号递增来检测更新。不递增版本号的发布流程是有问题的。
Complete Release Workflow
完整发布流程
Step 1: Development & Commit (Conventional Commits):
bash
undefined步骤1:开发与提交(Conventional Commits):
bash
undefinedMake 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:
```bashgit 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发布:
```bashPull 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)
--project claude-config
--config prd
--plain)
PYPI_TOKEN=$(doppler secrets get PYPI_TOKEN
--project claude-config
--config prd
--plain)
--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 () includes CI detection guards to prevent accidental execution in CI/CD pipelines.
scripts/publish-to-pypi.sh标准发布脚本()包含CI检测防护,防止在CI/CD流水线中意外执行。
scripts/publish-to-pypi.shEnvironment Variables Checked
检测的环境变量
- - Generic CI indicator
$CI - - GitHub Actions
$GITHUB_ACTIONS - - GitLab CI
$GITLAB_CI - - Jenkins
$JENKINS_URL - - CircleCI
$CIRCLECI
- - 通用CI标识
$CI - - GitHub Actions
$GITHUB_ACTIONS - - GitLab CI
$GITLAB_CI - - Jenkins
$JENKINS_URL - - 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
undefinedbash
undefinedThis 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:
Configs: (production), (development)
Secret Name:
claude-configprddevPYPI_TOKEN项目:
配置环境:(生产)、(开发)
密钥名称:
claude-configprddevPYPI_TOKENToken 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
undefinedbash
undefined1. Create new token on PyPI
1. 在PyPI上创建新令牌
2. Update Doppler
2. 更新Doppler中的令牌
doppler secrets set PYPI_TOKEN='new-token'
--project claude-config
--config prd
--project claude-config
--config prd
doppler secrets set PYPI_TOKEN='new-token'
--project claude-config
--config prd
--project claude-config
--config prd
3. Verify new token works
3. 验证新令牌是否有效
doppler secrets get PYPI_TOKEN
--project claude-config
--config prd
--plain
--project claude-config
--config prd
--plain
doppler secrets get PYPI_TOKEN
--project claude-config
--config prd
--plain
--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
undefinedVerify 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
--project claude-config
--config prd
undefineddoppler secrets set PYPI_TOKEN='your-token'
--project claude-config
--config prd
--project claude-config
--config prd
undefinedIssue: "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:
- 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
症状:脚本在步骤4失败,出现认证错误
根本原因:令牌过期或无效(PyPI自2024年起要求启用2FA)
解决方法:
- 验证PyPI账户已启用2FA
- 创建新令牌:https://pypi.org/manage/account/token/
- 更新Doppler中的令牌:
doppler secrets set PYPI_TOKEN='new-token' --project claude-config --config prd - 重新尝试发布
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: trueRoot Cause: Running in CI environment OR variable set locally
CIFix:
bash
undefined症状:
❌ ERROR: This script must ONLY be run on your LOCAL machine
Detected CI environment variables:
- CI: true根本原因:在CI环境中运行,或本地设置了变量
CI解决方法:
bash
undefinedCheck 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
undefinedAlways 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
undefinedIssue: "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):
- 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:
bash
undefined症状:脚本启动时失败
根本原因:未安装uv,或uv不在PATH中
脚本查找uv的优先级:
- 已在PATH中(Homebrew安装、直接安装、已配置shell)
- 常见直接安装路径(、
~/.local/bin/uv、~/.cargo/bin/uv)/opt/homebrew/bin/uv - 版本管理器作为备选(mise、asdf)
解决方法:通过任意方式安装uv:
bash
undefinedOfficial installer (recommended)
官方安装脚本(推荐)
curl -LsSf https://astral.sh/uv/install.sh | sh
curl -LsSf https://astral.sh/uv/install.sh | sh
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 or which waits for interactive input
~/.zshrc~/.bashrcFix: 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:
-
Get TestPyPI token:
- Visit: https://test.pypi.org/manage/account/token/
- Create token
-
Store in Doppler (separate key):bash
doppler secrets set TESTPYPI_TOKEN='your-test-token' \ --project claude-config \ --config prd -
Modify publish script temporarily:bash
undefined
/usr/bin/env bash << 'DOPPLER_EOF_2'
若要测试发布流程而不影响生产环境:
-
获取TestPyPI令牌:
-
存储到Doppler中(使用单独的密钥):bash
doppler secrets set TESTPYPI_TOKEN='your-test-token' \ --project claude-config \ --config prd -
临时修改发布脚本: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-
Verify on TestPyPI:
-
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-
在TestPyPI上验证:
-
将脚本恢复为生产配置
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 before building will fail because no wheels exist.
mise run release:pypitoml
undefined当使用mise任务编排发布流程时,发布任务必须依赖于构建任务。若没有此依赖,在构建前运行会失败,因为没有生成wheel包。
mise run release:pypitoml
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: - Architectural decision for local-only publishing
docs/architecture/decisions/0027-local-only-pypi-publishing.md - ADR-0028: - Skills alignment with ADR-0027
docs/architecture/decisions/0028-skills-documentation-alignment.md - PUBLISHING.md: - Complete release workflow guide
docs/development/PUBLISHING.md - semantic-release Skill: - Versioning automation (NO publishing)
semantic-release - mise-tasks Skill: - Task orchestration with dependency management
mise-tasks - Release Workflow Patterns: - DAG patterns and anti-patterns
release-workflow-patterns.md - Bundled Script: - Reference implementation with CI guards
scripts/publish-to-pypi.sh
- ADR-0027:- 仅本地PyPI发布的架构决策
docs/architecture/decisions/0027-local-only-pypi-publishing.md - ADR-0028:- 技能与ADR-0027的一致性
docs/architecture/decisions/0028-skills-documentation-alignment.md - PUBLISHING.md:- 完整发布流程指南
docs/development/PUBLISHING.md - semantic-release Skill:- 版本管理自动化(不包含发布)
semantic-release - mise-tasks Skill:- 带有依赖管理的任务编排
mise-tasks - Release Workflow Patterns:- DAG模式与反模式
release-workflow-patterns.md - 内置脚本:- 带有CI防护的参考实现
scripts/publish-to-pypi.sh
Validation History
验证历史
- 2025-12-03: Refactored to discovery-first, environment-agnostic approach
- checks PATH → direct installs → version managers (priority order)
discover_uv() - 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:重构为优先发现、环境无关的方式
- 按PATH → 直接安装路径 → 版本管理器的顺序查找uv
discover_uv() - 支持:curl安装、Homebrew、cargo、mise、asdf - 不强制特定方式
- 在工作流步骤启动前提前发现
- 新增非交互式shell问题的故障排除
- 2025-11-22:与ADR-0027对齐创建(工作区全局仅本地发布策略)
- 验证:CI检测防护已测试,Doppler集成已验证
最后更新:2025-12-03
策略:工作区全局仅本地PyPI发布(ADR-0027)
替代:无(从创建起即符合ADR-0027)