Lesson 0008 让 CI 在每次 push 时自动验证代码。现在流水线还差最后一段:把验证通过的代码变成用户能安装的扩展。本课串联 dev→publish 流水线的后半段——从 .vsix 打包格式到 Marketplace + Open VSX 双平台发布,以及 publish.yml 中 Personal Access Token 的安全设计。
vsce package 和 vsce publish 的区别,读懂 publish.yml 的每一行,知道如何安全地管理发布密钥。本课完成后,你将对整个"编码 → 测试 → CI → 打包 → 发布"流水线有完整的理解。
到目前为止,你的代码只存在于 temp_repo/ 目录里——.ts 源文件、package.json、图标、配置文件。用户要安装你的扩展,需要的是一个可分发的包。VS Code 生态里这个包就是 .vsix 文件。
.vsix(Visual Studio Extension)是一个 ZIP 压缩包,后缀名改了而已。它的内部结构:
claude-context-bar-1.4.1.vsix ← 文件名 = name + version ├─ extension/ ← VS Code 扩展的根目录 │ ├─ package.json ← 扩展清单(原样打包) │ ├─ out/ ← 编译后的 .js(tsc 产物) │ │ └─ extension.js │ ├─ images/ ← 图标、截图等静态资源 │ │ └─ icon.png │ └─ node_modules/ ← 运行时依赖(如果有) ├─ [Content_Types].xml ← OPC 标准需要的元数据 └─ extension.vsixmanifest ← VSIX 包清单(由 vsce 自动生成)
.vsix 里不包含 src/(TypeScript 源码)、node_modules 里的 devDependencies、.git/ 和测试文件。打包工具(vsce)默认排除这些——它只打包运行时需要的东西。
| VS Code Marketplace | Open VSX | |
|---|---|---|
| 是谁的 | Microsoft 官方运营 | Eclipse 基金会——社区驱动 |
| 谁用 | 标准 VS Code(绝大多数用户) | VS Codium、Cursor、Windsurf 等非 Microsoft 构建版 |
| 发布方式 | @vscode/vsce publish | ovsx publish |
| 认证 | Azure DevOps Personal Access Token | Eclipse Open VSX Registry 的 PAT |
| publisher 注册 | marketplace.visualstudio.com/manage | open-vsx.org |
claude-context-bar 同时发布到两个平台——这也是 publish.yml 有两个 publish 步骤的原因。对于一个想让尽可能多用户使用的扩展,双平台发布是标准做法。
不是 package.json 里所有东西都和发布有关。下表列出专门影响 Marketplace 展示和发布流程的字段:
| 字段 | 作用 | 不填会怎样 |
|---|---|---|
name | 扩展的唯一标识符。Marketplace 上组成 URL:publisher.name | ❌ vsce package 直接拒绝 |
version | SemVer 版本号。每次发布必须递增 | ❌ vsce publish 拒绝覆盖已有版本 |
publisher | 发布者 ID——在 Marketplace 注册时获得。和 name 一起构成完整标识符 | ❌ 打包和发布都需要 |
icon | Marketplace 和扩展页面上显示的图标路径(建议 128×128 PNG) | ⚠️ 使用默认图标——辨识度差 |
repository | GitHub 仓库 URL。Marketplace 页面上显示"Repository"链接 | ⚠️ 用户无法从 Marketplace 找到源码 |
galleryBanner | Marketplace 页面的顶部主题色(color + theme) | ⚠️ 使用默认配色 |
keywords | Marketplace 搜索关键词——用户搜这些词能找到你的扩展 | ⚠️ 可发现性差 |
engines.vscode | 支持的最低 VS Code 版本。决定你的扩展能出现在哪些用户的搜索结果中 | ❌ 打包和发布都需要 |
vscode:prepublish 不是摆设:回顾 package.json 的 scripts——"vscode:prepublish": "npm run compile"。这个钩子是 VS Code 扩展特有的:vsce package 在打包之前自动调用它。它确保你每次打包的都是最新的编译产物——不会发生"改了半天源码,打包的还是旧 .js"这种尴尬事。
打包工具叫 @vscode/vsce(VS Code Extension 的缩写)。它是 npm 包,通常通过 npx 直接调用而非全局安装:
# 打包——在本地产出 .vsix 文件 npx @vscode/vsce package # 等价于 package.json 中定义的快捷方式: # "package": "vsce package" npm run package
vsce package 的执行流程:
1. 读取 package.json → 验证 name、version、publisher、engines.vscode 都存在 2. 执行 vscode:prepublish 钩子 → npm run compile 3. 收集文件: ✅ 包含:package.json、out/、images/、README.md、CHANGELOG.md、LICENSE ❌ 排除:src/、node_modules/(devDependencies,除非声明为 dependency)、.git/、*.test.js 4. 生成 extension.vsixmanifest 和 [Content_Types].xml 5. 压缩为 .vsix → 输出文件名 name-version.vsix
code --install-extension claude-context-bar-1.4.1.vsix 在本地安装测试。这是发布前最后一道手动检查——确认打包的是你想要的,没有漏文件也没有多文件。
回到 temp_repo/.github/workflows/publish.yml——这个 VS Code 扩展模板自带的 workflow:
# ===== .github/workflows/publish.yml ===== name: Publish Extension on: push: tags: - 'v*' ← 推送 v1.0.0 这样的 tag 时触发 jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Publish to VS Code Marketplace run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }} - name: Publish to Open VSX run: npx ovsx publish -p ${{ secrets.OVSX_PAT }}
Lesson 0008 的 test.yml 在每次 push 到 main 时触发——这合理:每次改动都要验证。但发布不是每次 push 都该做的事——你不想修个 typo 就发一个新版本。Tag 触发给了你精确控制:只有在刻意打 v1.5.0 tag 时,才触发发布。
完整的发布流程: 1. 开发 → 本地测试通过 → push 到 main 2. CI (test.yml) 通过 ✅ 3. 决定发布 → npm version patch → git push origin main --tags 4. CI (publish.yml) 由 tag 触发 → 自动发布到 Marketplace + Open VSX
${{ secrets.VSCE_PAT }} 和 ${{ secrets.OVSX_PAT }} 是 GitHub Actions 的加密密钥机制。关键事实:
secrets.XXX——即使有人拿到了仓库源码,也看不到密钥明文secrets.VSCE_PAT 注入为环境变量。日志中这个值会被自动遮蔽——如果有人试图 echo $VSCE_PAT,输出显示为 ***| VSCE_PAT | OVSX_PAT | |
|---|---|---|
| 从哪里获取 | Azure DevOps → Personal Access Tokens | open-vsx.org → Settings → Access Tokens |
| 权限范围 | Marketplace (publish) | publish |
| 有效期 | 最长 1 年——需要定期轮换 | 用户自定义 |
| 类比 | 你的"身份卡"——证明你是 publisher | 另一个平台的"身份卡" |
publish.yml 静默失败——你打 tag 后等了几分钟才发现发布没发生。建议在 PAT 过期前 1 个月设置日历提醒轮换,或者用 workflow_dispatch 在发布前手动触发一次 vsce package 验证 PAT 仍有效。
VS Code 扩展使用 Semantic Versioning(语义化版本):major.minor.patch(如 1.4.1)。每次发布前做三件事:
| 步骤 | 命令 | 效果 |
|---|---|---|
| 1. 更新版本 | npm version patch | 1.4.1 → 1.4.2,同时自动 commit + 打 tag |
| 2. 更新 CHANGELOG | 手动编辑 CHANGELOG.md | 记录这个版本改了什么 |
| 3. 推送 tag | git push origin main --tags | 触发 publish.yml |
把 Lesson 0007、0008 和本课串起来,就是一条完整的 dev→publish 流水线:
编码 (Lesson 0001-0005) │ 写 extension.ts,注册命令,创建 StatusBarItem ↓ 本地测试 (Lesson 0007) │ npm test → 3 秒确认 8 个 fixture 全部通过 ↓ CI 验证 (Lesson 0008) │ git push → test.yml 在 3 个 Node 版本上并行运行 │ 通过 ✅ → 继续;失败 ❌ → 修 bug ↓ 发布 (本课) │ npm version patch → git push --tags → publish.yml 触发 │ vsce publish + ovsx publish → Marketplace + Open VSX ↓ 用户安装 │ 搜索 "Claude Context Bar" → Install → 🎉
temp_repo/ 中运行 npx @vscode/vsce package。观察输出:列出了哪些文件?总共多大?有没有包含 src/ 目录?然后解压 .vsix(改后缀为 .zip 然后解压),验证内部结构和第 1 节描述的一致temp_repo/CHANGELOG.md(如果不存在就创建一个)。格式参考 keepachangelog.com。思考:CHANGELOG 和 git log 的区别是什么?为什么两者都需要?package.json 中 icon、galleryBanner、repository、keywords 分别对应页面上的哪个位置VSCE_PAT,值填 test-token-123。这个 token 不会真的能发布,但你会知道密钥是怎么加上去的。然后想想:如果有人 fork 了你的仓库,fork 的 Actions 能读到原仓库的 secrets 吗?(答案:不能——GitHub 不传递 secrets 到 fork)publish.yml,在 publish 步骤之前增加一个 npm test 步骤。再想想:这个改动有什么问题?回顾 Lesson 0008 第 4 节的 callout——两个独立 workflow 之间没有依赖。更好的做法是什么?(提示:needs 关键字,或者把 test 和 publish 合并成一个 workflow).vsix 文件本质上是什么?
vsce package 和 vsce publish 的区别是什么?
publish.yml 用 push: tags: ['v*'] 触发而非 push: branches: [main]。为什么?
${{ secrets.VSCE_PAT }} 中的 PAT 存储在 GitHub 仓库的哪里?
一个 VS Code 扩展当前版本是 2.3.1。你只修改了 README 中的一个链接(之前指向旧文档,现在更新为新 URL)。你应该 bump 哪个数字?
.vsixmanifest 如何生成、排除规则如何配置(.vscodeignore).vscodeignore 文件来控制哪些文件不被打包?想了解如何设置 pre-release 版本供勇敢的用户尝鲜?想知道 vsce publish 的 --pre-release 标志怎么用?想深入了解 PAT 轮换的最佳实践?这些问题都可以继续追问。