#!/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby -w # == Synopsis # # upload [-k «keyfile»] [-d «destination»] [-m «json»] «file» # # == Usage # # --help: # show help. # # --keyfile/-k: # Keyfile used for signing. # # --destination/-d: # Which GitHub ‘«user»/«repository»’ we should upload to. # # --merge/-m: # JSON that should be placed in the result dictionary. # # --description/-s: # Optional description. # require 'getoptlong' require "rubygems" require "net/netrc" require "json" require 'base64' require 'openssl' require 'digest/sha1' def sign_file(path, keyfile, password) # %x{openssl dgst -dss1 -sign '#{keyfile}' -passin 'pass:#{password}' '#{path}'|openssl enc -base64}.chomp key = OpenSSL::PKey::DSA.new(File.read(keyfile), password) or abort "*** error reading keyfile: ‘#{keyfile}’." digest = Digest::SHA1.digest(File.read(path)) signature = key.syssign(digest) Base64.encode64(signature).gsub("\n", '') end def create_release(path, repository, tag, description, content_type) name = tag.sub(/^v(.*?)-(.*?)\.(.*)$/, 'TextMate \1 (\2 \3)') basename = File::basename(path) payload = { 'name' => name, 'tag_name' => tag, 'draft' => false, 'prerelease' => true } payload['body'] = description unless description.nil? open("|curl -snd '#{payload.to_json}' https://api.github.com/repos/#{repository}/releases") do |response| github = JSON.parse(response.read) if github.include?('errors') err = github['errors'].first if err['code'] == 'already_exists' && err['field'] == 'tag_name' open("|curl -sn https://api.github.com/repos/#{repository}/releases") do |io| github = JSON.parse(io.read) github = github.find { |e| e['tag_name'] == tag } STDERR << "Upload to existing release: #{github['html_url']}\n" end else abort "GitHub error: #{github['errors'].inspect} for #{repository}" if github.include?('errors') end end if url = github['upload_url'] url.sub!(/\{\?.*?\}/, '') upload_url = "|curl -nH'Content-Type: #{content_type}' --data-binary @'#{path}' '#{url}?name=#{basename}'" open(upload_url) do |io| res = JSON.parse(io.read) return "https://github.com/#{repository}/releases/download/#{tag}/#{basename}" if res.include?('url') STDERR << "Unexpected response (asset): #{res.inspect}\n" end else STDERR << "Unexpected response (release): #{github.inspect}\n" end open("|curl -snX DELETE #{github['url']}") { |io| STDERR << "Removing release: #{github['url']}\n" } end abort end if __FILE__ == $PROGRAM_NAME opts = GetoptLong.new( [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--keyfile', '-k', GetoptLong::REQUIRED_ARGUMENT ], [ '--destination', '-d', GetoptLong::REQUIRED_ARGUMENT ], [ '--tag', '-t', GetoptLong::REQUIRED_ARGUMENT ], [ '--merge', '-m', GetoptLong::REQUIRED_ARGUMENT ], [ '--description', '-s', GetoptLong::REQUIRED_ARGUMENT ] ) keyfile = nil destination = 'textmate/textmate' tag = nil base_info = { } description = nil netinfo = Net::Netrc.locate("sign.textmate.org") or abort "*** missing passphrase in ~/.netrc." opts.each do |opt, arg| case opt when '--help' then RDoc::usage when '--keyfile' then keyfile = arg when '--destination' then destination = arg when '--tag' then tag = arg when '--merge' then base_info = JSON.parse(arg) when '--description' then description = arg end end abort 'No signing key provided' if keyfile.nil? abort "No tag specified" if tag.nil? if path = ARGV.shift signature = sign_file(path, keyfile, netinfo.password) info = base_info.merge({ 'url' => create_release(path, destination, tag, description, 'application/x-bzip2'), 'signature' => signature, 'signee' => netinfo.login }) STDOUT << info.to_json end end