Skip to content

Commit 95069a3

Browse files
[codex] Accept Tart project release formulas (#20)
* Accept Tart project release formulas * Update README.md * Update README.md * Restore required validation check name
1 parent d31253d commit 95069a3

4 files changed

Lines changed: 117 additions & 37 deletions

File tree

.github/workflows/auto-merge-goreleaser.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Auto-merge GoReleaser cask PRs
1+
name: Auto-merge GoReleaser Homebrew PRs
22

33
on:
44
workflow_run:
@@ -29,7 +29,7 @@ jobs:
2929
pr-number: ${{ steps.verify.outputs.pr-number }}
3030

3131
steps:
32-
- name: Verify PR is an eligible GoReleaser cask update
32+
- name: Verify PR is an eligible GoReleaser recipe update
3333
id: verify
3434
env:
3535
GH_TOKEN: ${{ github.token }}
@@ -45,25 +45,39 @@ jobs:
4545
exit 1
4646
fi
4747
48+
case "${VALIDATED_HEAD_BRANCH}" in
49+
openai-*) expected_path="Casks/openai.rb" ;;
50+
orchard-*) expected_path="Formula/orchard.rb" ;;
51+
softnet-*) expected_path="Formula/softnet.rb" ;;
52+
tart-guest-agent-*) expected_path="Formula/tart-guest-agent.rb" ;;
53+
tart-*) expected_path="Formula/tart.rb" ;;
54+
*)
55+
echo "Unexpected GoReleaser branch: ${VALIDATED_HEAD_BRANCH}"
56+
exit 1
57+
;;
58+
esac
59+
4860
data="$(gh pr view "${PR_NUMBER}" --repo "${REPO}" \
4961
--json author,baseRefName,body,files,headRefName,headRefOid,headRepositoryOwner,isCrossRepository,isDraft,state,statusCheckRollup)"
5062
5163
echo "${data}" | jq -e \
5264
--arg sha "${VALIDATED_SHA}" \
5365
--arg head_branch "${VALIDATED_HEAD_BRANCH}" \
66+
--arg expected_path "${expected_path}" \
5467
'
5568
.state == "OPEN" and
5669
.isDraft == false and
5770
.author.login == "app/openai-homebrew-releaser" and
5871
.baseRefName == "main" and
5972
.headRefName == $head_branch and
60-
(.headRefName | test("^openai-[0-9]+\\.[0-9]+\\.[0-9]+$")) and
73+
(.headRefName | test("^(openai|orchard|softnet|tart|tart-guest-agent)-[0-9]+\\.[0-9]+\\.[0-9]+$")) and
6174
.headRefOid == $sha and
6275
.headRepositoryOwner.login == "openai" and
6376
.isCrossRepository == false and
6477
((.body // "") | contains("Automated with [GoReleaser]")) and
6578
(.files | length == 1) and
66-
(.files[0].path == "Casks/openai.rb") and
79+
(.files[0].path == $expected_path) and
80+
((.files[0].changeType == "ADDED") or (.files[0].changeType == "MODIFIED")) and
6781
any(.statusCheckRollup[]?;
6882
.__typename == "CheckRun" and
6983
.workflowName == "Validate cask" and
@@ -74,7 +88,7 @@ jobs:
7488
'
7589
7690
changed_files="$(gh pr diff "${PR_NUMBER}" --repo "${REPO}" --name-only)"
77-
if [ "${changed_files}" != "Casks/openai.rb" ]; then
91+
if [ "${changed_files}" != "${expected_path}" ]; then
7892
echo "Unexpected changed files:"
7993
printf '%s\n' "${changed_files}"
8094
exit 1

.github/workflows/validate-cask.yml

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,42 +23,19 @@ jobs:
2323
persist-credentials: false
2424

2525
- name: Check Ruby syntax
26-
run: ruby -c Casks/openai.rb
26+
run: |
27+
recipe_directories=(Casks)
28+
if [ -d Formula ]; then
29+
recipe_directories+=(Formula)
30+
fi
31+
find "${recipe_directories[@]}" -name '*.rb' -type f -print0 | \
32+
xargs -0 -n1 ruby -c
2733
2834
- name: Tap local repository
2935
run: brew tap openai/tools "$GITHUB_WORKSPACE"
3036

3137
- name: Check Homebrew can load the tap
3238
run: brew readall openai/tools
3339

34-
- name: Validate generated cask structure
35-
run: |
36-
ruby <<'RUBY'
37-
cask = File.read("Casks/openai.rb")
38-
39-
checks = {
40-
"generated header" => cask.start_with?("# This file was generated by GoReleaser. DO NOT EDIT.\n"),
41-
"openai cask token" => cask.match?(/^cask "openai" do$/),
42-
"semantic version" => cask.match?(/^ version "\d+\.\d+\.\d+"$/),
43-
"homepage" => cask.include?(' homepage "https://developers.openai.com/api/docs"'),
44-
"binary" => cask.include?(' binary "openai"'),
45-
"manpage" => cask.include?(' manpage "man/man1/openai.1.gz"'),
46-
}
47-
48-
expected_artifacts = {
49-
"macos amd64 zip" => /openai_#\{version\}_macos_amd64\.zip/,
50-
"macos arm64 zip" => /openai_#\{version\}_macos_arm64\.zip/,
51-
"linux amd64 tarball" => /openai_#\{version\}_linux_amd64\.tar\.gz/,
52-
"linux arm64 tarball" => /openai_#\{version\}_linux_arm64\.tar\.gz/,
53-
}
54-
55-
expected_artifacts.each do |name, pattern|
56-
checks[name] = cask.match?(pattern)
57-
end
58-
59-
checks["release host"] = cask.scan(%r{https://github\.com/openai/openai-cli/releases/download/v#\{version\}/}).length == 4
60-
checks["sha256 count"] = cask.scan(/^\s+sha256 "[a-f0-9]{64}"$/).length == 4
61-
62-
failed = checks.select { |_name, passed| !passed }.keys
63-
abort("Cask validation failed: #{failed.join(', ')}") if failed.any?
64-
RUBY
40+
- name: Validate generated recipe structure
41+
run: ruby scripts/validate_recipes.rb

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ brew install openai/tools/openai
1212

1313
The cask definition is generated by the OpenAI CLI release workflow.
1414

15+
## Tart-related projects
16+
17+
[Tart](https://github.com/openai/tart)-related projects publish the following formulas:
18+
19+
```sh
20+
brew install openai/tools/tart
21+
brew install openai/tools/softnet
22+
brew install openai/tools/orchard
23+
brew install openai/tools/tart-guest-agent
24+
```
25+
1526
## License
1627

1728
Copyright 2026 OpenAI

scripts/validate_recipes.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# frozen_string_literal: true
2+
3+
require "uri"
4+
5+
def validate(name, checks)
6+
failed = checks.reject { |_description, passed| passed }.keys
7+
abort("#{name} validation failed: #{failed.join(', ')}") unless failed.empty?
8+
end
9+
10+
cask = File.read("Casks/openai.rb")
11+
cask_checks = {
12+
"generated header" => cask.start_with?("# This file was generated by GoReleaser. DO NOT EDIT.\n"),
13+
"openai cask token" => cask.match?(/^cask "openai" do$/),
14+
"semantic version" => cask.match?(/^ version "\d+\.\d+\.\d+"$/),
15+
"homepage" => cask.include?(' homepage "https://developers.openai.com/api/docs"'),
16+
"binary" => cask.include?(' binary "openai"'),
17+
"manpage" => cask.include?(' manpage "man/man1/openai.1.gz"'),
18+
"macos amd64 zip" => cask.match?(/openai_#\{version\}_macos_amd64\.zip/),
19+
"macos arm64 zip" => cask.match?(/openai_#\{version\}_macos_arm64\.zip/),
20+
"linux amd64 tarball" => cask.match?(/openai_#\{version\}_linux_amd64\.tar\.gz/),
21+
"linux arm64 tarball" => cask.match?(/openai_#\{version\}_linux_arm64\.tar\.gz/),
22+
"release host" => cask.scan(%r{https://github\.com/openai/openai-cli/releases/download/v#\{version\}/}).length == 4,
23+
"sha256 count" => cask.scan(/^\s+sha256 "[a-f0-9]{64}"$/).length == 4,
24+
}
25+
validate("Casks/openai.rb", cask_checks)
26+
27+
formulas = {
28+
"Formula/orchard.rb" => {
29+
class_name: "Orchard",
30+
repository: "orchard",
31+
install: 'bin.install "orchard"',
32+
},
33+
"Formula/softnet.rb" => {
34+
class_name: "Softnet",
35+
repository: "softnet",
36+
install: 'bin.install "softnet"',
37+
},
38+
"Formula/tart-guest-agent.rb" => {
39+
class_name: "TartGuestAgent",
40+
repository: "tart-guest-agent",
41+
install: 'bin.install "tart-guest-agent"',
42+
},
43+
"Formula/tart.rb" => {
44+
class_name: "Tart",
45+
repository: "tart",
46+
install: 'bin.write_exec_script "#{libexec}/tart.app/Contents/MacOS/tart"',
47+
dependency: 'depends_on "openai/tools/softnet"',
48+
},
49+
}
50+
51+
unknown_formulas = Dir["Formula/*.rb"] - formulas.keys
52+
abort("Unexpected formula: #{unknown_formulas.join(', ')}") unless unknown_formulas.empty?
53+
54+
formulas.each do |path, expected|
55+
next unless File.exist?(path)
56+
57+
formula = File.read(path)
58+
version = formula[/^\s*version "(\d+\.\d+\.\d+)"$/, 1]
59+
urls = formula.scan(/^\s*url "([^"]+)"$/).flatten
60+
shas = formula.scan(/^\s*sha256 "([a-f0-9]+)"$/).flatten
61+
release_url = if version
62+
%r{\Ahttps://github\.com/openai/#{Regexp.escape(expected[:repository])}/releases/download/v?#{Regexp.escape(version)}/[^/]+\z}
63+
end
64+
artifact_name = %r{/#{Regexp.escape(expected[:repository])}(?:[-_.][^/]+)?\z}
65+
66+
checks = {
67+
"generated header" => formula.include?("# This file was generated by GoReleaser. DO NOT EDIT.\n"),
68+
"formula class" => formula.match?(/^class #{expected[:class_name]} < Formula$/),
69+
"semantic version" => !version.nil?,
70+
"homepage" => formula.include?(%Q(homepage "https://github.com/openai/#{expected[:repository]}")),
71+
"release URLs" => !urls.empty? && release_url && urls.all? { |url| url.match?(release_url) },
72+
"artifact names" => !urls.empty? && urls.all? { |url| URI.decode_www_form_component(url).match?(artifact_name) },
73+
"sha256 values" => shas.length == urls.length && shas.all? { |sha| sha.match?(/\A[a-f0-9]{64}\z/) },
74+
"binary install" => formula.include?(expected[:install]),
75+
"tap dependency" => !expected[:dependency] || formula.include?(expected[:dependency]),
76+
}
77+
validate(path, checks)
78+
end

0 commit comments

Comments
 (0)