From 62d0f786891af1d1324ea3ed039350b5efe2aef6 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:16:09 +1000 Subject: [PATCH 01/24] Bump the a8c CI toolkit plugin to 6.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5.3.1 was a placeholder carried over from the reference pipeline, marked `← infra: confirm current version` because nobody had checked. 6.3.0 is the current release; `buildkite-ci`'s own pipeline upload template is already on 5.6.0, so 5.3.1 was behind even the shared default. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- .buildkite/shared-pipeline-vars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index c32333620..b06d1b328 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -5,7 +5,7 @@ # a8c CI toolkit Buildkite plugin — provides install_gems, secret injection, and # GitHub status helpers on the macOS agent. -export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#5.3.1" # ← infra: confirm current version +export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#6.3.0" # Go toolchain the build scripts require. encoding/json/v2 is standard in Go 1.27. export GO_VERSION="1.27" # ← infra: match your agent provisioning From 4ba6e77bcd92367d3b4c8398268ef734c54ee786 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:18:03 +1000 Subject: [PATCH 02/24] Install Go on the agents instead of requiring it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No agent image provisions a Go toolchain — nothing under `src/agents/` in `Automattic/buildkite-ci` installs one, and there is no `mise` or `asdf` either — so `command -v go || exit 1` failed every build before it started. Each platform installs it the way that platform already does: the `mac` queue has Homebrew provisioned, the `windows` queue has Chocolatey, and the `default` queue runs Amazon Linux 2023, whose `golang` package is 1.25.12. None of those give us 1.27, and they do not have to. `GOTOOLCHAIN` defaults to `auto`, so any Go >= 1.21 downloads and uses the version the module asks for. The new `toolchain go1.27.0` directive is what pins it: the compiler is then identical on all three platforms no matter what brew, choco or dnf shipped, and the version lives in `go.mod` alone rather than being restated in `GO_VERSION`. `beeper/bridge-manager` reaches the same conclusion for the same reason — see `.buildkite/commands/sign-macos-binaries.sh` there. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- .buildkite/build-linux.sh | 6 +++++- .buildkite/build-macos.sh | 6 +++++- .buildkite/build-windows.ps1 | 11 +++++++++++ .buildkite/pipeline.yml | 2 +- .buildkite/shared-pipeline-vars | 3 --- go.mod | 2 ++ 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.buildkite/build-linux.sh b/.buildkite/build-linux.sh index 5e41bd731..03c8ddcd2 100755 --- a/.buildkite/build-linux.sh +++ b/.buildkite/build-linux.sh @@ -7,7 +7,11 @@ set -euo pipefail [ -f .buildkite/shared-pipeline-vars ] && . .buildkite/shared-pipeline-vars : "${BIN_BASE:=vip-next}" -command -v go >/dev/null 2>&1 || { echo "go not found; agent must provide Go ${GO_VERSION:-1.27}+" >&2; exit 1; } +# Any Go will do: go.mod's `toolchain` directive makes it fetch go1.27.0 itself. +if ! command -v go >/dev/null 2>&1; then + echo "--- :package: install go" + sudo dnf install -y golang +fi go version VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)" diff --git a/.buildkite/build-macos.sh b/.buildkite/build-macos.sh index 77d1bf060..dd2bb9b84 100755 --- a/.buildkite/build-macos.sh +++ b/.buildkite/build-macos.sh @@ -15,7 +15,11 @@ echo "--- :ruby: install gems" if command -v install_gems >/dev/null 2>&1; then install_gems; else bundle install; fi echo "--- :go: toolchain" -command -v go >/dev/null 2>&1 || { echo "go not found; agent must provide Go ${GO_VERSION:-1.27}+" >&2; exit 1; } +# Any Go will do: go.mod's `toolchain` directive makes it fetch go1.27.0 itself. +if ! command -v go >/dev/null 2>&1; then + echo "--- :package: install go" + brew install go +fi go version VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)" diff --git a/.buildkite/build-windows.ps1 b/.buildkite/build-windows.ps1 index c8d5865e7..7e8e318c1 100644 --- a/.buildkite/build-windows.ps1 +++ b/.buildkite/build-windows.ps1 @@ -15,6 +15,17 @@ $commit = Get-GitOr @('rev-parse','--short','HEAD') 'unknown' New-Item -ItemType Directory -Force -Path dist | Out-Null $out = "dist/$binBase-windows-amd64.exe" +# Any Go will do: go.mod's `toolchain` directive makes it fetch go1.27.0 itself. +if (-not (Get-Command go -ErrorAction SilentlyContinue)) { + Write-Host "--- :package: install go" + choco install golang -y --no-progress + if ($LASTEXITCODE -ne 0) { throw 'choco install golang failed' } + # choco updates the machine PATH, not this process's. + $env:PATH = "$env:PATH;$env:ProgramFiles\Go\bin" +} +go version +if ($LASTEXITCODE -ne 0) { throw 'go not usable after install' } + Write-Host "--- :go: build windows/amd64" $env:CGO_ENABLED = '0'; $env:GOOS = 'windows'; $env:GOARCH = 'amd64' $ldflags = "-s -w -X github.com/Automattic/vip/internal/version.Version=$version -X github.com/Automattic/vip/internal/version.Commit=$commit" diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 65cd37b1c..a00d2ef75 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,6 +1,6 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json --- -# Shared vars (CI_TOOLKIT_PLUGIN, GO_VERSION, BIN_BASE, signing identities) come +# Shared vars (CI_TOOLKIT_PLUGIN, BIN_BASE, signing identities) come # from .buildkite/shared-pipeline-vars, which our setup source's before # `buildkite-agent pipeline upload` interpolates this file. # diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index b06d1b328..a632e932a 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -7,9 +7,6 @@ # GitHub status helpers on the macOS agent. export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#6.3.0" -# Go toolchain the build scripts require. encoding/json/v2 is standard in Go 1.27. -export GO_VERSION="1.27" # ← infra: match your agent provisioning - # Binary base name + macOS signing identities (team PZYM8XX95Q = Automattic, Inc.). export BIN_BASE="vip-next" export MACOS_TEAM_ID="PZYM8XX95Q" diff --git a/go.mod b/go.mod index f10b4fa99..d18ddb0c8 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/Automattic/vip go 1.27 +toolchain go1.27.0 + require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Khan/genqlient v0.8.1 From 4f8f933914cdcc27ff85f8cf0c965b903a3a025f Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:20:34 +1000 Subject: [PATCH 03/24] Fix the dropped arch suffix in the build scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `local goarch="$1" out="…-${goarch}"` expands `$goarch` before the same `local` has assigned it, so every artifact was written to `…-linux-` and `…-darwin-` with no arch (verified under both bash and dash). Both arches therefore wrote to one path, the second clobbering the first, and the smoke test then looked for a `-amd64`/`-arm64` file that had never existed — so both scripts died on their first run regardless of signing. Nothing had caught it because these scripts have never executed: no pipeline was registered, and no workflow lints `.buildkite/`. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- .buildkite/build-linux.sh | 3 ++- .buildkite/build-macos.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.buildkite/build-linux.sh b/.buildkite/build-linux.sh index 03c8ddcd2..8a9dc0c99 100755 --- a/.buildkite/build-linux.sh +++ b/.buildkite/build-linux.sh @@ -18,7 +18,8 @@ VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)" COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" build() { - local goarch="$1" out="dist/${BIN_BASE}-linux-${goarch}" + local goarch="$1" + local out="dist/${BIN_BASE}-linux-${goarch}" echo "--- :go: build linux/${goarch}" CGO_ENABLED=0 GOOS=linux GOARCH="${goarch}" \ go build -buildvcs=false -trimpath \ diff --git a/.buildkite/build-macos.sh b/.buildkite/build-macos.sh index dd2bb9b84..764790950 100755 --- a/.buildkite/build-macos.sh +++ b/.buildkite/build-macos.sh @@ -26,7 +26,8 @@ VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)" COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" build() { - local goarch="$1" out="dist/${BIN_BASE}-darwin-${goarch}" + local goarch="$1" + local out="dist/${BIN_BASE}-darwin-${goarch}" echo "--- :go: build darwin/${goarch}" CGO_ENABLED=0 GOOS=darwin GOARCH="${goarch}" \ go build -buildvcs=false -trimpath \ From d2fef62a8dd723b1278f8102a82e71a0281b1921 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:22:21 +1000 Subject: [PATCH 04/24] Lint shell scripts on CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The arch-suffix bug fixed in the previous commit was a plain ShellCheck warning that sat in trunk because nothing reads these files: no Buildkite pipeline is registered, and `ci-go.yml`'s `paths:` filter excludes `.buildkite/`. Deliberately no `paths:` filter here — a paths filter is what let the directory go unlinted in the first place, and the repo's other small workflows run on every PR anyway. `-S warning` suppresses the `info` level, which is otherwise all SC1091 for the runtime-sourced `shared-pipeline-vars`. Every tracked script passes at that level today, so this starts green with nothing grandfathered. `build-windows.ps1` stays uncovered; PSScriptAnalyzer is a separate job. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- .github/workflows/shellcheck.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/shellcheck.yml diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 000000000..f5172c776 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,26 @@ +name: ShellCheck + +on: + pull_request: + push: + branches: + - develop + - trunk + workflow_dispatch: + +permissions: + contents: read + +jobs: + shellcheck: + name: Lint shell scripts + runs-on: ubuntu-latest + steps: + - name: Check out the source code + uses: actions/checkout@v7 + + # `-S warning` drops the `info` level, which is otherwise dominated by + # SC1091 for the `shared-pipeline-vars` file the build scripts source at + # runtime. ShellCheck ships preinstalled on the runner image. + - name: Run ShellCheck + run: git ls-files '*.sh' | xargs --no-run-if-empty shellcheck -S warning From 01d84acc92a43578cf9a0c0388359c25ddc0ad66 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:26:30 +1000 Subject: [PATCH 05/24] Bump the release toolkit to 15.0 15.0.0 is the current release, and its `EnvManager` is what replaces the vendored copy in the next commit. Neither of its breaking changes affects us: `ios_build_preflight` and `android_build_preflight` are not used here, and `EnvManager` now layering the `.env` into the process `ENV` is what our vendored copy already did via `Dotenv.load`. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 58cdbf207..01f80b08c 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' gem 'fastlane', '~> 2.237' -gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.10' +gem 'fastlane-plugin-wpmreleasetoolkit', '~> 15.0' # Avoids "certificate verify failed (unable to get certificate CRL)" on some hosts. # See https://github.com/ruby/openssl/issues/949 gem 'openssl', '~> 4.0' From df451308428d25a11c745f054e4b8e39657d4dec Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:27:10 +1000 Subject: [PATCH 06/24] Use the release toolkit's EnvManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `fastlane/lib/env_manager.rb` was a copy of the one in `Automattic/download`, carrying a note to replace it with the canonical version if one existed. It does — and the plugin providing it was already a dependency, so the copy sat unused next to the real thing. The class-level API (`set_up`, `require_env_vars!`) is unchanged in 15.0.0, so this is a drop-in: `configure_code_signing` still stops at the same guard with the same message. `beeper/bridge-manager` resolves it the same way. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- fastlane/Fastfile | 4 ++- fastlane/lib/env_manager.rb | 62 ------------------------------------- 2 files changed, 3 insertions(+), 63 deletions(-) delete mode 100644 fastlane/lib/env_manager.rb diff --git a/fastlane/Fastfile b/fastlane/Fastfile index dd0b6a2ea..be6e5d855 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -18,7 +18,9 @@ ASC_API_KEY_ENV_VARS = %w[ APP_STORE_CONNECT_API_KEY_KEY ].freeze -require_relative 'lib/env_manager' +require 'fastlane/plugin/wpmreleasetoolkit' + +EnvManager = Fastlane::Wpmreleasetoolkit::EnvManager APP_IDENTIFIER = 'com.automattic.vip-cli' # ← infra: reuse team Dev ID cert vs a new match entry TEAM_ID = 'PZYM8XX95Q' diff --git a/fastlane/lib/env_manager.rb b/fastlane/lib/env_manager.rb deleted file mode 100644 index c48d1bd41..000000000 --- a/fastlane/lib/env_manager.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -# Copied from Automattic/download (fastlane/lib/env_manager.rb). -# ← infra: replace with your canonical release-toolkit version if one exists. - -require 'dotenv' -require 'fastlane' - -# Manages loading of environment variables from a .env and accessing them in a user-friendly way. -class EnvManager - @env_path = nil - @env_example_path = nil - @print_error_lambda = nil - - def self.set_up( - env_file_name:, - env_file_folder: File.join(Dir.home, '.a8c-apps'), - example_env_file_path: 'fastlane/example.env', - print_error_lambda: ->(message) { FastlaneCore::UI.user_error!(message) } - ) - @env_path = File.join(env_file_folder, env_file_name) - @env_example_path = example_env_file_path - @print_error_lambda = print_error_lambda - - Dotenv.load(@env_path) - end - - def self.get_required_env!(key) - unless ENV.key?(key) - message = "Environment variable '#{key}' is not set." - - if running_on_ci? - @print_error_lambda.call(message) - elsif File.exist?(@env_path) - @print_error_lambda.call("#{message} Consider adding it to #{@env_path}.") - else - env_file_dir = File.dirname(@env_path) - env_file_name = File.basename(@env_path) - - @print_error_lambda.call <<~MSG - #{env_file_name} not found in #{env_file_dir} while looking for env var #{key}. - - Please copy #{@env_example_path} to #{@env_path} and fill in the value for #{key}. - - mkdir -p #{env_file_dir} && cp #{@env_example_path} #{@env_path} - MSG - end - end - - value = ENV.fetch(key) - FastlaneCore::UI.user_error!("Env var for key #{key} is set but empty. Please set a value for #{key}.") if value.to_s.empty? - value - end - - def self.require_env_vars!(*keys) - keys.each { |key| get_required_env!(key) } - end - - def self.running_on_ci? - ENV['CI'] == 'true' - end -end From f3c09082952419cc30906297e2950f9aef0db91e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:30:37 +1000 Subject: [PATCH 07/24] Check the credentials the lane actually uses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `configure_code_signing` demanded the App Store Connect key on every run and never checked the `match` credentials at all — exactly backwards for its default path. A readonly fetch only reads and decrypts the S3 bucket, so it needs `MATCH_S3_*` and `MATCH_PASSWORD` and nothing from App Store Connect. The ASC key is only needed to create or renew a certificate, which is what `readonly: false` means. Missing match credentials used to surface as a failure from inside `match`; they now fail at the guard, naming the variable. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- fastlane/Fastfile | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index be6e5d855..f5d71cb7e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -11,6 +11,13 @@ CODE_SIGNING_STORAGE_OPTIONS = { s3_region: 'us-east-2' }.freeze +# `match` reads these to pull the certs out of the S3 bucket and decrypt them. +CODE_SIGNING_ENV_VARS = %w[ + MATCH_S3_ACCESS_KEY + MATCH_S3_SECRET_ACCESS_KEY + MATCH_PASSWORD +].freeze + # app_store_connect_api_key reads these to build the ASC key. ASC_API_KEY_ENV_VARS = %w[ APP_STORE_CONNECT_API_KEY_KEY_ID @@ -34,20 +41,23 @@ end # Places the Developer ID Application cert (and — see the infra note — the Installer # cert) into the keychain so `codesign` / `productsign` can find them. lane :configure_code_signing do |readonly: true| - EnvManager.require_env_vars!(*ASC_API_KEY_ENV_VARS) - api_key = app_store_connect_api_key + EnvManager.require_env_vars!(*CODE_SIGNING_ENV_VARS) + # A readonly fetch only reads the bucket; creating or renewing a cert is the + # only thing that talks to App Store Connect. + EnvManager.require_env_vars!(*ASC_API_KEY_ENV_VARS) unless readonly # Developer ID *Application* cert — signs the binaries (codesign). sync_code_signing( app_identifier: APP_IDENTIFIER, platform: 'macos', type: 'developer_id', - api_key: api_key, team_id: TEAM_ID, readonly: readonly, + api_key: readonly ? nil : app_store_connect_api_key, + team_id: TEAM_ID, readonly: readonly, **CODE_SIGNING_STORAGE_OPTIONS ) # ← infra: ALSO provision the Developer ID *Installer* cert (for productsign on the # .pkg). The reference doesn't need it. Depending on your match setup this is likely: # sync_code_signing(app_identifier: APP_IDENTIFIER, platform: 'macos', type: 'developer_id', - # additional_cert_types: ['developer_id_installer'], api_key: api_key, + # additional_cert_types: ['developer_id_installer'], # team_id: TEAM_ID, readonly: readonly, **CODE_SIGNING_STORAGE_OPTIONS) # Confirm the exact option/flow, then enable it. end From 4e8c32aa23a0841aa0696ac0049657addf2c57c7 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:35:38 +1000 Subject: [PATCH 08/24] Ask match for the certificate, not a profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passing an app identifier made `match` look for a provisioning profile for `com.automattic.vip-cli`, and fail with "No matching provisioning profiles found and cannot create a new one because you enabled `readonly`". Nothing here needs a profile. `codesign` needs the Developer ID Application certificate, and Developer ID certs are not app-scoped — which also settles the open question of whether to reuse the team cert or make a new match entry for this app: there is no per-app entry to make. With this, `configure_code_signing` completes: it decrypts the bucket and installs the certificate. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- fastlane/Fastfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f5d71cb7e..fe23dd08f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -29,7 +29,6 @@ require 'fastlane/plugin/wpmreleasetoolkit' EnvManager = Fastlane::Wpmreleasetoolkit::EnvManager -APP_IDENTIFIER = 'com.automattic.vip-cli' # ← infra: reuse team Dev ID cert vs a new match entry TEAM_ID = 'PZYM8XX95Q' before_all do @@ -47,8 +46,10 @@ lane :configure_code_signing do |readonly: true| EnvManager.require_env_vars!(*ASC_API_KEY_ENV_VARS) unless readonly # Developer ID *Application* cert — signs the binaries (codesign). + # No app identifier: a Developer ID cert is not app-scoped, and naming one + # makes `match` look for a provisioning profile that does not exist. sync_code_signing( - app_identifier: APP_IDENTIFIER, platform: 'macos', type: 'developer_id', + app_identifier: [], platform: 'macos', type: 'developer_id', api_key: readonly ? nil : app_store_connect_api_key, team_id: TEAM_ID, readonly: readonly, **CODE_SIGNING_STORAGE_OPTIONS @@ -56,7 +57,7 @@ lane :configure_code_signing do |readonly: true| # ← infra: ALSO provision the Developer ID *Installer* cert (for productsign on the # .pkg). The reference doesn't need it. Depending on your match setup this is likely: - # sync_code_signing(app_identifier: APP_IDENTIFIER, platform: 'macos', type: 'developer_id', + # sync_code_signing(app_identifier: [], platform: 'macos', type: 'developer_id', # additional_cert_types: ['developer_id_installer'], # team_id: TEAM_ID, readonly: readonly, **CODE_SIGNING_STORAGE_OPTIONS) # Confirm the exact option/flow, then enable it. From ac3947a440d4b629a64e7acff8c4132838441637 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:35:59 +1000 Subject: [PATCH 09/24] Ignore the files fastlane regenerates Running any lane writes `fastlane/README.md` and `fastlane/report.xml` into the working tree, so both were one `git add .` away from being committed and then churning on every subsequent run. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 3d4aad8a2..af93ff2fe 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,8 @@ go.work.sum # third_party/go-search-replace/MANIFEST (which IS tracked). Binaries stay out # of git so the repo does not carry ~19 MB of executables per upgrade. third_party/go-search-replace/*/ + +# Regenerated by fastlane on every run; `bundle exec fastlane lanes` prints the +# same lane summary as the README. +fastlane/README.md +fastlane/report.xml From 8dc59e28d6618a26500f42e959bd277acf796b30 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:38:24 +1000 Subject: [PATCH 10/24] Remove the unused PROJECT_ROOT_FOLDER constant It is a convention carried over from the mobile Fastfiles, which use it to build paths. Nothing here does: no reference in this repo, the release toolkit, or any installed gem. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- fastlane/Fastfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index fe23dd08f..b946a2ba3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -2,8 +2,6 @@ UI.user_error!('Please run fastlane via `bundle exec`') unless FastlaneCore::Helper.bundler? -PROJECT_ROOT_FOLDER = File.dirname(File.expand_path(__dir__)) - # fastlane match cert storage (S3) — the shared a8c bucket. # ← infra: confirm CODE_SIGNING_STORAGE_OPTIONS = { storage_mode: 's3', From 1165b41c77c94f475061b62667b2fe00c6cf0c0b Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 19:41:27 +1000 Subject: [PATCH 11/24] Track Gemfile.lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without it every agent resolved fastlane, the release toolkit and their transitive dependencies afresh, so the gems that signed a release were neither pinned nor reviewable — and a bad upstream release would have landed straight in a signing run. Checked against the macOS agents before committing, since `install_gems` derives the Bundler version from this file: it will install Bundler 4.0.19, which needs Ruby >= 3.2.0, and the agents' rbenv default is 3.2.2. No gem in the lock requires a newer Ruby. That is no headroom, though — the release toolkit's floor is exactly 3.2.2, so a future bump to it needs the agent image moved first. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- Gemfile.lock | 478 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..16bfb23b3 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,478 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + abbrev (0.1.2) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1282.0) + aws-sdk-core (3.254.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.130.0) + aws-sdk-core (~> 3, >= 3.254.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.229.0) + aws-sdk-core (~> 3, >= 3.254.1) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.1.2) + buildkit (1.6.1) + sawyer (>= 0.6) + chroma (0.2.0) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + csv (3.3.6) + declarative (0.0.20) + diffy (3.4.4) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20260829) + dotenv (2.8.1) + emoji_regex (3.2.3) + erb (6.0.7) + erubi (1.13.1) + excon (1.7.1) + logger + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-cookie_jar (0.0.8) + faraday (>= 0.8.0) + http-cookie (>= 1.0.0) + faraday-follow_redirects (0.5.0) + faraday (>= 1, < 3) + faraday-multipart (1.2.0) + multipart-post (~> 2.0) + faraday-net_http (3.4.4) + net-http (~> 0.5) + faraday-retry (2.4.0) + faraday (~> 2.0) + fastimage (2.4.1) + fastlane (2.238.0) + CFPropertyList (>= 2.3, < 5.0.0) + abbrev (~> 0.1) + addressable (>= 2.9.0, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.197) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2) + benchmark (>= 0.1.0) + bundler (>= 2.4.0, < 5.0.0) + colored (~> 1.2) + commander (~> 4.6) + csv (~> 3.3) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 2.0.0) + faraday (~> 2.7) + faraday-cookie_jar (~> 0.0.8) + faraday-follow_redirects (~> 0.3) + faraday-multipart (~> 1.0) + faraday-retry (~> 2.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.1.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.3.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + irb (>= 1.8) + json (< 3.0.0) + jwt (>= 2.10.3, < 4) + logger (>= 1.6, < 2.0) + mini_magick (>= 4.9.4, < 5.0.0) + multi_json (~> 1.12) + multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3) + naturally (~> 2.2) + nkf (~> 0.2) + optparse (>= 0.1.1, < 1.0.0) + ostruct (>= 0.1.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 4) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-wpmreleasetoolkit (15.0.0) + buildkit (~> 1.5) + chroma (= 0.2.0) + diffy (~> 3.3) + dotenv (~> 2.8) + fastlane (~> 2.237) + gettext (~> 3.5) + git (~> 1.3) + google-cloud-storage (~> 1.31) + java-properties (~> 0.3.0) + nokogiri (~> 1.19, >= 1.19.4) + octokit (~> 6.1) + parallel (~> 1.14) + plist (~> 3.1) + progress_bar (~> 1.3) + rake (>= 12.3, < 14.0) + rake-compiler (~> 1.0) + xcodeproj (~> 1.22) + fastlane-sirp (1.1.0) + fiddle (1.1.8) + forwardable (1.4.0) + gettext (3.5.2) + erubi + locale (>= 2.0.5) + prime + racc + text (>= 1.3.0) + gh_inspector (1.1.3) + git (1.19.1) + addressable (~> 2.8) + rchardet (~> 1.8) + google-apis-androidpublisher_v3 (0.107.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-core (1.2.5) + addressable (~> 2.9) + faraday (~> 2.13) + faraday-follow_redirects (~> 0.3) + googleauth (~> 1.14) + mini_mime (~> 1.1) + multi_json (~> 1.11) + representable (~> 3.0) + retriable (>= 3.1, < 5.0) + google-apis-iamcredentials_v1 (0.28.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-playcustomapp_v1 (0.18.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-storage_v1 (0.66.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-core (1.9.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (2.2.2) + base64 (~> 0.2) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.7.0) + google-cloud-storage (1.62.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-core (>= 0.18, < 2) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (>= 0.42) + google-cloud-core (~> 1.6) + googleauth (~> 1.9) + mini_mime (~> 1.0) + google-logging-utils (0.2.0) + googleauth (1.17.4) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + jwt (>= 1.4, < 4.0) + os (>= 0.9, < 2.0) + pstore (~> 0.1) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + io-console (0.9.2) + irb (1.18.0) + pp (>= 0.6.0) + prism (>= 1.3.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + java-properties (0.3.0) + jmespath (1.6.2) + json (2.21.2) + jwt (3.2.0) + base64 + locale (2.1.5) + fiddle + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.21.1) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + net-http (0.9.1) + uri (>= 0.11.1) + nkf (0.3.0) + nokogiri (1.19.4-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.4-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.4-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-musl) + racc (~> 1.4) + octokit (6.1.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + openssl (4.0.2) + options (2.3.2) + optparse (0.8.1) + os (1.1.4) + ostruct (0.6.3) + parallel (1.28.0) + plist (3.7.2) + pp (0.6.4) + prettyprint + prettyprint (0.2.0) + prime (0.1.4) + forwardable + singleton + prism (1.9.0) + progress_bar (1.3.4) + highline (>= 1.6) + options (~> 2.3.0) + pstore (0.2.1) + public_suffix (7.0.5) + racc (1.8.1) + rake (13.4.2) + rake-compiler (1.3.1) + rake + rbs (4.2.0) + logger + prism (>= 1.6.0) + tsort + rchardet (1.10.2) + rdoc (8.0.0) + erb + prism (>= 1.6.0) + rbs (>= 4.0.0) + tsort + reline (0.7.0) + io-console (~> 0.5) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (4.2.0) + rexml (3.4.4) + rouge (3.28.0) + rubyzip (2.4.1) + sawyer (0.9.3) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + security (0.1.5) + signet (0.22.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + simctl (1.6.10) + CFPropertyList + naturally + singleton (0.3.0) + terminal-notifier (2.0.0) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) + text (1.3.1) + trailblazer-option (0.1.2) + tsort (0.2.0) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + word_wrap (1.0.0) + xcodeproj (1.28.1) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + base64 + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + nkf + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + fastlane (~> 2.237) + fastlane-plugin-wpmreleasetoolkit (~> 15.0) + openssl (~> 4.0) + +CHECKSUMS + CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261 + abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242 + addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af + artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263 + atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f + aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b + aws-partitions (1.1282.0) sha256=d6c7c4ad2e4f8cd4ca56445bfbb3958fec0deecd959c1e2bcd3a83c9aa308c18 + aws-sdk-core (3.254.1) sha256=518089e32134c3478cd4ec63d07fb966546a45e9e4cbf5f80d2cf16d5699d29b + aws-sdk-kms (1.130.0) sha256=a2e83662ca31b77a2a19c9aa2f40a98165a67270c18c718fe1c70d0cbd7cd749 + aws-sdk-s3 (1.229.0) sha256=7dd11a111875b3505d2ec0a0a383a6aab6c1badb3d4e94e7fbb958a24451f596 + aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 + babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99 + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c + bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd + buildkit (1.6.1) sha256=e0e51c0ce3334654c356bdef3410cba5c9b36b336d0d6542f3ecfb477c5fa97c + bundler (4.0.19) sha256=b48056e4c77fb3853cc47775b5447ede44aaa4f53c32f168014ab4fe86587d47 + chroma (0.2.0) sha256=64bdcd36a4765fbcd45adc64960cc153101300b4918f90ffdd89f4e2eb954b54 + claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e + colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c + colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a + commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9 + csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456 + declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 + diffy (3.4.4) sha256=79384ab5ca82d0e115b2771f0961e27c164c456074bd2ec46b637ebf7b6e47e3 + digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07 + domain_name (0.6.20260829) sha256=a9c1aa678423b9db3a4193b300fd76b9c21cb2a5f60d7dcf0a832b82d13764f0 + dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 + emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b + erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92 + erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9 + excon (1.7.1) sha256=dd2d870eece1b5ce4e4f9efd938e67a69ccd8c003c3825b638b937e1082eaac2 + faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278 + faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb + faraday-follow_redirects (0.5.0) sha256=5cde93c894b30943a5d2b93c2fe9284216a6b756f7af406a1e55f211d97d10ad + faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757 + faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588 + faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe + fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20 + fastlane (2.238.0) sha256=78e9252df2224c7e427012638e41051edfa29b133a40fda883fd2f1c13a77b98 + fastlane-plugin-wpmreleasetoolkit (15.0.0) sha256=6ffd06713e7e98c0d4a65df404b6cf8ffe66258b0976900899d888e57909aa67 + fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847 + fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee + forwardable (1.4.0) sha256=f1cd40cc9812937980e1c76f1aa053660990a7c9b6a98fc37d945468afcce838 + gettext (3.5.2) sha256=ada02c59aa7e9f56bd2522faedaed16421dd2f3ddb5fe28628c0be5abcbf3c74 + gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 + git (1.19.1) sha256=b0a422d9f6517353c48a330d6114de4db9e0c82dbe7202964a1d9f1fbc827d70 + google-apis-androidpublisher_v3 (0.107.0) sha256=b8207eecc89e6a9f7ea11aa88a07bccdcae5ca90121330079db4ef4696a2f3ff + google-apis-core (1.2.5) sha256=e21562b5627a0fc6a0c3165e429c3afed0f6a628eaa514e83f7204dda1adff69 + google-apis-iamcredentials_v1 (0.28.0) sha256=0a92ffe6cc39c569554af2a77a25dfc61519ed8bbb64ab04cffdd352dc5ef106 + google-apis-playcustomapp_v1 (0.18.0) sha256=44b277b9dee4a59ac5e9d98be1485edc5e382d2f9d73c79ae8908a455786a254 + google-apis-storage_v1 (0.66.0) sha256=fdf6d65d3fc77e7046b3494333a818ece9e2afd763bb161e1a7a9e70cf198351 + google-cloud-core (1.9.0) sha256=ab55409f51488e8deefb6edcc1ce4771dfb5da2fe7b3bc075709a030c2b682a4 + google-cloud-env (2.2.2) sha256=94bed40e05a67e9468ce1cb38389fba9a90aa8fc62fc9e173204c1dca59e21e7 + google-cloud-errors (1.7.0) sha256=6e682f42d89aae08689f36f28495de629d756da076bafff0003bac7f8042ebb3 + google-cloud-storage (1.62.0) sha256=e2c3c08bf8fd40d50be92304084942203314d4fc0ee52028e99f9359c3ad1330 + google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b + googleauth (1.17.4) sha256=acc2cac2a6011048f149c922cbc6c9675719e165aa5000c7fa3876c480fe9ae3 + highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479 + http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6 + io-console (0.9.2) sha256=efa74f891dd03c0939a931dfc6e74c2813d904763d456ea9762b0525e748db08 + irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 + java-properties (0.3.0) sha256=0a9fdda90c25ba9ba4de0e242d954a5688629652b592aab66ed54e2b16b93093 + jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 + json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a + jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7 + locale (2.1.5) sha256=1c6803e8aa6bdb2c29e91945d095050601bf6d58474993575adf6f3b89b32ef4 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9 + mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + multi_json (1.21.1) sha256=e6126a31808e3b4d19f483c775ceac34df190dffa62adfb63a165ee14ba68080 + multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8 + mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 + nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723 + naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01 + net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996 + nkf (0.3.0) sha256=357a8dbeba38b727b75930f665146546076a394a1c243faf634ff176e3588895 + nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f + nokogiri (1.19.4-aarch64-linux-musl) sha256=35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af + nokogiri (1.19.4-arm-linux-gnu) sha256=a301313e38bb065d68239e79734bcd6f56fb6efaacebde29e9abf2a4735340ca + nokogiri (1.19.4-arm-linux-musl) sha256=588923c101bcfa78869734d247d25b598674323e7f22474fc468f6e5647311eb + nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527 + nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551 + nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a + nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b + octokit (6.1.1) sha256=920e4a9d820205f70738f58de6a7e6ef0e2f25b27db954b5806a63105207b0bf + openssl (4.0.2) sha256=1037ad2868ae58df9ad917891c0c0f9815a1172f6846d4bcdd508e4c2ee747c2 + options (2.3.2) sha256=32413a4b9e363234eed2eecfb2a1a9deb32810f72c54820a37a62f65b905c5e8 + optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a + os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f + ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 + parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970 + plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42 + pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570 + prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 + prime (0.1.4) sha256=4d755ebf7c2994a6f3a3fee0d072063be3fff2d4042ebff6cd5eebd4747a225e + prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 + progress_bar (1.3.4) sha256=adb10e040275e08eadfbe405749584e4b01fd15e8e692fdcb4b1969e9c071c8c + pstore (0.2.1) sha256=03904d0f2c66579e96d1e6704cdabc0c88df7ea8ed8782d9f3569f6f6c702c1a + public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 + racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f + rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 + rake-compiler (1.3.1) sha256=6b351612b6e2d73ddd5563ee799bb58685176e05363db6758504bd11573d670a + rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674 + rchardet (1.10.2) sha256=e041cb195f464dc10e49ab130f78c8b5956cd9a4f4f6df84e0c183b87c135f33 + rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469 + reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d + representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace + retriable (4.2.0) sha256=90c3b257472a1c02f2a3dfa64dd35a420f7a624cef1a5981e20fdac5730f8cdc + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20 + rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 + sawyer (0.9.3) sha256=0d0f19298408047037638639fe62f4794483fb04320269169bd41af2bdcf5e41 + security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7 + signet (0.22.0) sha256=b76d495ccb07ad35dbc89f3e920665a9d8ed717141955034005d7843dcfe4780 + simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b + singleton (0.3.0) sha256=83ea1bca5f4aa34d00305ab842a7862ea5a8a11c73d362cb52379d94e9615778 + terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea + terminal-table (4.0.0) sha256=f504793203f8251b2ea7c7068333053f0beeea26093ec9962e62ea79f94301d2 + text (1.3.1) sha256=2fbbbc82c1ce79c4195b13018a87cbb00d762bda39241bb3cdc32792759dd3f4 + trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 + tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f + tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48 + tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50 + tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542 + uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc + unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 + unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f + uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 + word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7 + xcodeproj (1.28.1) sha256=6f12670f00739d9817ca27ac89d6ef01cc86050e22a0bc08a3131487e5b5cddc + xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892 + xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93 + +BUNDLED WITH + 4.0.19 From 398e15f86210dc38ab5adec6623d9f0ea9788b5f Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:02:39 +1000 Subject: [PATCH 12/24] Pin the macOS VM image via .xcode-version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macOS step could never have started: the `mac` queue's command hook exits with "You must specify an IMAGE_ID to use" when the variable is unset, and it was unset on the assumption that a Go build needs no Xcode image. It does — `codesign`, `pkgbuild`, `productsign` and `xcrun notarytool` all come from Xcode, whatever compiled the binary. `.xcode-version` rather than a literal, matching wpios, wcios, pcios and `beeper/bridge-manager`, so the pin is visible where people look for it. 26.6 is the newest image. `bridge-manager` pins 26.3 instead because it forces the Ruby platform and nokogiri will not build from source on 26.5's clang; that does not apply here — `Gemfile.lock` resolves the precompiled `nokogiri-1.19.4-arm64-darwin` and there is no `.bundle/config` overriding it. This also corrects 1165b41c, which said the agents default to Ruby 3.2.2 with no headroom. That was read off the 26.5 manifest. 26.6 defaults to 3.4.9 and also carries 3.3.11 and 4.0.5, so the locked gems have room above the release toolkit's 3.2.2 floor. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- .buildkite/pipeline.yml | 2 ++ .buildkite/shared-pipeline-vars | 8 +++++--- .xcode-version | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .xcode-version diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index a00d2ef75..4a85f92f2 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -17,6 +17,8 @@ steps: - $CI_TOOLKIT_PLUGIN agents: queue: mac + env: + IMAGE_ID: $IMAGE_ID notify: - github_commit_status: context: 'Build & sign (macOS)' diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index a632e932a..44236b41f 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -13,6 +13,8 @@ export MACOS_TEAM_ID="PZYM8XX95Q" export MACOS_SIGN_IDENTITY="Developer ID Application: Automattic, Inc. (PZYM8XX95Q)" export MACOS_INSTALLER_IDENTITY="Developer ID Installer: Automattic, Inc. (PZYM8XX95Q)" # ← infra: confirm this cert exists -# NOTE: unlike the reference we set no Xcode IMAGE_ID / .xcode-version — this is a -# plain Go build, no fyne/Xcode. If your mac queue requires a specific VM image, -# add: export IMAGE_ID="" # ← infra +# The macOS VM to run on. The `mac` queue's command hook aborts when IMAGE_ID is +# unset, and the signing step needs `codesign`, `pkgbuild`, `productsign` and +# `xcrun notarytool` regardless of Go being the compiler. +XCODE_VERSION=$(grep -Ev '^[[:space:]]*(#|$)' .xcode-version | head -n1 | sed -E 's/^[[:space:]]*//; s/^~> ?//; s/[[:space:]]*$//') +export IMAGE_ID="xcode-$XCODE_VERSION" diff --git a/.xcode-version b/.xcode-version new file mode 100644 index 000000000..7363977a2 --- /dev/null +++ b/.xcode-version @@ -0,0 +1 @@ +26.6 From bb3f07063591e810e388b50e0a1a2f011cc4f229 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:20:16 +1000 Subject: [PATCH 13/24] Vendor Ruby gems in-repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CI toolkit's `install_gems` ends with `save_cache vendor/bundle`, so without `BUNDLE_PATH` that directory never existed: gems went to the system gem dir and every build silently cached nothing, then reinstalled 127 gems from scratch. Ported from Simperium/simperium-ios@3452bee3, minus its `BUNDLE_FORCE_RUBY_PLATFORM`. That setting makes nokogiri build from source, which fails on current clang — `gumbo.c: fatal error: 'nokogiri_gumbo.h' file not found`, reproduced locally. It is why `beeper/bridge-manager` is pinned back to the Xcode 26.3 image; adopting it here would mean giving up 26.6 and its Ruby 3.4.9 default for 26.3's 3.2.2. Nothing needs it: `Gemfile.lock` resolves `nokogiri-1.19.4-arm64-darwin` precompiled, so there is no build to force a platform for. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- .bundle/config | 2 ++ .gitignore | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 .bundle/config diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 000000000..236922881 --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_PATH: "vendor/bundle" diff --git a/.gitignore b/.gitignore index af93ff2fe..04fcd4272 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ go.work.sum # of git so the repo does not carry ~19 MB of executables per upgrade. third_party/go-search-replace/*/ +# Ruby tooling +vendor/bundle/ + # Regenerated by fastlane on every run; `bundle exec fastlane lanes` prints the # same lane summary as the README. fastlane/README.md From c6fb1e1ef2eb9f970063ad80e5a1fcef8e7e32ee Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:31:58 +1000 Subject: [PATCH 14/24] Match studio's bundler retry and jobs settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Automattic/studio` is the closest precedent — an a8c app with the same fastlane setup that also builds on Linux — and it sets these alongside `BUNDLE_PATH`. Retries matter more on CI than locally, where a single flaky gem fetch fails the whole build. It notably does not set `BUNDLE_FORCE_RUBY_PLATFORM`, which is the other half of the simperium-ios config and the reason that repo cannot use a current Xcode image. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 5 --- .bundle/config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.bundle/config b/.bundle/config index 236922881..8515e56f3 100644 --- a/.bundle/config +++ b/.bundle/config @@ -1,2 +1,4 @@ --- BUNDLE_PATH: "vendor/bundle" +BUNDLE_JOBS: "3" +BUNDLE_RETRY: "3" From cc417a7c787f9bf9a5b0c86db98a2c14a6211f77 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:49:31 +1000 Subject: [PATCH 15/24] Pin fastlane to 2.238 AINFRA-2927 made ~> 2.238 the floor after `bundle_id` was removed from `notarize`. The lock already resolved 2.238.0; this only raises the constraint. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 01f80b08c..06c74010c 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'fastlane', '~> 2.237' +gem 'fastlane', '~> 2.238' gem 'fastlane-plugin-wpmreleasetoolkit', '~> 15.0' # Avoids "certificate verify failed (unable to get certificate CRL)" on some hosts. # See https://github.com/ruby/openssl/issues/949 diff --git a/Gemfile.lock b/Gemfile.lock index 16bfb23b3..2e629bb01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -335,7 +335,7 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES - fastlane (~> 2.237) + fastlane (~> 2.238) fastlane-plugin-wpmreleasetoolkit (~> 15.0) openssl (~> 4.0) From f6221d4b995f14cb7bf38433bc02cb3fea6e238f Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:49:51 +1000 Subject: [PATCH 16/24] Pin Ruby 3.4.9 via .ruby-version Matches the Xcode 26.6 image default and the wpios/pcios/Day One CLI pin, so `install_gems` cache keys off an explicit version rather than whichever rbenv default the agent happens to have. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- .ruby-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..7bcbb3808 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.4.9 From 1660da69a015a2c55ce0c480d88c10dd630366ed Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:51:13 +1000 Subject: [PATCH 17/24] Drop the release-toolkit EnvManager Day One CLI signs without the toolkit: a 10-line helper, not a gem whose nokogiri forces the Ruby-platform / Xcode-image dance. The plugin was only here for `EnvManager`. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- Gemfile | 1 - Gemfile.lock | 108 +--------------------------------------------- fastlane/Fastfile | 20 ++++----- 3 files changed, 12 insertions(+), 117 deletions(-) diff --git a/Gemfile b/Gemfile index 06c74010c..cdac02e04 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,6 @@ source 'https://rubygems.org' gem 'fastlane', '~> 2.238' -gem 'fastlane-plugin-wpmreleasetoolkit', '~> 15.0' # Avoids "certificate verify failed (unable to get certificate CRL)" on some hosts. # See https://github.com/ruby/openssl/issues/949 gem 'openssl', '~> 4.0' diff --git a/Gemfile.lock b/Gemfile.lock index 2e629bb01..8809815b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -30,9 +30,6 @@ GEM base64 (0.3.0) benchmark (0.5.0) bigdecimal (4.1.2) - buildkit (1.6.1) - sawyer (>= 0.6) - chroma (0.2.0) claide (1.1.0) colored (1.2) colored2 (3.1.2) @@ -40,14 +37,12 @@ GEM highline (~> 2.0.0) csv (3.3.6) declarative (0.0.20) - diffy (3.4.4) digest-crc (0.7.0) rake (>= 12.0.0, < 14.0.0) domain_name (0.6.20260829) dotenv (2.8.1) emoji_regex (3.2.3) erb (6.0.7) - erubi (1.13.1) excon (1.7.1) logger faraday (2.14.3) @@ -120,37 +115,8 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.4.1) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) - fastlane-plugin-wpmreleasetoolkit (15.0.0) - buildkit (~> 1.5) - chroma (= 0.2.0) - diffy (~> 3.3) - dotenv (~> 2.8) - fastlane (~> 2.237) - gettext (~> 3.5) - git (~> 1.3) - google-cloud-storage (~> 1.31) - java-properties (~> 0.3.0) - nokogiri (~> 1.19, >= 1.19.4) - octokit (~> 6.1) - parallel (~> 1.14) - plist (~> 3.1) - progress_bar (~> 1.3) - rake (>= 12.3, < 14.0) - rake-compiler (~> 1.0) - xcodeproj (~> 1.22) fastlane-sirp (1.1.0) - fiddle (1.1.8) - forwardable (1.4.0) - gettext (3.5.2) - erubi - locale (>= 2.0.5) - prime - racc - text (>= 1.3.0) gh_inspector (1.1.3) - git (1.19.1) - addressable (~> 2.8) - rchardet (~> 1.8) google-apis-androidpublisher_v3 (0.107.0) google-apis-core (>= 0.15.0, < 2.a) google-apis-core (1.2.5) @@ -202,13 +168,10 @@ GEM prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - java-properties (0.3.0) jmespath (1.6.2) json (2.21.2) jwt (3.2.0) base64 - locale (2.1.5) - fiddle logger (1.7.0) mini_magick (4.13.2) mini_mime (1.1.5) @@ -220,53 +183,22 @@ GEM net-http (0.9.1) uri (>= 0.11.1) nkf (0.3.0) - nokogiri (1.19.4-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.4-aarch64-linux-musl) - racc (~> 1.4) - nokogiri (1.19.4-arm-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.4-arm-linux-musl) - racc (~> 1.4) - nokogiri (1.19.4-arm64-darwin) - racc (~> 1.4) - nokogiri (1.19.4-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.19.4-x86_64-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.4-x86_64-linux-musl) - racc (~> 1.4) - octokit (6.1.1) - faraday (>= 1, < 3) - sawyer (~> 0.9) openssl (4.0.2) - options (2.3.2) optparse (0.8.1) os (1.1.4) ostruct (0.6.3) - parallel (1.28.0) plist (3.7.2) pp (0.6.4) prettyprint prettyprint (0.2.0) - prime (0.1.4) - forwardable - singleton prism (1.9.0) - progress_bar (1.3.4) - highline (>= 1.6) - options (~> 2.3.0) pstore (0.2.1) public_suffix (7.0.5) - racc (1.8.1) rake (13.4.2) - rake-compiler (1.3.1) - rake rbs (4.2.0) logger prism (>= 1.6.0) tsort - rchardet (1.10.2) rdoc (8.0.0) erb prism (>= 1.6.0) @@ -282,9 +214,6 @@ GEM rexml (3.4.4) rouge (3.28.0) rubyzip (2.4.1) - sawyer (0.9.3) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) security (0.1.5) signet (0.22.0) addressable (~> 2.8) @@ -293,11 +222,9 @@ GEM simctl (1.6.10) CFPropertyList naturally - singleton (0.3.0) terminal-notifier (2.0.0) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) - text (1.3.1) trailblazer-option (0.1.2) tsort (0.2.0) tty-cursor (0.7.1) @@ -336,7 +263,6 @@ PLATFORMS DEPENDENCIES fastlane (~> 2.238) - fastlane-plugin-wpmreleasetoolkit (~> 15.0) openssl (~> 4.0) CHECKSUMS @@ -355,22 +281,18 @@ CHECKSUMS base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd - buildkit (1.6.1) sha256=e0e51c0ce3334654c356bdef3410cba5c9b36b336d0d6542f3ecfb477c5fa97c - bundler (4.0.19) sha256=b48056e4c77fb3853cc47775b5447ede44aaa4f53c32f168014ab4fe86587d47 - chroma (0.2.0) sha256=64bdcd36a4765fbcd45adc64960cc153101300b4918f90ffdd89f4e2eb954b54 + bundler (4.0.18) sha256=02d9a17429de1847b4e0c9f27a9ee4b20c0a74c0a641b4e77195d6019e3618ac claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9 csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456 declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 - diffy (3.4.4) sha256=79384ab5ca82d0e115b2771f0961e27c164c456074bd2ec46b637ebf7b6e47e3 digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07 domain_name (0.6.20260829) sha256=a9c1aa678423b9db3a4193b300fd76b9c21cb2a5f60d7dcf0a832b82d13764f0 dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92 - erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9 excon (1.7.1) sha256=dd2d870eece1b5ce4e4f9efd938e67a69ccd8c003c3825b638b937e1082eaac2 faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278 faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb @@ -380,13 +302,8 @@ CHECKSUMS faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20 fastlane (2.238.0) sha256=78e9252df2224c7e427012638e41051edfa29b133a40fda883fd2f1c13a77b98 - fastlane-plugin-wpmreleasetoolkit (15.0.0) sha256=6ffd06713e7e98c0d4a65df404b6cf8ffe66258b0976900899d888e57909aa67 fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847 - fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee - forwardable (1.4.0) sha256=f1cd40cc9812937980e1c76f1aa053660990a7c9b6a98fc37d945468afcce838 - gettext (3.5.2) sha256=ada02c59aa7e9f56bd2522faedaed16421dd2f3ddb5fe28628c0be5abcbf3c74 gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 - git (1.19.1) sha256=b0a422d9f6517353c48a330d6114de4db9e0c82dbe7202964a1d9f1fbc827d70 google-apis-androidpublisher_v3 (0.107.0) sha256=b8207eecc89e6a9f7ea11aa88a07bccdcae5ca90121330079db4ef4696a2f3ff google-apis-core (1.2.5) sha256=e21562b5627a0fc6a0c3165e429c3afed0f6a628eaa514e83f7204dda1adff69 google-apis-iamcredentials_v1 (0.28.0) sha256=0a92ffe6cc39c569554af2a77a25dfc61519ed8bbb64ab04cffdd352dc5ef106 @@ -402,11 +319,9 @@ CHECKSUMS http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6 io-console (0.9.2) sha256=efa74f891dd03c0939a931dfc6e74c2813d904763d456ea9762b0525e748db08 irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 - java-properties (0.3.0) sha256=0a9fdda90c25ba9ba4de0e242d954a5688629652b592aab66ed54e2b16b93093 jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7 - locale (2.1.5) sha256=1c6803e8aa6bdb2c29e91945d095050601bf6d58474993575adf6f3b89b32ef4 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9 mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef @@ -417,34 +332,18 @@ CHECKSUMS naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01 net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996 nkf (0.3.0) sha256=357a8dbeba38b727b75930f665146546076a394a1c243faf634ff176e3588895 - nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f - nokogiri (1.19.4-aarch64-linux-musl) sha256=35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af - nokogiri (1.19.4-arm-linux-gnu) sha256=a301313e38bb065d68239e79734bcd6f56fb6efaacebde29e9abf2a4735340ca - nokogiri (1.19.4-arm-linux-musl) sha256=588923c101bcfa78869734d247d25b598674323e7f22474fc468f6e5647311eb - nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527 - nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551 - nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a - nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b - octokit (6.1.1) sha256=920e4a9d820205f70738f58de6a7e6ef0e2f25b27db954b5806a63105207b0bf openssl (4.0.2) sha256=1037ad2868ae58df9ad917891c0c0f9815a1172f6846d4bcdd508e4c2ee747c2 - options (2.3.2) sha256=32413a4b9e363234eed2eecfb2a1a9deb32810f72c54820a37a62f65b905c5e8 optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 - parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970 plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42 pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 - prime (0.1.4) sha256=4d755ebf7c2994a6f3a3fee0d072063be3fff2d4042ebff6cd5eebd4747a225e prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 - progress_bar (1.3.4) sha256=adb10e040275e08eadfbe405749584e4b01fd15e8e692fdcb4b1969e9c071c8c pstore (0.2.1) sha256=03904d0f2c66579e96d1e6704cdabc0c88df7ea8ed8782d9f3569f6f6c702c1a public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 - racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 - rake-compiler (1.3.1) sha256=6b351612b6e2d73ddd5563ee799bb58685176e05363db6758504bd11573d670a rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674 - rchardet (1.10.2) sha256=e041cb195f464dc10e49ab130f78c8b5956cd9a4f4f6df84e0c183b87c135f33 rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469 reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace @@ -452,14 +351,11 @@ CHECKSUMS rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20 rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 - sawyer (0.9.3) sha256=0d0f19298408047037638639fe62f4794483fb04320269169bd41af2bdcf5e41 security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7 signet (0.22.0) sha256=b76d495ccb07ad35dbc89f3e920665a9d8ed717141955034005d7843dcfe4780 simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b - singleton (0.3.0) sha256=83ea1bca5f4aa34d00305ab842a7862ea5a8a11c73d362cb52379d94e9615778 terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea terminal-table (4.0.0) sha256=f504793203f8251b2ea7c7068333053f0beeea26093ec9962e62ea79f94301d2 - text (1.3.1) sha256=2fbbbc82c1ce79c4195b13018a87cbb00d762bda39241bb3cdc32792759dd3f4 trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48 @@ -475,4 +371,4 @@ CHECKSUMS xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93 BUNDLED WITH - 4.0.19 + 4.0.18 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index b946a2ba3..929b87889 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -23,25 +23,20 @@ ASC_API_KEY_ENV_VARS = %w[ APP_STORE_CONNECT_API_KEY_KEY ].freeze -require 'fastlane/plugin/wpmreleasetoolkit' - -EnvManager = Fastlane::Wpmreleasetoolkit::EnvManager - TEAM_ID = 'PZYM8XX95Q' before_all do - setup_ci # required for match to work in CI; harmless locally - EnvManager.set_up(env_file_name: 'vip-cli.env') - check_for_toolkit_updates unless is_ci || ENV['FASTLANE_SKIP_TOOLKIT_UPDATE_CHECK'] + # Ensures `match` works on CI by setting up a temporary keychain. No-op locally. + setup_ci end # Places the Developer ID Application cert (and — see the infra note — the Installer # cert) into the keychain so `codesign` / `productsign` can find them. lane :configure_code_signing do |readonly: true| - EnvManager.require_env_vars!(*CODE_SIGNING_ENV_VARS) + require_env_vars!(*CODE_SIGNING_ENV_VARS) # A readonly fetch only reads the bucket; creating or renewing a cert is the # only thing that talks to App Store Connect. - EnvManager.require_env_vars!(*ASC_API_KEY_ENV_VARS) unless readonly + require_env_vars!(*ASC_API_KEY_ENV_VARS) unless readonly # Developer ID *Application* cert — signs the binaries (codesign). # No app identifier: a Developer ID cert is not app-scoped, and naming one @@ -66,7 +61,7 @@ end # be stapled → default true; the .pkg passes false so it gets stapled → offline-verified). lane :notarize_artifact do |options| UI.user_error!('notarize_artifact requires path:') unless options[:path] - EnvManager.require_env_vars!(*ASC_API_KEY_ENV_VARS) + require_env_vars!(*ASC_API_KEY_ENV_VARS) skip_stapling = options[:skip_stapling].to_s != 'false' notarize( @@ -76,3 +71,8 @@ lane :notarize_artifact do |options| print_log: true ) end + +def require_env_vars!(*names) + missing = names.select { |name| ENV[name].to_s.empty? } + UI.user_error!("Missing required environment variable(s): #{missing.join(', ')}") unless missing.empty? +end From 57eae57fcaeca09fdc905ed6e4d33efd72c3bb1d Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:53:38 +1000 Subject: [PATCH 18/24] Sign macOS binaries through fastlane Day One CLI is the reference: certs, codesign with `--identifier`, verify, and notarize live in lanes, not Bash. The `.pkg` path needed an Installer cert `match` never fetched, so it is gone. `GOFLAGS=-mod=mod` keeps Go from treating Bundler's `vendor/bundle` as a Go vendor tree. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- .buildkite/build-macos.sh | 50 +++---------- .buildkite/shared-pipeline-vars | 8 +-- fastlane/Fastfile | 121 ++++++++++++++++++++++++-------- 3 files changed, 104 insertions(+), 75 deletions(-) diff --git a/.buildkite/build-macos.sh b/.buildkite/build-macos.sh index 764790950..23f092403 100755 --- a/.buildkite/build-macos.sh +++ b/.buildkite/build-macos.sh @@ -1,12 +1,10 @@ #!/usr/bin/env bash set -euo pipefail -# Build, and on tag builds sign + notarize, the macOS vip-next artifacts, on a -# Buildkite macOS agent (queue: mac): -# - two bare per-arch binaries: codesigned + notarized (online-verified; a bare -# Mach-O can't be stapled) -# - one universal .pkg installer: codesigned + productsigned + notarized + STAPLED -# (offline-verified) -# Checksums are written AFTER signing (signing changes the bytes). +# Build, and on tag builds sign + notarize, the macOS vip-next artifacts on a +# Buildkite macOS agent (queue: mac). +# Two bare per-arch binaries: codesigned + notarized (online-verified; a bare +# Mach-O can't be stapled). Checksums are written AFTER signing (signing +# changes the bytes). [ -f .buildkite/shared-pipeline-vars ] && . .buildkite/shared-pipeline-vars : "${BIN_BASE:=vip-next}" @@ -22,6 +20,9 @@ if ! command -v go >/dev/null 2>&1; then fi go version +# Bundler installs to `vendor/bundle`, which makes Go take `-mod=vendor`. +export GOFLAGS="${GOFLAGS:--mod=mod}" + VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)" COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" @@ -63,40 +64,9 @@ fi echo "--- :closed_lock_with_key: fetch signing certs (fastlane match)" bundle exec fastlane configure_code_signing -# Build the universal binary from the UNSIGNED arches, then sign all three once. -uni="dist/${BIN_BASE}-darwin-universal" -lipo -create -output "${uni}" "dist/${BIN_BASE}-darwin-arm64" "dist/${BIN_BASE}-darwin-amd64" - -for bin in "dist/${BIN_BASE}-darwin-arm64" "dist/${BIN_BASE}-darwin-amd64" "${uni}"; do - echo "--- :closed_lock_with_key: codesign ${bin}" - codesign --remove-signature "${bin}" 2>/dev/null || true # drop Go's ad-hoc sig - codesign --sign "${MACOS_SIGN_IDENTITY}" --options runtime --timestamp --force "${bin}" - codesign --verify --strict --verbose=2 "${bin}" -done - -# Bare binaries: notarize (no staple — nothing to hold the ticket), then checksum. for arch in arm64 amd64; do bin="dist/${BIN_BASE}-darwin-${arch}" - echo "--- :cloud: notarize ${arch} (no staple)" - ditto -c -k --keepParent "${bin}" "${bin}.zip" - bundle exec fastlane notarize_artifact path:"${bin}.zip" - rm -f "${bin}.zip" + echo "--- :closed_lock_with_key: sign and notarize ${arch}" + bundle exec fastlane sign_and_notarize binary:"${bin}" checksum "${bin}" done - -# Universal .pkg: package the signed universal binary → sign the pkg → notarize + staple. -echo "--- :package: build + sign universal .pkg" -pkgroot="$(mktemp -d)" -cp "${uni}" "${pkgroot}/${BIN_BASE}" -pkg="dist/${BIN_BASE}-darwin-universal.pkg" -pkgbuild --root "${pkgroot}" --identifier com.automattic.vip-cli --version "${VERSION}" \ - --install-location /usr/local/bin "${pkg}.unsigned" -productsign --sign "${MACOS_INSTALLER_IDENTITY}" "${pkg}.unsigned" "${pkg}" -rm -f "${pkg}.unsigned" -rm -rf "${pkgroot}" - -echo "--- :cloud: notarize + staple .pkg" -bundle exec fastlane notarize_artifact path:"${pkg}" skip_stapling:false -xcrun stapler validate "${pkg}" -checksum "${uni}" -checksum "${pkg}" diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index 44236b41f..2af49705c 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -7,14 +7,10 @@ # GitHub status helpers on the macOS agent. export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#6.3.0" -# Binary base name + macOS signing identities (team PZYM8XX95Q = Automattic, Inc.). export BIN_BASE="vip-next" -export MACOS_TEAM_ID="PZYM8XX95Q" -export MACOS_SIGN_IDENTITY="Developer ID Application: Automattic, Inc. (PZYM8XX95Q)" -export MACOS_INSTALLER_IDENTITY="Developer ID Installer: Automattic, Inc. (PZYM8XX95Q)" # ← infra: confirm this cert exists # The macOS VM to run on. The `mac` queue's command hook aborts when IMAGE_ID is -# unset, and the signing step needs `codesign`, `pkgbuild`, `productsign` and -# `xcrun notarytool` regardless of Go being the compiler. +# unset, and the signing step needs `codesign` and `xcrun notarytool` +# regardless of Go being the compiler. XCODE_VERSION=$(grep -Ev '^[[:space:]]*(#|$)' .xcode-version | head -n1 | sed -E 's/^[[:space:]]*//; s/^~> ?//; s/[[:space:]]*$//') export IMAGE_ID="xcode-$XCODE_VERSION" diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 929b87889..145a586fe 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,75 +1,138 @@ # frozen_string_literal: true +require 'shellwords' +require 'fileutils' + UI.user_error!('Please run fastlane via `bundle exec`') unless FastlaneCore::Helper.bundler? -# fastlane match cert storage (S3) — the shared a8c bucket. # ← infra: confirm +PROJECT_ROOT_FOLDER = File.dirname(File.expand_path(__dir__)) + +APPLE_TEAM_ID = 'PZYM8XX95Q' +SIGNING_IDENTITY = "Developer ID Application: Automattic, Inc. (#{APPLE_TEAM_ID})" + +# The signature's designated requirement embeds this identifier, and the Keychain ACL +# guarding the master key is bound to that requirement. `codesign` otherwise derives it +# from the file name (`vip-next-darwin-arm64`), so a rename would read as a different app. +SIGNING_IDENTIFIER = 'com.automattic.vip-cli' + CODE_SIGNING_STORAGE_OPTIONS = { storage_mode: 's3', s3_bucket: 'a8c-fastlane-match', s3_region: 'us-east-2' }.freeze -# `match` reads these to pull the certs out of the S3 bucket and decrypt them. CODE_SIGNING_ENV_VARS = %w[ MATCH_S3_ACCESS_KEY MATCH_S3_SECRET_ACCESS_KEY MATCH_PASSWORD ].freeze -# app_store_connect_api_key reads these to build the ASC key. ASC_API_KEY_ENV_VARS = %w[ APP_STORE_CONNECT_API_KEY_KEY_ID APP_STORE_CONNECT_API_KEY_ISSUER_ID APP_STORE_CONNECT_API_KEY_KEY ].freeze -TEAM_ID = 'PZYM8XX95Q' - before_all do # Ensures `match` works on CI by setting up a temporary keychain. No-op locally. setup_ci end -# Places the Developer ID Application cert (and — see the infra note — the Installer -# cert) into the keychain so `codesign` / `productsign` can find them. +# Fetch the Developer ID Application certificate into the keychain. +# +# @param readonly [Boolean] Use `true` to only fetch the existing certificate from S3 via +# `match`. Use `false` to create or renew it on App Store Connect. +# lane :configure_code_signing do |readonly: true| require_env_vars!(*CODE_SIGNING_ENV_VARS) - # A readonly fetch only reads the bucket; creating or renewing a cert is the - # only thing that talks to App Store Connect. require_env_vars!(*ASC_API_KEY_ENV_VARS) unless readonly - # Developer ID *Application* cert — signs the binaries (codesign). - # No app identifier: a Developer ID cert is not app-scoped, and naming one - # makes `match` look for a provisioning profile that does not exist. sync_code_signing( - app_identifier: [], platform: 'macos', type: 'developer_id', + type: 'developer_id', + platform: 'macos', + team_id: APPLE_TEAM_ID, + # A CLI has no bundle to carry a provisioning profile, so there is no app identifier + # to key storage on. + app_identifier: [], api_key: readonly ? nil : app_store_connect_api_key, - team_id: TEAM_ID, readonly: readonly, + readonly: readonly, **CODE_SIGNING_STORAGE_OPTIONS ) - - # ← infra: ALSO provision the Developer ID *Installer* cert (for productsign on the - # .pkg). The reference doesn't need it. Depending on your match setup this is likely: - # sync_code_signing(app_identifier: [], platform: 'macos', type: 'developer_id', - # additional_cert_types: ['developer_id_installer'], - # team_id: TEAM_ID, readonly: readonly, **CODE_SIGNING_STORAGE_OPTIONS) - # Confirm the exact option/flow, then enable it. end -# Notarize a .zip (bare binary) or .pkg. Lane args arrive as strings, so an explicit -# `skip_stapling:false` is the ONLY thing that turns stapling on (bare binaries can't -# be stapled → default true; the .pkg passes false so it gets stapled → offline-verified). -lane :notarize_artifact do |options| - UI.user_error!('notarize_artifact requires path:') unless options[:path] +# Sign a built CLI binary with the Developer ID certificate and submit it for notarization. +# +# Run `configure_code_signing` first, or run this on a machine that already holds the +# certificate. +# +# @param binary [String] Path to the binary to sign, e.g. `dist/vip-next-darwin-arm64`. +# +lane :sign_and_notarize do |binary:| + binary = resolve_from_project_root(binary) + UI.user_error!("No binary at '#{binary}'") unless File.exist?(binary) require_env_vars!(*ASC_API_KEY_ENV_VARS) - skip_stapling = options[:skip_stapling].to_s != 'false' + sh( + 'codesign', + '--sign', SIGNING_IDENTITY, + '--identifier', SIGNING_IDENTIFIER, + # The hardened runtime is a precondition for notarization. + '--options', 'runtime', + '--timestamp', + '--force', + binary + ) + + verify_code_signing(binary: binary) + + # `notarytool` takes only .zip/.dmg/.pkg, and fastlane's `notarize` compresses `.app` + # bundles and nothing else, so a bare binary has to be archived here. + archive = "#{binary}.zip" + sh('ditto', '-c', '-k', '--keepParent', binary, archive) + notarize( - package: options[:path], + package: archive, + # Only bundles and disk images can carry a stapled ticket. A bare binary relies on + # the online Gatekeeper check. + skip_stapling: true, api_key: app_store_connect_api_key, - skip_stapling: skip_stapling, print_log: true ) + + FileUtils.rm_f(archive) +end + +# Verify that a binary carries an intact signature from our Developer ID, so a wrongly +# signed build fails CI instead of shipping. +# +# Gatekeeper acceptance (`spctl --assess`) is deliberately not asserted: a bare binary +# cannot be stapled, so that check needs an online lookup that lags a fresh submission. +# +# @param binary [String] Path to the binary to verify. +# +lane :verify_code_signing do |binary:| + binary = resolve_from_project_root(binary) + + sh('codesign', '--verify', '--strict', '--verbose=2', binary) + + # `codesign --display` reports on stderr, which `sh` does not capture. + details = sh("codesign --display --verbose=2 #{binary.shellescape} 2>&1", log: false) + + unless details.include?("Authority=#{SIGNING_IDENTITY}") + UI.user_error!("#{binary} is not signed by '#{SIGNING_IDENTITY}':\n#{details}") + end + + unless details.include?("Identifier=#{SIGNING_IDENTIFIER}") + UI.user_error!("#{binary} has the wrong signing identifier, expected '#{SIGNING_IDENTIFIER}':\n#{details}") + end + + UI.success("#{binary} is signed by #{SIGNING_IDENTITY}") +end + +# fastlane runs lanes from the `fastlane/` directory, so a path a caller wrote +# relative to the repo root would otherwise resolve one level too deep. +def resolve_from_project_root(path) + File.absolute_path(path, PROJECT_ROOT_FOLDER) end def require_env_vars!(*names) From 67df9b73c63064c3b14eb25658298311d89e052b Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:54:10 +1000 Subject: [PATCH 19/24] Sign macOS artifacts on every build Tag-gating is what left the 3013 path untested. Day One CLI signs every build and only gates GitHub upload; this matches that. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- .buildkite/build-macos.sh | 16 ++++------------ .buildkite/pipeline.yml | 8 ++++---- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.buildkite/build-macos.sh b/.buildkite/build-macos.sh index 23f092403..7e4afa5b2 100755 --- a/.buildkite/build-macos.sh +++ b/.buildkite/build-macos.sh @@ -1,10 +1,9 @@ #!/usr/bin/env bash set -euo pipefail -# Build, and on tag builds sign + notarize, the macOS vip-next artifacts on a -# Buildkite macOS agent (queue: mac). -# Two bare per-arch binaries: codesigned + notarized (online-verified; a bare -# Mach-O can't be stapled). Checksums are written AFTER signing (signing -# changes the bytes). +# Build, sign, and notarize the macOS vip-next artifacts on a Buildkite macOS +# agent (queue: mac). Two bare per-arch binaries: codesigned + notarized +# (online-verified; a bare Mach-O can't be stapled). Checksums are written +# AFTER signing (signing changes the bytes). [ -f .buildkite/shared-pipeline-vars ] && . .buildkite/shared-pipeline-vars : "${BIN_BASE:=vip-next}" @@ -54,13 +53,6 @@ if [ -n "${native}" ]; then "dist/${BIN_BASE}-darwin-${native}" whoami --help fi -if [ -z "${BUILDKITE_TAG:-}" ]; then - echo "--- not a tag build; skipping sign/notarize (unsigned checksums only)" - checksum "dist/${BIN_BASE}-darwin-arm64" - checksum "dist/${BIN_BASE}-darwin-amd64" - exit 0 -fi - echo "--- :closed_lock_with_key: fetch signing certs (fastlane match)" bundle exec fastlane configure_code_signing diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 4a85f92f2..008e4d403 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,11 +1,11 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json --- -# Shared vars (CI_TOOLKIT_PLUGIN, BIN_BASE, signing identities) come -# from .buildkite/shared-pipeline-vars, which our setup source's before +# Shared vars (CI_TOOLKIT_PLUGIN, BIN_BASE) come from +# .buildkite/shared-pipeline-vars, which our setup source's before # `buildkite-agent pipeline upload` interpolates this file. # -# Every commit builds + smoke-tests all three platforms. Signing + notarization -# run only on TAG builds — gated on $BUILDKITE_TAG inside each build script. +# Every commit builds + smoke-tests all three platforms. macOS signs and +# notarizes on every build so the path is not tag-only. env: DO_NOT_TRACK: '1' From a76714cb11ae1fefdab9a9116f7a40600a1a8bcd Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:56:16 +1000 Subject: [PATCH 20/24] Sign Windows with Azure Trusted Signing PFX is dead for EV certs since 2023. Studio and Simplenote Electron call `setup_azure_trusted_signing.ps1` from the CI toolkit on `queue: windows`; the six Azure vars are already on that queue. The plugin was missing from this step, so even the old PFX secrets would not have injected. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- .buildkite/build-windows.ps1 | 42 ++++++++++++++++-------------------- .buildkite/pipeline.yml | 7 ++++-- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/.buildkite/build-windows.ps1 b/.buildkite/build-windows.ps1 index 7e8e318c1..7157509fa 100644 --- a/.buildkite/build-windows.ps1 +++ b/.buildkite/build-windows.ps1 @@ -1,6 +1,6 @@ #Requires -Version 5.1 -# Build vip-next.exe and (on tag builds) Authenticode-sign it, on a Buildkite -# Windows agent. Checksum is computed AFTER signing (signing changes the bytes). +# Build and Authenticode-sign vip-next.exe on a Buildkite Windows agent. +# Checksum is computed AFTER signing (signing changes the bytes). $ErrorActionPreference = 'Stop' $binBase = if ($env:BIN_BASE) { $env:BIN_BASE } else { 'vip-next' } @@ -36,28 +36,22 @@ Write-Host "--- :test_tube: smoke" & $out --version & $out whoami --help -if ($env:BUILDKITE_TAG) { - Write-Host "--- :closed_lock_with_key: Authenticode sign" - # ← infra: confirm the Windows cert mechanism. Draft = PFX-from-base64-secret, - # mirroring the current GitHub Actions workflow. EV certs can NOT use a plain - # PFX (FIPS-hardware since June 2023) — if you use Azure Trusted Signing, swap - # the two signtool lines for `signtool sign /fd SHA256 /tr /td SHA256 /dlib /dmdf $out`. - $pfxB64 = $env:WINDOWS_CERTIFICATE_PFX_BASE64 - $pfxPw = $env:WINDOWS_CERTIFICATE_PASSWORD - $ts = if ($env:WINDOWS_TIMESTAMP_URL) { $env:WINDOWS_TIMESTAMP_URL } else { 'http://timestamp.digicert.com' } - if (-not $pfxB64 -or -not $pfxPw) { throw 'tag build but WINDOWS_CERTIFICATE_PFX_BASE64 / _PASSWORD not set' } - - $pfx = Join-Path $env:TEMP 'vip-codesign.pfx' - [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($pfxB64)) - try { - signtool sign /fd SHA256 /td SHA256 /tr $ts /f $pfx /p $pfxPw $out - if ($LASTEXITCODE -ne 0) { throw 'signtool sign failed' } - signtool verify /pa /v $out - if ($LASTEXITCODE -ne 0) { throw 'signtool verify failed' } - } finally { - Remove-Item $pfx -Force -ErrorAction SilentlyContinue - } -} +Write-Host "--- :closed_lock_with_key: Azure Trusted Signing" +$setupScript = (Get-Command setup_azure_trusted_signing.ps1 -ErrorAction Stop).Source +& $setupScript +if ($LASTEXITCODE -ne 0) { throw 'setup_azure_trusted_signing.ps1 failed' } + +Write-Host "--- :closed_lock_with_key: Authenticode sign" +& $env:SIGNTOOL_PATH sign /v ` + /fd $env:AZURE_FILE_DIGEST ` + /tr $env:AZURE_TIMESTAMP_SERVER ` + /td $env:AZURE_TIMESTAMP_DIGEST ` + /dlib $env:AZURE_CODE_SIGNING_DLIB ` + /dmdf $env:AZURE_METADATA_JSON ` + $out +if ($LASTEXITCODE -ne 0) { throw 'signtool sign failed' } +& $env:SIGNTOOL_PATH verify /pa /v $out +if ($LASTEXITCODE -ne 0) { throw 'signtool verify failed' } Write-Host "--- checksum" $hash = (Get-FileHash -Algorithm SHA256 $out).Hash.ToLower() diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 008e4d403..48d7236da 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -5,7 +5,8 @@ # `buildkite-agent pipeline upload` interpolates this file. # # Every commit builds + smoke-tests all three platforms. macOS signs and -# notarizes on every build so the path is not tag-only. +# notarizes, and Windows Authenticode-signs, on every build so those paths +# are not tag-only. env: DO_NOT_TRACK: '1' @@ -27,8 +28,10 @@ steps: - label: ':windows: Build & sign (Windows)' command: powershell -NoProfile -ExecutionPolicy Bypass -File .buildkite/build-windows.ps1 + plugins: + - $CI_TOOLKIT_PLUGIN agents: - queue: windows # ← infra: confirm Windows agent queue name + queue: windows notify: - github_commit_status: context: 'Build & sign (Windows)' From 5c6e66613617e53a7db016acf5e1a099a6aa0ce1 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 20:57:50 +1000 Subject: [PATCH 21/24] Remove leftover infra TODO markers AINFRA-3030 is done when none remain in shipped files. The queue names were already correct; the rest were unfinished decisions that this branch has now made. Docs catch up so they no longer describe the tag-gated `.pkg`/PFX path. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- .buildkite/build-linux.sh | 3 +- .buildkite/pipeline.yml | 2 +- docs/BUILD-SIGNING.md | 51 +++++++++++++------------------- docs/CUTOVER-BREAKING-CHANGES.md | 2 +- 4 files changed, 23 insertions(+), 35 deletions(-) diff --git a/.buildkite/build-linux.sh b/.buildkite/build-linux.sh index 8a9dc0c99..2dc4a1b45 100755 --- a/.buildkite/build-linux.sh +++ b/.buildkite/build-linux.sh @@ -44,5 +44,4 @@ if [ -n "${native}" ]; then "dist/${BIN_BASE}-linux-${native}" whoami --help fi -# Optional detached signature (needs an infra-owned key); checksums-only by default. -# gpg --armor --detach-sign "dist/${BIN_BASE}-linux-amd64" # ← infra: enable if desired + diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 48d7236da..a3f5c9308 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -41,7 +41,7 @@ steps: - label: ':linux: Build (Linux)' command: .buildkite/build-linux.sh agents: - queue: default # ← infra: confirm Linux agent queue name + queue: default notify: - github_commit_status: context: 'Build (Linux)' diff --git a/docs/BUILD-SIGNING.md b/docs/BUILD-SIGNING.md index f6cd33125..4f5731d0c 100644 --- a/docs/BUILD-SIGNING.md +++ b/docs/BUILD-SIGNING.md @@ -230,44 +230,33 @@ Release builds run on **Buildkite** (Automattic's signing stack), not GitHub Actions. See `.buildkite/pipeline.yml` and the per-platform scripts. - **Pipeline:** `.buildkite/pipeline.yml` — three independent steps (macOS, - Windows, Linux), each building and signing on its own native agent. + Windows, Linux), each building on its own native agent. `.buildkite/shared-pipeline-vars` is `source`'d before `buildkite-agent -pipeline upload` to supply shared values (toolkit plugin version, Go version, - signing identities). + pipeline upload` to supply the CI toolkit plugin pin, `BIN_BASE`, and + `IMAGE_ID`. - **Build scripts:** `.buildkite/build-macos.sh`, `.buildkite/build-windows.ps1`, `.buildkite/build-linux.sh`. -- **Trigger / gating:** every commit builds + smoke-tests all three platforms; - **signing + notarization run only on tag builds** (gated on `$BUILDKITE_TAG` - inside each script), so notary quota and real certs aren't touched on PRs. -- **macOS certs:** fastlane `match` (`fastlane/Fastfile` → `configure_code_signing`), - `type: developer_id`, stored in S3 (`a8c-fastlane-match`), authenticated with an - App Store Connect API key. Two certs are needed: **Developer ID Application** - (signs the binaries) and **Developer ID Installer** (signs the `.pkg`). +- **Trigger / gating:** every commit builds and smoke-tests all three + platforms. macOS signs and notarizes, and Windows Authenticode-signs, on + every build. +- **macOS certs:** fastlane `match` (`fastlane/Fastfile` → + `configure_code_signing`), `type: developer_id`, stored in S3 + (`a8c-fastlane-match`). Signing and notarization are the + `sign_and_notarize` lane (Developer ID Application, `--identifier + com.automattic.vip-cli`, no staple on a bare Mach-O). - **macOS artifacts:** two signed + notarized bare binaries (arm64, amd64; - online-verified) **plus** one signed + notarized + **stapled** universal `.pkg` - installer (offline-verified, installs `vip-next` to `/usr/local/bin`). -- **Windows / Linux artifacts:** signed `.exe` (Authenticode via `signtool`) and - the two Linux binaries with `.sha256` checksums. + online-verified). +- **Windows / Linux artifacts:** signed `.exe` (Azure Trusted Signing via + `setup_azure_trusted_signing.ps1`) and the two Linux binaries with + `.sha256` checksums. -### Secrets & values infra owns (`# ← infra:` in the files) +### Verifying a real run -Buildkite agent queues (`windows`/`default` names), the a8c-ci-toolkit plugin -version, the App Store Connect API key + `match` S3 credentials, the Developer ID -**Installer** certificate (net-new vs the reference — the `.pkg` half depends on -it), and the Windows certificate mechanism (PFX secret vs Azure Trusted Signing -vs hardware token — EV certs can no longer use a plain PFX). - -### Verifying a real run (infra manual gate) - -After the repo is registered in Buildkite, agents are provisioned, and secrets -are wired, run a **tag build** and confirm: +On a Buildkite build, confirm: - `notarytool` result **Accepted** for every submission. -- macOS binaries: `codesign --verify --strict --verbose=2` passes; - `spctl -a -t exec -vv ` assesses as accepted. -- macOS installer: `pkgutil --check-signature ` shows the Developer ID - Installer chain; `spctl -a -t install -vv ` accepts; `xcrun stapler -validate ` confirms the staple is present (offline). +- macOS binaries: `codesign --verify --strict --verbose=2` passes and + `codesign --display` shows `Identifier=com.automattic.vip-cli`. - Windows: `signtool verify /pa /v ` passes. - Every artifact has a matching `.sha256`. @@ -330,7 +319,7 @@ Upstream 0.0.11 publishes `darwin_{amd64,arm64}`, `linux_{386,amd64,arm64}`, `windows_{386,amd64,arm64}` — so `linux/arm64` (Graviton, ARM CI, Docker on Apple Silicon), previously unsupported, is covered with no self-building. -### ← infra: what changes in the signing pipeline +### Still needed in the signing pipeline 1. **Build agents need the `gh` CLI, authenticated**, for `make vendor-search-replace`. Alternatively pre-populate diff --git a/docs/CUTOVER-BREAKING-CHANGES.md b/docs/CUTOVER-BREAKING-CHANGES.md index 20a588599..405b077bc 100644 --- a/docs/CUTOVER-BREAKING-CHANGES.md +++ b/docs/CUTOVER-BREAKING-CHANGES.md @@ -250,7 +250,7 @@ here once rather than as 32 per-scenario `expected_drift` entries. - dev-env WordPress version validation unported — porting Node's naively would reject every valid version offline. - `make test-parity` build agents / `gh` auth for `make vendor-search-replace`, and the macOS - nested-binary signing step — see the `← infra:` items in `docs/BUILD-SIGNING.md`. + nested-binary signing step — see `docs/BUILD-SIGNING.md`. _Fixed since this list was written:_ the `--search-replace` double-apply (now 1.23), the unconditional table ANSI (`internal/output/table.go` now gates on `terminalTableIsTTY`, which is From 91361140462df28d72157c052b9a2ae1032d500b Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 21:15:00 +1000 Subject: [PATCH 22/24] Install Linux Go from the official tarball The `default` queue has no passwordless sudo, so `dnf` cannot work. Unpack under `$HOME/.local` (not `$HOME/go`, which is GOPATH) using the `toolchain` version from `go.mod`. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- .buildkite/build-linux.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.buildkite/build-linux.sh b/.buildkite/build-linux.sh index 2dc4a1b45..f6c54ab93 100755 --- a/.buildkite/build-linux.sh +++ b/.buildkite/build-linux.sh @@ -1,16 +1,26 @@ #!/usr/bin/env bash set -euo pipefail # Build the Linux vip-next binaries + checksums on a Buildkite Linux agent. -# Linux has no OS-enforced executable signature; we publish checksums (a detached -# GPG/cosign signature is optional — see the bottom of this file). +# Linux has no OS-enforced executable signature; we publish checksums. [ -f .buildkite/shared-pipeline-vars ] && . .buildkite/shared-pipeline-vars : "${BIN_BASE:=vip-next}" -# Any Go will do: go.mod's `toolchain` directive makes it fetch go1.27.0 itself. if ! command -v go >/dev/null 2>&1; then echo "--- :package: install go" - sudo dnf install -y golang + version="$(awk '/^toolchain / { print $2; exit }' go.mod)" + version="${version#go}" + [ -n "${version}" ] || { echo "no toolchain directive in go.mod" >&2; exit 1; } + case "$(uname -m)" in + x86_64|amd64) arch=amd64 ;; + aarch64|arm64) arch=arm64 ;; + *) echo "unsupported arch $(uname -m)" >&2; exit 1 ;; + esac + # Not `$HOME/go` — that is GOPATH. + prefix="${HOME}/.local" + mkdir -p "${prefix}" + curl -fsSL "https://go.dev/dl/go${version}.linux-${arch}.tar.gz" | tar -C "${prefix}" -xz + export PATH="${prefix}/go/bin:${PATH}" fi go version From 7fa71aad436174f1d5e04898a63216e744d15fe9 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 31 Aug 2026 21:29:01 +1000 Subject: [PATCH 23/24] Fix Prettier wrapping in BUILD-SIGNING.md --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- docs/BUILD-SIGNING.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/BUILD-SIGNING.md b/docs/BUILD-SIGNING.md index 4f5731d0c..354d77047 100644 --- a/docs/BUILD-SIGNING.md +++ b/docs/BUILD-SIGNING.md @@ -231,9 +231,8 @@ Actions. See `.buildkite/pipeline.yml` and the per-platform scripts. - **Pipeline:** `.buildkite/pipeline.yml` — three independent steps (macOS, Windows, Linux), each building on its own native agent. - `.buildkite/shared-pipeline-vars` is `source`'d before `buildkite-agent - pipeline upload` to supply the CI toolkit plugin pin, `BIN_BASE`, and - `IMAGE_ID`. + Setup sources `.buildkite/shared-pipeline-vars` before pipeline upload + (CI toolkit plugin pin, `BIN_BASE`, `IMAGE_ID`). - **Build scripts:** `.buildkite/build-macos.sh`, `.buildkite/build-windows.ps1`, `.buildkite/build-linux.sh`. - **Trigger / gating:** every commit builds and smoke-tests all three @@ -242,8 +241,8 @@ Actions. See `.buildkite/pipeline.yml` and the per-platform scripts. - **macOS certs:** fastlane `match` (`fastlane/Fastfile` → `configure_code_signing`), `type: developer_id`, stored in S3 (`a8c-fastlane-match`). Signing and notarization are the - `sign_and_notarize` lane (Developer ID Application, `--identifier - com.automattic.vip-cli`, no staple on a bare Mach-O). + `sign_and_notarize` lane: Developer ID Application, identifier + `com.automattic.vip-cli`, no staple on a bare Mach-O. - **macOS artifacts:** two signed + notarized bare binaries (arm64, amd64; online-verified). - **Windows / Linux artifacts:** signed `.exe` (Azure Trusted Signing via From 3cf479471cd6a81d2a0a806b8d4d4538ef9c6bc8 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 1 Sep 2026 14:22:32 +1000 Subject: [PATCH 24/24] Stamp vip-next as 5.0.0-dev. A 5.* tag is a Go release and is used verbatim. PRs and 4.x npm tags must not advertise a Node version, so they get 5.0.0-dev.. --- Generated with the help of Grok, https://grok.x.ai Co-Authored-By: Grok 4.6 --- .buildkite/build-linux.sh | 2 +- .buildkite/build-macos.sh | 2 +- .buildkite/build-windows.ps1 | 6 ++++-- Makefile | 2 +- cmd/stamp-version/main.go | 25 +++++++++++++++++++++++++ internal/version/stamp.go | 15 +++++++++++++++ internal/version/stamp_test.go | 25 +++++++++++++++++++++++++ 7 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 cmd/stamp-version/main.go create mode 100644 internal/version/stamp.go create mode 100644 internal/version/stamp_test.go diff --git a/.buildkite/build-linux.sh b/.buildkite/build-linux.sh index f6c54ab93..0ad594166 100755 --- a/.buildkite/build-linux.sh +++ b/.buildkite/build-linux.sh @@ -24,7 +24,7 @@ if ! command -v go >/dev/null 2>&1; then fi go version -VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)" +VERSION="$(go run -mod=mod ./cmd/stamp-version)" COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" build() { diff --git a/.buildkite/build-macos.sh b/.buildkite/build-macos.sh index 7e4afa5b2..94687183e 100755 --- a/.buildkite/build-macos.sh +++ b/.buildkite/build-macos.sh @@ -22,7 +22,7 @@ go version # Bundler installs to `vendor/bundle`, which makes Go take `-mod=vendor`. export GOFLAGS="${GOFLAGS:--mod=mod}" -VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)" +VERSION="$(go run -mod=mod ./cmd/stamp-version)" COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" build() { diff --git a/.buildkite/build-windows.ps1 b/.buildkite/build-windows.ps1 index 7157509fa..de51043ef 100644 --- a/.buildkite/build-windows.ps1 +++ b/.buildkite/build-windows.ps1 @@ -9,8 +9,6 @@ function Get-GitOr($cmd, $fallback) { try { $v = & git @cmd 2>$null; if ($LASTEXITCODE -eq 0 -and $v) { return $v.Trim() } } catch {} return $fallback } -$version = Get-GitOr @('describe','--tags','--always','--dirty') 'dev' -$commit = Get-GitOr @('rev-parse','--short','HEAD') 'unknown' New-Item -ItemType Directory -Force -Path dist | Out-Null $out = "dist/$binBase-windows-amd64.exe" @@ -26,6 +24,10 @@ if (-not (Get-Command go -ErrorAction SilentlyContinue)) { go version if ($LASTEXITCODE -ne 0) { throw 'go not usable after install' } +$version = (& go run -mod=mod ./cmd/stamp-version | Out-String).Trim() +if ($LASTEXITCODE -ne 0 -or -not $version) { throw 'stamp-version failed' } +$commit = Get-GitOr @('rev-parse','--short','HEAD') 'unknown' + Write-Host "--- :go: build windows/amd64" $env:CGO_ENABLED = '0'; $env:GOOS = 'windows'; $env:GOARCH = 'amd64' $ldflags = "-s -w -X github.com/Automattic/vip/internal/version.Version=$version -X github.com/Automattic/vip/internal/version.Commit=$commit" diff --git a/Makefile b/Makefile index 78ac8606f..4945fc2d5 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ GO ?= go GOFLAGS ?= LDFLAGS := -s -w \ - -X github.com/Automattic/vip/internal/version.Version=$(shell git describe --tags --always --dirty 2>/dev/null || echo dev) \ + -X github.com/Automattic/vip/internal/version.Version=$(shell GOFLAGS=-mod=mod $(GO) run ./cmd/stamp-version) \ -X github.com/Automattic/vip/internal/version.Commit=$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) BIN_DIR := bin diff --git a/cmd/stamp-version/main.go b/cmd/stamp-version/main.go new file mode 100644 index 000000000..dc1883544 --- /dev/null +++ b/cmd/stamp-version/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/Automattic/vip/internal/version" +) + +func main() { + tag := strings.TrimSpace(os.Getenv("BUILDKITE_TAG")) + if tag == "" { + out, err := exec.Command("git", "describe", "--tags", "--exact-match").Output() + if err == nil { + tag = strings.TrimSpace(string(out)) + } + } + sha := "unknown" + if out, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output(); err == nil { + sha = strings.TrimSpace(string(out)) + } + fmt.Print(version.Stamp(tag, sha)) +} diff --git a/internal/version/stamp.go b/internal/version/stamp.go new file mode 100644 index 000000000..793d75428 --- /dev/null +++ b/internal/version/stamp.go @@ -0,0 +1,15 @@ +package version + +import "strings" + +// Stamp is the Version ldflag. A 5.* git tag is a Go release; anything else +// (PRs, 4.x npm tags) is 5.0.0-dev.. +func Stamp(tag, shortCommit string) string { + if strings.HasPrefix(tag, "5.") { + return tag + } + if shortCommit == "" { + shortCommit = "unknown" + } + return "5.0.0-dev." + shortCommit +} diff --git a/internal/version/stamp_test.go b/internal/version/stamp_test.go new file mode 100644 index 000000000..830840231 --- /dev/null +++ b/internal/version/stamp_test.go @@ -0,0 +1,25 @@ +package version + +import "testing" + +func TestStamp(t *testing.T) { + t.Parallel() + + cases := []struct { + tag, sha, want string + }{ + {"5.0.0-beta1", "abc1234", "5.0.0-beta1"}, + {"5.0.0", "abc1234", "5.0.0"}, + {"5.1.0", "abc1234", "5.1.0"}, + {"4.1.1", "abc1234", "5.0.0-dev.abc1234"}, + {"", "abc1234", "5.0.0-dev.abc1234"}, + {"v5.0.0", "abc1234", "5.0.0-dev.abc1234"}, + {"", "", "5.0.0-dev.unknown"}, + } + for _, tc := range cases { + got := Stamp(tc.tag, tc.sha) + if got != tc.want { + t.Errorf("Stamp(%q, %q) = %q, want %q", tc.tag, tc.sha, got, tc.want) + } + } +}