shenzhen 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 41fbea6a348b6faa6ec8516a81c1d37328e0c62b
4
- data.tar.gz: b56480983fb412b9b5e32bdae83aa69c0a986e07
3
+ metadata.gz: 2745f53de64c6a957f3a244a80ed2bba9158bef2
4
+ data.tar.gz: 07d9564be28ee258ca0a34fd455d6cde603d6458
5
5
  SHA512:
6
- metadata.gz: 118b437781c43dc2076c71eb0802048b72e1cf2772e7f2719353a99a3b0192f61c75ab8c4d6c2737742e1571033da86e079041d222a239fd570d2985dd909216
7
- data.tar.gz: 793f40936c249c90c0d020fb4b04ab1a783f5fc572791127f626ddd0fef42bbd00bc35fe672ac56a3de1c70f0907bf4434b33b04abdcaeaf0c0c21affa63816a
6
+ metadata.gz: 9c66500a86a7a9ffa9120f565a0d151b4fa9dc87bad995730d1d186d8f06b4b03602e9ac0f5f577f4c86d818b95f4639968e7ddec82053c511079d2a8ff9ad8a
7
+ data.tar.gz: 3fab0ee6067d5f030bbed3de04d6ee3dd7e12aec3887b59da1e4a5a0266846a2b1b842e2923141015d6a4d52754d42e59736bfd13105a25c03dfc085c91d6b95
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shenzhen (0.8.2)
4
+ shenzhen (0.9.0)
5
5
  aws-sdk (~> 1.0)
6
6
  commander (~> 4.1)
7
7
  dotenv (~> 0.7)
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- ![Shenzhen](https://raw.github.com/mattt/nomad-cli.com/assets/shenzhen-banner.png)
1
+ ![Shenzhen](https://raw.github.com/nomad/nomad.github.io/assets/shenzhen-banner.png)
2
2
 
3
- Create `.ipa` files and then distribute them with [TestFlight](https://testflightapp.com/), [HockeyApp](http://www.hockeyapp.net), [DeployGate](https://deploygate.com), or even through [iTunes Connect](https://itunesconnect.apple.com)—all from the command line!
3
+ Create `.ipa` files and then distribute them with [TestFlight](https://testflightapp.com/), [HockeyApp](http://www.hockeyapp.net), [Crashlytics Beta](http://try.crashlytics.com/beta/), [DeployGate](https://deploygate.com), or even through [iTunes Connect](https://itunesconnect.apple.com)—all from the command line!
4
4
 
5
5
  Less cumbersome than clicking around in Xcode, and less hassle than rolling your own build script--Shenzhen radically improves the process of getting new builds out to testers and enterprises.
6
6
 
7
- > `shenzhen` is named for [深圳](http://en.wikipedia.org/wiki/Shenzhen), the Chinese city famous for its role as the center of manufacturing for a majority of consumer electronics, including iPhones and iPads.
8
- > It's part of a series of world-class command-line utilities for iOS development, which includes [Cupertino](https://github.com/mattt/cupertino) (Apple Dev Center management), [Houston](https://github.com/mattt/houston) (Push Notifications), [Venice](https://github.com/mattt/venice) (In-App Purchase Receipt Verification), and [Dubai](https://github.com/mattt/dubai) (Passbook pass generation).
7
+ > `shenzhen` is named for [深圳](http://en.wikipedia.org/wiki/Shenzhen), the Chinese city famous for being the center of manufacturing for a majority of consumer electronics, including iPhones and iPads.
8
+ > It's part of a series of world-class command-line utilities for iOS development, which includes [Cupertino](https://github.com/mattt/cupertino) (Apple Dev Center management), [Houston](https://github.com/mattt/houston) (Push Notifications), [Venice](https://github.com/mattt/venice) (In-App Purchase Receipt Verification), [Dubai](https://github.com/mattt/dubai) (Passbook pass generation), and [Nashville](https://github.com/nomad/nashville) (iTunes Store API).
9
9
 
10
10
  ## Installation
11
11
 
@@ -40,10 +40,11 @@ $ ipa
40
40
  build Create a new .ipa file for your app
41
41
  distribute:testflight Distribute an .ipa file over TestFlight
42
42
  distribute:hockeyapp Distribute an .ipa file over HockeyApp
43
+ distribute:crashlytics Distribute an .ipa file over Crashlytics
43
44
  distribute:deploygate Distribute an .ipa file over deploygate
44
45
  distribute:itunesconnect Upload an .ipa file to iTunes Connect for review
45
46
  distribute:ftp Distribute an .ipa file over FTP
46
- distribute:S3 Distribute an .ipa file over Amazon S3
47
+ distribute:s3 Distribute an .ipa file over Amazon S3
47
48
  info Show mobile provisioning information about an .ipa file
48
49
  help Display global or [command] help documentation.
49
50
 
@@ -75,11 +76,20 @@ $ ipa distribute:testflight -a API_TOKEN -T TEAM_TOKEN
75
76
  #### HockeyApp Distribution
76
77
 
77
78
  ```
78
- $ ipa distribute:hockeyapp --token API_TOKEN
79
+ $ ipa distribute:hockeyapp -a API_TOKEN
79
80
  ```
80
81
 
81
82
  > Shenzhen will load credentials from the environment variable `HOCKEYAPP_API_TOKEN` unless otherwise specified.
82
83
 
84
+ #### Crashlytics Beta Distribution
85
+
86
+ ```
87
+ $ ipa distribute:crashlytics -c /path/to/Crashlytics.framework -a API_TOKEN -s BUILD_SECRET
88
+ ```
89
+
90
+ > Shenzhen will load credentials from the environment variables `CRASHLYTICS_API_TOKEN` & `CRASHLYTICS_BUILD_SECRET`, and attempt to run the submit executable `submit` in the path to Crashlytics.framework specified by `CRASHLYTICS_FRAMEWORK_PATH` unless otherwise specified.
91
+
92
+
83
93
  #### DeployGate Distribution
84
94
 
85
95
  ```
@@ -110,13 +120,18 @@ $ ipa distribute:s3 -a ACCESS_KEY_ID -s SECRET_ACCESS_KEY -b BUCKET
110
120
 
111
121
  #### iTunes Connect Distribution
112
122
 
113
- $ ipa distribute:itunesconnect -a me@email.com -p myitunesconnectpassword --upload
123
+ ```
124
+ $ ipa distribute:itunesconnect -a me@email.com -p myitunesconnectpassword -i appleid --upload
125
+ ```
114
126
 
115
127
  > Shenzhen will load credentials from the environment variables `ITUNES_CONNECT_ACCOUNT` and `ITUNES_CONNECT_PASSWORD` unless otherwise specified. If only an account is provided, the keychain will be searched for a matching entry.
116
128
  >
117
- > If the `--upload` flag is omitted, Shenzen will validate the given credentials and check that the app is in the 'Waiting for Upload' state on iTunes Connect but will not upload the .ipa
129
+ > The `-i` (or `--apple-id`) flag is "An automatically generated ID assigned to your app". It can be found via iTunes Connect by navigating to:
130
+ > * My Apps -> [App Name] -> More -> About This App -> Apple ID
118
131
  >
119
- > For a fully hands-free upload, in a CI environment for example, ensure your iTunes Connect credentials are stored in your keychain, and that the keychain item has the Validation app in it's 'Always allow access' list. Running Shenzhen once with the `--save-keychain` flag, and clicking `Always Allow` on the prompt will set this up for you.
132
+ > For a fully hands-free upload, in a CI environment for example, ensure your iTunes Connect credentials are stored in your keychain, and that the keychain item has the Validation app in its 'Always allow access' list. Running Shenzhen once with the `--save-keychain` flag, and clicking `Always Allow` on the prompt will set this up for you.
133
+
134
+ **Apps built with Swift are not currently supported by this distribution method. Although initial upload checks may pass, it will eventually fail citing "Invalid Swift Support".**
120
135
 
121
136
  ### Displaying Embedded .mobileprovision Information
122
137
 
@@ -6,6 +6,7 @@ require 'plugins/deploygate'
6
6
  require 'plugins/itunesconnect'
7
7
  require 'plugins/ftp'
8
8
  require 'plugins/s3'
9
+ require 'plugins/crashlytics'
9
10
 
10
11
  require 'commands/build'
11
12
  require 'commands/distribute'
@@ -10,6 +10,7 @@ command :build do |c|
10
10
  c.option '-c', '--configuration CONFIGURATION', 'Configuration used to build'
11
11
  c.option '-s', '--scheme SCHEME', 'Scheme used to build app'
12
12
  c.option '--xcconfig XCCONFIG', 'use an extra XCCONFIG file to build the app'
13
+ c.option '--xcargs XCARGS', 'pass additional arguments to xcodebuild when building the app. Be sure to quote multiple args.'
13
14
  c.option '--[no-]clean', 'Clean project before building'
14
15
  c.option '--[no-]archive', 'Archive project after building'
15
16
  c.option '-d', '--destination DESTINATION', 'Destination. Defaults to current directory'
@@ -29,6 +30,7 @@ command :build do |c|
29
30
  @sdk = options.sdk || 'iphoneos'
30
31
  @configuration = options.configuration
31
32
  @xcconfig = options.xcconfig
33
+ @xcargs = options.xcargs
32
34
  @destination = options.destination || Dir.pwd
33
35
  FileUtils.mkdir_p(@destination) unless File.directory?(@destination)
34
36
 
@@ -51,6 +53,7 @@ command :build do |c|
51
53
  flags << %{-scheme "#{@scheme}"} if @scheme
52
54
  flags << %{-configuration "#{@configuration}"} if @configuration
53
55
  flags << %{-xcconfig "#{@xcconfig}"} if @xcconfig
56
+ flags << @xcargs if @xcargs
54
57
 
55
58
  @target, @xcodebuild_settings = Shenzhen::XcodeBuild.settings(*flags).detect{|target, settings| settings['WRAPPER_EXTENSION'] == "app"}
56
59
  say_error "App settings could not be found." and abort unless @xcodebuild_settings
@@ -0,0 +1,76 @@
1
+ module Shenzhen::Plugins
2
+ module Crashlytics
3
+ class Client
4
+
5
+ def initialize(crashlytics_path, api_token, build_secret)
6
+ @api_token, @build_secret = api_token, build_secret
7
+ @crashlytics_path = Pathname.new("#{crashlytics_path}/submit").cleanpath.to_s
8
+ say_error "Path to Crashlytics.framework/submit is invalid" and abort unless File.exists?(@crashlytics_path)
9
+ end
10
+
11
+ def upload_build(ipa, options)
12
+ command = "#{@crashlytics_path} #{@api_token} #{@build_secret} -ipaPath #{options[:file]}"
13
+ command += " -notesPath #{options[:notes]}" if options[:notes]
14
+ command += " -emails #{options[:emails]}" if options[:emails]
15
+ command += " -groupAliases #{options[:groups]}" if options[:groups]
16
+
17
+ system command
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ command :'distribute:crashlytics' do |c|
24
+ c.syntax = "ipa distribute:crashlytics [options]"
25
+ c.summary = "Distribute an .ipa file over Crashlytics"
26
+ c.description = ""
27
+ c.option '-c', '--crashlytics_path PATH', "/path/to/Crashlytics.framework/"
28
+ c.option '-f', '--file FILE', ".ipa file for the build"
29
+ c.option '-a', '--api_token TOKEN', "API Token. Available at https://www.crashlytics.com/settings/organizations"
30
+ c.option '-s', '--build_secret SECRET', "Build Secret. Available at https://www.crashlytics.com/settings/organizations"
31
+ c.option '-m', '--notes PATH', "Path to release notes file"
32
+ c.option '-e', '--emails EMAIL1,EMAIL2', "Emails of users for access"
33
+ c.option '-g', '--groups GROUPS', "Groups for users for access"
34
+
35
+ c.action do |args, options|
36
+ determine_file! unless @file = options.file
37
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
38
+
39
+ determine_crashlytics_path! unless @crashlytics_path = options.crashlytics_path || ENV['CRASHLYTICS_FRAMEWORK_PATH']
40
+ say_error "Missing path to Crashlytics.framework" and abort unless @crashlytics_path
41
+
42
+ determine_crashlytics_api_token! unless @api_token = options.api_token || ENV['CRASHLYTICS_API_TOKEN']
43
+ say_error "Missing API Token" and abort unless @api_token
44
+
45
+ determine_crashlytics_build_secret! unless @build_secret = options.build_secret || ENV['CRASHLYTICS_BUILD_SECRET']
46
+ say_error "Missing Build Secret" and abort unless @build_secret
47
+
48
+ parameters = {}
49
+ parameters[:file] = @file
50
+ parameters[:notes] = options.notes if options.notes
51
+ parameters[:emails] = options.emails if options.emails
52
+ parameters[:groups] = options.groups if options.groups
53
+
54
+ client = Shenzhen::Plugins::Crashlytics::Client.new(@crashlytics_path, @api_token, @build_secret)
55
+
56
+ if client.upload_build(@file, parameters)
57
+ say_ok "Build successfully uploaded to Crashlytics"
58
+ else
59
+ say_error "Error uploading to Crashlytics" and abort
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def determine_crashlytics_path!
66
+ @crashlytics_path ||= ask "Path to Crashlytics.framework:"
67
+ end
68
+
69
+ def determine_crashlytics_api_token!
70
+ @api_token ||= ask "API Token:"
71
+ end
72
+
73
+ def determine_crashlytics_build_secret!
74
+ @build_secret ||= ask "Build Secret:"
75
+ end
76
+ end
@@ -48,7 +48,7 @@ command :'distribute:hockeyapp' do |c|
48
48
  c.description = ""
49
49
  c.option '-f', '--file FILE', ".ipa file for the build"
50
50
  c.option '-d', '--dsym FILE', "zipped .dsym package for the build"
51
- c.option '--token TOKEN', "API Token. Available at https://rink.hockeyapp.net/manage/auth_tokens"
51
+ c.option '-a', '--token TOKEN', "API Token. Available at https://rink.hockeyapp.net/manage/auth_tokens"
52
52
  c.option '-i', '--identifier PUBLIC_IDENTIFIER', "Public identifier of the app you are targeting, if not specified HockeyApp will use the bundle identifier to choose the right"
53
53
  c.option '-m', '--notes NOTES', "Release notes for the build (Default: Textile)"
54
54
  c.option '-r', '--release RELEASE', [:alpha, :beta, :live], "Release type: 0 - Beta, 1 - Live, 2 - Alpha (Default: alpha)"
@@ -1,4 +1,7 @@
1
1
  require 'security'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ require 'digest/md5'
2
5
 
3
6
  module Shenzhen::Plugins
4
7
  module ITunesConnect
@@ -7,45 +10,69 @@ module Shenzhen::Plugins
7
10
  class Client
8
11
  attr_reader :ipa, :sdk, :params
9
12
 
10
- def initialize(ipa, sdk, params = [])
13
+ def initialize(ipa, apple_id, sdk, account, password, params = [])
11
14
  @ipa = ipa
15
+ @apple_id = apple_id
12
16
  @sdk = sdk
17
+ @account = account
18
+ @password = password
13
19
  @params = params
14
20
  end
15
21
 
16
- def ensure_itunesconnect!
17
- case xcrun(:Validation, [:online])
18
- when /(error)|(fail)/i
19
- say_error "An error occurred checking the status of the app in iTunes Connect.\nRun with --verbose for more info." and abort
20
- when /validation was skipped/i
21
- say_error "Validation was skipped. Double check your credentials and ensure the app in the 'Waiting for Upload' state." and abort
22
- end
23
- end
24
-
25
22
  def upload_build!
26
- case xcrun(:Validation, [:online, :upload])
23
+ size = File.size(@ipa)
24
+ checksum = Digest::MD5.file(@ipa)
25
+
26
+ FileUtils.mkdir_p("Package.itmsp")
27
+ FileUtils.copy_entry(@ipa, "Package.itmsp/#{@ipa}")
28
+
29
+ File.write("Package.itmsp/metadata.xml", metadata(@apple_id, checksum, size))
30
+
31
+ case transport
27
32
  when /(error)|(fail)/i
28
33
  say_error "An error occurred when trying to upload the build to iTunesConnect.\nRun with --verbose for more info." and abort
29
34
  end
35
+
36
+ FileUtils.rmdir("Package.itmsp")
30
37
  end
31
38
 
32
39
  private
33
40
 
34
- def xcrun(tool, options = [])
35
- args = ["xcrun", "-sdk #{sdk}", tool] + (options + @params).collect{|o| "-#{o}"} + [ipa, '2>&1']
36
- command = args.join(' ')
41
+ def transport
42
+ xcode = `xcode-select --print-path`.strip
43
+ tool = Pathname.new(xcode).parent + "Applications/Application Loader.app/Contents/MacOS/itms/bin/iTMSTransporter"
44
+ tool = tool.to_s.gsub(/ /, '\ ')
45
+
46
+ args = [tool.to_s, "-m upload", "-f Package.itmsp", "-u #{@account}", "-p #{@password}"]
47
+ command = args.join(' ').strip
37
48
 
38
49
  say "#{command}" if verbose?
39
50
 
40
- output = `#{command}`
51
+ output = `#{command} 2> /dev/null`
41
52
  say output.chomp if verbose?
42
53
 
43
- return output
54
+ output
44
55
  end
45
56
 
46
57
  def verbose?
47
58
  @params.collect(&:to_sym).include?(:verbose)
48
59
  end
60
+
61
+ def metadata(apple_id, checksum, size)
62
+ %{<?xml version="1.0" encoding="UTF-8"?>
63
+ <package version="software4.7" xmlns="http://apple.com/itunes/importer">
64
+ <software_assets apple_id="#{apple_id}">
65
+ <asset type="bundle">
66
+ <data_file>
67
+ <file_name>#{@ipa}</file_name>
68
+ <checksum type="md5">#{checksum}</checksum>
69
+ <size>#{size}</size>
70
+ </data_file>
71
+ </asset>
72
+ </software_assets>
73
+ </package>
74
+ }
75
+ end
49
76
  end
50
77
  end
51
78
  end
@@ -60,6 +87,7 @@ command :'distribute:itunesconnect' do |c|
60
87
  c.option '-u', '--upload', "Actually attempt to upload the build to iTunes Connect"
61
88
  c.option '-w', '--warnings', "Check for warnings when validating the ipa"
62
89
  c.option '-e', '--errors', "Check for errors when validating the ipa"
90
+ c.option '-i', '--apple-id STRING', "Apple ID from iTunes Connect"
63
91
  c.option '--verbose', "Run commands verbosely"
64
92
  c.option '--sdk SDK', "SDK to use when validating the ipa. Defaults to 'iphoneos'"
65
93
  c.option '--save-keychain', "Save the provided account in the keychain for future use"
@@ -73,6 +101,9 @@ command :'distribute:itunesconnect' do |c|
73
101
  determine_itunes_connect_account! unless @account = options.account || ENV['ITUNES_CONNECT_ACCOUNT']
74
102
  say_error "Missing iTunes Connect account" and abort unless @account
75
103
 
104
+ apple_id = options.apple_id
105
+ say_error "Missing Apple ID" and abort unless apple_id
106
+
76
107
  @password = options.password || ENV['ITUNES_CONNECT_PASSWORD']
77
108
  if @password ||= Security::GenericPassword.find(:s => Shenzhen::Plugins::ITunesConnect::ITUNES_CONNECT_SERVER, :a => @account)
78
109
  say_ok "Found password in keychain for account: #{@account}" if options.verbose
@@ -88,10 +119,7 @@ command :'distribute:itunesconnect' do |c|
88
119
  parameters << :warnings if options.warnings
89
120
  parameters << :errors if options.errors
90
121
 
91
- client = Shenzhen::Plugins::ITunesConnect::Client.new(@file, options.sdk, parameters)
92
-
93
- client.ensure_itunesconnect!
94
- say_warning "Upload not requested, skipping." and abort unless options.upload
122
+ client = Shenzhen::Plugins::ITunesConnect::Client.new(@file, apple_id, options.sdk, @account, @password, parameters)
95
123
 
96
124
  client.upload_build!
97
125
  say_ok "Upload complete. You may want to double check iTunes Connect to make sure it was received correctly."
@@ -10,7 +10,7 @@ module Shenzhen::Plugins
10
10
  end
11
11
 
12
12
  def upload_build(ipa, options)
13
- path = expand_path_with_substitutions_from_ipa_plist(ipa, options[:path])
13
+ path = expand_path_with_substitutions_from_ipa_plist(ipa, options[:path]) if options[:path]
14
14
 
15
15
  @s3.buckets.create(options[:bucket]) if options[:create]
16
16
 
@@ -18,9 +18,10 @@ module Shenzhen::Plugins
18
18
 
19
19
  files = []
20
20
  files << ipa
21
- files << options[:dsym]
21
+ files << options[:dsym] if options[:dsym]
22
22
  files.each do |file|
23
- key = File.join(path, File.basename(file))
23
+ basename = File.basename(file)
24
+ key = path ? File.join(path, basename) : basename
24
25
  File.open(file) do |descriptor|
25
26
  bucket.objects.create(key, descriptor, :acl => options[:acl])
26
27
  end
@@ -77,7 +78,7 @@ command :'distribute:s3' do |c|
77
78
  say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
78
79
 
79
80
  determine_dsym! unless @dsym = options.dsym
80
- say_error "Specified dSYM.zip file doesn't exist" unless @dsym and File.exist?(@dsym)
81
+ say_error "Specified dSYM.zip file doesn't exist" if @dsym and !File.exist?(@dsym)
81
82
 
82
83
  determine_access_key_id! unless @access_key_id = options.access_key_id
83
84
  say_error "Missing AWS Access Key ID" and abort unless @access_key_id
@@ -89,12 +90,11 @@ command :'distribute:s3' do |c|
89
90
  say_error "Missing bucket" and abort unless @bucket
90
91
 
91
92
  determine_region! unless @region = options.region
92
- say_error "Missing region" and abort unless @region
93
93
 
94
94
  determine_acl! unless @acl = options.acl
95
95
  say_error "Missing ACL" and abort unless @acl
96
96
 
97
- @path = options.path || ""
97
+ @path = options.path
98
98
 
99
99
  client = Shenzhen::Plugins::S3::Client.new(@access_key_id, @secret_access_key, @region)
100
100
 
@@ -124,7 +124,7 @@ command :'distribute:s3' do |c|
124
124
  end
125
125
 
126
126
  def determine_region!
127
- @region ||= ENV['AWS_REGION'] || ""
127
+ @region ||= ENV['AWS_REGION']
128
128
  end
129
129
 
130
130
  def determine_acl!
@@ -1,3 +1,3 @@
1
1
  module Shenzhen
2
- VERSION = '0.8.2'
2
+ VERSION = '0.9.0'
3
3
  end
@@ -22,7 +22,7 @@ module Shenzhen::XcodeBuild
22
22
  self
23
23
  end
24
24
  end
25
-
25
+
26
26
  class Error < StandardError; end
27
27
  class NilOutputError < Error; end
28
28
 
@@ -30,8 +30,10 @@ module Shenzhen::XcodeBuild
30
30
  def info(*args)
31
31
  options = args.last.is_a?(Hash) ? args.pop : {}
32
32
  output = `xcodebuild -list #{(args + args_from_options(options)).join(" ")} 2>&1`
33
+
33
34
  raise Error.new $1 if /^xcodebuild\: error\: (.+)$/ === output
34
- raise NilOutputError unless /\S/ === output
35
+
36
+ return nil unless /\S/ === output
35
37
 
36
38
  lines = output.split(/\n/)
37
39
  info, group = {}, nil
@@ -52,7 +54,7 @@ module Shenzhen::XcodeBuild
52
54
 
53
55
  info.each do |group, values|
54
56
  next unless Array === values
55
- values.delete("") and values.uniq!
57
+ values.delete("") and values.uniq!
56
58
  end
57
59
 
58
60
  Info.new(info)
@@ -61,8 +63,10 @@ module Shenzhen::XcodeBuild
61
63
  def settings(*args)
62
64
  options = args.last.is_a?(Hash) ? args.pop : {}
63
65
  output = `xcodebuild #{(args + args_from_options(options)).join(" ")} -showBuildSettings 2> /dev/null`
66
+
67
+ return nil unless /\S/ === output
68
+
64
69
  raise Error.new $1 if /^xcodebuild\: error\: (.+)$/ === output
65
- raise NilOutputError unless /\S/ === output
66
70
 
67
71
  lines = output.split(/\n/)
68
72
 
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shenzhen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mattt Thompson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-13 00:00:00.000000000 Z
11
+ date: 2014-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -207,6 +207,7 @@ files:
207
207
  - ./lib/shenzhen/commands/info.rb
208
208
  - ./lib/shenzhen/commands.rb
209
209
  - ./lib/shenzhen/plistbuddy.rb
210
+ - ./lib/shenzhen/plugins/crashlytics.rb
210
211
  - ./lib/shenzhen/plugins/deploygate.rb
211
212
  - ./lib/shenzhen/plugins/ftp.rb
212
213
  - ./lib/shenzhen/plugins/hockeyapp.rb
@@ -221,6 +222,7 @@ files:
221
222
  - ./README.md
222
223
  - ./shenzhen-0.8.0.gem
223
224
  - ./shenzhen-0.8.1.gem
225
+ - ./shenzhen-0.8.2.gem
224
226
  - ./shenzhen.gemspec
225
227
  - bin/ipa
226
228
  homepage: http://nomad-cli.com