npm + PyPI 双平台发布实战:从零到自动化的完整踩坑记录

📅 2026/7/11 6:07:33 👁️ 阅读次数 📝 编程学习
npm + PyPI 双平台发布实战:从零到自动化的完整踩坑记录

两个项目、两个平台、一个下午。npm 的 Granular Token、PyPI 的 twine 编码坑、GitHub Actions 的 CI/CD 管线——全部走通后的完整记录。


背景

最近做了两个开源项目:

项目语言类型发布平台
GEO CopilotJavaScriptChrome 扩展npm
LAN TransferPython命令行工具PyPI

两个都是零依赖的轻量项目,但发布流程走了不少弯路。这篇文章把两条路线一次性讲清楚,下次照着走,15 分钟一个。


第一部分:npm 发布(GEO Copilot)

项目准备

GEO Copilot 是 Chrome 扩展,纯 HTML+CSS+JS,零依赖。需要新增:

✅ package.json # npm 包配置 ✅ package-lock.json # 依赖锁(空包也要) ✅ test/basic.test.js # 基础测试 ✅ .github/workflows/npm-publish.yml ✅ .gitattributes # 统一换行符

package.json 关键配置

{"name":"@aicdragon/geo-copilot","version":"0.1.0","description":"GEO workflow browser extension","files":["manifest.json","src/","assets/","README.md"],"scripts":{"test":"node test/basic.test.js"},"publishConfig":{"access":"public"}}

踩坑:包名geo-copilot被 npm 拒绝(和已有geocopilot太相似),改用@aicdragon/geo-copilot

CI/CD 配置

name:Node.js Packageon:release:types:[created]jobs:build:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-node@v4with:{node-version:20}-run:npm install# 注意:用 install 不用 ci-run:npm testpublish-npm:needs:buildruns-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-node@v4with:node-version:20registry-url:https://registry.npmjs.org/-run:npm install-run:npm publish--access publicenv:NODE_AUTH_TOKEN:${{secrets.npm_token}}

npm Token — 2026 版全坑记录

2025 年 11 月起,npm 只支持Granular Access Token,Classic Token 取消。

正确流程:

  1. npmjs.com → 头像 → Access Tokens →Granular Access Token
  2. 必选:Packages and scopes →All Packages(下拉里有,不是搜索)
  3. 必选:Permissions →Read and write
  4. 必勾:✅Bypass two-factor authentication(没勾 = 403)
  5. GitHub Secrets → 新建npm_token

npm 报错速查

报错原因解决
401 UnauthorizedToken 无效重新生成
403 2FA required没勾 Bypass 2FA重新生成,勾上
404 Not Found包名冲突/无权限改用@用户名/包名
403 too similar包名和已有包太像加作用域前缀
CInpm cilockfile 版本不兼容改用npm install
Re-run 失败用旧 commit 重跑删 tag 重建 Release

第二部分:PyPI 发布(LAN Transfer)

项目准备

LAN Transfer 是 Python 工具,零依赖纯标准库。需要新增:

✅ pyproject.toml # 现代 Python 打包标准 ✅ test/test_transfer.py # 导入测试 ✅ .github/workflows/pypi-publish.yml ✅ .gitattributes # 统一换行符

pyproject.toml 关键配置

[build-system] requires = ["setuptools>=64", "wheel"] build-backend = "setuptools.build_meta" [project] name = "lan-transfer" version = "0.1.0" description = "LAN AirDrop — zero-dependency network file transfer tool" requires-python = ">=3.8" dependencies = [] # 零第三方依赖 [project.scripts] lan-transfer = "lan_transfer:main" lan-transfer-gui = "lan_transfer_gui:main"

踩坑:build-backend别写setuptools.backends._legacy——新版本里改名了,直接用setuptools.build_meta

CI/CD 配置

name:Python Packageon:release:types:[created]jobs:build:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-python@v5with:{python-version:"3.12"}-run:pip install build-run:python-m build-run:pip install dist/*.whl-run:python test/test_transfer.pypublish-pypi:needs:buildruns-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-python@v5with:{python-version:"3.12"}-run:pip install build-run:python-m build-uses:pypa/gh-action-pypi-publish@release/v1with:password:${{secrets.PYPI_TOKEN}}

PyPI Token

和 npm 不同,PyPI 的 Token 生成简单得多:

  1. pypi.org → Account Settings → API tokens → Add API token
  2. Scope:Entire account
  3. 生成后复制 → GitHub Secrets →PYPI_TOKEN

twine 上传踩坑

Windows 终端编码导致 twine 进度条崩溃:

UnicodeEncodeError: 'gbk' codec can't encode character '\u2022'

解决:加--disable-progress-bar并设置PYTHONUTF8=1

$env:PYTHONUTF8 ='1'python-m twine upload--disable-progress-bar dist/*

dist 目录注意

PyInstaller 打包生成的.exe也在dist/里,twine 会报InvalidDistribution。只上传.whl.tar.gz


第三部分:双平台对比

npmPyPI
配置文件package.jsonpyproject.toml
构建命令npm installpython -m build
测试命令npm testpython test/xxx.py
Token 类型Granular(2026强制)API Token(简单)
Token 复杂度⭐⭐⭐⭐⭐(5 步选)⭐(1 步)
2FA 问题必须勾 Bypass默认不需要
包名冲突@scope解决直接换名
发布命令npm publishtwine upload dist/*
CI 发布setup-nodepypa/gh-action-pypi-publish
安装命令npm i @scope/pkgpip install pkg
GitHub Secretnpm_tokenPYPI_TOKEN

结论:PyPI 的发布体验比 npm 顺滑太多了——npm 2026 年的 Granular Token 流程真的过于复杂。


第四部分:统一速查清单

npm 发布

□ 1. package.json 有 name/@scope、scripts.test、publishConfig、files □ 2. npm install → package-lock.json 生成 □ 3. npm test 本地通过 □ 4. .github/workflows/npm-publish.yml 配置完成 □ 5. npmjs.com → Granular Token → All Packages → Read&Write → Bypass 2FA □ 6. GitHub Secrets → npm_token 已存入 □ 7. Git push → GitHub Release → 等待 Actions 绿灯

PyPI 发布

□ 1. pyproject.toml 有 name、version、scripts、build-system □ 2. python -m build → 生成 .whl + .tar.gz □ 3. python test/ 测试通过 □ 4. .github/workflows/pypi-publish.yml 配置完成 □ 5. pypi.org → API Token → Entire account □ 6. GitHub Secrets → PYPI_TOKEN 已存入 □ 7. Git push → GitHub Release → 等待 Actions 绿灯

两个都需要的通用配置

□ .gitattributes 存在(统一 LF 换行符) □ GitHub Release 触发 CI(Re-run 用旧 commit,需删 tag 重建) □ 首次发布建议本地手动跑一次,排查命名冲突和权限问题

五、本次成功案例

项目平台包名链接
GEO Copilotnpm@aicdragon/geo-copilotnpmjs.com
LAN TransferPyPIlan-transferpypi.org

两个项目从零到双平台发布,总共踩了这些坑:

  1. npm Granular Token 必须选 All Packages + Bypass 2FA
  2. npm 包名冲突加@scope前缀
  3. GitHub Actionsnpm ci改用npm install
  4. Re-run 用的是旧 commit,需删 tag 重建 Release
  5. PyPI twine 在 Windows 终端编码崩溃
  6. dist/里有 PyInstaller 的 exe,twine 报 InvalidDistribution
  7. build-backend老教程写的是已废弃的_legacy

希望这份记录能帮你下次少踩这些坑。


🔗 相关资源:

  • npm:npmjs.com/package/@aicdragon/geo-copilot
  • PyPI:pypi.org/project/lan-transfer
  • GitHub:CDragon123-code/geo-copilot | CDragon123-code/lan-file-transfer