testflight 0.1.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.
Files changed (5) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +0 -0
  3. data/bin/takeoff +374 -0
  4. data/lib/testflight/version.rb +28 -0
  5. metadata +67 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Michael Berkovich
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
File without changes
data/bin/takeoff ADDED
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require 'pp'
6
+ require 'plist'
7
+
8
+ XCODE_BUILDER = "/usr/bin/xcodebuild"
9
+ XCODE_PACKAGER = "/usr/bin/xcrun"
10
+ TESTFLIGHT_ENDPOINT = "http://testflightapp.com/api/builds.json"
11
+
12
+ # read project definition
13
+ # read app version and build number
14
+ # add all files to git and commit files
15
+ # tag git with the version and build
16
+ # build files
17
+ # package files
18
+ # deploy to testflight app
19
+
20
+
21
+ def project_dir
22
+ Dir.pwd
23
+ end
24
+
25
+ def project_files
26
+ @project_files ||= Dir.entries(project_dir)
27
+ end
28
+
29
+ def file_name_by_ext(ext)
30
+ project_files.select{|file| file.match(/#{ext}$/)}.first
31
+ end
32
+
33
+
34
+ def workspace_name
35
+ @workspace_name ||= file_name_by_ext('xcworkspace')
36
+ end
37
+
38
+ def project_name
39
+ @project_name ||= file_name_by_ext('xcodeproj')
40
+ end
41
+
42
+ def type
43
+ return "unknown" if workspace_name.nil? and project_name.nil?
44
+ @type ||= workspace_name ? 'workspace' : 'project'
45
+ end
46
+
47
+ def application_name
48
+ @application_name ||= begin
49
+ name = workspace_name || project_name
50
+ name = name.split(".")[0..-2].join(".") if name
51
+ name
52
+ end
53
+ end
54
+
55
+ def default_config
56
+ {
57
+ "build" => {
58
+ "developer_name" => "As it appears in your Apple certificate",
59
+ "increment_bundle" => true,
60
+ "commit_changes" => true
61
+ },
62
+ "testflight" => {
63
+ "api_token" => "Get it from https://testflightapp.com/account/#api",
64
+ "team_token" => "Get it from https://testflightapp.com/dashboard/team/edit/",
65
+ "distribution_lists" => [""]
66
+ }
67
+ }
68
+ end
69
+
70
+ def valid_config?
71
+ return false unless config
72
+ return false if config.empty?
73
+ return false if config["build"].nil?
74
+ return false if config["build"]["developer_name"].nil?
75
+ return false if config["testflight"].nil?
76
+ return false if config["testflight"]["api_token"].nil?
77
+ return false if config["testflight"]["team_token"].nil?
78
+ return false if config["testflight"]["distribution_lists"].nil?
79
+ true
80
+ end
81
+
82
+ def config_file
83
+ '.testflight'
84
+ end
85
+
86
+ def config
87
+ @config ||= begin
88
+ if File.exist?(config_file)
89
+ YAML::load(File.open(config_file))
90
+ else
91
+ File.open(config_file, "w") do |f|
92
+ f.write(default_config.to_yaml)
93
+ end
94
+ YAML::load(File.open(config_file))
95
+ end
96
+ end
97
+ end
98
+
99
+ def build_dir
100
+ "#{project_dir}/build"
101
+ end
102
+
103
+ def provisioning_dir
104
+ "#{project_dir}/Provisioning"
105
+ end
106
+
107
+ def distributions_dir
108
+ "#{project_dir}/Distributions"
109
+ end
110
+
111
+ def build_workspace
112
+ cmd = "#{XCODE_BUILDER} -workspace '#{workspace_name}' "
113
+ cmd << "-scheme '#{application_name}' "
114
+ cmd << "-sdk 'iphoneos6.0' "
115
+ cmd << "-configuration 'AdHoc' "
116
+ cmd << "-arch 'armv6 armv7' "
117
+ cmd << "CONFIGURATION_BUILD_DIR='#{build_dir}' "
118
+ pp "Building Workspace..."
119
+ pp cmd
120
+ unless system(cmd)
121
+ pp "Failed to package the workspace"
122
+ exit 1
123
+ end
124
+ end
125
+
126
+ def build_project
127
+ cmd = "#{XCODE_BUILDER} -target '#{application_name}' "
128
+ cmd << "-sdk 'iphoneos6.0' "
129
+ cmd << "-configuration 'AdHoc' "
130
+ pp "Building Project..."
131
+ pp cmd
132
+ unless system(cmd)
133
+ pp "Failed to build the project"
134
+ exit 1
135
+ end
136
+ end
137
+
138
+ def ad_hoc_provisioning_name
139
+ @ad_hoc_provisioning_name ||= begin
140
+ files = Dir.entries(provisioning_dir)
141
+ files.select{|file| file.match(/mobileprovision$/)}.first
142
+ end
143
+ end
144
+
145
+ def distribution_file
146
+ "#{distributions_dir}/#{application_name}.ipa"
147
+ end
148
+
149
+ def package_workspace
150
+ cmd = "#{XCODE_PACKAGER} -sdk iphoneos PackageApplication "
151
+ cmd << "-v '#{build_dir}/#{application_name}.app' "
152
+ cmd << "-o '#{distribution_file}' "
153
+ cmd << "--sign '#{config["build"]["developer_name"]}' "
154
+ cmd << "--embed '#{provisioning_dir}/#{ad_hoc_provisioning_name}'"
155
+
156
+ pp "Packaging Workspace..."
157
+ pp cmd
158
+ unless system(cmd)
159
+ pp "Failed to package the workspace"
160
+ exit 1
161
+ end
162
+ end
163
+
164
+ def package_project
165
+ cmd = "#{XCODE_PACKAGER} -sdk iphoneos PackageApplication "
166
+ cmd << "-v '#{build_dir}/AdHoc-iphoneos/#{application_name}.app' "
167
+ cmd << "-o '#{distribution_file}' "
168
+ cmd << "--sign '#{config["build"]["developer_name"]}' "
169
+ cmd << "--embed '#{provisioning_dir}/#{ad_hoc_provisioning_name}'"
170
+
171
+ pp "Packaging Project..."
172
+ pp cmd
173
+ unless system(cmd)
174
+ pp "Failed to package the project"
175
+ exit 1
176
+ end
177
+ end
178
+
179
+ def setup
180
+ unless application_name
181
+ pp "This folder does not contain an xCode project or a workspace."
182
+ exit 1
183
+ end
184
+
185
+ unless valid_config?
186
+ pp "Ensure that you have provided all of the information in the #{config_file} config file"
187
+ exit 1
188
+ end
189
+
190
+ unless project_files.include?("Distributions")
191
+ FileUtils.mkdir("Distributions")
192
+ @project_files = nil
193
+ end
194
+
195
+ unless project_files.include?("Provisioning")
196
+ FileUtils.mkdir("Provisioning")
197
+ @project_files = nil
198
+ end
199
+
200
+ unless ad_hoc_provisioning_name
201
+ pp "Please copy your Ad Hoc Provisioning Profile into the provisioning folder."
202
+ exit 1
203
+ end
204
+ end
205
+
206
+ def read_options(question, opts=["Yes", "No"], vals=["y", "yes", "n", "no"], joiner = "/")
207
+ prompt = "(#{opts.join(joiner)})? "
208
+ pp question
209
+
210
+ $stdout.print(prompt)
211
+
212
+ $stdin.each_line do |line|
213
+ value = line.strip.downcase
214
+ return value if vals.include?(value)
215
+ $stdout.print(prompt)
216
+ end
217
+ end
218
+
219
+ def read_info(question, prompt="> ", allow_blank=false)
220
+ pp(question) if question
221
+
222
+ $stdout.print(prompt)
223
+
224
+ $stdin.each_line do |line|
225
+ value = line.strip.downcase
226
+ return value if allow_blank
227
+ return value unless value.empty?
228
+ $stdout.print(prompt)
229
+ end
230
+ end
231
+
232
+ def read_distribution_lists
233
+ lists = []
234
+ vals = read_info(nil, prompt="? ")
235
+ vals.split(",").each do |index|
236
+ index = index.to_i - 1
237
+ return nil if index<0 or index>=config["testflight"]["distribution_lists"].size
238
+ lists << config["testflight"]["distribution_lists"][index]
239
+ end
240
+ lists
241
+ end
242
+
243
+ def select_distribution_lists
244
+ return [] unless config["testflight"]["distribution_lists"]
245
+
246
+ pp "Which distribution lists would you like to send this build to? (Select one or more lists, separated with comma.)"
247
+ config["testflight"]["distribution_lists"].each_with_index do |list, index|
248
+ pp " #{index+1}) #{list}"
249
+ end
250
+
251
+ lists = read_distribution_lists
252
+ while lists.nil?
253
+ pp "Invalid selection, please try again."
254
+ lists = read_distribution_lists
255
+ end
256
+
257
+ lists
258
+ end
259
+
260
+ def upload_to_testflightapp
261
+ pp "Uploading to TestFlightApp..."
262
+
263
+ notes = read_info("What has changed in this build?")
264
+
265
+ lists = select_distribution_lists
266
+
267
+ notify = read_options("Would you like to notify your team members by email about this build?")
268
+ ["y", "yes"].include?(notify) ? notify = "true" : notify = "false"
269
+
270
+ cmd = "curl #{TESTFLIGHT_ENDPOINT} "
271
+ cmd << "-F file=@#{distribution_file} "
272
+ cmd << "-F api_token=#{config["testflight"]["api_token"]} "
273
+ cmd << "-F team_token=#{config["testflight"]["team_token"]} "
274
+ cmd << "-F notify=#{notify} "
275
+ cmd << "-F distribution_lists=#{lists.join(",")} "
276
+ cmd << "-F notes='#{notes}'"
277
+
278
+ pp cmd
279
+ unless system(cmd)
280
+ pp "Failed to upload to testflight"
281
+ exit 1
282
+ end
283
+ end
284
+
285
+ ####################################################################################
286
+ ## Project Versioning
287
+ ####################################################################################
288
+
289
+ def project_info_path
290
+ files = Dir["**/#{application_name}-Info.plist"]
291
+ if files.empty?
292
+ pp "Cannot locate #{application_name}-Info.plist file. Please make sure such file exists in your project."
293
+ exit 1
294
+ end
295
+ files.first
296
+ end
297
+
298
+ def project_info
299
+ @project_info ||= Plist::parse_xml(project_info_path)
300
+ end
301
+
302
+ def project_version
303
+ "#{project_info["CFBundleShortVersionString"]} (#{project_info["CFBundleVersion"]})"
304
+ end
305
+
306
+ def project_version_short
307
+ "#{project_info["CFBundleShortVersionString"]}.#{project_info["CFBundleVersion"]}"
308
+ end
309
+
310
+ def increment_bundle_version
311
+ project_info["CFBundleVersion"] = (project_info["CFBundleVersion"].to_i + 1).to_s
312
+ pp "Incrementing bundle version to #{project_info["CFBundleVersion"]}..."
313
+
314
+ File.open(project_info_path, "w") do |f|
315
+ f.write(project_info.to_plist)
316
+ end
317
+ @project_info = nil
318
+ end
319
+
320
+ ####################################################################################
321
+ ## Git Support
322
+ ####################################################################################
323
+ def update_git_ignore
324
+ # build
325
+ # Distributions
326
+ # Provisioning
327
+ # .testflight
328
+ end
329
+
330
+ def commit_changes(msg)
331
+ system("git add .")
332
+ system("git add . --update")
333
+ system("git commit -m '#{msg}'")
334
+ system("git push")
335
+
336
+ system("git tag -a #{project_version_short} -m 'Release #{project_version}'")
337
+ end
338
+
339
+ ####################################################################################
340
+ ## Main
341
+ ####################################################################################
342
+
343
+ def deploy
344
+ setup
345
+
346
+ if config["build"]["commit_changes"]
347
+ commit_changes("Preparing build #{project_version}")
348
+ end
349
+
350
+ if workspace_name
351
+ build_workspace
352
+ package_workspace
353
+ else
354
+ build_project
355
+ package_project
356
+ end
357
+
358
+ upload_to_testflightapp
359
+
360
+ pp ""
361
+
362
+ if config["build"]["increment_bundle"]
363
+ increment_bundle_version
364
+ if config["build"]["commit_changes"]
365
+ commit_changes("Incrementing build number to #{project_version}")
366
+ end
367
+ end
368
+
369
+ pp "Congratulations! The app has been deployed!"
370
+ end
371
+
372
+ deploy
373
+
374
+
@@ -0,0 +1,28 @@
1
+ #--
2
+ # Copyright (c) 2012 Michael Berkovich
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Testflight
25
+ VERSION = "0.1.1"
26
+ end
27
+
28
+
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: testflight
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Berkovich
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: plist
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.1.0
30
+ description: Mechanism for building, packaging, tagging and deploying XCode projects
31
+ to testflightapp.com
32
+ email:
33
+ - theiceberk@gmail.com
34
+ executables:
35
+ - takeoff
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - bin/takeoff
40
+ - lib/testflight/version.rb
41
+ - LICENSE
42
+ - README.md
43
+ homepage: https://github.com/berk/testflight
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.23
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: iOS application deployment automation
67
+ test_files: []