testflight 0.1.1 → 0.1.2
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.
- data/README.rdoc +48 -0
- data/bin/takeoff +48 -240
- data/lib/testflight/config.rb +221 -0
- data/lib/testflight/prompter.rb +81 -0
- data/lib/testflight/version.rb +1 -1
- metadata +6 -3
- data/README.md +0 -0
data/README.rdoc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= Introduction
|
2
|
+
|
3
|
+
If you are an iOS developer, like me, you probably have dealt with the tedious
|
4
|
+
tasks of manually building, packaging, tagging, commiting and deploying your
|
5
|
+
iOS applications to testflightapp.com. Your probably wondered if there is an easier way to do so.
|
6
|
+
|
7
|
+
Well, you are absolutely right! This gem is going to make your life much easier. Here is what it does:
|
8
|
+
|
9
|
+
1. Reads your XCode project defintion to get your app version and build number
|
10
|
+
2. Commits all your changes to the git repository
|
11
|
+
3. Builds your project (or workspace)
|
12
|
+
4. Packages your application into an IPA file
|
13
|
+
5. Uploads the IPA file to testflightapp.com
|
14
|
+
6. Tags your project with the version and build
|
15
|
+
7. Increments your app build number and commits it to the repo
|
16
|
+
|
17
|
+
All that with a simple command:
|
18
|
+
|
19
|
+
$ takeoff
|
20
|
+
|
21
|
+
|
22
|
+
= Configuration
|
23
|
+
|
24
|
+
All you have to do is add .tesflight file to your project folder with the following content:
|
25
|
+
|
26
|
+
build:
|
27
|
+
developer_name: # This must be your company name or your name as it appears in your certificate
|
28
|
+
increment_bundle: true
|
29
|
+
commit_changes: true
|
30
|
+
testflight:
|
31
|
+
api_token: # Get it from https://testflightapp.com/account/#api
|
32
|
+
team_token: # Get it from https://testflightapp.com/dashboard/team/edit/
|
33
|
+
distribution_lists: ["Internal", "Everyone"] # Configure your distribution lists on https://testflightapp.com
|
34
|
+
|
35
|
+
|
36
|
+
Then go to your project folder and simple type:
|
37
|
+
|
38
|
+
$ takeoff
|
39
|
+
|
40
|
+
|
41
|
+
Have a safe flight!
|
42
|
+
|
43
|
+
|
44
|
+
= Contributions
|
45
|
+
|
46
|
+
Clone the repository, make any changes you like and send me a pull request.
|
47
|
+
|
48
|
+
|
data/bin/takeoff
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'fileutils'
|
4
|
-
require 'yaml'
|
5
3
|
require 'pp'
|
6
|
-
|
4
|
+
|
5
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "../lib/testflight/config.rb"))
|
6
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "../lib/testflight/prompter.rb"))
|
7
7
|
|
8
8
|
XCODE_BUILDER = "/usr/bin/xcodebuild"
|
9
9
|
XCODE_PACKAGER = "/usr/bin/xcrun"
|
@@ -17,260 +17,90 @@ TESTFLIGHT_ENDPOINT = "http://testflightapp.com/api/builds.json"
|
|
17
17
|
# package files
|
18
18
|
# deploy to testflight app
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
20
|
+
####################################################################################
|
21
|
+
## Building Project
|
22
|
+
####################################################################################
|
110
23
|
|
111
24
|
def build_workspace
|
112
|
-
cmd = "#{XCODE_BUILDER} -workspace '#{workspace_name}' "
|
113
|
-
cmd << "-scheme '#{application_name}' "
|
25
|
+
cmd = "#{XCODE_BUILDER} -workspace '#{Testflight::Config.workspace_name}' "
|
26
|
+
cmd << "-scheme '#{Testflight::Config.application_name}' "
|
114
27
|
cmd << "-sdk 'iphoneos6.0' "
|
115
28
|
cmd << "-configuration 'AdHoc' "
|
116
29
|
cmd << "-arch 'armv6 armv7' "
|
117
|
-
cmd << "CONFIGURATION_BUILD_DIR='#{build_dir}' "
|
30
|
+
cmd << "CONFIGURATION_BUILD_DIR='#{Testflight::Config.build_dir}' "
|
118
31
|
pp "Building Workspace..."
|
119
32
|
pp cmd
|
120
33
|
unless system(cmd)
|
121
|
-
pp "Failed to
|
34
|
+
pp "Failed to build workspace"
|
122
35
|
exit 1
|
123
36
|
end
|
124
37
|
end
|
125
38
|
|
126
39
|
def build_project
|
127
|
-
cmd = "#{XCODE_BUILDER} -target '#{application_name}' "
|
40
|
+
cmd = "#{XCODE_BUILDER} -target '#{Testflight::Config.application_name}' "
|
128
41
|
cmd << "-sdk 'iphoneos6.0' "
|
129
42
|
cmd << "-configuration 'AdHoc' "
|
43
|
+
|
130
44
|
pp "Building Project..."
|
131
45
|
pp cmd
|
132
46
|
unless system(cmd)
|
133
|
-
pp "Failed to build
|
47
|
+
pp "Failed to build project"
|
134
48
|
exit 1
|
135
49
|
end
|
136
50
|
end
|
137
51
|
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
52
|
+
####################################################################################
|
53
|
+
## Packaging Project
|
54
|
+
####################################################################################
|
148
55
|
|
149
56
|
def package_workspace
|
150
57
|
cmd = "#{XCODE_PACKAGER} -sdk iphoneos PackageApplication "
|
151
|
-
cmd << "-v '#{build_dir}/#{application_name}.app' "
|
152
|
-
cmd << "-o '#{distribution_file}' "
|
153
|
-
cmd << "--sign '#{
|
154
|
-
cmd << "--embed '#{provisioning_dir}/#{ad_hoc_provisioning_name}'"
|
58
|
+
cmd << "-v '#{Testflight::Config.build_dir}/#{Testflight::Config.application_name}.app' "
|
59
|
+
cmd << "-o '#{Testflight::Config.distribution_file}' "
|
60
|
+
cmd << "--sign '#{Testflight::Config.developer_name}' "
|
61
|
+
cmd << "--embed '#{Testflight::Config.provisioning_dir}/#{Testflight::Config.ad_hoc_provisioning_name}'"
|
155
62
|
|
156
63
|
pp "Packaging Workspace..."
|
157
64
|
pp cmd
|
158
65
|
unless system(cmd)
|
159
|
-
pp "Failed to package
|
66
|
+
pp "Failed to package workspace"
|
160
67
|
exit 1
|
161
68
|
end
|
162
69
|
end
|
163
70
|
|
164
71
|
def package_project
|
165
72
|
cmd = "#{XCODE_PACKAGER} -sdk iphoneos PackageApplication "
|
166
|
-
cmd << "-v '#{build_dir}/AdHoc-iphoneos/#{application_name}.app' "
|
167
|
-
cmd << "-o '#{distribution_file}' "
|
168
|
-
cmd << "--sign '#{
|
169
|
-
cmd << "--embed '#{provisioning_dir}/#{ad_hoc_provisioning_name}'"
|
73
|
+
cmd << "-v '#{Testflight::Config.build_dir}/AdHoc-iphoneos/#{Testflight::Config.application_name}.app' "
|
74
|
+
cmd << "-o '#{Testflight::Config.distribution_file}' "
|
75
|
+
cmd << "--sign '#{Testflight::Config.developer_name}' "
|
76
|
+
cmd << "--embed '#{Testflight::Config.provisioning_dir}/#{Testflight::Config.ad_hoc_provisioning_name}'"
|
170
77
|
|
171
78
|
pp "Packaging Project..."
|
172
79
|
pp cmd
|
173
80
|
unless system(cmd)
|
174
|
-
pp "Failed to package
|
81
|
+
pp "Failed to package project"
|
175
82
|
exit 1
|
176
83
|
end
|
177
84
|
end
|
178
85
|
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
86
|
+
####################################################################################
|
87
|
+
## Uploading Project
|
88
|
+
####################################################################################
|
259
89
|
|
260
90
|
def upload_to_testflightapp
|
261
91
|
pp "Uploading to TestFlightApp..."
|
262
92
|
|
263
|
-
notes = read_info("What has changed in this build?")
|
93
|
+
notes = Testflight::Prompter.read_info("What has changed in this build?")
|
264
94
|
|
265
|
-
lists =
|
95
|
+
lists = Testflight::Prompter.select_set("Which distribution lists would you like to send this build to? Select one or more lists, separated with comma.", Testflight::Config.distribution_lists)
|
266
96
|
|
267
|
-
notify = read_options("Would you like to notify your team members by email about this build?")
|
97
|
+
notify = Testflight::Prompter.read_options("Would you like to notify your team members by email about this build?")
|
268
98
|
["y", "yes"].include?(notify) ? notify = "true" : notify = "false"
|
269
99
|
|
270
100
|
cmd = "curl #{TESTFLIGHT_ENDPOINT} "
|
271
|
-
cmd << "-F file=@#{distribution_file} "
|
272
|
-
cmd << "-F api_token=#{
|
273
|
-
cmd << "-F team_token=#{
|
101
|
+
cmd << "-F file=@#{Testflight::Config.distribution_file} "
|
102
|
+
cmd << "-F api_token=#{Testflight::Config.api_token} "
|
103
|
+
cmd << "-F team_token=#{Testflight::Config.team_token} "
|
274
104
|
cmd << "-F notify=#{notify} "
|
275
105
|
cmd << "-F distribution_lists=#{lists.join(",")} "
|
276
106
|
cmd << "-F notes='#{notes}'"
|
@@ -286,35 +116,13 @@ end
|
|
286
116
|
## Project Versioning
|
287
117
|
####################################################################################
|
288
118
|
|
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
119
|
def increment_bundle_version
|
311
|
-
project_info["CFBundleVersion"] = (
|
312
|
-
pp "Incrementing bundle version to #{
|
120
|
+
Testflight::Config.project_info["CFBundleVersion"] = (Testflight::Config.build_number.to_i + 1).to_s
|
121
|
+
pp "Incrementing bundle version to #{Testflight::Config.build_number}..."
|
313
122
|
|
314
|
-
File.open(project_info_path, "w") do |f|
|
315
|
-
f.write(project_info.to_plist)
|
123
|
+
File.open(Testflight::Config.project_info_path, "w") do |f|
|
124
|
+
f.write(Testflight::Config.project_info.to_plist)
|
316
125
|
end
|
317
|
-
@project_info = nil
|
318
126
|
end
|
319
127
|
|
320
128
|
####################################################################################
|
@@ -333,7 +141,7 @@ def commit_changes(msg)
|
|
333
141
|
system("git commit -m '#{msg}'")
|
334
142
|
system("git push")
|
335
143
|
|
336
|
-
system("git tag -a #{project_version_short} -m 'Release #{project_version}'")
|
144
|
+
system("git tag -a #{Testflight::Config.project_version_short} -m 'Release #{Testflight::Config.project_version}'")
|
337
145
|
end
|
338
146
|
|
339
147
|
####################################################################################
|
@@ -341,13 +149,13 @@ end
|
|
341
149
|
####################################################################################
|
342
150
|
|
343
151
|
def deploy
|
344
|
-
setup
|
152
|
+
Testflight::Config.setup
|
345
153
|
|
346
|
-
if
|
347
|
-
commit_changes("Preparing build #{project_version}")
|
154
|
+
if Testflight::Config.commit_changes?
|
155
|
+
commit_changes("Preparing build #{Testflight::Config.project_version}")
|
348
156
|
end
|
349
|
-
|
350
|
-
if
|
157
|
+
|
158
|
+
if Testflight::Config.workspace?
|
351
159
|
build_workspace
|
352
160
|
package_workspace
|
353
161
|
else
|
@@ -359,10 +167,10 @@ def deploy
|
|
359
167
|
|
360
168
|
pp ""
|
361
169
|
|
362
|
-
if
|
170
|
+
if Testflight::Config.increment_bundle?
|
363
171
|
increment_bundle_version
|
364
|
-
if
|
365
|
-
commit_changes("Incrementing build number to #{project_version}")
|
172
|
+
if Testflight::Config.commit_changes?
|
173
|
+
commit_changes("Incrementing build number to #{Testflight::Config.project_version}")
|
366
174
|
end
|
367
175
|
end
|
368
176
|
|
@@ -0,0 +1,221 @@
|
|
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
|
+
require 'fileutils'
|
25
|
+
require 'yaml'
|
26
|
+
require 'pp'
|
27
|
+
require 'plist'
|
28
|
+
|
29
|
+
module Testflight
|
30
|
+
class Config
|
31
|
+
|
32
|
+
def self.path
|
33
|
+
'.testflight'
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.defaults
|
37
|
+
{
|
38
|
+
"build" => {
|
39
|
+
"developer_name" => "As it appears in your Apple certificate",
|
40
|
+
"increment_bundle" => true,
|
41
|
+
"commit_changes" => true
|
42
|
+
},
|
43
|
+
"testflight" => {
|
44
|
+
"api_token" => "Get it from https://testflightapp.com/account/#api",
|
45
|
+
"team_token" => "Get it from https://testflightapp.com/dashboard/team/edit/",
|
46
|
+
"distribution_lists" => [""]
|
47
|
+
}
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.config
|
52
|
+
@config ||= begin
|
53
|
+
if File.exist?(path)
|
54
|
+
YAML::load(File.open(path))
|
55
|
+
else
|
56
|
+
File.open(path, "w") do |f|
|
57
|
+
f.write(defaults.to_yaml)
|
58
|
+
end
|
59
|
+
raise "Please update #{path} with all necessary properties."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.commit_changes?
|
65
|
+
config["build"]["commit_changes"]
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.increment_bundle?
|
69
|
+
config["build"]["increment_bundle"]
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.developer_name
|
73
|
+
config["build"]["developer_name"]
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.distribution_lists
|
77
|
+
config["testflight"]["distribution_lists"]
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.api_token
|
81
|
+
config["testflight"]["api_token"]
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.team_token
|
85
|
+
config["testflight"]["team_token"]
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.valid?
|
89
|
+
return false unless config
|
90
|
+
return false if config.empty?
|
91
|
+
return false if config["build"].nil?
|
92
|
+
return false if config["build"]["developer_name"].nil?
|
93
|
+
return false if config["testflight"].nil?
|
94
|
+
return false if config["testflight"]["api_token"].nil?
|
95
|
+
return false if config["testflight"]["team_token"].nil?
|
96
|
+
return false if config["testflight"]["distribution_lists"].nil?
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.project_dir
|
101
|
+
Dir.pwd
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.project_files
|
105
|
+
@project_files ||= Dir.entries(project_dir)
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.file_name_by_ext(ext)
|
109
|
+
project_files.select{|file| file.match(/#{ext}$/)}.first
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.workspace_name
|
113
|
+
@workspace_name ||= file_name_by_ext('xcworkspace')
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.project_name
|
117
|
+
@project_name ||= file_name_by_ext('xcodeproj')
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.type
|
121
|
+
return "unknown" if workspace_name.nil? and project_name.nil?
|
122
|
+
@type ||= workspace_name ? 'workspace' : 'project'
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.workspace?
|
126
|
+
type == "workspace"
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.project?
|
130
|
+
type == "project"
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.application_name
|
134
|
+
@application_name ||= begin
|
135
|
+
name = workspace_name || project_name
|
136
|
+
name = name.split(".")[0..-2].join(".") if name
|
137
|
+
name
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.build_dir
|
142
|
+
"#{project_dir}/build"
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.provisioning_dir
|
146
|
+
"#{project_dir}/Provisioning"
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.distributions_dir
|
150
|
+
"#{project_dir}/Distributions"
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.ad_hoc_provisioning_name
|
154
|
+
@ad_hoc_provisioning_name ||= begin
|
155
|
+
files = Dir.entries(provisioning_dir)
|
156
|
+
files.select{|file| file.match(/mobileprovision$/)}.first
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.distribution_file
|
161
|
+
"#{distributions_dir}/#{application_name}.ipa"
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.setup
|
165
|
+
unless application_name
|
166
|
+
pp "This folder does not contain an xCode project or a workspace."
|
167
|
+
exit 1
|
168
|
+
end
|
169
|
+
|
170
|
+
unless valid?
|
171
|
+
pp "Ensure that you have provided all of the information in the #{config_file} config file"
|
172
|
+
exit 1
|
173
|
+
end
|
174
|
+
|
175
|
+
unless project_files.include?("Distributions")
|
176
|
+
FileUtils.mkdir("Distributions")
|
177
|
+
@project_files = nil
|
178
|
+
end
|
179
|
+
|
180
|
+
unless project_files.include?("Provisioning")
|
181
|
+
FileUtils.mkdir("Provisioning")
|
182
|
+
@project_files = nil
|
183
|
+
end
|
184
|
+
|
185
|
+
unless ad_hoc_provisioning_name
|
186
|
+
pp "Please copy your Ad Hoc Provisioning Profile into the provisioning folder."
|
187
|
+
exit 1
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.project_info_path
|
192
|
+
files = Dir["**/#{application_name}-Info.plist"]
|
193
|
+
if files.empty?
|
194
|
+
pp "Cannot locate #{application_name}-Info.plist file. Please make sure such file exists in your project."
|
195
|
+
exit 1
|
196
|
+
end
|
197
|
+
files.first
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.project_info
|
201
|
+
@project_info ||= Plist::parse_xml(project_info_path)
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.version
|
205
|
+
project_info["CFBundleShortVersionString"]
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.build_number
|
209
|
+
project_info["CFBundleVersion"]
|
210
|
+
end
|
211
|
+
|
212
|
+
def self.project_version
|
213
|
+
"#{version} (#{build_number})"
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.project_version_short
|
217
|
+
"#{version}.#{build_number}"
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,81 @@
|
|
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
|
+
class Prompter
|
26
|
+
|
27
|
+
def self.read_options(question=nil, opts=["Yes", "No"], vals=["y", "yes", "n", "no"], joiner = "/")
|
28
|
+
prompt = "(#{opts.join(joiner)})? "
|
29
|
+
pp question if question
|
30
|
+
|
31
|
+
$stdout.print(prompt)
|
32
|
+
|
33
|
+
$stdin.each_line do |line|
|
34
|
+
value = line.strip.downcase
|
35
|
+
return value if vals.include?(value)
|
36
|
+
$stdout.print(prompt)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.read_info(question=nil, prompt="> ", allow_blank=false)
|
41
|
+
pp question if question
|
42
|
+
|
43
|
+
$stdout.print(prompt)
|
44
|
+
|
45
|
+
$stdin.each_line do |line|
|
46
|
+
value = line.strip.downcase
|
47
|
+
return value if allow_blank
|
48
|
+
return value unless value.empty?
|
49
|
+
$stdout.print(prompt)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.read_set_values(opts)
|
54
|
+
lists = []
|
55
|
+
vals = read_info(nil, prompt="? ")
|
56
|
+
vals.split(",").each do |index|
|
57
|
+
index = index.to_i - 1
|
58
|
+
return nil if index<0 or index>=opts.size
|
59
|
+
lists << opts[index]
|
60
|
+
end
|
61
|
+
lists
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.select_set(question, opts = [])
|
65
|
+
pp question
|
66
|
+
|
67
|
+
opts.each_with_index do |opt, index|
|
68
|
+
pp " #{index+1}) #{opt}"
|
69
|
+
end
|
70
|
+
|
71
|
+
vals = read_set_values(opts)
|
72
|
+
while vals.nil?
|
73
|
+
pp "Invalid selection, please try again."
|
74
|
+
vals = read_set_values(opts)
|
75
|
+
end
|
76
|
+
|
77
|
+
vals
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
data/lib/testflight/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: testflight
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -37,15 +37,18 @@ extensions: []
|
|
37
37
|
extra_rdoc_files: []
|
38
38
|
files:
|
39
39
|
- bin/takeoff
|
40
|
+
- lib/testflight/config.rb
|
41
|
+
- lib/testflight/prompter.rb
|
40
42
|
- lib/testflight/version.rb
|
41
43
|
- LICENSE
|
42
|
-
- README.
|
44
|
+
- README.rdoc
|
43
45
|
homepage: https://github.com/berk/testflight
|
44
46
|
licenses: []
|
45
47
|
post_install_message:
|
46
48
|
rdoc_options: []
|
47
49
|
require_paths:
|
48
|
-
- lib
|
50
|
+
- - lib
|
51
|
+
- lib/testflight
|
49
52
|
required_ruby_version: !ruby/object:Gem::Requirement
|
50
53
|
none: false
|
51
54
|
requirements:
|
data/README.md
DELETED
File without changes
|