两个项目、两个平台、一个下午。npm 的 Granular Token、PyPI 的 twine 编码坑、GitHub Actions 的 CI/CD 管线——全部走通后的完整记录。
背景
最近做了两个开源项目:
| 项目 | 语言 | 类型 | 发布平台 |
|---|---|---|---|
| GEO Copilot | JavaScript | Chrome 扩展 | npm |
| LAN Transfer | Python | 命令行工具 | 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 取消。
正确流程:
- npmjs.com → 头像 → Access Tokens →Granular Access Token
- 必选:Packages and scopes →All Packages(下拉里有,不是搜索)
- 必选:Permissions →Read and write
- 必勾:✅Bypass two-factor authentication(没勾 = 403)
- GitHub Secrets → 新建
npm_token
npm 报错速查
| 报错 | 原因 | 解决 |
|---|---|---|
401 Unauthorized | Token 无效 | 重新生成 |
403 2FA required | 没勾 Bypass 2FA | 重新生成,勾上 |
404 Not Found | 包名冲突/无权限 | 改用@用户名/包名 |
403 too similar | 包名和已有包太像 | 加作用域前缀 |
CInpm ci挂 | lockfile 版本不兼容 | 改用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 生成简单得多:
- pypi.org → Account Settings → API tokens → Add API token
- Scope:Entire account
- 生成后复制 → 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。
第三部分:双平台对比
| npm | PyPI | |
|---|---|---|
| 配置文件 | package.json | pyproject.toml |
| 构建命令 | npm install | python -m build |
| 测试命令 | npm test | python test/xxx.py |
| Token 类型 | Granular(2026强制) | API Token(简单) |
| Token 复杂度 | ⭐⭐⭐⭐⭐(5 步选) | ⭐(1 步) |
| 2FA 问题 | 必须勾 Bypass | 默认不需要 |
| 包名冲突 | 加@scope解决 | 直接换名 |
| 发布命令 | npm publish | twine upload dist/* |
| CI 发布 | setup-node | pypa/gh-action-pypi-publish |
| 安装命令 | npm i @scope/pkg | pip install pkg |
| GitHub Secret | npm_token | PYPI_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 Copilot | npm | @aicdragon/geo-copilot | npmjs.com |
| LAN Transfer | PyPI | lan-transfer | pypi.org |
两个项目从零到双平台发布,总共踩了这些坑:
- npm Granular Token 必须选 All Packages + Bypass 2FA
- npm 包名冲突加
@scope前缀 - GitHub Actions
npm ci改用npm install - Re-run 用的是旧 commit,需删 tag 重建 Release
- PyPI twine 在 Windows 终端编码崩溃
dist/里有 PyInstaller 的 exe,twine 报 InvalidDistributionbuild-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