发布周期操作手册
从本地闸门、经签名的 GitHub Release,到 npm 发布的完整发布工作流。
本操作手册将 apothem 的一次 vX.Y.Z 发布从干净的工作树推进到经过验证的分发表面。该流程是为使用 gh、git 和发布级 shell 的维护者编写的;它以一个经过验证的 Release 页面、一份已归档的 CHANGELOG,以及一次确认每个面向用户的表面都能响应规范安装命令的安装路径冒烟测试作为结束。
发布模型是追加式的。每次发布在 main 上添加一个提交和一个经签名的附注 vX.Y.Z 标签;任何内容都不会被强制推送,任何较早的标签、GitHub Release 或 npm 版本都不会被移除。每个发布都保留其标签、其 GitHub Release 和其 npm 版本,并且全部保持已发布状态:npm 版本不可变(已发布的 name@version 永远无法被替换),而固定版本的安装依赖于这些标签。
标签推送会启动三个工作流。.github/workflows/release.yml 构建、签名、证明并发布 GitHub Release 及其完整的产物集:sdist、wheel、CycloneDX SBOM、Sigstore cosign 签名包、SLSA-3 出处溯源,以及平台运行时归档。publish-npm.yml 通过 npm 受信任发布将 @ahmed-g-gad/apothem 发布到 npmjs.com,publish-vscode.yml 打包 VS Code 扩展,并将其发布到 Visual Studio Marketplace。带版本号的 CHANGELOG 部分随发布提交一同落地;操作者在两个工作流都完成后运行安装路径冒烟测试。
1. 前置条件
维护者在打标签前确认以下各项:
-
工作树。 干净(
git status显示无任何待处理项)。该发布的所有功能工作都已在main上。 -
CI 为绿色。 针对最新的
main,gh run list --branch=main --limit=1 --json conclusion --jq '.[0].conclusion'返回success。 -
工具链。
gh(GitHub CLI)2.40 或更高版本,已针对ahmed-g-gad完成认证;git2.40 或更高版本,已加载 GPG 签名子密钥;@ahmed-g-gad/apothem的 npm 受信任发布者绑定已生效(按照发布者账户设置操作手册验证)。 -
版本锚点已对齐。
pyproject.toml中声明的版本字符串与计划的标签去掉v前缀后一致(由 §2.1 连同其他所有锚点一起设置)。验证:grep '^version' pyproject.toml预期输出(针对一次
vX.Y.Z发布;占位符代表正在进行的发布标签):version = "X.Y.Z" -
CHANGELOG 条目已暂存。 发布要点位于 Keep-a-Changelog 标题体系中的
## [Unreleased]下;§2.1 会在落地该标签的提交中将它们转为带日期的版本部分。
2. 打标签并触发
2.1 编写发布提交
版本号分布在超过一打的文件中,因此不要手工编辑它们。一条命令即可更新全部文件:
git checkout main
git pull --ff-only origin main
python scripts/release/bump_version.py X.Y.Zbump_version.py 会一并更新每个版本锚点:
-
每个打包、扩展、插件和市场清单,以及引用文件(版本号和发布日期)。完整列表:
pyproject.toml package.json site/package.json site/package-lock.json (the project entries only) vscode-extension/package.json gemini-extension.json qwen-extension.json .claude-plugin/marketplace.json (top level and metadata) plugins/apothem/plugin.json plugins/apothem/.codex-plugin/plugin.json CITATION.cff -
CHANGELOG.md:[Unreleased]条目变为带日期的## [X.Y.Z]部分,[Unreleased]改为从vX.Y.Z开始比较,并添加一个[X.Y.Z]链接。 -
SECURITY.md:新的次版本会获得一个受支持的X.Y.x行。较旧的次版本是否继续列出由你决定;脚本会打印一条提醒。
随后它会重新生成派生树:Claude Code 插件包、行为差异黄金基准,以及文档参考页和变更日志页。传入 --dry-run 可先列出更改,传入 --date YYYY-MM-DD 可设置发布日期(默认:UTC 时区的今天),传入 --no-regen 可跳过重新生成。该脚本会拒绝格式错误的版本号或降级,而用当前版本号重新运行它只会重新对齐零散的锚点。
审查差异,然后提交:
git add -A
git commit -S -m "chore(release): cut vX.Y.Z"
git push origin main-S 标志使用 GPG 密钥对提交进行签名。该推送会触发标准 CI 矩阵(lint、test、build);在打标签之前等待其变绿。
§2.4 中的就绪检查还要求 CodeQL 和合规工作流在发布提交上均为绿色。这两个工作流都只在各自的路径发生变化时才会运行,而发布提交通常不会改动这些路径,因此请在发布提交成为 main 的最新提交之后启动它们,然后确认每次运行都对应该提交:
gh workflow run codeql.yml --ref main
gh workflow run conformity.yml --ref main
gh run list --workflow codeql.yml --commit "$(git rev-parse HEAD)" --limit 1
gh run list --workflow conformity.yml --commit "$(git rev-parse HEAD)" --limit 1在打标签之前,等待两者都成功;在发布完成之前,不要向 main 推送任何其他内容:之后的推送会使 main 偏离发布提交,而且 CI 会取消同一分支上仍在进行中的运行。
2.2 签名并推送标签
git tag -a -s vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.Z-a -s 组合会生成一个经 GPG 签名的附注标签。该推送会触发 .github/workflows/release.yml,由它构建、签名、证明并发布。
2.3 确认发布工作流已完成
gh run watch $(gh run list --workflow=release.yml --limit=1 --json databaseId --jq '.[0].databaseId')该工作流运行构建(sdist + wheel,然后为这些分发包生成一份 CycloneDX SBOM,写在它们旁边)、无密钥 cosign 签名、SLSA-3 出处溯源的生成与验证、平台归档构建(darwin / linux / windows)、带有聚合 SHA256SUMS 清单的归档签名,以及最终的 GitHub Release 发布。维护者检查 publish GitHub Release 作业日志中的产物上传确认信息。
publish GitHub Release 作业在 release 环境中运行,该环境需要维护者批准,因此前面的作业通过后,运行会停在此处等待。可在运行页面上批准(Review deployments,审查部署),也可以在 shell 中执行以下命令:
run_id=$(gh run list --workflow=release.yml --limit=1 --json databaseId --jq '.[0].databaseId')
env_id=$(gh api repos/ahmed-g-gad/apothem/environments/release --jq .id)
gh api -X POST "repos/ahmed-g-gad/apothem/actions/runs/${run_id}/pending_deployments" \
-F "environment_ids[]=${env_id}" -f state=approved -f comment="vX.Y.Z"标签推送还会启动 npm 和 VS Code 扩展工作流。它们的发布作业会等待同样的批准,直到 §2.4 中处理。
2.4 确认 npm 发布
标签推送还会与 release.yml 并行运行 publish-npm.yml。npm 工作流不等待发布工作流;把关点是 release 环境的批准。待 §2.3 中的发布工作流结束之后,再确认就绪状态。该检查读取发布记录、工作流运行记录以及已发布的 SBOM,因此请先准备两项输入:
GH_TOKEN。 即便gh已登录,缺少该变量时脚本也会拒绝运行。请使用仅限于ahmed-g-gad/apothem的细粒度个人访问令牌,并授予 Actions: Read(工作流运行)与 Contents: Read(发布版本)权限。该检查不写入任何内容,因此无需写入权限。由gh auth token得到的令牌会带上该登录的全部权限范围,因此应优先使用细粒度令牌。- 已发布的 SBOM。 该检查读取
dist/release-assets/sbom.cdx.json,即发布所附带的 CycloneDX 文件。请下载发布版本所发布的副本,而不是本地构建的版本。
read -rs GH_TOKEN && export GH_TOKEN
mkdir -p dist/release-assets
gh release download vX.Y.Z --repo ahmed-g-gad/apothem \
--pattern sbom.cdx.json --dir dist/release-assets --clobber
bash scripts/release/check-release-ready.sh vX.Y.Z以下任一情况都会使检查失败:发布为草稿或预发布版本;标签所指提交的某个必需工作流未通过;SBOM 缺失;SBOM 描述的是其他版本;或 SBOM 漏掉了某个 vendored 包。失败信息会指明原因。
检查通过后,请按照 §2.3 中的方式批准等待中的 Publish npm 运行(其运行 ID 来自 gh run list --workflow=publish-npm.yml)。该工作流通过 npm 受信任发布(OIDC)将 @ahmed-g-gad/[email protected] 发布到 npmjs.com;仓库中不存储任何注册表令牌。也请批准等待中的 Publish VS Code Extension 运行。它会打包 apothem.vsix,并以 ahmed-g-gad.apothem-vscode 的名称将其发布到 Visual Studio Marketplace。发布步骤仅当仓库变量 VSCODE_MARKETPLACE_READY 为 true 且配置了一种凭据时运行:变量 VSCE_AUTH=oidc(Marketplace 受信任发布,不存储机密)、变量 VSCE_AUTH=entra、AZURE_CLIENT_ID 和 AZURE_TENANT_ID(一个短期有效的 Microsoft Entra ID 令牌,不存储机密),或 VSCE_PAT 机密。三者都没有时,该运行会打包扩展,并跳过发布。凭据是一个开关,而不是重试:某条路径上的发布失败不会自行回退到另一条路径。每条路径的设置见下方的通过受信任发布进行 Marketplace 发布(首选)和通过 Microsoft Entra ID 进行 Marketplace 发布。
若要在一次失败的运行之后再次发布,为同一标签调度该工作流(gh workflow run publish-npm.yml --field tag=vX.Y.Z)。当该版本已在注册表上时,它会跳过发布,因为 npm 版本不可变。
通过受信任发布进行 Marketplace 发布
受信任发布让 Marketplace 直接接受本仓库的 GitHub OIDC 令牌:vsce publish --oidc 将发布作业的令牌换取为一个短期有效的 Marketplace 凭据。这里没有存储的机密,也没有 Azure 资源,因此这是首选路径。下方的 Entra 路径和 VSCE_PAT 机密作为由操作者选择的替代方案保留。
-
使用支持它的 vsce 版本。 在这条路径上,发布作业会在发布之前检查
tools/vsce中锁定的 vsce 版本;当锁定的版本较旧时,它会以一条指明最低版本的错误停止。Dependabot 会为该锁文件提出 vsce 更新;在切换之前,合并达到最低版本的那一个。 -
创建策略。 以发布者的 Owner 身份登录,打开
https://marketplace.visualstudio.com/manage/publishers/ahmed-g-gad,并在 Trusted Publishing 下添加一条策略:- 策略名称(Policy name):任意名称,例如
apothem-release - 仓库所有者(Repository owner):
Ahmed-G-Gad - 仓库(Repository):
apothem - 工作流(Workflow):
publish-vscode.yml - 环境(Environment):
release
这些字段名称取自 VS Code 发布指南中关于使用 GitHub Actions 和受信任发布进行发布的那一节(
https://code.visualstudio.com/api/working-with-extensions/publishing-extension)。未确认:是否每个发布者都已能看到 Trusted Publishing 选项卡。如果你的发布者看不到,请使用 Entra 路径。 - 策略名称(Policy name):任意名称,例如
-
切换路径。 保持
VSCODE_MARKETPLACE_READY为true,并设置选择器。请在没有标签等待时设置它,因为下一次标签推送会通过这条路径发布。gh variable set VSCE_AUTH --body oidc -
发布。 下一次标签推送,或针对现有标签的一次调度,会通过受信任发布进行发布。这条路径没有试运行:第一次发布就是检查,而带
dry_run的调度只会警告未验证任何内容。 -
失败时回退。 删除选择器,并为同一标签再次调度;设置了
VSCE_PAT机密时,由令牌路径发布。失败的发布没有发布任何内容,因此可以为同一标签再次调度。gh variable delete VSCE_AUTH
通过 Microsoft Entra ID 进行 Marketplace 发布
Entra 路径以一个短期有效的令牌取代存储的个人访问令牌。publish-vscode.yml 向 GitHub 请求一个 OIDC 令牌,Azure/login action 通过联合凭据将其换取为一次 Entra 登录,然后 vsce publish --azure-credential 使用由此得到的令牌进行发布。不存在客户端机密。VSCE_PAT 机密作为由操作者选择的回退方案保留;工作流从不自行切换到它。
tools/vsce 中固定的 vsce 版本支持这条路径。它需要一个 Entra 租户、一个应用程序注册以及发布者成员身份,而上方的受信任发布不需要这些;当发布者无法使用受信任发布时,请使用这条路径。
-
检查前置条件。 找到拥有发布者
ahmed-g-gad的账户(https://marketplace.visualstudio.com/manage/publishers/ahmed-g-gad上的 Members 选项卡),以及你可以在其中注册应用程序的 Entra 租户。未确认:发布者所有者的租户是否必须与应用程序的租户一致,以及 Azure DevOps 组织Ahmed-G-Gad是否必须连接到 Entra(dev.azure.com/Ahmed-G-Gad,Organization settings,Microsoft Entra)。没有任何来源说明其中任何一项。如果第 5 步或第 6 步失败,请先检查这些。 -
注册应用程序。 在
https://entra.microsoft.com中,打开 App registrations,然后点击 New registration。将其命名为apothem-vscode-publish,选择单租户,并将重定向 URI 留空。在 Overview 中,复制 Application (client) ID 和 Directory (tenant) ID。不要创建客户端机密,也不要授予任何 API 权限。此步骤需要应用程序所有者权限,或诸如 Application Administrator 之类的 Entra 角色。 -
添加联合凭据。 在该应用程序上,打开 Certificates & secrets、Federated credentials、Add credential,并选择场景“GitHub Actions deploying Azure resources”。输入组织
Ahmed-G-Gad、仓库apothem、实体类型Environment、环境名称release,以及凭据名称apothem-release。生成的字段必须为:issuer: https://token.actions.githubusercontent.com subject: repo:Ahmed-G-Gad/apothem:environment:release audience: api://AzureADTokenExchange匹配是精确且区分大小写的。如果之后的登录以 AADSTS700213 或 AADSTS7002138 失败,请将该凭据与
Azure/login写入作业日志的subject claim行进行比较。创建后立即出现 AADSTS70021 表示该凭据尚未传播;请等待几分钟后重试。 -
设置仓库变量。 当
VSCODE_MARKETPLACE_READY为true时,三个变量一旦全部设置,Entra 路径即生效,因此请在没有发布标签等待批准时设置它们。没有VSCODE_MARKETPLACE_READY=true时,不会进行任何登录,运行会跳过发布。gh variable set AZURE_CLIENT_ID --body "<application-client-id>" gh variable set AZURE_TENANT_ID --body "<directory-tenant-id>" gh variable set VSCE_AUTH --body entra保持
VSCODE_MARKETPLACE_READY和VSCE_PAT不变。当VSCE_AUTH为entra但AZURE_CLIENT_ID或AZURE_TENANT_ID为空时,无论是否设置了VSCE_PAT,工作流都会记录一条警告,并且不使用 Entra 路径。随后,如果设置了VSCE_PAT机密,它就使用该机密;没有该机密时,它不发布任何内容,而运行仍以绿色结束,因此请留意该警告。 -
执行试运行。 针对一个现有的发布标签调度该工作流,并设置
dry_run。在 Entra 路径上,它会登录、打印 Azure DevOps 配置文件,并在不发布的情况下验证发布者访问权限。令牌路径没有试运行:如果日志显示Marketplace credential mode: pat,运行会以绿色结束并带有一条警告,且未验证任何内容。请批准等待中的release环境运行。gh workflow run publish-vscode.yml --ref main -f tag=vX.Y.Z -f dry_run=true预期:
Marketplace credential mode: entra、一行与第 3 步中的 subject 相同的subject claim,以及由第一个试运行步骤打印的配置文件id和displayName。在第 6 步之前,验证步骤预计会以 403 失败。未确认:在没有 Azure 订阅的情况下登录,能否通过 Azure CLI 获得用于 Marketplace 的令牌。这次运行是对此的首次测试。如果在这里失败,请改为在某个订阅中注册一个用户分配的托管标识,这是 Microsoft 记载的路径。 -
将应用程序添加到发布者。 在第 1 步中的 Members 选项卡上,以发布者 Owner 身份登录,并使用第 5 步中的配置文件
id添加一名成员,角色为 Contributor。未确认:该对话框的字段标签,以及在应用程序成为某个 Azure DevOps 组织的成员之前,它是否接受该 id。按照一般规则,Azure DevOps 按组织添加应用程序标识。如果对话框拒绝该 id,请将服务主体添加到组织Ahmed-G-Gad(Organization settings,Users,使用 Enterprise application 的 Object ID),然后重试。同样未确认:在该标识首次调用 Azure DevOps 之前,配置文件是否已存在。 -
再次执行试运行。 验证步骤现在会以“verification succeeded for the publisher”成功;其消息仍然提到 Personal Access Token,因为 Entra 令牌走的是同一条路径。该检查证明的是发布者成员身份(Creator、Owner、Contributor 或 Reader),而不是发布权限。未确认:Contributor 是否足以发布更新。Microsoft 的角色说明称足够,而没有任何来源展示过以该角色进行的发布,因此第一次真正的发布就是证明。
-
发布一个版本。 按照 §2.2 打标签并批准运行。步骤
Publish to Visual Studio Marketplace (Entra ID)会运行,其环境头部只列出AZURE_TENANT_ID,令牌回退步骤被跳过,vsce 打印DONE Published ahmed-g-gad.apothem-vscode。大约四分钟后,Marketplace 库会列出新版本。 -
决定如何处理令牌。 保留
VSCE_PAT,直到第一次成功的 Entra 发布;之后如果不需要回退,就删除该机密。一旦删除,被删除或为空的 Entra 变量会使运行以绿色结束且不发布:当VSCE_AUTH仍为entra时会带有一条警告,否则只有跳过通知。每次打标签后,请阅读该运行的注解。若要在全局个人访问令牌停用之后仍保留回退,请为Ahmed-G-Gad创建一个组织范围的令牌,使用自定义范围 Marketplace Manage 和你可接受的最短有效期,并将其存储为VSCE_PAT。未确认:此步骤中的门户标签来自记忆,而非一手来源;Microsoft 称组织范围的令牌是一种临时变通办法,并且可能停止接受它们;而且 Microsoft 的各个页面对全局令牌的停用日期说法不一。 -
回退到令牌。 删除选择器并再次调度,然后批准运行。失败的 Entra 发布没有发布任何内容,因此可以为同一标签再次调度;请先检查 Marketplace 页面,因为已落地的版本无法发布两次。
gh variable delete VSCE_AUTH gh workflow run publish-vscode.yml --ref main -f tag=vX.Y.Z
当 Entra 发布失败时,请阅读步骤日志:
- 以“Can not acquire a Microsoft Entra ID access token”开头的消息表示登录或凭据链失败。请先检查
Sign in to Microsoft Entra ID步骤和联合凭据。如果登录为绿色,请从工作流中 vsce 步骤的env里移除AZURE_TENANT_ID;GitHub 自己的扩展发布工作流不导出它。 - “InvalidAccessException”或“not allowed”表示该令牌的标识不是发布者成员。请将试运行中的配置文件
id与 Members 选项卡进行比较。 - 试运行验证步骤返回的 403 表示该应用程序尚无角色。
这条路径有三项限制:
id-token: write被授予整个发布作业,因此其中的每个步骤都可以请求 OIDC 令牌,在令牌模式下也是如此。该令牌的 subject 绑定到release环境,而该环境需要审查者的批准。release环境没有部署分支或标签策略,因此维护者批准的任何 ref 都可以获得环境 subject 令牌。要将其限制为v*.*.*标签,需要先检查release.yml和publish-npm.yml调度了什么。- 仓库重命名或转移会改变 OIDC subject 格式,并悄无声息地破坏联合凭据。任何重命名之后,请将
Azure/login日志中的 subject 重新复制到该凭据中。
2.5 刷新模板摘要固定值
标签存在之后,将 tests/unit/test_template_digests.py 中的 _LATEST_RELEASE 和 _LATEST_RELEASE_DIGESTS 设置为新版本,以及该版本所发布的每个纯文本模板的 SHA-256 摘要(git show vX.Y.Z:<template path> | sha256sum)。请在对 main 的下一次更改中执行此操作;当模板发生变化时,将之前的摘要添加到清单条目的 earlier_digests 中。release-preflight CI 作业会检出每个标签,因此在固定值刷新之前,它会以这些标签名称失败。
3. 分发表面
每个表面都有一条回答“发布是否落地于此?”的验证命令:
| # | 表面 | 产物 | 验证 |
|---|---|---|---|
| 1 | GitHub Release(Python 分发包) | apothem-X.Y.Z.tar.gz(sdist)+ apothem-X.Y.Z-py3-none-any.whl | gh release view vX.Y.Z --json assets --jq '.assets[].name' 包含这两个文件名 |
| 2 | GitHub Release(SBOM) | sbom.cdx.json + sbom.cdx.json.cosign.bundle | 产物列表包含两者;SBOM 的 metadata.component.purl 为 pkg:pypi/[email protected],且其组件包含 src/apothem/_vendor/vendor.txt 中的每个包 |
| 3 | GitHub Release(签名) | sdist、wheel、SBOM、VS Code 扩展、每个平台归档和 SHA256SUMS 各对应一个 <asset>.cosign.bundle(较早的发布将归档和 SHA256SUMS 的签名包命名为 <asset>.sig) | 使用 §5.5 中的确切身份运行 cosign verify-blob,退出码为 0 |
| 4 | GitHub Release(出处溯源) | provenance.intoto.jsonl | bash scripts/release/verify-provenance.sh <download-dir> 退出码为 0(§5.5) |
| 5 | GitHub Release(平台归档) | apothem-vX.Y.Z-darwin.tar.gz、apothem-vX.Y.Z-linux.tar.gz、apothem-vX.Y.Z-windows.zip | 产物列表包含每个归档以及 SHA256SUMS。darwin 和 linux 归档是同一份字节、两个名称(运行时与平台无关;--platform 只决定使用 tar.gz 还是 zip),因此它们的 SHA256SUMS 行会两次显示同一个摘要 |
| 6 | GitHub Release(VS Code 扩展) | apothem.vsix + apothem.vsix.cosign.bundle | 产物列表包含两者;code --install-extension apothem.vsix 可安装它。在发布工作流开始打包该扩展之前的发布两者都不包含 |
| 7 | npmjs.com | @ahmed-g-gad/[email protected] | npm view @ahmed-g-gad/apothem version 返回 X.Y.Z;npx @ahmed-g-gad/[email protected] --version 打印 Apothem, version X.Y.Z |
| 8 | Pages(脚本安装器) | 文档站点上的 install.sh / install.ps1 | curl -fsSL https://apothem.ahmedgad.com/install.sh | head -n 1 返回脚本头部 |
| 9 | Visual Studio Marketplace | ahmed-g-gad.apothem-vscode 版本 X.Y.Z | 该扩展的 Marketplace 页面列出 X.Y.Z;code --install-extension ahmed-g-gad.apothem-vscode 可安装它 |
GitHub Release 是规范的产物表面;npm 包和脚本安装器是叠加在其之上的安装路径。npm 发布在同一次标签推送上与发布工作流并行运行。
4. CHANGELOG 纪律
CHANGELOG.md 按照 Keep-a-Changelog 约定,每个发布对应一个部分。发布提交对 CHANGELOG 的编辑:
- 确认版本部分使用
## [X.Y.Z] - YYYY-MM-DD标题(连字符;由bump_version.py写入,而extract_release_notes.py匹配## [X.Y.Z]前缀)。 - 确认在适用之处使用标准标题——Added、Changed、Deprecated、Removed、Fixed、Security。空标题会从渲染后的文件中删去。
- 记录的是能力,而非实现。仅使用领域语言;不包含计划内部引用;不包含提交哈希;不包含计划或阶段标识符。
经过维护者审查而保留下来的 CHANGELOG 部分,即可作为公告叙述的发布说明锚点。
5. 安装路径冒烟测试
两个阶段都完成后,维护者运行冒烟测试。该冒烟测试是对面向用户的安装命令能够响应的规范验证。
5.1 Claude Code 插件
/plugin marketplace add ahmed-g-gad/apothem
/plugin install apothem@apothem预期:插件完成安装,且 apothem 命令在 Claude Code 中可用。
5.2 Node.js(npx)
npx @ahmed-g-gad/[email protected] --version预期输出:Apothem, version X.Y.Z。
5.3 一键脚本安装器(POSIX)
安装器会将 apothem 启动器写入 ~/.local/bin;若设置了 APOTHEM_BIN_DIR,则写入该变量指定的目录。下方的检查需要该目录位于 PATH 上。若不在,安装器会打印需要添加的 export PATH 行。在该目录位于 PATH 上之前,shell 无法找到 apothem,检查会失败。若安装器无法放置启动器,它会打印警告,而不会打印 export PATH 行。在尚未持有维护者签名密钥的主机上,代码块中的第一行会导入该密钥;缺少该密钥时,安装器会停止。
curl -fsSL https://github.com/ahmed-g-gad.gpg | gpg --import
curl -fsSL https://apothem.ahmedgad.com/install.sh | sh
apothem --version预期输出:Apothem, version X.Y.Z。
5.4 一键脚本安装器(Windows)
第一行通过 Git 自带的 bash 导入维护者签名密钥,该 gpg 的密钥环正是 git verify-tag 所读取的那一个。
& "$env:ProgramFiles\Git\bin\bash.exe" -c 'curl -fsSL https://github.com/ahmed-g-gad.gpg | gpg --import'
irm https://apothem.ahmedgad.com/install.ps1 | iex
apothem --version预期输出:Apothem, version X.Y.Z。
5.5 产物验证(供应链证据)
将每个产物下载到一个空目录中,然后进行验证。证书身份是位于这个确切标签处的发布工作流。身份检查区分大小写,而证书按照 GitHub 报告的形式携带所有者,即 Ahmed-G-Gad,因此小写的所有者会验证失败。
mkdir release-vX.Y.Z && cd release-vX.Y.Z
gh release download vX.Y.Z --repo ahmed-g-gad/apothem
cosign verify-blob \
--bundle apothem-X.Y.Z-py3-none-any.whl.cosign.bundle \
--certificate-identity https://github.com/Ahmed-G-Gad/apothem/.github/workflows/release.yml@refs/tags/vX.Y.Z \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
apothem-X.Y.Z-py3-none-any.whl
slsa-verifier verify-artifact \
--provenance-path provenance.intoto.jsonl \
--source-uri github.com/Ahmed-G-Gad/apothem \
--source-tag vX.Y.Z \
apothem-X.Y.Z-py3-none-any.whl apothem-X.Y.Z.tar.gz
bash ../scripts/release/verify-provenance.sh .预期:Verified OK、PASSED: SLSA verification passed 以及 verify-provenance: chain OK (3 of 3 layers verified)。该脚本在一次运行中检查 SHA256SUMS、每个签名包和出处溯源;它从 wheel 文件名推导出标签(--tag vX.Y.Z 可覆盖该值)。
冒烟测试为每个表面产出一行 PASS / FAIL 记录;将这些记录写入维护者的发布日志。
6. 故障恢复
发布工作流针对发布引擎和 npm 发布者有已记录在案的故障模式:
| 故障 | 诊断 | 恢复 |
|---|---|---|
| 标签推送被拒绝(签名失败) | git push origin vX.Y.Z 返回 error: failed to push some refs 并带有 GPG 相关消息 | 验证 GPG 子密钥已加载(gpg --list-secret-keys --keyid-format=long);修复密钥环后在本地用 -s 重新打标签;重新推送 |
发布构建停在 Check the plugin version changed with the plugin | 构建作业日志会指出该插件和上一个标签;没有任何产物被构建或发布 | 插件包自上一次发布以来有变化,但其版本号没有变化。删除该标签(git push --delete origin vX.Y.Z、git tag -d vX.Y.Z),运行 python scripts/release/bump_version.py X.Y.Z,提交,然后再次签名并推送标签(§2.2)。同一检查会在每个拉取请求上以警告注解的形式运行,因此该问题通常会在打标签之前暴露出来 |
release.yml 作业在单个阶段失败 | gh run view <run-id> --log 会指出失败的作业(build(它也会写入 SBOM)/ sign / provenance / archive / publish) | 通过 gh run rerun <run-id> --failed 单独重新运行失败的作业;如果故障再次出现,在重新打标签之前从源头修复该作业的输入 |
| SLSA 出处溯源验证失败 | verify SLSA provenance artifact 作业以非零状态退出 | 检查出处溯源产物是否存在主体摘要不匹配;从同一标签重新构建——出处溯源绑定到确切的产物摘要,因此任何产物的重新生成都需要完整地重新运行工作流 |
| npmjs.com 发布被拒绝 | npm 作业在 npm publish 期间以非零状态退出;PUT 期间的注册表 404 表示授权 / 受信任发布者不匹配,并不能证明包代码缺失 | 验证 @ahmed-g-gad/apothem 的 npm 受信任发布指定了所有者 ahmed-g-gad、仓库 apothem、工作流 publish-npm.yml 以及允许的操作 npm publish;确认该作业以工作流中固定的 Node 版本和 npm CLI OIDC 下限运行 |
| 标签可见但产物缺失 | gh release view vX.Y.Z 返回一个空壳发布 | 重新运行发布工作流的 publish 作业;它会编辑现有的发布并重新上传每个产物。只有当该标签的任何内容都未到达用户(没有产物,没有 npm 版本)时,才删除并重新打标签;否则用下一个补丁发布向前修复(发布恢复操作手册 §2) |
如果一个恢复周期未能在一轮诊断内使某个表面落地,则将该表面作为已知故障记入发布说明,并附上指向未关闭问题的链接;在补丁发布中修复损坏的表面期间,将下游用户引导至可用的表面。
7. 交叉引用
- 发布者设置。 发布者账户设置操作手册建立了 npm 受信任发布者绑定,以及本操作手册所假定的 GitHub 侧前置条件。
- 恢复流程。 发布恢复操作手册遵循相同的追加式模型:它逐阶段恢复失败的标签或发布者,并通过将修复作为新的补丁版本发布来回滚,绝不通过删除或重新指向已发布的标签、发布或 npm 版本来回滚。
- Pages 启用。
publish-static-site.yml工作流验证项目网站并将其部署到自定义域名;Pages 启用操作手册(site/content/docs/runbooks/pages-enablement.mdx)是最初确立该自定义域名的上游流程。 - 版本策略。
site/content/docs/governance/release-engineering-policy.mdx载明了本操作手册所遵循的 SemVer + 签名 + 弃用策略。