krausefx-shenzhen 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ private
2
+
3
+ def determine_file!
4
+ files = Dir['*.ipa']
5
+ @file ||= case files.length
6
+ when 0 then nil
7
+ when 1 then files.first
8
+ else
9
+ @file = choose "Select an .ipa File:", *files
10
+ end
11
+ end
12
+
13
+ def determine_dsym!
14
+ dsym_files = Dir['*.dSYM.zip']
15
+ @dsym ||= case dsym_files.length
16
+ when 0 then nil
17
+ when 1 then dsym_files.first
18
+ else
19
+ dsym_files.detect do |dsym|
20
+ File.basename(dsym, ".app.dSYM.zip") == File.basename(@file, ".ipa")
21
+ end or choose "Select a .dSYM.zip file:", *dsym_files
22
+ end
23
+ end
24
+
25
+ def determine_notes!
26
+ placeholder = %{What's new in this release: }
27
+
28
+ @notes = ask_editor placeholder
29
+ @notes = nil if @notes == placeholder
30
+ end
@@ -0,0 +1,94 @@
1
+ require 'plist'
2
+ require 'tempfile'
3
+ require 'zip'
4
+ require 'zip/filesystem'
5
+
6
+ command :info do |c|
7
+ c.syntax = 'ipa info [options]'
8
+ c.summary = 'Show mobile provisioning information about an .ipa file'
9
+ c.description = ''
10
+
11
+ c.action do |args, options|
12
+ say_error "`security` command not found in $PATH" and abort if `which security` == ""
13
+ say_error "`codesign` command not found in $PATH" and abort if `which codesign` == ""
14
+
15
+ determine_file! unless @file = args.pop
16
+ say_error "Missing or unspecified .ipa file" and abort unless @file and ::File.exist?(@file)
17
+
18
+ Zip::File.open(@file) do |zipfile|
19
+ app_entry = zipfile.find_entry("Payload/#{File.basename(@file, File.extname(@file))}.app")
20
+ provisioning_profile_entry = zipfile.find_entry("#{app_entry.name}embedded.mobileprovision") if app_entry
21
+
22
+ if (!provisioning_profile_entry)
23
+ zipfile.dir.entries("Payload").each do |dir_entry|
24
+ if dir_entry =~ /.app$/
25
+ say "Using .app: #{dir_entry}"
26
+ app_entry = zipfile.find_entry("Payload/#{dir_entry}")
27
+ provisioning_profile_entry = zipfile.find_entry("#{app_entry.name}embedded.mobileprovision") if app_entry
28
+ break
29
+ end
30
+ end
31
+ end
32
+
33
+ say_error "Embedded mobile provisioning file not found in #{@file}" and abort unless provisioning_profile_entry
34
+
35
+ tempdir = ::File.new(Dir.mktmpdir)
36
+ begin
37
+ zipfile.each do |zip_entry|
38
+ temp_entry_path = ::File.join(tempdir.path, zip_entry.name)
39
+
40
+ FileUtils.mkdir_p(::File.dirname(temp_entry_path))
41
+ zipfile.extract(zip_entry, temp_entry_path) unless ::File.exist?(temp_entry_path)
42
+ end
43
+
44
+ temp_provisioning_profile = ::File.new(::File.join(tempdir.path, provisioning_profile_entry.name))
45
+ temp_app_directory = ::File.new(::File.join(tempdir.path, app_entry.name))
46
+
47
+ plist = Plist::parse_xml(`security cms -D -i #{temp_provisioning_profile.path}`)
48
+
49
+ codesign = `codesign -dv "#{temp_app_directory.path}" 2>&1`
50
+ codesigned = /Signed Time/ === codesign
51
+
52
+ table = Terminal::Table.new do |t|
53
+ plist.each do |key, value|
54
+ next if key == "DeveloperCertificates"
55
+
56
+ columns = []
57
+ columns << key
58
+ columns << case value
59
+ when Hash
60
+ value.collect{|k, v| "#{k}: #{v}"}.join("\n")
61
+ when Array
62
+ value.join("\n")
63
+ else
64
+ value.to_s
65
+ end
66
+
67
+ t << columns
68
+ end
69
+
70
+ t << ["Codesigned", codesigned.to_s.capitalize]
71
+ end
72
+
73
+ puts table
74
+
75
+ rescue => e
76
+ say_error e.message
77
+ ensure
78
+ FileUtils.remove_entry_secure tempdir
79
+ end
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def determine_file!
86
+ files = Dir['*.ipa']
87
+ @file ||= case files.length
88
+ when 0 then nil
89
+ when 1 then files.first
90
+ else
91
+ @file = choose "Select an .ipa File:", *files
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,9 @@
1
+ module Shenzhen::PlistBuddy
2
+ class << self
3
+ def print(file, key)
4
+ output = `/usr/libexec/PlistBuddy -c "Print :#{key}" "#{file}" 2> /dev/null`
5
+
6
+ !output || output.empty? || /Does Not Exist/ === output ? nil : output.strip
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,82 @@
1
+ require 'pathname'
2
+
3
+ module Shenzhen::Plugins
4
+ module Crashlytics
5
+ class Client
6
+
7
+ def initialize(crashlytics_path, api_token, build_secret)
8
+ @api_token, @build_secret = api_token, build_secret
9
+
10
+ @crashlytics_path = Pathname.new("#{crashlytics_path}/submit").cleanpath.to_s
11
+ say_error "Path to Crashlytics.framework/submit is invalid" and abort unless File.exists?(@crashlytics_path)
12
+ end
13
+
14
+ def upload_build(ipa, options)
15
+ command = "#{@crashlytics_path} #{@api_token} #{@build_secret} -ipaPath '#{options[:file]}'"
16
+ command += " -notesPath '#{options[:notes]}'" if options[:notes]
17
+ command += " -emails #{options[:emails]}" if options[:emails]
18
+ command += " -groupAliases #{options[:groups]}" if options[:groups]
19
+ command += " -notifications #{options[:notifications] ? 'YES' : 'NO'}"
20
+
21
+ system command
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ command :'distribute:crashlytics' do |c|
28
+ c.syntax = "ipa distribute:crashlytics [options]"
29
+ c.summary = "Distribute an .ipa file over Crashlytics"
30
+ c.description = ""
31
+ c.option '-c', '--crashlytics_path PATH', "/path/to/Crashlytics.framework/"
32
+ c.option '-f', '--file FILE', ".ipa file for the build"
33
+ c.option '-a', '--api_token TOKEN', "API Token. Available at https://www.crashlytics.com/settings/organizations"
34
+ c.option '-s', '--build_secret SECRET', "Build Secret. Available at https://www.crashlytics.com/settings/organizations"
35
+ c.option '-m', '--notes PATH', "Path to release notes file"
36
+ c.option '-e', '--emails EMAIL1,EMAIL2', "Emails of users for access"
37
+ c.option '-g', '--groups GROUPS', "Groups for users for access"
38
+ c.option '-n', '--notifications [YES | NO]', "Should send notification email to testers?"
39
+
40
+ c.action do |args, options|
41
+ determine_file! unless @file = options.file
42
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
43
+
44
+ determine_crashlytics_path! unless @crashlytics_path = options.crashlytics_path || ENV['CRASHLYTICS_FRAMEWORK_PATH']
45
+ say_error "Missing path to Crashlytics.framework" and abort unless @crashlytics_path
46
+
47
+ determine_crashlytics_api_token! unless @api_token = options.api_token || ENV['CRASHLYTICS_API_TOKEN']
48
+ say_error "Missing API Token" and abort unless @api_token
49
+
50
+ determine_crashlytics_build_secret! unless @build_secret = options.build_secret || ENV['CRASHLYTICS_BUILD_SECRET']
51
+ say_error "Missing Build Secret" and abort unless @build_secret
52
+
53
+ parameters = {}
54
+ parameters[:file] = @file
55
+ parameters[:notes] = options.notes if options.notes
56
+ parameters[:emails] = options.emails if options.emails
57
+ parameters[:groups] = options.groups if options.groups
58
+ parameters[:notifications] = options.notifications == 'YES' if options.notifications
59
+
60
+ client = Shenzhen::Plugins::Crashlytics::Client.new(@crashlytics_path, @api_token, @build_secret)
61
+
62
+ if client.upload_build(@file, parameters)
63
+ say_ok "Build successfully uploaded to Crashlytics"
64
+ else
65
+ say_error "Error uploading to Crashlytics" and abort
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def determine_crashlytics_path!
72
+ @crashlytics_path ||= ask "Path to Crashlytics.framework:"
73
+ end
74
+
75
+ def determine_crashlytics_api_token!
76
+ @api_token ||= ask "API Token:"
77
+ end
78
+
79
+ def determine_crashlytics_build_secret!
80
+ @build_secret ||= ask "Build Secret:"
81
+ end
82
+ end
@@ -0,0 +1,97 @@
1
+ require 'json'
2
+ require 'openssl'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module Shenzhen::Plugins
7
+ module DeployGate
8
+ class Client
9
+ HOSTNAME = 'deploygate.com'
10
+
11
+ def initialize(api_token, user_name)
12
+ @api_token, @user_name = api_token, user_name
13
+ @connection = Faraday.new(:url => "https://#{HOSTNAME}", :request => { :timeout => 120 }) do |builder|
14
+ builder.request :multipart
15
+ builder.request :json
16
+ builder.response :json, :content_type => /\bjson$/
17
+ builder.use FaradayMiddleware::FollowRedirects
18
+ builder.adapter :net_http
19
+ end
20
+ end
21
+
22
+ def upload_build(ipa, options)
23
+ options.update({
24
+ :token => @api_token,
25
+ :file => Faraday::UploadIO.new(ipa, 'application/octet-stream'),
26
+ :message => options[:message] || ''
27
+ })
28
+
29
+ @connection.post("/api/users/#{@user_name}/apps", options).on_complete do |env|
30
+ yield env[:status], env[:body] if block_given?
31
+ end
32
+
33
+ rescue Faraday::Error::TimeoutError
34
+ say_error "Timed out while uploading build. Check https://deploygate.com/ to see if the upload was completed." and abort
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ command :'distribute:deploygate' do |c|
41
+ c.syntax = "ipa distribute:deploygate [options]"
42
+ c.summary = "Distribute an .ipa file over deploygate"
43
+ c.description = ""
44
+ c.option '-f', '--file FILE', ".ipa file for the build"
45
+ c.option '-a', '--api_token TOKEN', "API Token. Available at https://deploygate.com/settings"
46
+ c.option '-u', '--user_name USER_NAME', "User Name. Available at https://deploygate.com/settings"
47
+ c.option '-m', '--message MESSAGE', "Release message for the build"
48
+ c.option '-d', '--distribution_key DESTRIBUTION_KEY', "distribution key for distribution page"
49
+ c.option '-n', '--disable_notify', "disable notification"
50
+ c.option '-r', '--release_note RELEASE_NOTE', "release note for distribution page"
51
+ c.option '-v', '--visibility (private|public)', "privacy setting ( require public for personal free account)"
52
+
53
+ c.action do |args, options|
54
+ determine_file! unless @file = options.file
55
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
56
+
57
+ determine_deploygate_api_token! unless @api_token = options.api_token || ENV['DEPLOYGATE_API_TOKEN']
58
+ say_error "Missing API Token" and abort unless @api_token
59
+
60
+ determine_deploygate_user_name! unless @user_name = options.user_name || ENV['DEPLOYGATE_USER_NAME']
61
+ say_error "Missing User Name" and abort unless @api_token
62
+
63
+ @message = options.message
64
+ @distribution_key = options.distribution_key || ENV['DEPLOYGATE_DESTRIBUTION_KEY']
65
+ @release_note = options.release_note
66
+ @disable_notify = ! options.disable_notify.nil? ? "yes" : nil
67
+ @visibility = options.visibility
68
+ @message = options.message
69
+
70
+ parameters = {}
71
+ parameters[:file] = @file
72
+ parameters[:message] = @message
73
+ parameters[:distribution_key] = @distribution_key if @distribution_key
74
+ parameters[:release_note] = @release_note if @release_note
75
+ parameters[:disable_notify] = @disable_notify if @disable_notify
76
+ parameters[:visibility] = @visibility if @visibility
77
+ parameters[:replace] = "true" if options.replace
78
+
79
+ client = Shenzhen::Plugins::DeployGate::Client.new(@api_token, @user_name)
80
+ response = client.upload_build(@file, parameters)
81
+ if (200...300) === response.status and not response.body["error"]
82
+ say_ok "Build successfully uploaded to DeployGate"
83
+ else
84
+ say_error "Error uploading to DeployGate: #{response.body["error"] || "(Unknown Error)"}" and abort
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def determine_deploygate_api_token!
91
+ @api_token ||= ask "API Token:"
92
+ end
93
+
94
+ def determine_deploygate_user_name!
95
+ @user_name ||= ask "User Name:"
96
+ end
97
+ end
@@ -0,0 +1,145 @@
1
+ require 'json'
2
+ require 'openssl'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module Shenzhen::Plugins
7
+ module Fir
8
+ class Client
9
+ HOSTNAME = 'fir.im'
10
+ VERSION = 'v2'
11
+
12
+ def initialize(user_token)
13
+ @user_token = user_token
14
+
15
+ @connection = Faraday.new(:url => "http://#{HOSTNAME}") do |builder|
16
+ builder.request :url_encoded
17
+ builder.response :json
18
+ builder.use FaradayMiddleware::FollowRedirects
19
+ builder.adapter :net_http
20
+ end
21
+ end
22
+
23
+ def get_app_info(app_id)
24
+ options = {
25
+ :type => 'ios',
26
+ :token => @user_token,
27
+ }
28
+
29
+ @connection.get("/api/#{VERSION}/app/info/#{app_id}", options) do |env|
30
+ yield env[:status], env[:body] if block_given?
31
+ end
32
+ rescue Faraday::Error::TimeoutError
33
+ say_error "Timed out while geting app info." and abort
34
+ end
35
+
36
+ def update_app_info(app_id, options)
37
+ @connection.put("/api/#{VERSION}/app/#{app_id}?token=#{@user_token}", options) do |env|
38
+ yield env[:status], env[:body] if block_given?
39
+ end
40
+ rescue Faraday::Error::TimeoutError
41
+ say_error "Timed out while geting app info." and abort
42
+ end
43
+
44
+ def upload_build(ipa, options)
45
+ connection = Faraday.new(:url => options['url'], :request => { :timeout => 360 }) do |builder|
46
+ builder.request :multipart
47
+ builder.response :json
48
+ builder.use FaradayMiddleware::FollowRedirects
49
+ builder.adapter :net_http
50
+ end
51
+
52
+ options = {
53
+ :key => options['key'],
54
+ :token => options['token'],
55
+ :file => Faraday::UploadIO.new(ipa, 'application/octet-stream')
56
+ }
57
+
58
+ connection.post('/', options).on_complete do |env|
59
+ yield env[:status], env[:body] if block_given?
60
+ end
61
+ rescue Errno::EPIPE
62
+ say_error "Upload failed. Check internet connection is ok." and abort
63
+ rescue Faraday::Error::TimeoutError
64
+ say_error "Timed out while uploading build. Check https://fir.im// to see if the upload was completed." and abort
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ command :'distribute:fir' do |c|
71
+ c.syntax = "ipa distribute:fir [options]"
72
+ c.summary = "Distribute an .ipa file over fir.im"
73
+ c.description = ""
74
+ c.option '-f', '--file FILE', ".ipa file for the build"
75
+ c.option '-u', '--user_token TOKEN', "User Token. Available at http://fir.im/user/info"
76
+ c.option '-a', '--app_id APPID', "App Id (iOS Bundle identifier)"
77
+ c.option '-n', '--notes NOTES', "Release notes for the build"
78
+ c.option '-V', '--app_version VERSION', "App Version"
79
+ c.option '-S', '--short_version SHORT', "App Short Version"
80
+
81
+ c.action do |args, options|
82
+ determine_file! unless @file = options.file
83
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
84
+
85
+ determine_fir_user_token! unless @user_token = options.user_token || ENV['FIR_USER_TOKEN']
86
+ say_error "Missing User Token" and abort unless @user_token
87
+
88
+ determine_fir_app_id! unless @app_id = options.app_id || ENV['FIR_APP_ID']
89
+ say_error "Missing App Id" and abort unless @app_id
90
+
91
+ determine_notes! unless @notes = options.notes
92
+ say_error "Missing release notes" and abort unless @notes
93
+
94
+ determine_app_version! unless @app_version = options.app_version
95
+
96
+ determine_short_version! unless @short_version = options.short_version
97
+
98
+ client = Shenzhen::Plugins::Fir::Client.new(@user_token)
99
+ app_response = client.get_app_info(@app_id)
100
+ if app_response.status == 200
101
+ upload_response = client.upload_build(@file, app_response.body['bundle']['pkg'])
102
+
103
+ if upload_response.status == 200
104
+ oid = upload_response.body['appOid']
105
+ today = Time.now.strftime('%Y-%m-%d %H:%M:%S')
106
+ @notes ||= "Upload on #{today}"
107
+
108
+ app_response = client.update_app_info(oid, {
109
+ :changelog => @notes,
110
+ :version => @app_version,
111
+ :versionShort => @short_version
112
+ })
113
+
114
+ if app_response.status == 200
115
+ app_short_uri = app_response.body['short']
116
+ say_ok "Build successfully uploaded to Fir, visit url: http://fir.im/#{app_short_uri}"
117
+ else
118
+ say_error "Error updating build information: #{app_response.body[:error]}" and abort
119
+ end
120
+ else
121
+ say_error "Error uploading to Fir: #{upload_response.body[:error]}" and abort
122
+ end
123
+ else
124
+ say_error "Error getting app information: #{response.body[:error]}"
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def determine_fir_user_token!
131
+ @user_token ||= ask "User Token:"
132
+ end
133
+
134
+ def determine_fir_app_id!
135
+ @app_id ||= ask "App Id:"
136
+ end
137
+
138
+ def determine_app_version!
139
+ @app_version ||= ask "App Version:"
140
+ end
141
+
142
+ def determine_short_version!
143
+ @short_version ||= ask "Short Version:"
144
+ end
145
+ end