fir-cli 0.2.3.1 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +13 -82
- data/CHANGELOG +36 -0
- data/Gemfile +3 -1
- data/LICENSE.txt +1 -1
- data/README.md +83 -103
- data/Rakefile +1 -0
- data/bin/fir +9 -11
- data/fir-cli.gemspec +47 -26
- data/lib/fir/api.yml +5 -0
- data/lib/fir/cli.rb +107 -0
- data/lib/fir/patches/bin/pngcrush +0 -0
- data/lib/fir/patches/native_patch.rb +199 -0
- data/lib/fir/patches/os_patch.rb +22 -0
- data/lib/fir/patches/parser_patch.rb +165 -0
- data/lib/fir/patches.rb +5 -0
- data/lib/fir/util/build.rb +158 -0
- data/lib/fir/util/info.rb +79 -0
- data/lib/fir/util/login.rb +17 -0
- data/lib/fir/util/publish.rb +103 -0
- data/lib/fir/util.rb +45 -0
- data/lib/fir/version.rb +5 -0
- data/lib/fir-cli.rb +3 -0
- data/lib/fir.rb +87 -0
- data/lib/fir_cli.rb +3 -0
- metadata +57 -109
- data/Gemfile.lock +0 -45
- data/fir-cli.rb +0 -27
- data/lib/fir-cli/version.rb +0 -5
- data/lib/fir-cli-commands/00-info.rb +0 -17
- data/lib/fir-cli-commands/00-login.rb +0 -30
- data/lib/fir-cli-commands/00-profile.rb +0 -31
- data/lib/fir-cli-commands/00-upgrade.rb +0 -15
- data/lib/fir-cli-commands/00-version.rb +0 -10
- data/lib/fir-cli-commands/01-config.rb +0 -17
- data/lib/fir-cli-commands/100-resign_codesign.rb +0 -59
- data/lib/fir-cli-commands/100-resign_tapbeta.rb +0 -85
- data/lib/fir-cli-commands/101-resign.rb +0 -26
- data/lib/fir-cli-commands/11-publish.rb +0 -73
- data/lib/fir-cli-commands/12-build_ipa.rb +0 -153
- data/lib/fir-cli.chk.rb +0 -33
- data/lib/fir-cli.core.rb +0 -96
- data/lib/fir-cli.fir.rb +0 -49
- data/lib/fir-cli.opt.rb +0 -35
- data/lib/fir-cli.output.rb +0 -55
- data/lib/fir-cli.utils.rb +0 -108
- data/lib/lagunitas.ext.rb +0 -56
- data/lib/lagunitas.patch.rb +0 -51
- data/lib/user_config.patch.rb +0 -10
@@ -0,0 +1,199 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# activesupport/lib/active_support/core_ext/object/blank.rb
|
5
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
6
|
+
# For example, '', ' ', +nil+, [], and {} are all blank.
|
7
|
+
#
|
8
|
+
# This simplifies
|
9
|
+
#
|
10
|
+
# address.nil? || address.empty?
|
11
|
+
#
|
12
|
+
# to
|
13
|
+
#
|
14
|
+
# address.blank?
|
15
|
+
#
|
16
|
+
# @return [true, false]
|
17
|
+
def blank?
|
18
|
+
respond_to?(:empty?) ? !!empty? : !self
|
19
|
+
end
|
20
|
+
|
21
|
+
# An object is present if it's not blank.
|
22
|
+
#
|
23
|
+
# @return [true, false]
|
24
|
+
def present?
|
25
|
+
!blank?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the receiver if it's present otherwise returns +nil+.
|
29
|
+
# <tt>object.presence</tt> is equivalent to
|
30
|
+
#
|
31
|
+
# object.present? ? object : nil
|
32
|
+
#
|
33
|
+
# For example, something like
|
34
|
+
#
|
35
|
+
# state = params[:state] if params[:state].present?
|
36
|
+
# country = params[:country] if params[:country].present?
|
37
|
+
# region = state || country || 'US'
|
38
|
+
#
|
39
|
+
# becomes
|
40
|
+
#
|
41
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
42
|
+
#
|
43
|
+
# @return [Object]
|
44
|
+
def presence
|
45
|
+
self if present?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class NilClass
|
50
|
+
# +nil+ is blank:
|
51
|
+
#
|
52
|
+
# nil.blank? # => true
|
53
|
+
#
|
54
|
+
# @return [true]
|
55
|
+
def blank?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class FalseClass
|
61
|
+
# +false+ is blank:
|
62
|
+
#
|
63
|
+
# false.blank? # => true
|
64
|
+
#
|
65
|
+
# @return [true]
|
66
|
+
def blank?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class TrueClass
|
72
|
+
# +true+ is not blank:
|
73
|
+
#
|
74
|
+
# true.blank? # => false
|
75
|
+
#
|
76
|
+
# @return [false]
|
77
|
+
def blank?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Array
|
83
|
+
# An array is blank if it's empty:
|
84
|
+
#
|
85
|
+
# [].blank? # => true
|
86
|
+
# [1,2,3].blank? # => false
|
87
|
+
#
|
88
|
+
# @return [true, false]
|
89
|
+
alias_method :blank?, :empty?
|
90
|
+
end
|
91
|
+
|
92
|
+
class Hash
|
93
|
+
# A hash is blank if it's empty:
|
94
|
+
#
|
95
|
+
# {}.blank? # => true
|
96
|
+
# { key: 'value' }.blank? # => false
|
97
|
+
#
|
98
|
+
# @return [true, false]
|
99
|
+
alias_method :blank?, :empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
class String
|
103
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
104
|
+
|
105
|
+
# A string is blank if it's empty or contains whitespaces only:
|
106
|
+
#
|
107
|
+
# ''.blank? # => true
|
108
|
+
# ' '.blank? # => true
|
109
|
+
# "\t\n\r".blank? # => true
|
110
|
+
# ' blah '.blank? # => false
|
111
|
+
#
|
112
|
+
# Unicode whitespace is supported:
|
113
|
+
#
|
114
|
+
# "\u00a0".blank? # => true
|
115
|
+
#
|
116
|
+
# @return [true, false]
|
117
|
+
def blank?
|
118
|
+
BLANK_RE === self
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Numeric #:nodoc:
|
123
|
+
# No number is blank:
|
124
|
+
#
|
125
|
+
# 1.blank? # => false
|
126
|
+
# 0.blank? # => false
|
127
|
+
#
|
128
|
+
# @return [false]
|
129
|
+
def blank?
|
130
|
+
false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class Hash
|
135
|
+
# Returns a new hash with all keys converted using the block operation.
|
136
|
+
#
|
137
|
+
# hash = { name: 'Rob', age: '28' }
|
138
|
+
#
|
139
|
+
# hash.transform_keys{ |key| key.to_s.upcase }
|
140
|
+
# # => {"NAME"=>"Rob", "AGE"=>"28"}
|
141
|
+
def transform_keys
|
142
|
+
return enum_for(:transform_keys) unless block_given?
|
143
|
+
result = self.class.new
|
144
|
+
each_key do |key|
|
145
|
+
result[yield(key)] = self[key]
|
146
|
+
end
|
147
|
+
result
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
151
|
+
# they respond to +to_sym+.
|
152
|
+
#
|
153
|
+
# hash = { 'name' => 'Rob', 'age' => '28' }
|
154
|
+
#
|
155
|
+
# hash.symbolize_keys
|
156
|
+
# # => {:name=>"Rob", :age=>"28"}
|
157
|
+
def symbolize_keys
|
158
|
+
transform_keys{ |key| key.to_sym rescue key }
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns a new hash with all keys converted by the block operation.
|
162
|
+
# This includes the keys from the root hash and from all
|
163
|
+
# nested hashes and arrays.
|
164
|
+
#
|
165
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
166
|
+
#
|
167
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
168
|
+
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
169
|
+
def deep_transform_keys(&block)
|
170
|
+
_deep_transform_keys_in_object(self, &block)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
174
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
175
|
+
# and from all nested hashes and arrays.
|
176
|
+
#
|
177
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
178
|
+
#
|
179
|
+
# hash.deep_symbolize_keys
|
180
|
+
# # => {:person=>{:name=>"Rob", :age=>"28"}}
|
181
|
+
def deep_symbolize_keys
|
182
|
+
deep_transform_keys{ |key| key.to_sym rescue key }
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
# support methods for deep transforming nested hashes and arrays
|
187
|
+
def _deep_transform_keys_in_object(object, &block)
|
188
|
+
case object
|
189
|
+
when Hash
|
190
|
+
object.each_with_object({}) do |(key, value), result|
|
191
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
192
|
+
end
|
193
|
+
when Array
|
194
|
+
object.map {|e| _deep_transform_keys_in_object(e, &block) }
|
195
|
+
else
|
196
|
+
object
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module OS
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def windows?
|
7
|
+
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def mac?
|
11
|
+
(/darwin/ =~ RUBY_PLATFORM) != nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def unix?
|
15
|
+
!OS.windows?
|
16
|
+
end
|
17
|
+
|
18
|
+
def linux?
|
19
|
+
OS.unix? && !OS.mac?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Parser
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def png_bin
|
8
|
+
@png_bin ||= File.expand_path("../bin/pngcrush", __FILE__)
|
9
|
+
end
|
10
|
+
|
11
|
+
def uncrush_icon crushed_icon_path, uncrushed_icon_path
|
12
|
+
system("#{png_bin} -revert-iphone-optimizations #{crushed_icon_path} #{uncrushed_icon_path} &> /dev/null")
|
13
|
+
end
|
14
|
+
|
15
|
+
def crush_icon uncrushed_icon_path, crushed_icon_path
|
16
|
+
system("#{png_bin} -iphone #{uncrushed_icon_path} #{crushed_icon_path} &> /dev/null")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class IPA
|
21
|
+
|
22
|
+
def initialize(path)
|
23
|
+
@path = path
|
24
|
+
end
|
25
|
+
|
26
|
+
def app
|
27
|
+
@app ||= App.new(app_path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def app_path
|
31
|
+
@app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
|
32
|
+
end
|
33
|
+
|
34
|
+
def cleanup
|
35
|
+
return unless @contents
|
36
|
+
FileUtils.rm_rf(@contents)
|
37
|
+
@contents = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def metadata
|
41
|
+
return unless has_metadata?
|
42
|
+
@metadata ||= CFPropertyList.native_types(CFPropertyList::List.new(file: metadata_path).value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_metadata?
|
46
|
+
File.file? metadata_path
|
47
|
+
end
|
48
|
+
|
49
|
+
def metadata_path
|
50
|
+
@metadata_path ||= File.join(@contents, 'iTunesMetadata.plist')
|
51
|
+
end
|
52
|
+
|
53
|
+
def release_type
|
54
|
+
has_metadata? ? 'store' : 'adhoc'
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def contents
|
60
|
+
return if @contents
|
61
|
+
@contents = "tmp/ipa_files-#{Time.now.to_i}"
|
62
|
+
|
63
|
+
Zip::File.open(@path) do |zip_file|
|
64
|
+
zip_file.each do |f|
|
65
|
+
f_path = File.join(@contents, f.name)
|
66
|
+
FileUtils.mkdir_p(File.dirname(f_path))
|
67
|
+
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@contents
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class App
|
76
|
+
|
77
|
+
def initialize(path)
|
78
|
+
@path = path
|
79
|
+
end
|
80
|
+
|
81
|
+
def info
|
82
|
+
@info ||= CFPropertyList.native_types(
|
83
|
+
CFPropertyList::List.new(file: File.join(@path, 'Info.plist')).value)
|
84
|
+
end
|
85
|
+
|
86
|
+
def name
|
87
|
+
info['CFBundleName']
|
88
|
+
end
|
89
|
+
|
90
|
+
def identifier
|
91
|
+
info['CFBundleIdentifier']
|
92
|
+
end
|
93
|
+
|
94
|
+
def display_name
|
95
|
+
info['CFBundleDisplayName']
|
96
|
+
end
|
97
|
+
|
98
|
+
def version
|
99
|
+
info['CFBundleVersion']
|
100
|
+
end
|
101
|
+
|
102
|
+
def short_version
|
103
|
+
info['CFBundleShortVersionString']
|
104
|
+
end
|
105
|
+
|
106
|
+
def icons
|
107
|
+
@icons ||= begin
|
108
|
+
icons = []
|
109
|
+
info['CFBundleIcons']['CFBundlePrimaryIcon']['CFBundleIconFiles'].each do |name|
|
110
|
+
icons << get_image(name)
|
111
|
+
icons << get_image("#{name}@2x")
|
112
|
+
end
|
113
|
+
icons.delete_if { |i| !i }
|
114
|
+
rescue NoMethodError
|
115
|
+
[]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def mobileprovision
|
120
|
+
return unless has_mobileprovision?
|
121
|
+
return @mobileprovision if @mobileprovision
|
122
|
+
|
123
|
+
@mobileprovision = CFPropertyList.native_types(
|
124
|
+
CFPropertyList::List.new(data: `security cms -D -i #{mobileprovision_path}`).value)
|
125
|
+
end
|
126
|
+
|
127
|
+
def has_mobileprovision?
|
128
|
+
File.file? mobileprovision_path
|
129
|
+
end
|
130
|
+
|
131
|
+
def mobileprovision_path
|
132
|
+
@mobileprovision_path ||= File.join(@path, 'embedded.mobileprovision')
|
133
|
+
end
|
134
|
+
|
135
|
+
def hide_developer_certificates
|
136
|
+
mobileprovision.delete('DeveloperCertificates') if has_mobileprovision?
|
137
|
+
end
|
138
|
+
|
139
|
+
def devices
|
140
|
+
mobileprovision['ProvisionedDevices'] if has_mobileprovision?
|
141
|
+
end
|
142
|
+
|
143
|
+
def distribution_name
|
144
|
+
"#{mobileprovision['Name']} - #{mobileprovision['TeamName']}" if has_mobileprovision?
|
145
|
+
end
|
146
|
+
|
147
|
+
def release_type
|
148
|
+
if has_mobileprovision?
|
149
|
+
if devices
|
150
|
+
'adhoc'
|
151
|
+
else
|
152
|
+
'inhouse'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def get_image name
|
160
|
+
path = File.join(@path, "#{name}.png")
|
161
|
+
return nil unless File.exist?(path)
|
162
|
+
path
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/fir/patches.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FIR
|
4
|
+
module Build
|
5
|
+
|
6
|
+
def build_ipa *args, options
|
7
|
+
# initialize build options
|
8
|
+
if args.first.blank? || !File.exist?(args.first)
|
9
|
+
build_dir = Dir.pwd
|
10
|
+
else
|
11
|
+
build_dir = File.absolute_path(args.shift.to_s) # pop the first param
|
12
|
+
end
|
13
|
+
|
14
|
+
build_cmd = "xcodebuild build -sdk iphoneos"
|
15
|
+
build_tmp_dir = Dir.mktmpdir
|
16
|
+
custom_settings = parse_custom_settings(args) # convert ['a=1', 'b=2'] => { 'a' => '1', 'b' => '2' }
|
17
|
+
configuration = options[:configuration]
|
18
|
+
target_name = options[:target]
|
19
|
+
scheme_name = options[:scheme]
|
20
|
+
output_path = options[:output].blank? ? "#{build_dir}/build_ipa" : File.absolute_path(options[:output].to_s)
|
21
|
+
|
22
|
+
# check build environment and make build cmd
|
23
|
+
check_osx
|
24
|
+
if options.workspace?
|
25
|
+
workspace = check_and_find_workspace(build_dir)
|
26
|
+
check_scheme(scheme_name)
|
27
|
+
build_cmd += " -workspace '#{workspace}' -scheme '#{scheme_name}'"
|
28
|
+
else
|
29
|
+
project = check_and_find_project(build_dir)
|
30
|
+
build_cmd += " -project '#{project}'"
|
31
|
+
end
|
32
|
+
|
33
|
+
build_cmd += " -configuration '#{configuration}'" unless configuration.blank?
|
34
|
+
build_cmd += " -target '#{target_name}'" unless target_name.blank?
|
35
|
+
|
36
|
+
# convert { "a" => "1", "b" => "2" } => "a='1' b='2'"
|
37
|
+
setting_str = custom_settings.collect { |k, v| "#{k}='#{v}'" }.join(' ')
|
38
|
+
setting_str += " TARGET_BUILD_DIR='#{build_tmp_dir}'" unless custom_settings['TARGET_BUILD_DIR']
|
39
|
+
setting_str += " CONFIGURATION_BUILD_DIR='#{build_tmp_dir}'" unless custom_settings['CONFIGURATION_BUILD_DIR']
|
40
|
+
setting_str += " DWARF_DSYM_FOLDER_PATH='#{output_path}'" unless custom_settings['DWARF_DSYM_FOLDER_PATH']
|
41
|
+
|
42
|
+
build_cmd += " #{setting_str} 2>&1"
|
43
|
+
puts build_cmd if $DEBUG
|
44
|
+
|
45
|
+
logger.info "Building......"
|
46
|
+
logger_info_dividing_line
|
47
|
+
|
48
|
+
logger.info `#{build_cmd}`
|
49
|
+
|
50
|
+
FileUtils.mkdir_p(output_path) unless File.exist?(output_path)
|
51
|
+
Dir.chdir(build_tmp_dir) do
|
52
|
+
apps = Dir["*.app"]
|
53
|
+
if apps.length == 0
|
54
|
+
logger.error "Builded has no output app, Can not be packaged"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
apps.each do |app|
|
59
|
+
ipa_path = File.join(output_path, "#{File.basename(app, '.app')}.ipa")
|
60
|
+
zip_app2ipa(File.join(build_tmp_dir, app), ipa_path)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
logger.info "Build Success"
|
65
|
+
|
66
|
+
if options.publish?
|
67
|
+
ipa_path = Dir["#{output_path}/*.ipa"].first
|
68
|
+
publish(ipa_path, short: options[:short], changelog: options[:changelog], token: options[:token])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_apk *args, options
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def parse_custom_settings args
|
78
|
+
hash = {}
|
79
|
+
args.each do |setting|
|
80
|
+
k, v = setting.split('=', 2).map(&:strip)
|
81
|
+
hash[k] = v
|
82
|
+
end
|
83
|
+
hash
|
84
|
+
end
|
85
|
+
|
86
|
+
def check_osx
|
87
|
+
unless OS.mac?
|
88
|
+
logger.error "Unsupported OS type, `build_ipa` only support for OSX"
|
89
|
+
exit 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def check_and_find_project path
|
94
|
+
unless File.exist?(path)
|
95
|
+
logger.error "The first param BUILD_DIR must be a xcodeproj directory"
|
96
|
+
exit 1
|
97
|
+
end
|
98
|
+
|
99
|
+
if is_project?(path)
|
100
|
+
project = path
|
101
|
+
else
|
102
|
+
project = Dir["#{path}/*.xcodeproj"].first
|
103
|
+
if project.blank?
|
104
|
+
logger.error "The xcodeproj file is missing, check the BUILD_DIR"
|
105
|
+
exit 1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
project
|
110
|
+
end
|
111
|
+
|
112
|
+
def check_and_find_workspace path
|
113
|
+
unless File.exist?(path)
|
114
|
+
logger.error "The first param BUILD_DIR must be a xcworkspace directory"
|
115
|
+
exit 1
|
116
|
+
end
|
117
|
+
|
118
|
+
if is_workspace?(path)
|
119
|
+
workspace = path
|
120
|
+
else
|
121
|
+
workspace = Dir["#{path}/*.xcworkspace"].first
|
122
|
+
if workspace.blank?
|
123
|
+
logger.error "The xcworkspace file is missing, check the BUILD_DIR"
|
124
|
+
exit 1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
workspace
|
129
|
+
end
|
130
|
+
|
131
|
+
def check_scheme scheme_name
|
132
|
+
if scheme_name.blank?
|
133
|
+
logger.error "Must provide a scheme by `-s` option when build a workspace"
|
134
|
+
exit 1
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def is_project? path
|
139
|
+
File.extname(path) == '.xcodeproj'
|
140
|
+
end
|
141
|
+
|
142
|
+
def is_workspace? path
|
143
|
+
File.extname(path) == '.xcworkspace'
|
144
|
+
end
|
145
|
+
|
146
|
+
def zip_app2ipa app_path, ipa_path
|
147
|
+
Dir.mktmpdir do |tmpdir|
|
148
|
+
Dir.chdir(tmpdir) do
|
149
|
+
Dir.mkdir("Payload")
|
150
|
+
FileUtils.cp_r(app_path, "Payload")
|
151
|
+
system("rm -rf #{ipa_path}") if File.file? ipa_path
|
152
|
+
system("zip -qr #{ipa_path} Payload")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FIR
|
4
|
+
module Info
|
5
|
+
|
6
|
+
def info *args, options
|
7
|
+
file_path = File.absolute_path(args.first.to_s)
|
8
|
+
is_all = !options[:all].blank?
|
9
|
+
|
10
|
+
check_supported_file file_path
|
11
|
+
|
12
|
+
file_type = File.extname(file_path).delete('.')
|
13
|
+
|
14
|
+
logger.info "Analyzing #{file_type} file......"
|
15
|
+
logger_info_dividing_line
|
16
|
+
|
17
|
+
app_info = send("#{file_type}_info", file_path, is_all)
|
18
|
+
app_info.each { |k, v| logger.info "#{k}: #{v}" }
|
19
|
+
end
|
20
|
+
|
21
|
+
def ipa_info ipa_path, is_all
|
22
|
+
ipa = Parser::IPA.new(ipa_path)
|
23
|
+
app = ipa.app
|
24
|
+
|
25
|
+
info = {
|
26
|
+
type: 'ios',
|
27
|
+
identifier: app.identifier,
|
28
|
+
name: app.name,
|
29
|
+
display_name: app.display_name,
|
30
|
+
version: app.version,
|
31
|
+
short_version: app.short_version,
|
32
|
+
devices: app.devices,
|
33
|
+
release_type: app.release_type || ipa.release_type,
|
34
|
+
distribution_name: app.distribution_name
|
35
|
+
}
|
36
|
+
|
37
|
+
if is_all
|
38
|
+
info[:icons] = []
|
39
|
+
app.icons.each do |icon|
|
40
|
+
tmp_icon_path = "#{Dir.tmpdir}/icon-#{SecureRandom.hex[4..9]}.png"
|
41
|
+
FileUtils.cp(icon, tmp_icon_path)
|
42
|
+
info[:icons] << tmp_icon_path
|
43
|
+
end
|
44
|
+
|
45
|
+
app.hide_developer_certificates
|
46
|
+
|
47
|
+
info[:plist] = app.info
|
48
|
+
info[:mobileprovision] = app.mobileprovision
|
49
|
+
end
|
50
|
+
|
51
|
+
ipa.cleanup
|
52
|
+
info
|
53
|
+
end
|
54
|
+
|
55
|
+
def apk_info apk_path, is_all
|
56
|
+
apk = Android::Apk.new(apk_path)
|
57
|
+
info = {
|
58
|
+
type: 'android',
|
59
|
+
identifier: apk.manifest.package_name,
|
60
|
+
name: apk.label,
|
61
|
+
version: apk.manifest.version_code,
|
62
|
+
short_version: apk.manifest.version_name
|
63
|
+
}
|
64
|
+
|
65
|
+
# apk.icon is a hash, { icon_name: icon_data }
|
66
|
+
if is_all
|
67
|
+
info[:icons] = []
|
68
|
+
apk.icon.each do |name, data|
|
69
|
+
tmp_icon_path = "#{Dir.tmpdir}/icon-#{SecureRandom.hex[4..9]}.png"
|
70
|
+
File.open(tmp_icon_path, 'w+') { |f| f << data }
|
71
|
+
info[:icons] << tmp_icon_path
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
info
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FIR
|
4
|
+
module Login
|
5
|
+
|
6
|
+
def login token
|
7
|
+
check_token_cannot_be_blank token
|
8
|
+
|
9
|
+
user_info = fetch_user_info(token)
|
10
|
+
|
11
|
+
logger.info "Login succeed, previous user's email: #{config[:email]}" unless config.blank?
|
12
|
+
write_config(email: user_info.fetch(:email, ''), token: user_info.fetch(:token, ''))
|
13
|
+
reload_config
|
14
|
+
logger.info "Login succeed, current user's email: #{config[:email]}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|