Lesson 0009 打包与发布:从代码到 Marketplace

Lesson 0008 让 CI 在每次 push 时自动验证代码。现在流水线还差最后一段:把验证通过的代码变成用户能安装的扩展。本课串联 dev→publish 流水线的后半段——从 .vsix 打包格式到 Marketplace + Open VSX 双平台发布,以及 publish.yml 中 Personal Access Token 的安全设计。

📌 本课目标:理解 vsce packagevsce publish 的区别,读懂 publish.yml 的每一行,知道如何安全地管理发布密钥。本课完成后,你将对整个"编码 → 测试 → CI → 打包 → 发布"流水线有完整的理解。

1. 你写的代码怎么变成用户能装的东西

到目前为止,你的代码只存在于 temp_repo/ 目录里——.ts 源文件、package.json、图标、配置文件。用户要安装你的扩展,需要的是一个可分发的包。VS Code 生态里这个包就是 .vsix 文件。

1.1 .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)默认排除这些——它只打包运行时需要的东西

1.2 分发路径:Marketplace 和 Open VSX

VS Code MarketplaceOpen VSX
是谁的Microsoft 官方运营Eclipse 基金会——社区驱动
谁用标准 VS Code(绝大多数用户)VS Codium、Cursor、Windsurf 等非 Microsoft 构建版
发布方式@vscode/vsce publishovsx publish
认证Azure DevOps Personal Access TokenEclipse Open VSX Registry 的 PAT
publisher 注册marketplace.visualstudio.com/manageopen-vsx.org

claude-context-bar 同时发布到两个平台——这也是 publish.yml 有两个 publish 步骤的原因。对于一个想让尽可能多用户使用的扩展,双平台发布是标准做法。

2. package.json 中的发布字段

不是 package.json 里所有东西都和发布有关。下表列出专门影响 Marketplace 展示和发布流程的字段:

字段作用不填会怎样
name扩展的唯一标识符。Marketplace 上组成 URL:publisher.namevsce package 直接拒绝
versionSemVer 版本号。每次发布必须递增vsce publish 拒绝覆盖已有版本
publisher发布者 ID——在 Marketplace 注册时获得。和 name 一起构成完整标识符❌ 打包和发布都需要
iconMarketplace 和扩展页面上显示的图标路径(建议 128×128 PNG)⚠️ 使用默认图标——辨识度差
repositoryGitHub 仓库 URL。Marketplace 页面上显示"Repository"链接⚠️ 用户无法从 Marketplace 找到源码
galleryBannerMarketplace 页面的顶部主题色(color + theme⚠️ 使用默认配色
keywordsMarketplace 搜索关键词——用户搜这些词能找到你的扩展⚠️ 可发现性差
engines.vscode支持的最低 VS Code 版本。决定你的扩展能出现在哪些用户的搜索结果中❌ 打包和发布都需要
💡 vscode:prepublish 不是摆设:回顾 package.json 的 scripts——"vscode:prepublish": "npm run compile"。这个钩子是 VS Code 扩展特有的:vsce package 在打包之前自动调用它。它确保你每次打包的都是最新的编译产物——不会发生"改了半天源码,打包的还是旧 .js"这种尴尬事。

3. vsce package:本地打包

打包工具叫 @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
🧪 验证你的 .vsix:打出 .vsix 后,可以用 code --install-extension claude-context-bar-1.4.1.vsix 在本地安装测试。这是发布前最后一道手动检查——确认打包的是你想要的,没有漏文件也没有多文件。

4. publish.yml 逐行拆解

回到 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 }}

4.1 为什么用 push tag 触发而不是 push branch?

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

4.2 Personal Access Token:密钥安全的核心

${{ secrets.VSCE_PAT }}${{ secrets.OVSX_PAT }} 是 GitHub Actions 的加密密钥机制。关键事实:

  1. 密钥不在 workflow 文件里。你在 GitHub 仓库的 Settings → Secrets and variables → Actions 中手动添加。workflow 文件只引用 secrets.XXX——即使有人拿到了仓库源码,也看不到密钥明文
  2. 运行时注入环境变量。GitHub Actions 在 job 启动时将 secrets.VSCE_PAT 注入为环境变量。日志中这个值会被自动遮蔽——如果有人试图 echo $VSCE_PAT,输出显示为 ***
  3. 最小权限原则。每个 PAT 只授予它需要的权限——VSCE_PAT 只需要 Marketplace 的 publish 权限,OVSX_PAT 只需要 Open VSX 的 publish 权限
VSCE_PATOVSX_PAT
从哪里获取Azure DevOps → Personal Access Tokensopen-vsx.org → Settings → Access Tokens
权限范围Marketplace (publish)publish
有效期最长 1 年——需要定期轮换用户自定义
类比你的"身份卡"——证明你是 publisher另一个平台的"身份卡"
⚠️ 密钥轮换——容易被忽略的安全实践:PAT 有过期时间。Azure DevOps PAT 最长 1 年,过期后 publish.yml 静默失败——你打 tag 后等了几分钟才发现发布没发生。建议在 PAT 过期前 1 个月设置日历提醒轮换,或者用 workflow_dispatch 在发布前手动触发一次 vsce package 验证 PAT 仍有效。

5. 版本管理:SemVer 实践

VS Code 扩展使用 Semantic Versioning(语义化版本):major.minor.patch(如 1.4.1)。每次发布前做三件事:

步骤命令效果
1. 更新版本npm version patch1.4.11.4.2,同时自动 commit + 打 tag
2. 更新 CHANGELOG手动编辑 CHANGELOG.md记录这个版本改了什么
3. 推送 taggit push origin main --tags触发 publish.yml
💡 三个数字什么时候变?
patch(1.4.1 → 1.4.2):bug 修复,行为不变
minor(1.4.1 → 1.5.0):新功能,向后兼容
major(1.4.1 → 2.0.0):破坏性改动——用户升级后需要改配置或改代码

6. 完整流水线:从编码到发布

把 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 → 🎉

7. 练习

  1. temp_repo/ 中运行 npx @vscode/vsce package。观察输出:列出了哪些文件?总共多大?有没有包含 src/ 目录?然后解压 .vsix(改后缀为 .zip 然后解压),验证内部结构和第 1 节描述的一致
  2. 阅读 temp_repo/CHANGELOG.md(如果不存在就创建一个)。格式参考 keepachangelog.com。思考:CHANGELOG 和 git log 的区别是什么?为什么两者都需要?
  3. (思维练习)你修改了一个 typo(文档里多了一个空格)。你应该 bump 哪个版本号——patch、minor 还是 major?如果这个 typo 是功能描述中的,修复后改变了用户对功能的理解呢?
  4. 打开 Marketplace 页面,找到 package.jsonicongalleryBannerrepositorykeywords 分别对应页面上的哪个位置
  5. (延伸)在 GitHub 仓库中设置一个假的 PAT(Settings → Secrets → Actions → New repository secret),命名为 VSCE_PAT,值填 test-token-123。这个 token 不会真的能发布,但你会知道密钥是怎么加上去的。然后想想:如果有人 fork 了你的仓库,fork 的 Actions 能读到原仓库的 secrets 吗?(答案:不能——GitHub 不传递 secrets 到 fork)
  6. (延伸)修改 publish.yml,在 publish 步骤之前增加一个 npm test 步骤。再想想:这个改动有什么问题?回顾 Lesson 0008 第 4 节的 callout——两个独立 workflow 之间没有依赖。更好的做法是什么?(提示:needs 关键字,或者把 test 和 publish 合并成一个 workflow)

8. 知识检查

问题 1

.vsix 文件本质上是什么?

问题 2

vsce packagevsce publish 的区别是什么?

问题 3

publish.ymlpush: tags: ['v*'] 触发而非 push: branches: [main]。为什么?

问题 4

${{ secrets.VSCE_PAT }} 中的 PAT 存储在 GitHub 仓库的哪里?

问题 5

一个 VS Code 扩展当前版本是 2.3.1。你只修改了 README 中的一个链接(之前指向旧文档,现在更新为新 URL)。你应该 bump 哪个数字?

9. 推荐深入阅读

💡 提问提示:想知道如何创建 .vscodeignore 文件来控制哪些文件不被打包?想了解如何设置 pre-release 版本供勇敢的用户尝鲜?想知道 vsce publish--pre-release 标志怎么用?想深入了解 PAT 轮换的最佳实践?这些问题都可以继续追问。