shenzhen_fir 0.14.5

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,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,16 @@
1
+ $:.push File.expand_path('../', __FILE__)
2
+
3
+ require 'plugins/rivierabuild'
4
+ require 'plugins/hockeyapp'
5
+ require 'plugins/testfairy'
6
+ require 'plugins/deploygate'
7
+ require 'plugins/itunesconnect'
8
+ require 'plugins/ftp'
9
+ require 'plugins/s3'
10
+ require 'plugins/crashlytics'
11
+ require 'plugins/fir'
12
+ require 'plugins/pgyer'
13
+
14
+ require 'commands/build'
15
+ require 'commands/distribute'
16
+ require 'commands/info'
@@ -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,151 @@
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 = 'api.fir.im'
10
+ VERSION = 'v2'
11
+
12
+
13
+ def initialize(user_token)
14
+ @user_token = user_token
15
+
16
+ @connection = Faraday.new(:url => "http://#{HOSTNAME}") do |builder|
17
+ builder.request :url_encoded
18
+ builder.response :json
19
+ builder.use FaradayMiddleware::FollowRedirects
20
+ builder.adapter :net_http
21
+ end
22
+ end
23
+
24
+
25
+ #
26
+ #get upload ticket
27
+ #
28
+ def get_upload_ticket bundle_id
29
+ options = {
30
+ :type => 'ios',
31
+ :bundle_id => bundle_id,
32
+ :api_token => @user_token
33
+ }
34
+
35
+ response = @connection.post('/apps', options) do |env|
36
+ yield env[:status], env[:body] if block_given?
37
+ end
38
+
39
+ rescue Faraday::Error::TimeoutError
40
+ say_error "Timed out while geting upload ticket." and abort
41
+ end
42
+
43
+ #
44
+ #upload file
45
+ #
46
+ def upload_file_and_update_app_info ipa, options
47
+ connection = Faraday.new(:url => options['upload_url'], :request => { :timeout => 360 }) do |builder|
48
+ builder.request :multipart
49
+ builder.response :json
50
+ builder.use FaradayMiddleware::FollowRedirects
51
+ builder.adapter :net_http
52
+ end
53
+
54
+ form_options = {
55
+ :key => options['key'],
56
+ :token => options['token'],
57
+ :file => Faraday::UploadIO.new(ipa, 'application/octet-stream'),
58
+ "x:name" => options[:name],
59
+ "x:version" => options[:version],
60
+ "x:build" => options[:build],
61
+ "x:release_type" => options[:release_type],
62
+ "x:changelog" => options[:changelog]
63
+ }
64
+ p "=================uploading====================="
65
+ connection.post('/', form_options).on_complete do |env|
66
+ yield env[:status], env[:body] if block_given?
67
+ end
68
+ rescue Errno::EPIPE
69
+ say_error "Upload failed. Check internet connection is ok." and abort
70
+ rescue Faraday::Error::TimeoutError
71
+ say_error "Timed out while uploading build. Check https://fir.im// to see if the upload was completed." and abort
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ command :'distribute:fir' do |c|
78
+ c.syntax = "ipa distribute:fir [options]"
79
+ c.summary = "请使用新版api_token => http://fir.im/user/info 获取 \n Distribute an .ipa file over fir.im"
80
+ c.description = ""
81
+ c.option '-f', '--file FILE', ".ipa file for the build"
82
+ c.option '-u', '--user_token 即fir.im 的 api_token ', "fir.im 的 api_token 在 http://fir.im/user/info 获取"
83
+ c.option '-a', '--app_id APPID', "App Id (iOS Bundle identifier)"
84
+ c.option '-n', '--notes NOTES', "Release notes for the build"
85
+ c.option '-N', '--app_name APP_NAME', "the name for app"
86
+ c.option '-R', '--release_type RELEASE_TYPE', "release_type for app default adhoc"
87
+ c.option '-V', '--app_version VERSION', "应用编译号 build"
88
+ c.option '-S', '--short_version SHORT', "App Short Version"
89
+
90
+ c.action do |args, options|
91
+ determine_file! unless @file = options.file
92
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
93
+
94
+ determine_fir_user_token! unless @user_token = options.user_token || ENV['FIR_USER_TOKEN']
95
+ say_error "Missing User Token" and abort unless @user_token
96
+ determine_fir_app_id! unless @app_id = options.app_id || ENV['FIR_APP_ID']
97
+ say_error "Missing App Id" and abort unless @app_id
98
+
99
+ determine_notes! unless @notes = options.notes
100
+ say_error "Missing release notes" and abort unless @notes
101
+
102
+ determine_app_version! unless @app_version = options.app_version
103
+
104
+ determine_short_version! unless @short_version = options.short_version
105
+
106
+ client = Shenzhen::Plugins::Fir::Client.new(@user_token)
107
+ #get upload ticket
108
+ app_response = client.get_upload_ticket(@app_id)
109
+ if app_response.status == 201
110
+
111
+ upload_app_options = app_response.body['cert']['binary']
112
+ app_short_uri = app_response.body['short']
113
+ if options.app_name || ENV['APP_NAME']
114
+ upload_app_options[:name] = options.app_name || ENV['APP_NAME']
115
+ end
116
+ upload_app_options[:release_type] = options.release_type || "adhoc"
117
+ upload_app_options[:version] = @short_version
118
+ upload_app_options[:build] = @app_version
119
+ upload_app_options[:changelog] = @notes
120
+
121
+ #upload file
122
+ upload_response = client.upload_file_and_update_app_info(@file, upload_app_options)
123
+
124
+ if upload_response.status == 200
125
+ say_ok "Build successfully uploaded to Fir, visit url: http://fir.im/#{app_short_uri}"
126
+ else
127
+ say_error "Error uploading to Fir: #{upload_response.body[:error]}" and abort
128
+ end
129
+ else
130
+ say_error "Error getting app information: #{app_response.body[:error]}"
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def determine_fir_user_token!
137
+ @user_token ||= ask "User Token:"
138
+ end
139
+
140
+ def determine_fir_app_id!
141
+ @app_id ||= ask "App Id:"
142
+ end
143
+
144
+ def determine_app_version!
145
+ @app_version ||= ask "App Version:"
146
+ end
147
+
148
+ def determine_short_version!
149
+ @short_version ||= ask "Short Version:"
150
+ end
151
+ end
@@ -0,0 +1,181 @@
1
+ require 'net/ftp'
2
+ require 'net/sftp'
3
+
4
+ module Shenzhen::Plugins
5
+ module FTP
6
+ class Client
7
+
8
+ def initialize(host, port, user, password)
9
+ @host, @port, @user, @password = host, port, user, password
10
+ end
11
+
12
+ def upload(ipa, options = {})
13
+ connection = Net::FTP.new
14
+ connection.passive = true
15
+ connection.connect(@host, @port)
16
+
17
+ path = expand_path_with_substitutions_from_ipa_plist(ipa, options[:path])
18
+
19
+ begin
20
+ connection.login(@user, @password) rescue raise "Login authentication failed"
21
+
22
+ if options[:mkdir]
23
+ components, pwd = path.split(/\//).reject(&:empty?), nil
24
+ components.each do |component|
25
+ pwd = File.join(*[pwd, component].compact)
26
+
27
+ begin
28
+ connection.mkdir pwd
29
+ rescue => exception
30
+ raise exception unless /File exists/ === exception.message
31
+ end
32
+ end
33
+ end
34
+
35
+ connection.chdir path unless path.empty?
36
+ connection.putbinaryfile ipa, File.basename(ipa)
37
+ connection.putbinaryfile(options[:dsym], File.basename(options[:dsym])) if options[:dsym]
38
+ ensure
39
+ connection.close
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def expand_path_with_substitutions_from_ipa_plist(ipa, path)
46
+ substitutions = path.scan(/\{CFBundle[^}]+\}/)
47
+ return path if substitutions.empty?
48
+
49
+ Dir.mktmpdir do |dir|
50
+ system "unzip -q #{ipa} -d #{dir} 2> /dev/null"
51
+
52
+ plist = Dir["#{dir}/**/*.app/Info.plist"].last
53
+
54
+ substitutions.uniq.each do |substitution|
55
+ key = substitution[1...-1]
56
+ value = Shenzhen::PlistBuddy.print(plist, key)
57
+
58
+ path.gsub!(Regexp.new(substitution), value) if value
59
+ end
60
+ end
61
+
62
+ return path
63
+ end
64
+ end
65
+ end
66
+
67
+ module SFTP
68
+ class Client < Shenzhen::Plugins::FTP::Client
69
+ def upload(ipa, options = {})
70
+ session = Net::SSH.start(@host, @user, :password => @password, :port => @port)
71
+ connection = Net::SFTP::Session.new(session).connect!
72
+
73
+ path = expand_path_with_substitutions_from_ipa_plist(ipa, options[:path])
74
+
75
+ begin
76
+ connection.stat!(path) do |response|
77
+ connection.mkdir! path if options[:mkdir] and not response.ok?
78
+
79
+ connection.upload! ipa, determine_file_path(File.basename(ipa), path)
80
+ connection.upload! options[:dsym], determine_file_path(File.basename(options[:dsym]), path) if options[:dsym]
81
+ end
82
+ ensure
83
+ connection.close_channel
84
+ session.shutdown!
85
+ end
86
+ end
87
+
88
+ def determine_file_path(file_name, path)
89
+ if path.empty?
90
+ file_name
91
+ else
92
+ "#{path}/#{file_name}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ command :'distribute:ftp' do |c|
100
+ c.syntax = "ipa distribute:ftp [options]"
101
+ c.summary = "Distribute an .ipa file over FTP"
102
+ c.description = ""
103
+
104
+ c.example '', '$ ipa distribute:ftp --host 127.0.0.1 -f ./file.ipa -u username --path "/path/to/folder/{CFBundleVersion}/" --mkdir'
105
+
106
+ c.option '-f', '--file FILE', ".ipa file for the build"
107
+ c.option '-d', '--dsym FILE', "zipped .dsym package for the build"
108
+ c.option '-h', '--host HOST', "FTP host"
109
+ c.option '-u', '--user USER', "FTP user"
110
+ c.option '-p', '--password PASS', "FTP password"
111
+ c.option '-P', '--path PATH', "FTP path. Values from Info.plist will be substituted for keys wrapped in {} \n\t\t e.g. \"/path/to/folder/{CFBundleVersion}/\" would be evaluated as \"/path/to/folder/1.0.0/\""
112
+ c.option '--port PORT', "FTP port"
113
+ c.option '--protocol [PROTOCOL]', [:ftp, :sftp], "Protocol to use (ftp, sftp)"
114
+ c.option '--[no-]mkdir', "Create directories on FTP if they don't already exist"
115
+
116
+ c.action do |args, options|
117
+ options.default :mkdir => true
118
+
119
+ determine_file! unless @file = options.file
120
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
121
+
122
+ determine_dsym! unless @dsym = options.dsym
123
+ say_warning "Specified dSYM.zip file doesn't exist" unless @dsym and File.exist?(@dsym)
124
+
125
+ determine_host! unless @host = options.host
126
+ say_error "Missing FTP host" and abort unless @host
127
+
128
+ determine_port!(options.protocol) unless @port = options.port
129
+
130
+ determine_user! unless @user = options.user
131
+ say_error "Missing FTP user" and abort unless @user
132
+
133
+ @password = options.password
134
+ if !@password && options.protocol != :sftp
135
+ determine_password!
136
+ say_error "Missing FTP password" and abort unless @password
137
+ end
138
+
139
+ @path = options.path || ""
140
+
141
+ client = case options.protocol
142
+ when :sftp
143
+ Shenzhen::Plugins::SFTP::Client.new(@host, @port, @user, @password)
144
+ else
145
+ Shenzhen::Plugins::FTP::Client.new(@host, @port, @user, @password)
146
+ end
147
+
148
+ begin
149
+ client.upload @file, {:path => @path, :dsym => @dsym, :mkdir => !!options.mkdir}
150
+ say_ok "Build successfully uploaded to FTP"
151
+ rescue => exception
152
+ say_error "Error while uploading to FTP: #{exception}"
153
+ raise if options.trace
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ def determine_host!
160
+ @host ||= ask "FTP Host:"
161
+ end
162
+
163
+ def determine_port!(protocol)
164
+ @port = case protocol
165
+ when :sftp
166
+ Net::SSH::Transport::Session::DEFAULT_PORT
167
+ else
168
+ Net::FTP::FTP_PORT
169
+ end
170
+ end
171
+
172
+ def determine_user!
173
+ @user ||= ask "Username:"
174
+ end
175
+
176
+ def determine_password!
177
+ @password ||= password "Password:"
178
+ end
179
+ end
180
+
181
+ alias_command :'distribute:sftp', :'distribute:ftp', '--protocol', 'sftp'