Files
codex-proxy-rs/.github/scripts/published-channels.rb
T

106 lines
4.2 KiB
Ruby

require 'base64'
require 'uri'
require_relative 'release-metadata'
# 各发行线共用 main 通道表;只合并本次已核实的版本
module PublishedChannels
PATH = 'release/version.yaml'
LIMIT = 128 * 1024
def self.advance(document, version)
version = ReleaseMetadata.version(version)
major, = version.split('.', 2)
core, stage = version.split('+', 2).first.split('-', 2)
channels = (document.fetch('published')[major] ||= {})
if stage&.start_with?('exp.')
channels = (channels['exp'] ||= {})
keys = [core]
else
stages = %w[stable rc beta alpha]
keys = stages.drop(stages.index(stage ? stage.split('.').first : 'stable'))
end
incoming = ReleaseMetadata.sort_key(version)
changed = false
keys.each do |key|
current = channels[key]
if current
comparison = incoming <=> ReleaseMetadata.sort_key(current)
raise "Conflicting release identities: #{current}, #{version}" if comparison.zero? && current != version
next unless comparison.positive?
end
channels[key] = version
changed = true
end
changed
end
class Publisher
def api(endpoint, method: 'GET', data: nil, optional: false)
args = ['gh', 'api', endpoint, '--method', method]
args.concat(%w[--input -]) if data
out, error, status = Open3.capture3(*args, stdin_data: data ? JSON.generate(data) : '')
return nil if optional && !status.success? && error.include?('(HTTP 404)')
raise "Cannot access #{endpoint}: #{error.strip}" unless status.success?
JSON.parse(out)
end
def initialize(repository)
@base = "repos/#{repository}"
end
def find_release(version)
api("#{@base}/releases/tags/v#{ReleaseMetadata.version(version)}", optional: true)
end
def verify_assets(version)
release = find_release(version)
tag = "v#{version}"
raise "Release is not public or tag differs: #{tag}" unless release && release.fetch('tag_name') == tag &&
!release.fetch('draft') && release.fetch('prerelease') == ReleaseMetadata.prerelease?(version)
data = api("#{@base}/contents/release/platforms.yaml?ref=#{URI.encode_www_form_component(tag)}")
platforms = YAML.safe_load(Base64.decode64(data.fetch('content'))).fetch('platforms')
raise "Missing platforms for #{tag}" if platforms.empty?
expected = platforms.map { |p| ReleaseMetadata.archive_name(version, p) }
expected.concat(%w[checksums.txt compose.yaml config.example.yaml])
uploaded = release.fetch('assets').filter_map do |asset|
asset.fetch('name') if asset.fetch('state') == 'uploaded' && asset.fetch('size').positive?
end
missing = expected - uploaded
raise "Incomplete release #{tag}: #{missing.join(', ')}" unless missing.empty?
end
def check_unpublished(version)
existing = find_release(version)
raise "Release v#{version} already public; use index-only recovery" if existing && !existing.fetch('draft')
end
def publish(version)
verify_assets(version)
# 读取 main 的当前文件,实验分支和较晚结束的旧构建都不能覆盖其他通道或构建版本
endpoint = "#{@base}/contents/#{PATH}"
file = api("#{endpoint}?ref=main")
document = YAML.safe_load(Base64.decode64(file.fetch('content')))
return unless PublishedChannels.advance(document, version)
content = YAML.dump(document).delete_prefix("---\n")
raise 'Published index exceeds the client size limit' if content.bytesize > LIMIT
# 按读取的文件 SHA 写入;并发备版改过同一文件时交给恢复任务重新合并
api(endpoint, method: 'PUT', data: {
branch: 'main', sha: file.fetch('sha'), content: Base64.strict_encode64(content),
message: "chore(release): update release channels to #{version}"
})
end
end
end
if $PROGRAM_NAME == __FILE__
mode, raw = ARGV
version = ReleaseMetadata.version(raw || '')
publisher = PublishedChannels::Publisher.new(ENV.fetch('GITHUB_REPOSITORY'))
case mode
when 'check' then publisher.check_unpublished(version)
when 'publish' then publisher.publish(version)
else abort 'usage: published-channels.rb check|publish VERSION'
end
end