omt-cli 1.6.3 → 1.6.4

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +8 -0
  3. data/.gitignore +24 -0
  4. data/.travis.yml +31 -0
  5. data/CHANGELOG +188 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +35 -0
  9. data/Rakefile +10 -0
  10. data/bin/console +11 -0
  11. data/bin/fir +14 -0
  12. data/bin/setup +7 -0
  13. data/doc/build_apk.md +42 -0
  14. data/doc/build_ipa.md +66 -0
  15. data/doc/help.md +34 -0
  16. data/doc/info.md +44 -0
  17. data/doc/install.md +65 -0
  18. data/doc/login.md +19 -0
  19. data/doc/mapping.md +22 -0
  20. data/doc/publish.md +35 -0
  21. data/doc/upgrade.md +7 -0
  22. data/lib/fir.rb +28 -0
  23. data/lib/fir/api.yml +13 -0
  24. data/lib/fir/api.yml.bak +13 -0
  25. data/lib/fir/cli.rb +195 -0
  26. data/lib/fir/patches.rb +10 -0
  27. data/lib/fir/patches/blank.rb +131 -0
  28. data/lib/fir/patches/concern.rb +146 -0
  29. data/lib/fir/patches/default_headers.rb +9 -0
  30. data/lib/fir/patches/hash.rb +79 -0
  31. data/lib/fir/patches/instance_variables.rb +30 -0
  32. data/lib/fir/patches/native_patch.rb +28 -0
  33. data/lib/fir/patches/os_patch.rb +28 -0
  34. data/lib/fir/patches/try.rb +102 -0
  35. data/lib/fir/util.rb +87 -0
  36. data/lib/fir/util/build_apk.rb +76 -0
  37. data/lib/fir/util/build_common.rb +93 -0
  38. data/lib/fir/util/build_ipa.rb +240 -0
  39. data/lib/fir/util/config.rb +42 -0
  40. data/lib/fir/util/http.rb +30 -0
  41. data/lib/fir/util/info.rb +39 -0
  42. data/lib/fir/util/login.rb +18 -0
  43. data/lib/fir/util/mapping.rb +98 -0
  44. data/lib/fir/util/me.rb +19 -0
  45. data/lib/fir/util/parser/apk.rb +43 -0
  46. data/lib/fir/util/parser/bin/pngcrush +0 -0
  47. data/lib/fir/util/parser/common.rb +24 -0
  48. data/lib/fir/util/parser/ipa.rb +188 -0
  49. data/lib/fir/util/parser/pngcrush.rb +23 -0
  50. data/lib/fir/util/publish.rb +106 -0
  51. data/lib/fir/util/publish.rb.bak +185 -0
  52. data/lib/fir/version.rb +5 -0
  53. data/lib/fir/xcode_wrapper.sh +29 -0
  54. data/lib/omt-cli.rb +3 -0
  55. data/lib/omt_cli.rb +3 -0
  56. data/omt-cli.gemspec +48 -0
  57. data/test/build_ipa_test.rb +17 -0
  58. data/test/cases/test_apk.apk +0 -0
  59. data/test/cases/test_apk_txt +1 -0
  60. data/test/cases/test_ipa.ipa +0 -0
  61. data/test/cases/test_ipa_dsym +0 -0
  62. data/test/info_test.rb +36 -0
  63. data/test/login_test.rb +12 -0
  64. data/test/mapping_test.rb +18 -0
  65. data/test/me_test.rb +17 -0
  66. data/test/publish_test.rb +44 -0
  67. data/test/test_helper.rb +98 -0
  68. metadata +84 -4
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Config
5
+ CONFIG_PATH = "#{ENV['HOME']}/.omt-cli"
6
+ APP_INFO_PATH = "#{ENV['HOME']}/.omt-cli-app"
7
+ API_YML_PATH = File.expand_path('../../', __FILE__) + '/api.yml'
8
+ XCODE_WRAPPER_PATH = File.expand_path('../../', __FILE__) + '/xcode_wrapper.sh'
9
+ APP_FILE_TYPE = %w(.ipa .apk).freeze
10
+
11
+ def fir_api
12
+ @fir_api ||= YAML.load_file(API_YML_PATH).deep_symbolize_keys[:fir]
13
+ end
14
+
15
+ def bughd_api
16
+ @bughd_api ||= YAML.load_file(API_YML_PATH).deep_symbolize_keys[:bughd]
17
+ end
18
+
19
+ def config
20
+ return unless File.exist?(CONFIG_PATH)
21
+ @config ||= YAML.load_file(CONFIG_PATH).deep_symbolize_keys
22
+ end
23
+
24
+ def reload_config
25
+ @config = YAML.load_file(CONFIG_PATH).deep_symbolize_keys
26
+ end
27
+
28
+ def write_config(hash)
29
+ File.open(CONFIG_PATH, 'w+') { |f| f << YAML.dump(hash) }
30
+ end
31
+
32
+ def write_app_info(hash)
33
+ File.open(APP_INFO_PATH, 'w+') { |f| f << YAML.dump(hash) }
34
+ end
35
+
36
+ def current_token
37
+ @token ||= config[:token] if config
38
+ end
39
+
40
+ alias_method :☠, :exit
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Http
5
+ MAX_RETRIES = 5
6
+
7
+ %w(get post patch put).each do |_m|
8
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
9
+ def #{_m}(url, params = {})
10
+ query = :#{_m} == :get ? { params: params } : params
11
+ begin
12
+ res = ::RestClient.#{_m}(url, query)
13
+ rescue => e
14
+ @retries ||= 0
15
+ logger.error(e.message.to_s)
16
+ if @retries < MAX_RETRIES
17
+ @retries += 1
18
+ logger.info("Retry \#{@retries} times......")
19
+ sleep 2
20
+ retry
21
+ else
22
+ exit 1
23
+ end
24
+ end
25
+ JSON.parse(res.body.force_encoding('UTF-8'), symbolize_names: true)
26
+ end
27
+ METHOD
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,39 @@
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_file_exist file_path
11
+ check_supported_file file_path
12
+
13
+ file_type = File.extname(file_path).delete('.')
14
+
15
+ logger.info "Analyzing #{file_type} file......"
16
+ logger_info_dividing_line
17
+
18
+ app_info = send("#{file_type}_info", file_path, full_info: is_all)
19
+ app_info.each { |k, v| logger.info "#{k}: #{v}" }
20
+
21
+ logger_info_blank_line
22
+ end
23
+
24
+ def ipa_info(ipa_path, options = {})
25
+ ipa = FIR::Parser::Ipa.new(ipa_path)
26
+ app = ipa.app
27
+ info = app.full_info(options)
28
+
29
+ ipa.cleanup
30
+ info
31
+ end
32
+
33
+ def apk_info(apk_path, options = {})
34
+ apk = FIR::Parser::Apk.new(apk_path)
35
+ info = apk.full_info(options)
36
+ info
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
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: token)
13
+ reload_config
14
+ logger.info "Login succeed, current user's email: #{config[:email]}"
15
+ logger_info_blank_line
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Mapping
5
+
6
+ def mapping(*args, options)
7
+ initialize_and_check_mapping_options(args, options)
8
+ check_file_and_token
9
+
10
+ logger.info "Creating bughd project's version......."
11
+ logger_info_dividing_line
12
+
13
+ @full_version = find_or_create_bughd_full_version
14
+
15
+ logger.info 'Uploading mapping file.......'
16
+
17
+ upload_mapping_file
18
+ logger_info_dividing_line
19
+
20
+ logger.info "Uploaded succeed: #{bughd_api[:domain]}/project/#{@proj}/settings"
21
+ logger_info_blank_line
22
+ end
23
+
24
+ def find_or_create_bughd_full_version
25
+ url = bughd_api[:project_url] + "/#{@proj}/full_versions"
26
+ post url, version: @version, build: @build, uuid: uuid
27
+ end
28
+
29
+ def upload_mapping_file
30
+ tmp_file_path = generate_temp_mapping_file
31
+
32
+ url = bughd_api[:full_version_url] + "/#{@full_version[:id]}"
33
+ patch url, file: File.new(tmp_file_path, 'rb'), project_id: @proj, uuid: uuid
34
+ end
35
+
36
+ private
37
+
38
+ def initialize_and_check_mapping_options(args, options)
39
+ @file_path = File.absolute_path(args.first.to_s)
40
+ @token = options[:token] || current_token
41
+ @proj = options[:proj].to_s
42
+ @version = options[:version].to_s
43
+ @build = options[:build].to_s
44
+ end
45
+
46
+ def check_file_and_token
47
+ check_file_exist(@file_path)
48
+ check_token_cannot_be_blank(@token)
49
+ check_project_id_cannot_be_blank
50
+ end
51
+
52
+ def check_project_id_cannot_be_blank
53
+ return unless @proj.blank?
54
+
55
+ logger.error "Project id can't be blank"
56
+ exit 1
57
+ end
58
+
59
+ def uuid
60
+ @uuid ||= fetch_user_uuid(@token)
61
+ end
62
+
63
+ def generate_temp_mapping_file
64
+ tmp_file_path = "#{Dir.tmpdir}/fircli-#{File.basename(@file_path)}"
65
+ FileUtils.cp(@file_path, tmp_file_path)
66
+
67
+ tmp_file_path = zip_mapping_file(tmp_file_path)
68
+ tmp_file_path = dsym_or_txt_file(tmp_file_path)
69
+
70
+ tmp_file_path
71
+ end
72
+
73
+ def zip_mapping_file(tmp_file_path)
74
+ if File.size?(tmp_file_path) > 50 * 1000 * 1000
75
+ logger.info 'Zipping mapping file.......'
76
+
77
+ system("zip -qr #{tmp_file_path}.zip #{tmp_file_path}")
78
+ tmp_file_path += '.zip'
79
+
80
+ logger.info "Zipped Mapping file size - #{File.size?(tmp_file_path)}"
81
+ end
82
+
83
+ tmp_file_path
84
+ end
85
+
86
+ def dsym_or_txt_file(tmp_file_path)
87
+ if File.dsym?(@file_path)
88
+ FileUtils.mv(tmp_file_path, tmp_file_path + '.dSYM')
89
+ tmp_file_path += '.dSYM'
90
+ elsif File.text?(@file_path)
91
+ FileUtils.mv(tmp_file_path, tmp_file_path + '.txt')
92
+ tmp_file_path += '.txt'
93
+ end
94
+
95
+ tmp_file_path
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Me
5
+
6
+ def me
7
+ check_logined
8
+
9
+ user_info = fetch_user_info(current_token)
10
+
11
+ email = user_info.fetch(:email, '')
12
+ name = user_info.fetch(:name, '')
13
+
14
+ logger.info "Login succeed, current user's email: #{email}"
15
+ logger.info "Login succeed, current user's name: #{name}"
16
+ logger_info_blank_line
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative './common'
4
+
5
+ module FIR
6
+ module Parser
7
+ class Apk
8
+ include Parser::Common
9
+
10
+ def initialize(path)
11
+ Zip.warn_invalid_date = false
12
+ @apk = ::Android::Apk.new(path)
13
+ end
14
+
15
+ def full_info(options)
16
+ if options.fetch(:full_info, false)
17
+ basic_info.merge!(icons: tmp_icons)
18
+ end
19
+
20
+ basic_info
21
+ end
22
+
23
+ def basic_info
24
+ @basic_info ||= {
25
+ type: 'android',
26
+ identifier: @apk.manifest.package_name,
27
+ name: @apk.label,
28
+ build: @apk.manifest.version_code.to_s,
29
+ version: @apk.manifest.version_name.to_s
30
+ }
31
+ end
32
+
33
+ # @apk.icon is a hash, { icon_name: icon_binary_data }
34
+ def tmp_icons
35
+ begin
36
+ @apk.icon.map { |_, data| generate_tmp_icon(data, :apk) }
37
+ rescue
38
+ []
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module FIR
4
+ module Parser
5
+ module Common
6
+
7
+ # when type is ipa, the icon data is a png file.
8
+ # when type is apk, the icon data is a binary data.
9
+ def generate_tmp_icon data, type
10
+ tmp_icon_path = "#{Dir.tmpdir}/icon-#{SecureRandom.hex[4..9]}.png"
11
+
12
+ if type == :ipa
13
+ FileUtils.cp(data, tmp_icon_path)
14
+ elsif type == :apk
15
+ File.open(tmp_icon_path, 'w+') { |f| f << data }
16
+ else
17
+ return
18
+ end
19
+
20
+ tmp_icon_path
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,188 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative './common'
4
+
5
+ module FIR
6
+ module Parser
7
+ class Ipa
8
+ include Parser::Common
9
+
10
+ def initialize(path)
11
+ @path = path
12
+ end
13
+
14
+ def app
15
+ @app ||= App.new(app_path, is_stored)
16
+ end
17
+
18
+ def app_path
19
+ @app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
20
+ end
21
+
22
+ def cleanup
23
+ return unless @contents
24
+ FileUtils.rm_rf(@contents)
25
+ @contents = nil
26
+ end
27
+
28
+ def metadata
29
+ return unless has_metadata?
30
+ @metadata ||= CFPropertyList.native_types(CFPropertyList::List.new(file: metadata_path).value)
31
+ end
32
+
33
+ def has_metadata?
34
+ File.file?(metadata_path)
35
+ end
36
+
37
+ def metadata_path
38
+ @metadata_path ||= File.join(@contents, 'iTunesMetadata.plist')
39
+ end
40
+
41
+ def is_stored
42
+ has_metadata? ? true : false
43
+ end
44
+
45
+ def contents
46
+ return if @contents
47
+ @contents = "#{Dir.tmpdir}/ipa_files-#{Time.now.to_i}"
48
+
49
+ Zip::File.open(@path) do |zip_file|
50
+ zip_file.each do |f|
51
+ f_path = File.join(@contents, f.name)
52
+ FileUtils.mkdir_p(File.dirname(f_path))
53
+ zip_file.extract(f, f_path) unless File.exist?(f_path)
54
+ end
55
+ end
56
+
57
+ @contents
58
+ end
59
+
60
+ class App
61
+ include Parser::Common
62
+
63
+ def initialize(path, is_stored = false)
64
+ @path = path
65
+ @is_stored = is_stored
66
+ end
67
+
68
+ def full_info(options)
69
+ if options.fetch(:full_info, false)
70
+ basic_info.merge!(icons: tmp_icons)
71
+ end
72
+
73
+ basic_info
74
+ end
75
+
76
+ def basic_info
77
+ @basic_info ||= {
78
+ type: 'ios',
79
+ identifier: identifier,
80
+ name: name,
81
+ display_name: display_name,
82
+ build: version.to_s,
83
+ version: short_version.to_s,
84
+ devices: devices,
85
+ release_type: release_type,
86
+ distribution_name: distribution_name
87
+ }
88
+ end
89
+
90
+ def info
91
+ @info ||= CFPropertyList.native_types(
92
+ CFPropertyList::List.new(file: File.join(@path, 'Info.plist')).value)
93
+ end
94
+
95
+ def name
96
+ info['CFBundleName']
97
+ end
98
+
99
+ def identifier
100
+ info['CFBundleIdentifier']
101
+ end
102
+
103
+ def display_name
104
+ info['CFBundleDisplayName']
105
+ end
106
+
107
+ def version
108
+ info['CFBundleVersion']
109
+ end
110
+
111
+ def short_version
112
+ info['CFBundleShortVersionString']
113
+ end
114
+
115
+ def tmp_icons
116
+ icons.map { |data| generate_tmp_icon(data, :ipa) }
117
+ end
118
+
119
+ def icons
120
+ @icons ||= begin
121
+ icons = []
122
+ info['CFBundleIcons']['CFBundlePrimaryIcon']['CFBundleIconFiles'].each do |name|
123
+ icons << get_image(name)
124
+ icons << get_image("#{name}@2x")
125
+ end
126
+ icons.delete_if &:!
127
+ rescue NoMethodError
128
+ []
129
+ end
130
+ end
131
+
132
+ def mobileprovision
133
+ return unless has_mobileprovision?
134
+ return @mobileprovision if @mobileprovision
135
+
136
+ cmd = "security cms -D -i \"#{mobileprovision_path}\""
137
+ begin
138
+ @mobileprovision = CFPropertyList.native_types(CFPropertyList::List.new(data: `#{cmd}`).value)
139
+ rescue CFFormatError
140
+ @mobileprovision = {}
141
+ end
142
+ end
143
+
144
+ def has_mobileprovision?
145
+ File.file? mobileprovision_path
146
+ end
147
+
148
+ def mobileprovision_path
149
+ @mobileprovision_path ||= File.join(@path, 'embedded.mobileprovision')
150
+ end
151
+
152
+ def hide_developer_certificates
153
+ mobileprovision.delete('DeveloperCertificates') if has_mobileprovision?
154
+ end
155
+
156
+ def devices
157
+ mobileprovision['ProvisionedDevices'] if has_mobileprovision?
158
+ end
159
+
160
+ def distribution_name
161
+ "#{mobileprovision['Name']} - #{mobileprovision['TeamName']}" if has_mobileprovision?
162
+ end
163
+
164
+ def release_type
165
+ if @is_stored
166
+ 'store'
167
+ else
168
+ if has_mobileprovision?
169
+ if devices
170
+ 'adhoc'
171
+ else
172
+ 'inhouse'
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ private
179
+
180
+ def get_image(name)
181
+ path = File.join(@path, "#{name}.png")
182
+ return nil unless File.exist?(path)
183
+ path
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end