mirror of
https://github.com/larksuite/cli.git
synced 2026-08-03 08:32:46 +08:00
131 lines
4.6 KiB
Bash
Executable File
131 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
set -euo pipefail
|
|
|
|
# This verifies the release workflow's declarative contract. The shell commands
|
|
# inside individual steps are exercised by the beta release rehearsal instead.
|
|
ruby -ryaml <<'RUBY'
|
|
workflow = YAML.load_file(".github/workflows/release.yml")
|
|
goreleaser = YAML.load_file(".goreleaser.yml")
|
|
|
|
def fail(message)
|
|
abort("release workflow contract: #{message}")
|
|
end
|
|
|
|
def expect_equal(actual, expected, description)
|
|
return if actual == expected
|
|
fail("#{description}; expected #{expected.inspect}, got #{actual.inspect}")
|
|
end
|
|
|
|
def scalar_values(value)
|
|
case value
|
|
when Hash then value.values.flat_map { |item| scalar_values(item) }
|
|
when Array then value.flat_map { |item| scalar_values(item) }
|
|
else [value]
|
|
end
|
|
end
|
|
|
|
def action_references(value)
|
|
case value
|
|
when Hash
|
|
value.flat_map { |key, item| key == "uses" ? [item] : action_references(item) }
|
|
when Array
|
|
value.flat_map { |item| action_references(item) }
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
jobs = workflow.fetch("jobs")
|
|
expected_jobs = %w[preflight build-sign-notarize create-draft-release verify-macos publish-github publish-npm]
|
|
expect_equal(jobs.keys.sort, expected_jobs.sort, "release jobs")
|
|
|
|
expect_equal(workflow.fetch("concurrency"), {
|
|
"group" => "release-${{ github.ref_name }}",
|
|
"cancel-in-progress" => false,
|
|
}, "release concurrency")
|
|
|
|
expected_needs = {
|
|
"preflight" => nil,
|
|
"build-sign-notarize" => "preflight",
|
|
"create-draft-release" => %w[preflight build-sign-notarize],
|
|
"verify-macos" => %w[preflight create-draft-release],
|
|
"publish-github" => %w[preflight create-draft-release verify-macos],
|
|
"publish-npm" => %w[preflight build-sign-notarize publish-github],
|
|
}
|
|
expected_needs.each do |job_name, needs|
|
|
expect_equal(jobs.fetch(job_name)["needs"], needs, "#{job_name} dependencies")
|
|
end
|
|
|
|
expected_permissions = {
|
|
"preflight" => { "contents" => "read" },
|
|
"build-sign-notarize" => { "contents" => "read" },
|
|
"create-draft-release" => { "contents" => "write" },
|
|
"verify-macos" => { "contents" => "write" },
|
|
"publish-github" => { "contents" => "write" },
|
|
"publish-npm" => { "contents" => "read", "id-token" => "write" },
|
|
}
|
|
expected_permissions.each do |job_name, permissions|
|
|
expect_equal(jobs.fetch(job_name)["permissions"], permissions, "#{job_name} permissions")
|
|
end
|
|
|
|
signing_references = %w[
|
|
secrets.MACOS_SIGN_P12
|
|
secrets.MACOS_SIGN_PASSWORD
|
|
secrets.MACOS_NOTARY_KEY
|
|
vars.MACOS_NOTARY_KEY_ID
|
|
vars.MACOS_NOTARY_ISSUER_ID
|
|
]
|
|
team_reference = "vars.MACOS_TEAM_ID"
|
|
jobs.each do |job_name, job|
|
|
references = scalar_values(job).grep(String).flat_map do |value|
|
|
(signing_references + [team_reference]).select { |reference| value.include?(reference) }
|
|
end.uniq.sort
|
|
expected_references = case job_name
|
|
when "build-sign-notarize" then signing_references + [team_reference]
|
|
when "verify-macos" then [team_reference]
|
|
else []
|
|
end
|
|
expect_equal(
|
|
references,
|
|
expected_references.sort,
|
|
"#{job_name} Apple credential scope",
|
|
)
|
|
end
|
|
|
|
macos = jobs.fetch("verify-macos")
|
|
expect_equal(macos.fetch("strategy").fetch("matrix").fetch("include"), [
|
|
{ "runner" => "macos-15-intel", "arch" => "amd64" },
|
|
{ "runner" => "macos-15", "arch" => "arm64" },
|
|
], "macOS verification matrix")
|
|
expect_equal(macos.fetch("runs-on"), "${{ matrix.runner }}", "macOS matrix runner")
|
|
|
|
npm_steps = jobs.fetch("publish-npm").fetch("steps")
|
|
pinned_npm = npm_steps.find { |step| step["name"] == "Install pinned npm" }
|
|
fail("publish-npm must install npm 11.16.0 for trusted publishing") unless pinned_npm&.fetch("run", nil) == "npm install --global npm@11.16.0"
|
|
|
|
action_references(workflow).each do |reference|
|
|
fail("action is not pinned to a full commit SHA: #{reference}") unless reference.match?(%r{\A[^@]+@[0-9a-f]{40}\z})
|
|
end
|
|
|
|
notarize = goreleaser.fetch("notarize").fetch("macos")
|
|
expect_equal(notarize.length, 1, "number of macOS notarization configurations")
|
|
macos_notarize = notarize.first
|
|
expect_equal(macos_notarize.fetch("ids"), ["lark-cli"], "notarized build IDs")
|
|
expect_equal(macos_notarize.fetch("sign"), {
|
|
"certificate" => "{{ .Env.MACOS_SIGN_P12 }}",
|
|
"password" => "{{ .Env.MACOS_SIGN_PASSWORD }}",
|
|
}, "macOS signing inputs")
|
|
expect_equal(macos_notarize.fetch("notarize"), {
|
|
"issuer_id" => "{{ .Env.MACOS_NOTARY_ISSUER_ID }}",
|
|
"key_id" => "{{ .Env.MACOS_NOTARY_KEY_ID }}",
|
|
"key" => "{{ .Env.MACOS_NOTARY_KEY_PATH }}",
|
|
"wait" => true,
|
|
"timeout" => "20m",
|
|
}, "macOS notarization inputs")
|
|
|
|
puts "release workflow contract passed"
|
|
RUBY
|