appsendr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,198 @@
1
+ require 'zip/zip'
2
+ require 'ftools'
3
+ module AppSendr::Command
4
+ class Build < Base
5
+ attr_accessor :app_path, :app_dir, :app_name, :ipa_path, :provisioning_name, :provisioning_path, :configuration, :project_info
6
+
7
+
8
+ def index
9
+ if require_project(1,"build","your active configuration name")
10
+ @configuration = load_configuration
11
+ display "=== Building Xcode project with configuration #{@configuration}"
12
+ compile(@configuration)
13
+
14
+ @app_dir = path_to_app_dir
15
+ @app_path = @app_dir+"/#{app_name}"
16
+ @ipa_path = build_ipa_with_app_at_path
17
+ info = app_info
18
+ bundle_identifier ||= info['CFBundleIdentifier']
19
+ icon_path = icon_path(info)
20
+ appsendr.upload(read_app_id,@ipa_path, profile_path,nil,bundle_identifier,icon_path)
21
+ FileUtils.rm_rf @ipa_path
22
+
23
+ end
24
+ end
25
+
26
+
27
+ def clean
28
+ require_project_dir("clean")
29
+ require_project_droppr
30
+ config = args.join(" ").strip
31
+ if !config.empty?
32
+ config = "-configuration #{config} "
33
+ end
34
+ %x[xcodebuild #{config} clean]
35
+ end
36
+
37
+ private
38
+
39
+ def compile(build_style, ossdk=nil, target=nil)
40
+ iossdk = "iphoneos4.1"
41
+ iossdk ||= ossdk
42
+ sdk_str = "-sdk "+iossdk
43
+
44
+ if target
45
+ target_str = "-target "+target
46
+ end
47
+ xcode = "xcodebuild #{sdk_str} #{target_str}-configuration #{build_style} > /dev/null 2>&1"
48
+ system(xcode)
49
+ end
50
+
51
+ def build_ipa_with_app_at_path
52
+ payload_path = @app_dir+"/Payload"
53
+ ipa_path = @app_dir+"/#{ipa_app_name(app_name)}"
54
+
55
+
56
+ FileUtils.rm_rf payload_path
57
+ Dir.mkdir(payload_path)
58
+ `cp -r #{@app_path.gsub(" ","\\ ")} #{payload_path.gsub(" ","\\ ")+"/"+app_name.gsub(" ","\\ ")}`
59
+ build_ipa(payload_path.gsub(" ","\\ "),ipa_path.gsub(" ","\\ "))
60
+
61
+ FileUtils.rm_rf payload_path
62
+
63
+ return ipa_path
64
+
65
+ end
66
+
67
+ def build_ipa(payload_path,ipa_path)
68
+ #ditto_exec = "ditto -c -k --keepParent -rsrc #{payload_path} #{ipa_path}"
69
+ ditto_exec = "ditto -c -k --keepParent --sequesterRsrc #{payload_path} #{ipa_path}"
70
+ #display ditto_exec
71
+ system(ditto_exec)
72
+ end
73
+
74
+
75
+
76
+ #povisioning
77
+
78
+ def profile_path
79
+ return @provisioning_path if @provisioning_path
80
+ @provisioning_path = provisioning_profiles_dir + "/#{profile_name}"
81
+ @provisioning_path
82
+ end
83
+
84
+ def provisioning_profiles_dir
85
+ File.expand_path("~/Library/MobileDevice/Provisioning\ Profiles/")
86
+ end
87
+
88
+
89
+ def profile_name
90
+ return @provisioning_name if @provisioning_name
91
+ error "Build is not valid for AdHoc distribution" unless valid_build?
92
+
93
+ mem_buf = File.open(@app_path+"/embedded.mobileprovision","rb") {|io| io.read}
94
+ name = mem_buf.scan(/UUID<\/key>[\s]+<string>(.*)<\/string>/).flatten.first
95
+ @provisioning_name = name + ".mobileprovision"
96
+ @provisioning_name
97
+ end
98
+
99
+
100
+ ## helpers
101
+ def load_configuration
102
+ @configuration = args.join(" ").strip
103
+ error "That configuration does not exist.\nAvailable configurations:\n#{project_info[:configurations].join("\n")}" unless valid_configuration?
104
+ @configuration
105
+ end
106
+
107
+ def valid_configuration?
108
+ return false unless @configuration
109
+ info = project_info
110
+ info[:configurations].include?(@configuration)
111
+ end
112
+
113
+ def project_info
114
+ return @project_info if @project_info
115
+ result = %x[xcodebuild -list]
116
+ project_info = result.split("\n")
117
+ name = project_info.shift.scan(/\"(.*)\"/).flatten.first
118
+ project_info.shift
119
+ project_info.pop #remove last line
120
+ project_info.pop #remove
121
+ on_targets = true
122
+ targets = []
123
+ configs = []
124
+ project_info.each{|ln|
125
+ if ln.strip == "Build Configurations:"
126
+ on_targets = false
127
+ end
128
+ if ln.strip != "Build Configurations:" and ln.strip != "Targets:" and ln.strip!=""
129
+ ln = ln.gsub("(Active)","").strip
130
+ on_targets ? targets.push(ln) : configs.push(ln)
131
+ end
132
+ }
133
+
134
+ @project_info = {:name => name, :targets=>targets, :configurations=>configs}
135
+ @project_info
136
+ end
137
+ def icon_path(info)
138
+ icon_files = info['CFBundleIconFiles']
139
+ icon_path = nil
140
+ icon_file = nil
141
+ if icon_files.nil? or icon_files.count == 0
142
+ icon_file = info['CFBundleIconFile']
143
+ else
144
+ large_icon = icon_files.grep(/@2x/i)
145
+ if large_icon.nil?
146
+ large_icon = icon_files.first
147
+ end
148
+ icon_file = large_icon
149
+ end
150
+ if icon_file and !icon_file.empty?
151
+ all_dir_contents = Dir["**/*"]
152
+ all_dir_contents = all_dir_contents - all_dir_contents.grep(/^build/)
153
+
154
+ icon = all_dir_contents.grep(%r{#{icon_file}}).flatten.first
155
+ if icon
156
+ icon_path = Dir.pwd+"/#{icon}"
157
+ end
158
+ end
159
+ return icon_path
160
+ end
161
+
162
+
163
+ def valid_build?
164
+ return unless @app_path
165
+ File.exist?(@app_path+"/embedded.mobileprovision")
166
+ end
167
+
168
+ def app_info
169
+ return unless @app_path
170
+ mem_buf = File.open(@app_path+"/Info.plist","rb") {|io| io.read}
171
+ plist = AppSendr::Plist::Binary.decode_binary_plist(mem_buf)
172
+ plist
173
+ end
174
+
175
+ def path_to_app_dir
176
+ Dir.pwd+"/build/#{@configuration}-iphoneos"
177
+ end
178
+
179
+ def app_name
180
+ if @app_dir.nil?
181
+ return unless @configuration
182
+ @app_dir = path_to_app_dir
183
+ end
184
+ return @app_name if @app_name
185
+ @app_name = Dir.entries(@app_dir).grep(/.+\.app$/).first
186
+ @app_name
187
+ end
188
+
189
+ def ipa_app_name(app_name)
190
+ app_name.gsub(/\.app$/,".ipa")
191
+ end
192
+
193
+ def project_name
194
+ return Dir.entries(Dir.pwd).grep(/.+\.xcodeproj/).first.sub("",".xcodeproj")
195
+ end
196
+
197
+ end
198
+ end
@@ -0,0 +1,41 @@
1
+ require 'optparse'
2
+
3
+ module AppSendr::Command
4
+ class Deploy < Build ## Inherhits from Deploy
5
+
6
+ def index
7
+ #
8
+ # opts = OptionParser.new
9
+ # opts.on('-m', '--message') { output_version ; exit 0 }
10
+ #
11
+ # opts.parse!(args) rescue return false
12
+ #
13
+ #
14
+ @configuration = load_configuration
15
+ if require_project(1,"deploy","your active configuration name",true)
16
+ @app_dir = path_to_app_dir
17
+ @app_path = @app_dir+"/#{app_name}"
18
+ if @app_name
19
+ @notes = ask_for_notes
20
+
21
+ @ipa_path = build_ipa_with_app_at_path
22
+ info = app_info
23
+ bundle_identifier ||= info['CFBundleIdentifier']
24
+ icon_path = icon_path(info)
25
+ appsendr.upload(read_app_id,@ipa_path, profile_path,@notes,bundle_identifier,icon_path)
26
+ FileUtils.rm_rf @ipa_path
27
+ else
28
+ error "No .app file was built for the configuration \"#{@configuration}\". Compile your app or use the \"build\" command."
29
+ end
30
+ end
31
+ end
32
+
33
+ protected
34
+ def ask_for_notes
35
+ print "Release Notes: "
36
+ notes = ask
37
+ return notes.strip
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,87 @@
1
+ module AppSendr::Command
2
+ class Help < Base
3
+ class HelpGroup < Array
4
+ attr_reader :title
5
+
6
+ def initialize(title)
7
+ @title = title
8
+ end
9
+
10
+ def command(name, description)
11
+ self << [name, description]
12
+ end
13
+
14
+ def space
15
+ self << ['', '']
16
+ end
17
+ end
18
+
19
+ def self.groups
20
+ @groups ||= []
21
+ end
22
+
23
+ def self.group(title, &block)
24
+ groups << begin
25
+ group = HelpGroup.new(title)
26
+ yield group
27
+ group
28
+ end
29
+ end
30
+
31
+ def self.create_default_groups!
32
+ group 'Commands' do |group|
33
+ group.command 'help', 'show this usage'
34
+ group.command 'version', 'show the gem version'
35
+ group.space
36
+ group.command 'list', 'list your apps'
37
+ group.command 'link', 'link your app with an exsiting one in appsendr'
38
+ group.command 'create <name>', 'create a new app'
39
+ group.space
40
+ group.command 'build <active configuration>', 'build your xcode project and deploy'
41
+ group.command 'build:clean', 'clean your xcode project'
42
+ group.space
43
+ group.command 'deploy <active configuration>', 'deploy the current build'
44
+ group.space
45
+ group.command 'testers', 'list testers'
46
+ group.command 'testers:add <email> <name>', 'add a tester'
47
+ group.command 'testers:remove <email> ', 'remove a tester'
48
+ group.command 'testers:clear', 'remove all testers'
49
+ group.command 'testers:notify', 'notify testers about the latest build'
50
+
51
+ end
52
+
53
+ end
54
+
55
+ def index
56
+ display usage
57
+ end
58
+
59
+ def version
60
+ display AppSendr::Client.version
61
+ end
62
+
63
+ def usage
64
+ longest_command_length = self.class.groups.map do |group|
65
+ group.map { |g| g.first.length }
66
+ end.flatten.max
67
+
68
+ self.class.groups.inject(StringIO.new) do |output, group|
69
+ output.puts "=== %s" % group.title
70
+ output.puts
71
+
72
+ group.each do |command, description|
73
+ if command.empty?
74
+ output.puts
75
+ else
76
+ output.puts "%-*s # %s" % [longest_command_length, command, description]
77
+ end
78
+ end
79
+
80
+ output.puts
81
+ output
82
+ end.string
83
+ end
84
+ end
85
+ end
86
+
87
+ AppSendr::Command::Help.create_default_groups!
@@ -0,0 +1,115 @@
1
+ module AppSendr::Command
2
+ class Testers < Base
3
+ def index #output testers
4
+ if in_project_dir?
5
+ unless has_project_droppr?
6
+ require_project_droppr
7
+ return
8
+ end
9
+
10
+ testers = appsendr.testers(read_app_id)
11
+ if testers["message"].size > 0
12
+ display "=== Your testers"
13
+ i = 0
14
+ display testers["message"].map {|app, id|
15
+ "#{i+=1}. #{app['tester']['name']} - #{app['tester']['email']}"
16
+ }.join("\n")
17
+ else
18
+ display "You have no testers."
19
+ end
20
+
21
+ # f = testers_file
22
+ # return unless f
23
+ # display "=== Testers"
24
+ # f.each do |line|
25
+ # tester = line.split(',')
26
+ # email = tester.first.strip
27
+ # display "#{email},#{name}"
28
+ # end
29
+ # f.close
30
+ end
31
+ end
32
+
33
+ def add
34
+
35
+ if require_project(2,"add testers","an email and name", true)
36
+ f = testers_file_append
37
+ return unless f
38
+ email = args.shift.strip
39
+ name = args.join(" ").strip
40
+ f.puts "#{email},#{name}"
41
+ f.close
42
+
43
+ appsendr.add_tester(read_app_id,email,name)
44
+
45
+ end
46
+ end
47
+
48
+ def remove
49
+ if require_project(1,"remove testers","an email")
50
+ entered_email = args.shift.strip
51
+
52
+ f = testers_file
53
+ return unless f
54
+ out = ""
55
+ found_email = nil
56
+ f.each do |line|
57
+ tester = line.split(',')
58
+ email = tester.first.strip
59
+ if email == entered_email
60
+ found_email = email
61
+ out << ""
62
+ else
63
+ out << line
64
+ end
65
+ end
66
+ f.pos = 0
67
+ f.print out
68
+ f.truncate(f.pos)
69
+
70
+ appsendr.remove_tester(read_app_id,entered_email)
71
+
72
+ end
73
+ end
74
+
75
+ def clear
76
+ if require_project(0,"clear testers",nil)
77
+ f = testers_file_truncate
78
+ return unless f
79
+ f.close
80
+
81
+ appsendr.add_tester(read_app_id)
82
+
83
+ end
84
+ end
85
+
86
+ def notify
87
+ require_project_dir("to notify testers")
88
+ require_project_droppr
89
+ appsendr.notify(read_app_id)
90
+
91
+ end
92
+
93
+ protected
94
+
95
+ def testers
96
+ AppSendr::PROJECT_DIR+"/testers"
97
+ end
98
+
99
+ def testers_file
100
+ return nil unless in_project_dir?
101
+ return File.open(testers, File::RDWR|File::CREAT, 0600)
102
+ end
103
+
104
+ def testers_file_append
105
+ return nil unless in_project_dir?
106
+ return File.open(testers, File::WRONLY|File::APPEND|File::CREAT, 0600)
107
+ end
108
+
109
+ def testers_file_truncate
110
+ return nil unless in_project_dir?
111
+ return File.open(path, File::WRONLY|File::TRUNC|File::CREAT, 0600)
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,7 @@
1
+ module AppSendr::Command
2
+ class Version < Base
3
+ def index
4
+ display AppSendr::Client.gem_version_string
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module AppSendr
2
+ VERSION = "0.0.1"
3
+ PROJECT_DIR = ".appsendr"
4
+ APPDROPPR_DIR = ".appsendr"
5
+ end
@@ -0,0 +1,120 @@
1
+ module AppSendr
2
+ module Helpers
3
+ def home_directory
4
+ running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
5
+ end
6
+
7
+ def running_on_windows?
8
+ RUBY_PLATFORM =~ /mswin32|mingw32/
9
+ end
10
+
11
+ def running_on_a_mac?
12
+ RUBY_PLATFORM =~ /-darwin\d/
13
+ end
14
+
15
+ def display(msg, newline=true)
16
+ if newline
17
+ puts(msg)
18
+ else
19
+ print(msg)
20
+ STDOUT.flush
21
+ end
22
+ end
23
+
24
+ def credentials_file
25
+ "#{home_directory}/#{AppSendr::APPDROPPR_DIR}/credentials"
26
+ end
27
+
28
+ def credentials_setup?
29
+ File.exist?(credentials_file)
30
+ end
31
+
32
+ def project_appsendr
33
+ Dir.pwd+"/#{AppSendr::PROJECT_DIR}"
34
+ end
35
+
36
+ def project_appsendr_app
37
+ project_appsendr+"/app"
38
+ end
39
+
40
+ def read_app_id
41
+ File.exists?(project_appsendr_app) and File.read(project_appsendr_app).split("\n").first
42
+ end
43
+ def read_app
44
+ File.exists?(project_appsendr_app) and File.read(project_appsendr_app).split("\n")
45
+ end
46
+
47
+ def has_project_droppr?
48
+ return false unless File.directory?(project_appsendr)
49
+ return File.exist?(project_appsendr_app)
50
+ end
51
+
52
+ def require_project_droppr
53
+ error "Project directory doesn't exist. Have you created an app droppr for this app?" unless has_project_droppr?
54
+ end
55
+
56
+ def require_project_dir(action)
57
+ error "You must be in a Xcode project directory to #{action}" unless in_project_dir?
58
+ end
59
+
60
+ def in_project_dir?
61
+ return Dir.entries(Dir.pwd).grep(/.+\.xcodeproj/).first
62
+ end
63
+
64
+ def require_project(num_args,action,wrong_args,greater_than=false)
65
+ if in_project_dir? and has_project_droppr?
66
+ if args.count >= num_args
67
+ if greater_than
68
+ return true
69
+ elsif !greater_than and (args.count == num_args)
70
+ return true
71
+ else
72
+ return false
73
+ end
74
+ elsif wrong_args
75
+ error "You must include #{wrong_args}"
76
+ end
77
+ else
78
+ require_project_dir(action)
79
+ require_project_droppr
80
+ end
81
+
82
+ return false
83
+ end
84
+
85
+ def require_in_project_and_no_droppr(num_args,action,wrong_args,greater_than=false)
86
+ if in_project_dir? and !has_project_droppr?
87
+ if args.count >= num_args
88
+ if greater_than
89
+ return true
90
+ elsif !greater_than and (args.count == num_args)
91
+ return true
92
+ else
93
+ return false
94
+ end
95
+ elsif wrong_args
96
+ error "You must include #{wrong_args}"
97
+ end
98
+ else
99
+ require_project_dir(action)
100
+ error "This app is already linked to appsendr." unless !has_project_droppr?
101
+
102
+ end
103
+
104
+ return false
105
+ end
106
+
107
+ def error(msg)
108
+ STDERR.puts(msg)
109
+ exit 1
110
+ end
111
+ end
112
+ end
113
+
114
+ unless String.method_defined?(:shellescape)
115
+ class String
116
+ def shellescape
117
+ empty? ? "''" : gsub(/([^A-Za-z0-9_\-.,:\/@\n])/n, '\\\\\\1').gsub(/\n/, "'\n'")
118
+ end
119
+ end
120
+ end
data/lib/appsendr.rb ADDED
@@ -0,0 +1,3 @@
1
+ module AppSendr; end
2
+
3
+ require 'appsendr/client'