news 2026/9/6 22:31:05

GitHub CLI 发布流程深度解析:cli/cli 的跨平台构建、签名、公证与包仓库发布机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GitHub CLI 发布流程深度解析:cli/cli 的跨平台构建、签名、公证与包仓库发布机制

GitHub CLI 发布流程深度解析:cli/cli 的跨平台构建、签名、公证与包仓库发布机制

【免费下载链接】cliGitHub’s official command line tool项目地址: https://gitcode.com/GitHub_Trending/cli/cli

本文基于 cli/cli 仓库中的 docs/release-process-deep-dive.md 展开,逐层剖析gh官方命令行工具的完整发布流水线:从deployment.yml工作流的标签校验、三大平台并行构建,到 macOS 代码签名与公证、Windows Azure HSM 签名、Linux 包仓库 GPG 签名,再到 GoReleaser 脚本级裁剪机制与 dry run 安全设计。读完本文,你将能够完整理解一次生产发布如何从./script/release出发、在各 Job 间传递制品,并最终安全地发布到 GitHub Release 与cli.github.com站点。

背景:为什么需要这份"发布流程考古文档"

文档开篇交代了一个工程现实:当前的发布工作流与配套脚本诞生于更早的维护者时代,且当时的维护者已全部离开。在此后发布 macOS 安装器、迁移到 Azure HSM 签名、更新过期 GPG 密钥等若干场合,现任维护者都曾花大量时间重新研究这套工作流。这份文档的定位正是给未来维护者的指南——它不是一份操作手册,而是对deployment.yml工作流 及其配套脚本的逐 Job 源码级解读。

高层概览:一次发布的完整链路

从高层看,发布工作流的职责可以概括为六步:

  1. workflow_dispatch事件触发(通常是执行./script/release的结果);
  2. 并行地构建、打包并签名 Linux、macOS、Windows 三平台的制品;
  3. 使用 GPG 签名 Debian 与 Red Hat 仓库制品;
  4. 构建并更新 CLI 手册 与包仓库;
  5. 为制品创建 GitHub Attestations(溯源证明);
  6. 创建 GitHub Release 并挂载所有制品。

工作流通过.github/workflows/deployment.yml中的workflow_dispatch输入参数控制行为,当前仓库中定义的核心输入包括:

  • tag_name:必填,发布标签(如v2.100.0或预发布v2.100.0-rc.1);
  • environment:默认production,可设为staging等非生产环境;
  • platforms:默认linux,macos,windows,逗号分隔,可指定平台子集;
  • release:是否执行最终的 release Job(默认true);
  • dry_run:是否干跑(workflow_dispatch表单默认true)。

此外,工作流还设置了concurrency组(按 workflow + ref name 分组、cancel-in-progress: true)避免同一标签的重复运行互相干扰,并通过permissions声明了attestations: writecontents: writeid-token: write三个必需权限。

工作流的子集运行方式

文档特别指出了工作流的几种"部分执行"模式,这是理解整个发布系统安全模型的关键:

  • 跳过 release Jobinputs.release设为false时,整个 release Job 被跳过(该能力未通过./script/release暴露);
  • staging 模式:大量步骤由if: inputs.environment == 'production'守卫,这些守卫保护两类操作——需要密钥的步骤(如签名)和产生外部变更的步骤(如创建 GitHub Release)。./script/release通过--staging标志触发该模式;
  • dry runinputs.dry_run设为true时(表单默认值),工作流仍然执行完整的 production 签名与打包步骤,但不发布任何东西——不创建 Attestations、不创建 Release、不推送站点仓库。这使得维护者可以在不产生任何外部可见变更的前提下验证整条生产构建链路,详见后文发布行为与 dry run;
  • 单平台调试platforms只填linuxmacoswindows之一时可单独调试某个构建 Job。此时 release Job 不应运行,因为它needs: [linux, macos, windows],依赖全部三个 OS 构建。

所有 OS 构建 Job(以及 release Job)都会 checkout 触发workflow_dispatch的 ref(即传给gh workflow run--ref,由./script/release--branch参数得出),并设置timeout-minutes: 20——让挂死的构建(例如等待远程服务的代码签名步骤)快速失败,而不是耗完默认的整段 Job 超时时间。

Job 一:validate-tag-name 标签格式校验

validate-tag-nameJob 运行在ubuntu-latest上,核心逻辑只有一段 bash 正则:

if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then echo "Invalid tag name format. Must be in the form v1.2.3 or v1.2.3-rc.1" exit 1 fi

该 Job 的目的是防止打错标签的发布:强制标签遵循v前缀的语义化版本major.minor.patch形式,允许-rc.1这类可选的预发布后缀,但拒绝+之后的构建元数据。原因很具体——工作流后续通过"标签中是否含连字符"来识别预发布,构建元数据会破坏这个约定(当前工作流中此点已内联为注释)。

标签中的连字符会同时改变三件事:

  1. release Job 创建 GitHub Release 时带上--prerelease
  2. 站点(cli.github.com)不会被发布;
  3. Windows MSI 会丢弃该后缀,从而让ProductVersion保持纯数字。

这一设计从源码中可以得到印证:在 deployment.yml 的Create the release步骤里,if [[ $TAG_NAME == *-* ]]; then release_args+=( --prerelease ); fi正是"看连字符定预发布"的实现;而Publish site步骤的DO_PUBLISH条件里也包含了!contains(inputs.tag_name, '-')

Job 二~四:三大平台的并行构建与签名

标签校验通过后,工作流在ubuntumacoswindows三类 runner 上并行执行。这些 Job 的首要职责是构建并签名发布制品,制品通过actions/upload-artifact传递给 release Job(由actions/download-artifact取回),上传均设置if-no-files-found: errorretention-days: 7

linux Job:构建二进制、包与 Web 手册

linux Job 的步骤编排是:checkout →actions/setup-go(版本取自go.mod)→ 安装 GoReleaser →在 HEAD 上打一个临时标签git tag "$TAG_NAME",不推送远端,目的是让构建出的二进制内嵌正确的版本号)→ 执行script/release --local "$TAG_NAME" --platform linux→ 生成 Web 手册页 → 上传dist/*.tar.gzdist/*.rpmdist/*.deb

两个值得注意的细节:

  1. linux Job 还负责构建 CLI 手册 网页版,供后续 release Job 更新站点使用:

    go run ./cmd/gen-docs --website --doc-path dist/manual tar -czvf dist/manual.tar.gz -C dist -- manual

    手册页由 cmd/gen-docs 从命令树生成,随后被压缩为manual.tar.gz作为 linux 制品的一部分上传。

  2. linux Job 本身不做任何签名——.deb/.rpm的 GPG 签名发生在 release Job 中(见后文),而 Linux 二进制无需平台级代码签名。

构建细节(GoReleaser 如何被裁剪运行)参见后文 script/release 工作机制。

macos Job:签名、公证与 Universal 安装器

macos Job 是三个平台中签名链路最复杂的,文档将其归纳为三个层级的"签名"

  1. Go 可执行文件签名:由 .goreleaser.yml 中 macos 构建条目的posthook 触发(./script/sign '{{ .Path }}');
  2. .zip归档公证:工作流中直接执行script/sign dist/gh_*_macOS_*.zipNotarize macOS archives步骤,仅 production 环境);
  3. .pkg安装器签名:在script/pkgmacos内部调用productbuild时完成。

文档在此附了一条重要的术语警告:该步骤标题虽为Build & notarize universal macOS pkg installer,但productbuild文档只提到 signing,因此这里的"notarization"用词可能并不准确。另有一条历史 NOTE:由于仓库变量APPLE_DEVELOPER_INSTALLER_ID曾被遗漏而从未设置,.pkg安装器历史上实际上从未被真正签名;工作流仍在传递该变量,一旦补齐该变量,productbuild的签名即会生效。这一点在当前 script/pkgmacos 源码中得到印证:

# include signing if developer id is set if [ -n "$APPLE_DEVELOPER_INSTALLER_ID" ]; then PRODUCTBUILD_ARGS+=("--timestamp") PRODUCTBUILD_ARGS+=("--sign") PRODUCTBUILD_ARGS+=("${APPLE_DEVELOPER_INSTALLER_ID}") else echo "skipping macOS pkg code-signing; APPLE_DEVELOPER_INSTALLER_ID not set" >&2 fi
三个 production 专属的准备步骤

签名与公证的准备横跨三个只在inputs.environment == 'production'时运行的步骤:

  1. Install code signing certificate:创建专用 keychain,导入codesign使用的 Developer ID Application 证书;
  2. Add App Store Connect API key to keychain:使用nodeselector/setup-apple-codesignaction 将 App Store Connect API key(.p8格式,notarytool的认证凭证)物化,并输出 key path、key id、issuer id;
  3. Configure notarization credentials:执行xcrun notarytool store-credentials,把上述 API key 信息以notarytool-password档案名持久化进 keychain,后续notarytool submit即可引用档案而无需直传凭证。

第一步的 keychain 脚本(含工作流中补充的注释)如下:

# 用 run 编号派生每次运行独立的 keychain 密码,避免硬编码 PW=pwd.${{ github.run_number }} # 创建存放凭证的新 keychain security create-keychain -p $PW "$RUNNER_TEMP/build.keychain" # 提高自动锁定超时(6 小时),避免构建中途锁定 security set-keychain-settings -lut 21600 "$RUNNER_TEMP/build.keychain" # 标记为系统默认 keychain,后续签名步骤无需再按名引用 security default-keychain -s "$RUNNER_TEMP/build.keychain" # 解锁 keychain,后续操作可直接读取秘密 security unlock-keychain -p $PW "$RUNNER_TEMP/build.keychain" # 把 base64 编码的证书秘密解码为 .p12。证书与密码经 env 变量传入 # (DEVELOPER_ID_CERT / DEVELOPER_ID_CERT_PASSWORD 映射自 secrets), # 而不是直接插值进脚本,含 shell 元字符的密码不会破坏引号或注入命令 base64 -d <<< "$DEVELOPER_ID_CERT" > "$RUNNER_TEMP/cert.p12" # 导入证书,供后续签名步骤使用 # -k 指定 keychain;-P 直接给解包口令(默认会弹 GUI); # -T 授予 /usr/bin/codesign 访问导入私钥的权限 security import "$RUNNER_TEMP/cert.p12" -k "$RUNNER_TEMP/build.keychain" -P "$DEVELOPER_ID_CERT_PASSWORD" -T /usr/bin/codesign # 设置 partition list:只有签名相关的应用可以访问 keychain # 三个分区值: # apple-tool: → Apple 开发工具 # apple: → Apple 通用加密工具 # codesign: → codesign 工具(用于签名二进制与应用) security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $PW "$RUNNER_TEMP/build.keychain" # 清理证书文件,避免遗留给后续步骤泄露 rm "$RUNNER_TEMP/cert.p12"

文档还特别说明了凭证安全性的演进:证书与口令来自GATEWATCHER_DEVELOPER_ID_CERT/GATEWATCHER_DEVELOPER_ID_PASSWORD两个 secret,取代了旧的APPLE_APPLICATION_CERT系列 secret;通过env:映射后以"$DEVELOPER_ID_CERT"形式引用,杜绝了 shell 插值注入风险;keychain 也从旧名buildagent.keychain改名为build.keychain,并通过KEYCHAIN环境变量显式传给签名脚本。

script/sign:签名与公证的实际执行者

codesign --timestamp --options=runtime -s "${DEVELOPER_ID_CERT_IDENTIFIER?}" -v "$1"这条命令中,codesign会在 keychain 中查找与DEVELOPER_ID_CERT_IDENTIFIER(源自仓库变量MAC_APP_SIGNING_IDENTITY)匹配的证书;--timestamp--options=runtime(启用 hardened runtime)是公证的前置硬性要求。当前仓库中的 script/sign 实现印证了文档描述的分发逻辑:

sign_macos() { if [[ -z "$DO_SIGN_ARTIFACTS" || "$DO_SIGN_ARTIFACTS" == "false" ]]; then echo "skipping macOS code-signing; DO_SIGN_ARTIFACTS not set or false" >&2 return 0 fi # ... 类似检查 DEVELOPER_ID_CERT_IDENTIFIER 与 KEYCHAIN ... if [[ $1 == *.zip ]]; then xcrun notarytool submit "$1" --keychain "$KEYCHAIN" --keychain-profile "notarytool-password" --wait else codesign --timestamp --options=runtime -s "${DEVELOPER_ID_CERT_IDENTIFIER?}" -v "$1" fi }

即:zip 文件走 notarytool 公证(认证凭据来自前述notarytool-password档案,取代了旧的--apple-id/--team-id/--password流程),可执行文件走 codesignDO_SIGN_ARTIFACTSfalse才执行签名这一守卫,保证了 staging 构建在 keychain 从未配置的情况下能优雅跳过而非报错。

文档附带的排障 TIP 同样实用:若codesign***: no identity found,说明vars.MAC_APP_SIGNING_IDENTITY与 keychain 中实际导入的标识不匹配,执行security find-identity -v -p codesigning "$KEYCHAIN"列出可用标识后精确更新该仓库变量即可。

概念澄清:代码签名 vs 公证

文档在分隔线后厘清了两个常被混淆的概念:代码签名证明一个gh可执行文件确实由 GitHub 创建;**公证(Notarization)**则是把软件提交给 Apple 服务器做自动化扫描,通过后 Apple 生成一张ticket,可staple(装订)到软件上,使 macOS Gatekeeper 知晓其已获审核。

windows Job:Azure HSM 远程签名与 WiX MSI 构建

windows Job 的签名走的是Azure HSM(Azure Dedicated HSM)远程代码签名,链路分四步:

第一步:下载 Azure Code Signing 客户端 DLLsigntool.exe需要该 DLL 才能与 HSM 服务交互:

Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Azure.CodeSigning.Client/1.0.43 -OutFile $Env:ACS_ZIP -Verbose Expand-Archive $Env:ACS_ZIP -Destination $Env:ACS_DIR -Force -Verbose

第二步:生成 signtool 所需的 metadata.json

@{ CertificateProfileName = "GitHubInc" CodeSigningAccountName = "GitHubInc" CorrelationId = $Env:CORRELATION_ID # 指向本次 Actions 运行,便于审计关联 Endpoint = "https://wus.codesigning.azure.net/" } | ConvertTo-Json | Out-File -FilePath $Env:METADATA_PATH

第三步:script/sign.ps1 定位signtool

$signtool = Resolve-Path "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | Select-Object -Last 1

第四步:执行签名:

& $signtool sign /d "GitHub CLI" /fd sha256 /td sha256 /tr http://timestamp.acs.microsoft.com /v /dlib "$Env:DLIB_PATH" /dmdf "$Env:METADATA_PATH" $Args[0]

各参数含义:/fd为文件摘要算法、/td为时间戳摘要算法、/tr指定时间戳服务器(在签名中证明签名时间)、/dlib指向已解压的 DLL、/dmdf指向 metadata 文件。当前仓库的 script/sign.ps1 中还保留着与 macos 对称的守卫:DO_SIGN_ARTIFACTSDLIB_PATHMETADATA_PATH任一缺失即打印提示并退出,保证非生产构建不会触发 HSM 签名。

windows Job 的两个签名层级分别是:GoReleaser 产出的 Go 可执行文件(在 .goreleaser.yml 的 windows 构建条目posthook 中执行pwsh .\script\sign.ps1 '{{ .Path }}')与MSI 安装器Sign .msi release binaries步骤逐个执行.\script\sign.ps1)。两处均设置DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }}——尽管 MSI 签名步骤本身已有 environment 守卫,保留该变量是为了与构建步骤保持一致。

MSI 构建:MSBuild + WiX

除了 GoReleaser 产出的.zip,windows Job 还使用MSBuild.exe为每种架构构建 MSI 安装器(包装 GoReleaser 生成的架构 zip):

"${MSBUILD_PATH}\MSBuild.exe" ./build/windows/gh.wixproj -p:SourceDir="$source_dir" -p:OutputPath="$PWD/dist" -p:OutputName="$MSI_NAME" -p:ProductVersion="${MSI_VERSION#v}" -p:Platform="$platform"

-p:ProductVersion="${MSI_VERSION#v}"${...#v}正好实现了前述"MSI 丢弃预发布后缀、保持数字版本号"的行为(bash 的#v去掉 v 前缀,且MSI_VERSIONcut -d- -f1处已截断连字符后缀)。这段循环对_386/_amd64/_arm64三种架构 zip 分别映射到windows_windows_386_sse2windows_windows_amd64_v1windows_windows_arm64_v8.0等 GoReleaser 产物目录(当前工作流中的架构目录名,如sse2后缀与arm64_v8.0,与文档快照时的命名略有演进),最终调用 build/windows 下的 WiX 工程文件。文档指出这些文件"相当晦涩",构成一种清单(manifest),其内容与动机参见引入它们的 PR。

当前仓库状态说明:以当前 deployment.yml 为准,windows Job 已改用windows-2022runner,并新增了azure/loginOIDC 认证步骤(allow-no-subscriptions: true);Azure Code Signing 客户端包名也已演进为Microsoft.Trusted.Signing.Client。这些属于文档快照之后的安全加固,机制与文档描述一致。

Job 五:release 聚合、签名仓库与发布

release Job 运行在ubuntu-latest上,needs: [linux, macos, windows],整体受if: inputs.release守卫。文档说明其小节并非严格按工作流顺序排列,而是按职责归类。

站点手册更新

release Job 会在cli.github.com站点仓库中创建一个包含 linux Job 上传的手册内容的 git 提交(但此时不推送,推送到后面包仓库就绪之后)。站点仓库使用短生命周期的 GitHub App 安装 token而非长期 PAT:Generate site deploy token步骤(仅 production)通过actions/create-github-app-token配合SITE_DEPLOY_APP_CLIENT_ID/SITE_DEPLOY_APP_PRIVATE_KEY铸造一个仅作用于github/cli.github.com仓库的 token,取代了旧的SITE_DEPLOY_PATsecret。所有站点相关步骤(checkout 站点、更新手册、createrepo、reprepro、Publish site)都由if: inputs.environment == 'production'守卫,非生产环境下站点既不被 checkout 也不被变更;即便在 production,推送仍由独立的DO_PUBLISH门控。

手册更新脚本还通过sed更新站点index.html中的assign version = "..."字段,并用git -C site diff --quiet --cached || git -C site commit保证"无实际变更则不产生空提交"。

GPG 密钥准备

为了提供安全的包安装方式,cli.github.com托管的 RPM 与 Debian 包仓库(支撑 docs/install_linux.md 中的"官方软件源"安装方式)中的所有制品都由 GPG 密钥签名。release Job 先把密钥载入gpg

# 非交互式导入公钥与私钥 base64 -d <<<"$GPG_PUBKEY" | gpg --import --no-tty --batch --yes base64 -d <<<"$GPG_KEY" | gpg --import --no-tty --batch --yes # 配置 gpg 允许预设口令,避免后续每次操作都提供口令 echo "allow-preset-passphrase" > ~/.gnupg/gpg-agent.conf # 通知 gpg 重载配置 gpg-connect-agent RELOADAGENT /bye # 将特定密钥(按 keygrip 引用)的口令存入内存 /usr/lib/gnupg2/gpg-preset-passphrase --preset "$GPG_KEYGRIP" <<<"$GPG_PASSPHRASE"

当前仓库状态说明:当前 deployment.yml 中已同时导入两代 GPG 密钥GPG_*GPG_*_2026两组 secret,口令以 base64 管道传入gpg-preset-passphrase),Run createrepo步骤的repomd.xml分离签名也显式指定了--default-key 2C6106201985B60E6C7AC87323F3D4EA75716059——这正是 script/rpmmacros 中%_gpg_name与 script/distributions 各SignWith行引用的那个 Key ID,对应文档开头提到的"更新过期 GPG 密钥"这一维护事件。

RPM 仓库:rpmsign + createrepo

linux Job 上传的.rpmrpmsign --addsign dist/*.rpm签名(前一步cp script/rpmmacros ~/.rpmmacros提供签名者配置)。createrepo 工具生成描述 Red Hat 仓库内容的repomd.xml元数据;制品与repomd.xml拷入站点仓库后,用gpg --yes --detach-sign --armor repodata/repomd.xml产出分离签名文件。文档附带一条 WARNING:createrepo目前仍在一个 Docker 容器(fedora:32镜像 +createrepo_c)中执行,这是出于"包管理原因"的历史选择(参见当年 PR),该理由可能已不再成立。当前 script/createrepo.sh 源码确认了这一实现原样保留:动态生成 Dockerfile、docker build后以卷挂载dist/执行。

Debian 仓库:reprepro

.deb文件按 Debian 发行版逐个迭代处理,使用reprepro生成 Debian 包仓库的目录与文件结构:

for release in $RELEASES; do for file in dist/*.deb; do reprepro --confdir="+b/script" includedeb "$release" "$file" done done

其中RELEASES列表当前包含stable oldstable testing sid unstable、Ubuntu 各版本代号(cosmicimpish等历史版本)以及kali-rolling。script/distributions 配置文件中每个 Codename 段落的SignWith行指明了reprepro应使用哪个 GPG Key ID 签名。文档在此留了两条值得记住的备注:其一,apt 安装文档已统一要求使用stable,不再向发行版列表新增条目,列表中的遗留发行版未来会移除;其二,遗留发行版的清理时间点尚无明确计划。

Attestations 溯源证明

release Job 通过actions/attest为每个发布制品创建 Attestations(溯源记录,证明制品由哪次构建产生)。该步骤的守卫是inputs.environment == 'production' && !inputs.dry_run——因为 Attestation 属于外部可见的溯源记录,只应为真实发布产生,干跑一律跳过。

GitHub Release 与校验和

所有制品生成并签名后,Create the release步骤先为dist/下全部gh_*文件生成 SHA-256 校验和(shasum -a 256 gh_* > checksums.txt,改名为gh_${TAG_NAME#v}_checksums.txt),供gh用户校验所下载制品,然后调用gh release create创建 Release、挂载全部归档/包/安装器与校验和文件。制品文件名附带人类可读的显示标签,机制如下:

Upload a release asset with a display label $ gh release create v1.2.3 '/path/to/asset.zip#My display label'

生成标签逻辑由 script/label-assets 实现:

label="$(basename "$asset")" label="${label%.*}" # 去掉扩展名 label="${label%.tar}" # 处理 .tar.gz 的双扩展 label="GitHub CLI $(tr '_' ' ' <<<"${label#gh_}")" case "$asset" in *.msi ) label="${label} installer" ;; *.deb ) label="${label} deb" ;; *.rpm ) label="${label} RPM" ;; esac printf '"%s#%s"\n' "$asset" "$label"

输出的"路径#标签"形式经xargs交给gh release create。至于当初为何使用可读标签,文档承认除了相关 PR 中"这是有意为之"的评论外并无更多说明。

Release 创建的关键参数:--title "GitHub CLI ${TAG_NAME#v}"--target "$GITHUB_SHA"(精确锚定到构建的提交)、--generate-notes,以及标签含连字符时追加的--prerelease

站点发布与 Homebrew

Publish site步骤在./site工作目录下提交包仓库结构并推送,推送触发站点仓库自身的部署工作流。推送仅当DO_PUBLISHtrue(production、非预发布标签、非 dry run)时发生,否则打印git log --oneline @{upstream}..git diff --name-status @{upstream}..供检查。文档还提到一个运维风险:由于长期托管大量大型二进制品,站点仓库偶尔会变得臃肿,处理方法见该仓库 README。

关于 Homebrew:历史上发布流程使用bump-homebrew-formula-actiongh的 homebrew-core formula 创建 PR(fork 仓库由个人账号持有,因为组织间的 PR 不受支持)。由于该方式依赖遗留 PAT 在两个仓库间开 PR、被评估为安全风险过高,现已改由Homebrew 的 autobump 机制自动跟进新版 formula,发布工作流不再直接参与。

发布行为与 dry run

dry_run输入(workflow_dispatch表单默认true的布尔值)是叠加在environment守卫之上的最终安全阀。当dry_runtrue时,工作流仍执行完整的 production 构建——包括代码签名、公证与包仓库生成——但跳过一切会变更外部可见状态的步骤:

步骤守卫条件
Attest release artifactsinputs.environment == 'production' && !inputs.dry_run
Create the release(gh release createDO_PUBLISH: inputs.environment == 'production' && !inputs.dry_run
Publish site(推送cli.github.comDO_PUBLISH: inputs.environment == 'production' && !contains(inputs.tag_name, '-') && !inputs.dry_run

Create the releasePublish site步骤通过DO_PUBLISH环境变量落地这一门控:当其为false时,release 命令前缀echogh release create只被打印而不执行,见工作流中guard="echo"; [ "$DO_PUBLISH" = "false" ] || guard=""的写法),站点推送则被替换为待提交内容的git log/git diff。这意味着 dry run 能把整条流水线端到端跑通,是验证签名与打包变更而不产生任何外部副作用的安全方式。

为了让 dry run 在 Actions UI 中一眼可辨,工作流的run-nameinputs.dry_runtrue时追加(dry run)后缀:

run-name: ${{ inputs.tag_name }} / ${{ inputs.environment }}${{ inputs.dry_run == true && ' (dry run)' || '' }}

重要提醒(文档原文的 IMPORTANT 框)dry_run的默认值取决于触发方式,两者恰好相反。workflow_dispatch表单默认true——手动触发除非明确取消勾选,否则是干跑;而./script/release默认dry_run=false,仅当带--dry-run标志时才转发dry_run=true。换言之:./script/release <tag-name>执行真实发布,./script/release --dry-run <tag-name>完整跑流程但不发布。

Deepest Dive:核心脚本机制

script/release 的工作机制

script/release 是维护者创建新发布的入口(对应 docs/releasing.md 的操作流程)。调用时它执行gh workflow run触发前述工作流;但工作流各 OS Job 又会以--local标志回调script/release,在 runner 机器上实际产出制品,并附加--platform指定平台。

该脚本最"反直觉"的行为是sed裁剪基础 .goreleaser.yml,只保留目标平台的配置段。当前源码清晰展示了这一点:

build_local() { local goreleaser_config=".goreleaser.yml" case "$platform" in linux ) sed '/#build:windows/,/^$/d; /#build:macos/,/^$/d' .goreleaser.yml >.goreleaser.generated.yml goreleaser_config=".goreleaser.generated.yml" ;; # macos / windows 同理,删除另两个平台的标记段 esac [ -z "$tag_name" ] || export GORELEASER_CURRENT_TAG="$tag_name" announce goreleaser release -f "$goreleaser_config" --clean --skip validate,publish,announce --release-notes="$(mktemp)" }

裁剪依赖.goreleaser.yml中各配置段落的#build:<platform>标记注释。例如 linux 平台下,只有标记为#build:linuxlinuxbuild 条目与nfpms段(.deb/.rpm包配置)会生效;archives段则通过ids: [linux]这类前置平台构建约束自动衔接。每个 build 条目声明支持的平台与架构,例如:

- id: linux #build:linux goos: [linux] goarch: ["386", arm, amd64, arm64]

从 .goreleaser.yml 还能看到发布构建的几个关键工程细节:

  • 版本内嵌:三个平台(macos/linux/windows)的构建都通过ldflags注入-X github.com/cli/cli/v2/internal/build.Version={{.Version}} -X github.com/cli/cli/v2/internal/build.Date=...,配合工作流中的"临时打标签"步骤保证二进制gh version输出正确;
  • 前置 hookbefore段在各平台生成 manpages 与 shell 补全(Windows 端用echo跳过本机生成,改为交叉产物打包);非 Windows 构建还会执行 script/gen-winres.ps1 基于 script/versioninfo.template.json 生成各架构的.syso文件,把 Windows 版本信息资源嵌入 exe;
  • 签名 hook:macos 与 windows 条目的posthook 分别调用./script/signpwsh .\script\sign.ps1,这正是文档所述"可执行文件签名发生在 GoReleaser hook 中"的落点;
  • nfpms 打包:linux 的deb/rpm包除二进制外还安装 man 页与各 shell 补全脚本(含 Debian/Ubuntu zsh 的vendor-completions双路径处理);
  • release 段draft: true注释写明"只在 Windows MSI 上传后才真正发布",prerelease: auto

此外,script/releasetrigger_deployment函数展示了对用户的友好设计:announce会先以$TMPDIR占位符打印命令(避免泄露临时目录路径),再实际执行;生产部署触发后会提示"Go to Slack to manually approve this production deployment",即production 环境部署还叠加了人工审批关卡(GitHub Environments 的审批机制)。脚本还支持--current(基于当前分支构建 staging 二进制、自动取最近 tag)等模式。

注意工作流中 GoReleaser 的版本是有意固定的:工作流注释明确说明固定版本不仅出于安全,还因为配套脚本依赖 GoReleaser 生成的特定文件命名。当前仓库固定为v2.13.1(文档快照时为~1.17.1)。

script/pkgmacos 的工作机制

script/pkgmacos(zsh 脚本,要求 macOS 12+ 与 Xcode Command Line Tools)被 macos Job 用于构建 Universal.pkg安装器,串联三个核心工具:

  1. lipo:把arm64amd64两个 Go 二进制合并为单一 Universal 二进制:

    lipo -create -output "${payload_local_bin}/gh" \ ./dist/macos_darwin_arm64_v8.0/bin/gh ./dist/macos_darwin_amd64_v1/bin/gh
  2. pkgbuild:构建"component package"——安装器实际要装的载荷。对gh而言该包标识为com.github.cli,内容为 Universal 二进制、zsh 补全(Catalina 起 macOS 默认 shell 为 zsh,故只含 zsh 补全)与 man 页:

    pkgbuild \ --root "$payload_root" \ --identifier "com.github.cli" \ --version "$tag_name" \ --install-location "/" \ "./dist/com.github.cli.pkg"
  3. productbuild:构建"product archive"——安装器真正使用的产品包。除 component package 外,product archive 还可携带定制化安装元素:gh打包了LICENSE文件,并使用仓库中的 build/macOS/distribution.xml 作为安装器 GUI 脚本。

distribution.xml是文档未展开的实操细节,值得补充:它声明了安装器标题GitHub CLI、以text/plain呈现的 LICENSE 协议页、arm64,x86_64双架构选项,并通过内嵌 JavaScript 的installCheck()函数在系统低于 macOS 12 时给出用户友好的致命错误提示("GitHub CLI requires macOS 12 or later."),配合<allowed-os-versions>min="12.0"双重把关。脚本最后通过trap cleanup EXIT删除中间产物com.github.cli.pkg,确保它不会被误上传为发布资产;最终产物命名为gh_${tag_name}_macOS_universal.pkg

pkgbuildproductbuild的区别(前者是"载荷组件包",后者是"面向安装器的产品归档")是理解 macOS 安装器体系的关键,文档建议读者自行查阅相关资料深入。

小结:这套发布系统的防御性设计

综合文档与源码,cli/cli 的发布流程在多个层面体现了纵深防御的思路,值得借鉴:

  • 入口约束:标签格式正则在最前端拦截错误发布,连字符语义贯穿 Release/站点/MSI 三处行为;
  • 环境分层staging/production环境守卫 +dry_run二次门控 + production 人工审批,三重机制保证"能完整演练但默认不产生外部副作用";
  • 密钥卫生:凭证一律经env:注入而非 shell 插值(防注入)、base64 编码存储、短期 token(GitHub App token / OIDC)替代长期 PAT、keychain 每 run 独立密码、用后rm证书文件;
  • 信任链闭环:二进制签名(codesign/Azure HSM)→ 公证(notarytool)→ 包仓库 GPG 签名(rpmsign/reprepro/repomd)→ SHA-256 校验和 → GitHub Attestations,从下载到溯源逐层可验证。

对需要维护或复刻类似发布流水线的开发者而言,本文引用的全部关键路径均已给出:工作流主体.github/workflows/deployment.yml、发布入口script/release、打包配置.goreleaser.yml、签名脚本script/signscript/sign.ps1、macOS 安装器script/pkgmacosbuild/macOS/distribution.xml、仓库元数据script/createrepo.shscript/distributionsscript/rpmmacros与资产标签script/label-assets。结合 docs/release-process-deep-dive.md 原文,即可对整条发布链路做到逐行可核对。

【免费下载链接】cliGitHub’s official command line tool项目地址: https://gitcode.com/GitHub_Trending/cli/cli

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 22:31:02

子序列问题

子序列问题最长递增自序列摆动序列最长递增子序列的个数最长数对链最长定差子序列最长的斐波那契子序列的长度最长等差数列等差数列划分||-子序列最长递增自序列 动态规划 状态表示&#xff1a; dp[i]&#xff1a;以i位置结尾&#xff0c;所有自序列中&#xff0c;最长长度 状态…

作者头像 李华
网站建设 2026/9/6 22:30:07

猫抓资源嗅探扩展:网页视频从嗅探到本地保存的 3 步操作

猫抓资源嗅探扩展&#xff1a;网页视频从嗅探到本地保存的 3 步操作 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 网页视频右键只有「复制链接」…

作者头像 李华
网站建设 2026/9/6 22:27:33

CSSBB备考攻略:如何高效使用Primer吃透六西格玛黑带考试

简介&#xff1a;一份由 Quality Council of Indiana 出版的六西格玛黑带经典入门资料&#xff08;CSSBB Primer&#xff09;&#xff0c;适合六西格玛黑带认证考生、质量管理专员与精益改善从业者阅读&#xff0c;用于系统掌握企业级六西格玛部署逻辑、领导力要求及统计改进工…

作者头像 李华
网站建设 2026/9/6 22:27:22

论文降重避坑指南:识别不可靠服务与建立高效修改流程

引言 毕业论文写作是每位学子必须跨越的重要关卡&#xff0c;而降重与文本改写往往是其中最具挑战性的环节之一。面对市场上琳琅满目的降重服务&#xff0c;如何辨别优劣、避免踩坑&#xff0c;成为许多同学关注的焦点。本文将系统梳理不可靠降重服务的典型特征&#xff0c;并…

作者头像 李华
网站建设 2026/9/6 22:27:11

WeKnora 本地部署全指南:5 分钟跑通离线 RAG 知识库

WeKnora 本地部署全指南&#xff1a;5 分钟跑通离线 RAG 知识库 【免费下载链接】WeKnora Open-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki. 项目地址: https://gitcode.com/Git…

作者头像 李华