shenzhen 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,31 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ shenzhen (0.0.1)
5
+ commander (~> 4.1.2)
6
+ faraday (~> 0.8.0)
7
+ faraday_middleware (~> 0.8.7)
8
+ json (~> 1.7.3)
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ commander (4.1.2)
14
+ highline (~> 1.6.11)
15
+ faraday (0.8.1)
16
+ multipart-post (~> 1.1)
17
+ faraday_middleware (0.8.7)
18
+ faraday (>= 0.7.4, < 0.9)
19
+ highline (1.6.15)
20
+ json (1.7.5)
21
+ multipart-post (1.1.5)
22
+ rake (0.9.2.2)
23
+ rspec (0.6.4)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ rake (~> 0.9.2)
30
+ rspec (~> 0.6.1)
31
+ shenzhen!
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ # Shenzhen
2
+ **CLI for Building & Distributing iOS Apps (.ipa Files)**
3
+
4
+ Create `.ipa` files and then distribute them to TestFlight, all from the command line!
5
+
6
+ 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.
7
+
8
+ This project is starting with TestFlight, but will move to support other popular distribution methods, such as S3, CloudApp, and /or Dropbox. Suggestions (and pull requests) are very welcome.
9
+
10
+ > `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. Its [sister project](https://github.com/mattt/cupertino)'s namesake, [Cupertino, CA](http://en.wikipedia.org/wiki/Cupertino,_California), is home to Apple, Inc.'s world headquarters.
11
+
12
+ ## Installation
13
+
14
+ ```
15
+ $ gem install shenzhen
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Shenzhen adds the `ipa` command to your PATH,
21
+
22
+ ```
23
+ $ ipa
24
+
25
+ Build and distribute iOS apps (.ipa files)
26
+
27
+ Commands:
28
+ build Create a new .ipa file for your app
29
+ distribute:testflight Distribute an .ipa file over testflight
30
+ help Display global or [command] help documentation.
31
+
32
+ Aliases:
33
+ distribute distribute:testflight
34
+
35
+ Global Options:
36
+ -h, --help Display help documentation
37
+ -v, --version Display version information
38
+ -t, --trace Display backtrace when an error occurs
39
+ ```
40
+
41
+ ### Building & Distribution
42
+
43
+ ```
44
+ $ cd /path/to/iOS Project/
45
+ $ ipa build
46
+ $ ipa distribute
47
+ ```
48
+
49
+ ## Contact
50
+
51
+ Mattt Thompson
52
+
53
+ - http://github.com/mattt
54
+ - http://twitter.com/mattt
55
+ - m@mattt.me
56
+
57
+ ## License
58
+
59
+ Shenzhen is available under the MIT license. See the LICENSE file for more info.
@@ -0,0 +1,10 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ gemspec = eval(File.read("shenzhen.gemspec"))
5
+
6
+ task :build => "#{gemspec.full_name}.gem"
7
+
8
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["shenzhen.gemspec"] do
9
+ system "gem build shenzhen.gemspec"
10
+ end
data/bin/ipa ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'commander/import'
4
+
5
+ $:.push File.expand_path("../../lib", __FILE__)
6
+ require 'shenzhen'
7
+
8
+ HighLine.track_eof = false # Fix for built-in Ruby
9
+
10
+ program :version, Shenzhen::VERSION
11
+ program :description, 'Build and distribute iOS apps (.ipa files)'
12
+
13
+ program :help, 'Author', 'Mattt Thompson <m@mattt.me>'
14
+ program :help, 'Website', 'http://mattt.me'
15
+ program :help_formatter, :compact
16
+
17
+ # global_option '--verbose'
18
+ default_command :help
19
+
20
+ require 'shenzhen/commands'
@@ -0,0 +1,6 @@
1
+ module Shenzhen
2
+ VERSION = '0.0.1'
3
+ end
4
+
5
+ require 'shenzhen/agvtool'
6
+ require 'shenzhen/xcodebuild'
@@ -0,0 +1,17 @@
1
+ module Shenzhen::Agvtool
2
+ class << self
3
+ def what_version
4
+ output = `agvtool what-version -terse`
5
+ output.length > 0 ? output : nil
6
+ end
7
+
8
+ alias :vers :what_version
9
+
10
+ def what_marketing_version
11
+ output = `agvtool what-marketing-version -terse`
12
+ output.scan(/\=(.+)$/).flatten.first
13
+ end
14
+
15
+ alias :mvers :what_marketing_version
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ $:.push File.expand_path('../', __FILE__)
2
+
3
+ require 'plugins/testflight'
4
+
5
+ require 'commands/build'
6
+ require 'commands/distribute'
7
+
@@ -0,0 +1,101 @@
1
+ command :build do |c|
2
+ c.syntax = 'ipa build [options] [output]'
3
+ c.summary = 'Create a new .ipa file for your app'
4
+ c.description = ''
5
+
6
+ c.example 'description', 'ipa release 478 --tag 3.7.2 --scheme MyApp'
7
+ c.option '-w', '--workspace WORKSPACE', 'Workspace (.xcworkspace) file to use to build app (automatically detected in current directory)'
8
+ c.option '-p', '--project PROJECT', 'Project (.xcodeproj) file to use to build app (automatically detected in current directory, overridden by --workspace option, if passed)'
9
+ c.option '-c', '--configuration CONFIGURATION', 'Configuration used to build'
10
+ c.option '-s', '--scheme SCHEME', 'Scheme used to build app'
11
+ c.option '-q', '--quiet', 'Silence warning and success messages'
12
+
13
+ c.action do |args, options|
14
+ validate_xcode_version!
15
+
16
+ @xcodebuild_info = Shenzhen::XcodeBuild.info
17
+
18
+ @workspace = options.workspace
19
+ @project = options.project
20
+ @scheme = options.scheme
21
+ @configuration = options.configuration
22
+
23
+ determine_workspace_or_project! unless @workspace || @project
24
+
25
+ determine_configuration! unless @configuration
26
+ say_error "Configuration #{@configuration} not found" and abort unless @xcodebuild_info.build_configurations.include?(@configuration)
27
+
28
+ determine_scheme! unless @scheme
29
+ say_error "Scheme #{@scheme} not found" and abort unless @xcodebuild_info.schemes.include?(@scheme)
30
+
31
+ say_warning "Building \"#{@workspace || @project}\" with Scheme \"#{@scheme}\" and Configuration \"#{@configuration}\"\n" unless options.quiet
32
+
33
+ log "xcodebuild", (@workspace || @project)
34
+
35
+ flags = []
36
+ flags << "-sdk iphoneos"
37
+ flags << "-workspace '#{@workspace}'" if @workspace
38
+ flags << "-project '#{@project}'" if @project
39
+ flags << "-scheme '#{@scheme}'" if @scheme
40
+ flags << "-configuration Release"
41
+
42
+ ENV['CC'] = nil # Fix for RVM
43
+ abort unless system "xcodebuild #{flags.join(' ')} clean build 1> /dev/null"
44
+
45
+ log "xcrun", "PackageApplication"
46
+
47
+ @xcodebuild_settings = Shenzhen::XcodeBuild.settings(flags)
48
+
49
+ @app_path = File.join(@xcodebuild_settings['BUILT_PRODUCTS_DIR'], @xcodebuild_settings['PRODUCT_NAME']) + ".app"
50
+ @dsym_path = @app_path + ".dSYM"
51
+ @ipa_path = File.join(Dir.pwd, @xcodebuild_settings['PRODUCT_NAME']) + ".ipa"
52
+ abort unless system %{xcrun -sdk iphoneos PackageApplication -v "#{@app_path}" -o "#{@ipa_path}" --embed "#{@dsym_path}" 1> /dev/null}
53
+
54
+ say_ok "#{File.basename(@ipa_path)} successfully built" unless options.quiet
55
+ end
56
+
57
+ private
58
+
59
+ def validate_xcode_version!
60
+ version = Shenzhen::XcodeBuild.version
61
+ say_error "Shenzhen requires Xcode 4 (found #{version}). Please install or switch to the latest Xcode." and abort if version < "4.0.0"
62
+ end
63
+
64
+ def determine_workspace_or_project!
65
+ workspaces, projects = Dir["*.xcworkspace"], Dir["*.xcodeproj"]
66
+
67
+ if workspaces.empty?
68
+ if projects.empty?
69
+ say_error "No Xcode projects or workspaces found in current directory" and abort
70
+ else
71
+ if projects.length == 1
72
+ @project = projects.first
73
+ else
74
+ @project = choose "Select a project:", *projects
75
+ end
76
+ end
77
+ else
78
+ if workspaces.length == 1
79
+ @workspace = workspaces.first
80
+ else
81
+ @workspace = choose "Select a workspace:", *workspaces
82
+ end
83
+ end
84
+ end
85
+
86
+ def determine_scheme!
87
+ if @xcodebuild_info.schemes.length == 1
88
+ @scheme = @xcodebuild_info.schemes.first
89
+ else
90
+ @scheme = choose "Select a scheme:", *@xcodebuild_info.schemes
91
+ end
92
+ end
93
+
94
+ def determine_configuration!
95
+ if @xcodebuild_info.build_configurations.length == 1
96
+ @configuration = @xcodebuild_info.build_configurations.first
97
+ else
98
+ @configuration = choose "Select a configuration:", *@xcodebuild_info.build_configurations
99
+ end
100
+ end
101
+ end
@@ -0,0 +1 @@
1
+ alias_command :distribute, :'distribute:testflight'
@@ -0,0 +1,104 @@
1
+ require 'openssl'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+
5
+ module Shenzhen::Plugins
6
+ module TestFlight
7
+ class Client
8
+ HOSTNAME = 'testflightapp.com'
9
+
10
+ def initialize(api_token, team_token)
11
+ @api_token, @team_token = api_token, team_token
12
+ @connection = Faraday.new(:url => "http://#{HOSTNAME}") do |builder|
13
+ builder.request :multipart
14
+ builder.request :json
15
+ builder.response :json, :content_type => /\bjson$/
16
+ builder.use FaradayMiddleware::FollowRedirects
17
+ builder.adapter :net_http
18
+ end
19
+ end
20
+
21
+ def upload_build(ipa, options)
22
+ options.update({
23
+ :api_token => @api_token,
24
+ :team_token => @team_token,
25
+ :file => Faraday::UploadIO.new(ipa, 'application/octet-stream')
26
+ })
27
+
28
+ @connection.post("/api/builds.json", options).on_complete do |env|
29
+ yield env[:status], env[:body] if block_given?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ command :'distribute:testflight' do |c|
37
+ c.syntax = "ipa distribute:testflight [options]"
38
+ c.summary = "Distribute an .ipa file over testflight"
39
+ c.description = ""
40
+ c.option '-f', '--file FILE', ".ipa file for the build"
41
+ c.option '-t', '--api_token TOKEN', "API Token. Available at https://testflightapp.com/account/#api-token"
42
+ c.option '-T', '--team_token TOKEN', "Team Token. Available at https://testflightapp.com/dashboard/team/edit/"
43
+ c.option '-m', '--notes NOTES', "Release notes for the build"
44
+ c.option '-l', '--lists LISTS', "Comma separated distribution list names which will receive access to the build"
45
+ c.option '--notify', "Notify permitted teammates to install the build"
46
+ c.option '--replace', "Replace binary for an existing build if one is found with the same name/bundle version"
47
+ c.option '-q', '--quiet', "Silence warning and success messages"
48
+
49
+ c.action do |args, options|
50
+ determine_file! unless @file = options.file
51
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
52
+
53
+ determine_api_token! unless @api_token = options.api_token
54
+ say_error "Missing API Token" and abort unless @api_token
55
+
56
+ determine_team_token! unless @team_token = options.team_token
57
+
58
+ determine_notes! unless @notes = options.notes
59
+ say_error "Missing release notes" and abort unless @notes
60
+
61
+ parameters = {}
62
+ parameters[:file] = @file
63
+ parameters[:notes] = @notes
64
+ parameters[:notify] = "true" if options.notify
65
+ parameters[:replace] = "true" if options.replace
66
+ parameters[:distribution_lists] = options.lists if options.lists
67
+
68
+ client = Shenzhen::Plugins::TestFlight::Client.new(@api_token, @team_token)
69
+ response = client.upload_build(@file, parameters)
70
+ case response.status
71
+ when 200...300
72
+ say_ok "Build successfully uploaded to TestFlight"
73
+ else
74
+ say_error "Error uploading to TestFlight: #{response.body}"
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def determine_api_token!
81
+ @api_token ||= ask "API Token:"
82
+ end
83
+
84
+ def determine_team_token!
85
+ @team_token ||= ask "Team Token:"
86
+ end
87
+
88
+ def determine_file!
89
+ files = Dir['*.ipa']
90
+ @file ||= case files.length
91
+ when 0 then nil
92
+ when 1 then files.first
93
+ else
94
+ @file = choose "Select an .ipa File:", *files
95
+ end
96
+ end
97
+
98
+ def determine_notes!
99
+ placeholder = %{What's new in this release: }
100
+
101
+ @notes = ask_editor placeholder
102
+ @notes = nil if @notes == placeholder
103
+ end
104
+ end
@@ -0,0 +1,63 @@
1
+ require 'ostruct'
2
+
3
+ module Shenzhen::XcodeBuild
4
+ class Info < OpenStruct; end
5
+
6
+ class Error < StandardError; end
7
+ class NilOutputError < Error; end
8
+
9
+ class << self
10
+ def info
11
+ output = `xcodebuild -list 2> /dev/null`
12
+ raise Error.new $1 if /^xcodebuild\: error\: (.+)$/ === output
13
+ raise NilOutputError unless /\S/ === output
14
+
15
+ lines = output.split(/\n/)
16
+ hash = {}
17
+ group = nil
18
+
19
+ hash[:project] = lines.shift.match(/\"(.+)\"\:/)[1]
20
+
21
+ lines.each do |line|
22
+ if /\:$/ === line
23
+ group = line.strip[0...-1].downcase.gsub(/\s+/, '_')
24
+ hash[group] = []
25
+ next
26
+ end
27
+
28
+ unless group.nil? or /\.$/ === line
29
+ hash[group] << line.strip
30
+ end
31
+ end
32
+
33
+ hash.each do |group, values|
34
+ next unless Array === values
35
+ values.delete("") and values.uniq!
36
+ end
37
+
38
+ Info.new(hash)
39
+ end
40
+
41
+ def settings(flags = [])
42
+ output = `xcodebuild #{flags.join(' ')} -showBuildSettings 2> /dev/null`
43
+ raise Error.new $1 if /^xcodebuild\: error\: (.+)$/ === output
44
+ raise NilOutputError unless /\S/ === output
45
+
46
+ lines = output.split(/\n/)
47
+ lines.shift
48
+
49
+ hash = {}
50
+ lines.each do |line|
51
+ key, value = line.split(/\=/).collect(&:strip)
52
+ hash[key] = value
53
+ end
54
+
55
+ hash
56
+ end
57
+
58
+ def version
59
+ output = `xcodebuild -version`
60
+ output.scan(/([\d\.?]+)/).flatten.first rescue nil
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "shenzhen"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "shenzhen"
7
+ s.authors = ["Mattt Thompson"]
8
+ s.email = "m@mattt.me"
9
+ s.homepage = "http://github.com/mattt/shenzhen"
10
+ s.version = Shenzhen::VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.summary = "Shenzhen"
13
+ s.description = "CLI for Building & Distributing iOS Apps (.ipa Files)"
14
+
15
+ s.add_development_dependency "rspec", "~> 0.6.1"
16
+ s.add_development_dependency "rake", "~> 0.9.2"
17
+
18
+ s.add_dependency "commander", "~> 4.1.2"
19
+ s.add_dependency "json", "~> 1.7.3"
20
+ s.add_dependency "faraday", "~> 0.8.0"
21
+ s.add_dependency "faraday_middleware", "~> 0.8.7"
22
+
23
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shenzhen
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mattt Thompson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-28 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70338229563260 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.6.1
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70338229563260
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70338229562040 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.2
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70338229562040
36
+ - !ruby/object:Gem::Dependency
37
+ name: commander
38
+ requirement: &70338229560720 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 4.1.2
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70338229560720
47
+ - !ruby/object:Gem::Dependency
48
+ name: json
49
+ requirement: &70338229557880 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.7.3
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70338229557880
58
+ - !ruby/object:Gem::Dependency
59
+ name: faraday
60
+ requirement: &70338229546680 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 0.8.0
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70338229546680
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday_middleware
71
+ requirement: &70338229545900 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 0.8.7
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70338229545900
80
+ description: CLI for Building & Distributing iOS Apps (.ipa Files)
81
+ email: m@mattt.me
82
+ executables:
83
+ - ipa
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - ./Gemfile
88
+ - ./Gemfile.lock
89
+ - ./lib/shenzhen/agvtool.rb
90
+ - ./lib/shenzhen/commands/build.rb
91
+ - ./lib/shenzhen/commands/distribute.rb
92
+ - ./lib/shenzhen/commands.rb
93
+ - ./lib/shenzhen/plugins/testflight.rb
94
+ - ./lib/shenzhen/xcodebuild.rb
95
+ - ./lib/shenzhen.rb
96
+ - ./LICENSE
97
+ - ./Rakefile
98
+ - ./README.md
99
+ - ./shenzhen.gemspec
100
+ - bin/ipa
101
+ homepage: http://github.com/mattt/shenzhen
102
+ licenses: []
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ segments:
114
+ - 0
115
+ hash: 522475160615829235
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ segments:
123
+ - 0
124
+ hash: 522475160615829235
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 1.8.15
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: Shenzhen
131
+ test_files: []