flappy-cli 0.3.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +41 -0
  7. data/Rakefile +2 -0
  8. data/bin/console +14 -0
  9. data/bin/flappy +14 -0
  10. data/bin/setup +8 -0
  11. data/flappy-cli.gemspec +50 -0
  12. data/lib/flappy/api.yml +7 -0
  13. data/lib/flappy/cli.rb +145 -0
  14. data/lib/flappy/patches/blank.rb +131 -0
  15. data/lib/flappy/patches/concern.rb +146 -0
  16. data/lib/flappy/patches/default_headers.rb +9 -0
  17. data/lib/flappy/patches/hash.rb +79 -0
  18. data/lib/flappy/patches/instance_variables.rb +30 -0
  19. data/lib/flappy/patches/native_patch.rb +28 -0
  20. data/lib/flappy/patches/os_patch.rb +28 -0
  21. data/lib/flappy/patches/try.rb +102 -0
  22. data/lib/flappy/patches.rb +12 -0
  23. data/lib/flappy/util/archive_ipa.rb +55 -0
  24. data/lib/flappy/util/build_apk.rb +104 -0
  25. data/lib/flappy/util/build_common.rb +90 -0
  26. data/lib/flappy/util/build_ipa.rb +331 -0
  27. data/lib/flappy/util/config.rb +41 -0
  28. data/lib/flappy/util/http.rb +30 -0
  29. data/lib/flappy/util/iOS_check.rb +29 -0
  30. data/lib/flappy/util/iOS_env_config.rb +54 -0
  31. data/lib/flappy/util/iOS_logger.rb +113 -0
  32. data/lib/flappy/util/info.rb +39 -0
  33. data/lib/flappy/util/ipa_info.rb +198 -0
  34. data/lib/flappy/util/mapping.rb +84 -0
  35. data/lib/flappy/util/multi_io.rb +27 -0
  36. data/lib/flappy/util/parser/apk.rb +42 -0
  37. data/lib/flappy/util/parser/bin/pngcrush +0 -0
  38. data/lib/flappy/util/parser/common.rb +24 -0
  39. data/lib/flappy/util/parser/pngcrush.rb +23 -0
  40. data/lib/flappy/util/publish.rb +185 -0
  41. data/lib/flappy/util/update_pod.rb +264 -0
  42. data/lib/flappy/util.rb +105 -0
  43. data/lib/flappy/version.rb +3 -0
  44. data/lib/flappy-cli.rb +3 -0
  45. data/lib/flappy.rb +28 -0
  46. data/output/Flappy-Archives/iOS/WorkspaceForPackageTest/20170103000308/20170103000308_WorkspaceForPackageTest.json +1 -0
  47. data/output/Flappy-Archives/iOS/WorkspaceForPackageTest/20170103000308/20170103000308_WorkspaceForPackageTest_Podfile +16 -0
  48. data/output/Flappy-Archives/iOS/WorkspaceForPackageTest/20170103000308/20170103000308_WorkspaceForPackageTest_Podfile_lock +40 -0
  49. data/output/Flappy-Archives/iOS/WorkspaceForPackageTest/20170103000314/20170103000314_WorkspaceForPackageTest.json +1 -0
  50. data/output/Flappy-Archives/iOS/WorkspaceForPackageTest/20170103000314/20170103000314_WorkspaceForPackageTest_Podfile +16 -0
  51. data/output/Flappy-Archives/iOS/WorkspaceForPackageTest/20170103000314/20170103000314_WorkspaceForPackageTest_Podfile_lock +40 -0
  52. metadata +223 -0
@@ -0,0 +1,198 @@
1
+ # encoding: utf-8
2
+
3
+ module Flappy
4
+ module IpaInfo
5
+ class Ipa
6
+ # include Parser::Common
7
+
8
+ def initialize(path)
9
+ @path = path # ipa包的路径
10
+ end
11
+
12
+ def contents # contents为ipa解压路径
13
+ return @contents if @contents
14
+
15
+ @contents = "#{Dir.tmpdir}/#{Time.now.strftime('%Y%m%d%H%M%S')}_ipa_files"
16
+ Zip::File.open(@path) do |zip_file| # zip_file为待解压的文件路径
17
+ # puts "zipfile: #{zip_file}"
18
+ zip_file.each do |f| # f为解压出来的文件
19
+ # puts "file: #{f}"
20
+ f_path = File.join(@contents, f.name) # f_path为解压出来的文件路径
21
+ # puts "path: #{f_path}"
22
+ # FileUtils.mkdir_p(File.dirname(f_path)) # File.dirname(f_path)为所有的文件夹、资源束文件;这里建立压缩包里面的每个文件夹,这一步可以省略的
23
+ # puts "dir: #{File.dirname(f_path)}"
24
+ zip_file.extract(f, f_path) unless File.exist?(f_path) # 解压文件到相应位置
25
+ end
26
+ end
27
+ @contents
28
+ end
29
+
30
+ def app_path
31
+ @app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
32
+ end
33
+
34
+ def app
35
+ @app ||= App.new(app_path, is_stored)
36
+ end
37
+
38
+ def cleanup
39
+ return unless @contents
40
+ FileUtils.rm_rf(@contents)
41
+ @contents = nil
42
+ end
43
+
44
+ def metadata
45
+ return unless has_metadata?
46
+ begin
47
+ @metadata ||= CFPropertyList.native_types(CFPropertyList::List.new(file: metadata_path).value)
48
+ rescue CFFormatError
49
+ @metadata = {}
50
+ end
51
+ end
52
+
53
+ def has_metadata?
54
+ File.file?(metadata_path)
55
+ end
56
+
57
+ def metadata_path
58
+ @metadata_path ||= File.join(contents, 'iTunesMetadata.plist')
59
+ end
60
+
61
+ def is_stored # 是否是appstore包
62
+ has_metadata? ? true : false
63
+ end
64
+ end
65
+
66
+
67
+ class App
68
+ def initialize(path, is_stored = false)
69
+ @path = path # app的路径
70
+ @is_stored = is_stored
71
+ end
72
+
73
+ def full_info(options)
74
+ # if options.fetch(:full_info, false)
75
+ # basic_info.merge!(icons: tmp_icons)
76
+ # end
77
+
78
+ basic_info
79
+ end
80
+
81
+ def basic_info
82
+ @basic_info ||= {
83
+ type: 'ios',
84
+ identifier: identifier,
85
+ name: name,
86
+ display_name: display_name,
87
+ build: version.to_s,
88
+ version: short_version.to_s,
89
+ devices: devices,
90
+ release_type: release_type,
91
+ distribution_name: distribution_name
92
+ }
93
+ end
94
+
95
+ def info
96
+ @info ||= CFPropertyList.native_types(
97
+ CFPropertyList::List.new(file: File.join(@path, 'Info.plist')).value)
98
+ end
99
+
100
+ def identifier
101
+ info['CFBundleIdentifier']
102
+ end
103
+
104
+ def name
105
+ info['CFBundleName']
106
+ end
107
+
108
+ def display_name
109
+ info['CFBundleDisplayName']
110
+ end
111
+
112
+ def version
113
+ info['CFBundleVersion']
114
+ end
115
+
116
+ def short_version
117
+ info['CFBundleShortVersionString']
118
+ end
119
+
120
+ def mobileprovision_path
121
+ @mobileprovision_path ||= File.join(@path, 'embedded.mobileprovision')
122
+ end
123
+
124
+ def has_mobileprovision?
125
+ File.file? mobileprovision_path
126
+ end
127
+
128
+ def mobileprovision
129
+ return unless has_mobileprovision?
130
+ return @mobileprovision if @mobileprovision
131
+
132
+ cmd = "security cms -D -i \"#{mobileprovision_path}\""
133
+ begin
134
+ @mobileprovision = CFPropertyList.native_types(CFPropertyList::List.new(data: `#{cmd}`).value)
135
+ rescue CFFormatError
136
+ @mobileprovision = {}
137
+ end
138
+ end
139
+
140
+ def devices
141
+ mobileprovision['ProvisionedDevices'] if has_mobileprovision?
142
+ end
143
+
144
+ def hide_developer_certificates
145
+ mobileprovision.delete('DeveloperCertificates') if has_mobileprovision?
146
+ end
147
+
148
+ def distribution_name
149
+ "#{mobileprovision['Name']} - #{mobileprovision['TeamName']}" if has_mobileprovision?
150
+ end
151
+
152
+ def release_type
153
+ if @is_stored
154
+ 'Release'
155
+ else
156
+ if has_mobileprovision?
157
+ if devices
158
+ 'AdHoc'
159
+ else
160
+ 'InHouse'
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ def icons
167
+ @icons ||= begin
168
+ icons = []
169
+ info['CFBundleIcons']['CFBundlePrimaryIcon']['CFBundleIconFiles'].each do |name|
170
+ icons << get_image_path(name)
171
+ icons << get_image_path("#{name}@2x")
172
+ icons << get_image_path("#{name}@3x")
173
+ end
174
+ icons.delete_if &:!
175
+ rescue NoMethodError
176
+ []
177
+ end
178
+
179
+ begin
180
+ info['CFBundleIcons~ipad']['CFBundlePrimaryIcon']['CFBundleIconFiles'].each do |name|
181
+ icons << get_image_path("#{name}~ipad")
182
+ icons << get_image_path("#{name}@2x~ipad")
183
+ end
184
+ icons.delete_if &:!
185
+ rescue NoMethodError
186
+ []
187
+ end
188
+ end
189
+
190
+ def get_image_path(name)
191
+ path = File.join(@path, "#{name}.png")
192
+ return nil unless File.exist?(path)
193
+ path
194
+ end
195
+ end
196
+
197
+ end
198
+ end
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+
3
+ module Flappy
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
+ logger.info 'Uploading mapping file.......'
14
+
15
+ # upload_mapping_file
16
+ logger_info_dividing_line
17
+
18
+ logger.info "save mapping succeed"
19
+ logger_info_blank_line
20
+ end
21
+
22
+ private
23
+
24
+ def initialize_and_check_mapping_options(args, options)
25
+ @file_path = File.absolute_path(args.first.to_s)
26
+ @token = options[:token] || current_token
27
+ @proj = options[:proj].to_s
28
+ @version = options[:version].to_s
29
+ @build = options[:build].to_s
30
+ end
31
+
32
+ def check_file_and_token
33
+ check_file_exist(@file_path)
34
+ check_token_cannot_be_blank(@token)
35
+ check_project_id_cannot_be_blank
36
+ end
37
+
38
+ def check_project_id_cannot_be_blank
39
+ return unless @proj.blank?
40
+
41
+ logger.error "Project id can't be blank"
42
+ exit 1
43
+ end
44
+
45
+ def uuid
46
+ @uuid ||= fetch_user_uuid(@token)
47
+ end
48
+
49
+ def generate_temp_mapping_file
50
+ tmp_file_path = "#{Dir.tmpdir}/flappycli-#{File.basename(@file_path)}"
51
+ FileUtils.cp(@file_path, tmp_file_path)
52
+
53
+ tmp_file_path = zip_mapping_file(tmp_file_path)
54
+ tmp_file_path = dsym_or_txt_file(tmp_file_path)
55
+
56
+ tmp_file_path
57
+ end
58
+
59
+ def zip_mapping_file(tmp_file_path)
60
+ if File.size?(tmp_file_path) > 50 * 1000 * 1000
61
+ logger.info 'Zipping mapping file.......'
62
+
63
+ system("zip -qr #{tmp_file_path}.zip #{tmp_file_path}")
64
+ tmp_file_path += '.zip'
65
+
66
+ logger.info "Zipped Mapping file size - #{File.size?(tmp_file_path)}"
67
+ end
68
+
69
+ tmp_file_path
70
+ end
71
+
72
+ def dsym_or_txt_file(tmp_file_path)
73
+ if File.dsym?(@file_path)
74
+ FileUtils.mv(tmp_file_path, tmp_file_path + '.dSYM')
75
+ tmp_file_path += '.dSYM'
76
+ elsif File.text?(@file_path)
77
+ FileUtils.mv(tmp_file_path, tmp_file_path + '.txt')
78
+ tmp_file_path += '.txt'
79
+ end
80
+
81
+ tmp_file_path
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,27 @@
1
+ require 'logger'
2
+
3
+ module Flappy
4
+ module MultiIO
5
+
6
+ class MultiDelegator
7
+ def initialize(*targets)
8
+ @targets = targets
9
+ end
10
+
11
+ def self.delegate(*methods)
12
+ methods.each do |m|
13
+ define_method(m) do |*args|
14
+ @targets.map { |t| t.send(m, *args) }
15
+ end
16
+ end
17
+ self
18
+ end
19
+
20
+ class <<self
21
+ alias to new
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative './common'
4
+
5
+ module Flappy
6
+ module Parser
7
+ class Apk
8
+ include Parser::Common
9
+
10
+ def initialize(path)
11
+ @apk = ::Android::Apk.new(path)
12
+ end
13
+
14
+ def full_info(options)
15
+ if options.fetch(:full_info, false)
16
+ basic_info.merge!(icons: tmp_icons)
17
+ end
18
+
19
+ basic_info
20
+ end
21
+
22
+ def basic_info
23
+ @basic_info ||= {
24
+ type: 'android',
25
+ identifier: @apk.manifest.package_name,
26
+ name: @apk.label,
27
+ build: @apk.manifest.version_code.to_s,
28
+ version: @apk.manifest.version_name.to_s
29
+ }
30
+ end
31
+
32
+ # @apk.icon is a hash, { icon_name: icon_binary_data }
33
+ def tmp_icons
34
+ begin
35
+ @apk.icon.map { |_, data| generate_tmp_icon(data, :apk) }
36
+ rescue
37
+ []
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
Binary file
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module Flappy
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,23 @@
1
+ # encoding: utf-8
2
+
3
+ module Flappy
4
+ module Parser
5
+ module Pngcrush
6
+
7
+ class << self
8
+
9
+ def png_bin
10
+ @png_bin ||= File.expand_path('../bin/pngcrush', __FILE__)
11
+ end
12
+
13
+ def uncrush_icon crushed_icon_path, uncrushed_icon_path
14
+ system("#{png_bin} -revert-iphone-optimizations #{crushed_icon_path} #{uncrushed_icon_path} &> /dev/null")
15
+ end
16
+
17
+ def crush_icon uncrushed_icon_path, crushed_icon_path
18
+ system("#{png_bin} -iphone #{uncrushed_icon_path} #{crushed_icon_path} &> /dev/null")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,185 @@
1
+ # encoding: utf-8
2
+
3
+ module Flappy
4
+ module Publish
5
+
6
+ def publish(*args, options)
7
+ initialize_publish_options(args, options)
8
+ check_supported_file_and_token
9
+
10
+ logger_info_publishing_message
11
+
12
+ @app_info = send("#{@file_type}_info", @file_path, full_info: true)
13
+ @uploading_info = fetch_uploading_info
14
+ @app_id = @uploading_info[:id]
15
+
16
+ upload_app
17
+
18
+ logger_info_dividing_line
19
+ logger_info_app_short_and_qrcode
20
+
21
+ upload_mapping_file_with_publish(options)
22
+ logger_info_blank_line
23
+ end
24
+
25
+ def logger_info_publishing_message
26
+ user_info = fetch_user_info(@token)
27
+
28
+ email = user_info.fetch(:email, '')
29
+ name = user_info.fetch(:name, '')
30
+
31
+ logger.info "Publishing app via #{name}<#{email}>......."
32
+ logger_info_dividing_line
33
+ end
34
+
35
+ def upload_app
36
+ @icon_cert = @uploading_info[:cert][:icon]
37
+ @binary_cert = @uploading_info[:cert][:binary]
38
+
39
+ upload_app_icon unless @app_info[:icons].blank?
40
+ upload_app_binary
41
+ upload_device_info
42
+ update_app_info
43
+ fetch_app_info
44
+ end
45
+
46
+ %w(icon binary).each do |postfix|
47
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
48
+ def upload_app_#{postfix}
49
+ logger.info "Uploading app #{postfix}......"
50
+ uploaded_info = post(@#{postfix}_cert[:upload_url], uploading_#{postfix}_info)
51
+
52
+ return if uploaded_info[:is_completed]
53
+
54
+ logger.error "Uploading app #{postfix} failed"
55
+ exit 1
56
+ end
57
+ METHOD
58
+ end
59
+
60
+ def uploading_icon_info
61
+ large_icon_path = @app_info[:icons].max_by { |f| File.size(f) }
62
+ uncrushed_icon_path = convert_icon(large_icon_path)
63
+
64
+ {
65
+ key: @icon_cert[:key],
66
+ token: @icon_cert[:token],
67
+ file: File.new(uncrushed_icon_path, 'rb'),
68
+ 'x:is_converted' => '1'
69
+ }
70
+ end
71
+
72
+ def uploading_binary_info
73
+ {
74
+ key: @binary_cert[:key],
75
+ token: @binary_cert[:token],
76
+ file: File.new(@file_path, 'rb'),
77
+ # Custom variables
78
+ 'x:name' => @app_info[:display_name] || @app_info[:name],
79
+ 'x:build' => @app_info[:build],
80
+ 'x:version' => @app_info[:version],
81
+ 'x:changelog' => @changelog,
82
+ 'x:release_type' => @app_info[:release_type],
83
+ 'x:distribution_name' => @app_info[:distribution_name]
84
+ }
85
+ end
86
+
87
+ def upload_device_info
88
+ return if @app_info[:devices].blank?
89
+
90
+ logger.info 'Updating devices info......'
91
+
92
+ post fir_api[:udids_url], key: @binary_cert[:key],
93
+ udids: @app_info[:devices].join(','),
94
+ api_token: @token
95
+ end
96
+
97
+ def update_app_info
98
+ update_info = { short: @short, passwd: @passwd, is_opened: @is_opened }.compact
99
+
100
+ return if update_info.blank?
101
+
102
+ logger.info "Updating app info......"
103
+
104
+ patch fir_api[:app_url] + "/#{@app_id}", update_info.merge(api_token: @token)
105
+ end
106
+
107
+ def fetch_uploading_info
108
+ logger.info "Fetching #{@app_info[:identifier]}@fir.im uploading info......"
109
+ logger.info "Uploading app: #{@app_info[:name]}-#{@app_info[:version]}(Build #{@app_info[:build]})"
110
+
111
+ post fir_api[:app_url], type: @app_info[:type],
112
+ bundle_id: @app_info[:identifier],
113
+ api_token: @token
114
+ end
115
+
116
+ def fetch_app_info
117
+ logger.info 'Fetch app info from fir.im'
118
+
119
+ @fir_app_info = get(fir_api[:app_url] + "/#{@app_id}", api_token: @token)
120
+ write_app_info(id: @fir_app_info[:id], short: @fir_app_info[:short], name: @fir_app_info[:name])
121
+ @fir_app_info
122
+ end
123
+
124
+ def upload_mapping_file_with_publish(options)
125
+ return if !options[:mappingfile] || !options[:proj]
126
+
127
+ logger_info_blank_line
128
+
129
+ mapping options[:mappingfile], proj: options[:proj],
130
+ build: @app_info[:build],
131
+ version: @app_info[:version],
132
+ token: @token
133
+ end
134
+
135
+ def logger_info_app_short_and_qrcode
136
+ short = "#{fir_api[:domain]}/#{@fir_app_info[:short]}"
137
+
138
+ logger.info "Published succeed: #{short}"
139
+
140
+ if @export_qrcode
141
+ qrcode_path = "#{File.dirname(@file_path)}/fir-#{@app_info[:name]}.png"
142
+ Flappy.generate_rqrcode(short, qrcode_path)
143
+
144
+ logger.info "Local qrcode file: #{qrcode_path}"
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def initialize_publish_options(args, options)
151
+ @file_path = File.absolute_path(args.first.to_s)
152
+ @file_type = File.extname(@file_path).delete('.')
153
+ @token = options[:firToken] || current_token
154
+ @changelog = read_changelog(options[:changelog]).to_s.to_utf8
155
+ @short = options[:short].to_s
156
+ @passwd = options[:password].to_s
157
+ @is_opened = @passwd.blank? ? options[:open] : false
158
+ @export_qrcode = !!options[:qrcode]
159
+ end
160
+
161
+ def read_changelog(changelog)
162
+ return if changelog.blank?
163
+ File.exist?(changelog) ? File.read(changelog) : changelog
164
+ end
165
+
166
+ def check_supported_file_and_token
167
+ check_file_exist(@file_path)
168
+ check_supported_file(@file_path)
169
+ check_token_cannot_be_blank(@token)
170
+ fetch_user_info(@token)
171
+ end
172
+
173
+ def convert_icon origin_path
174
+ logger.info "Converting app's icon......"
175
+
176
+ if @app_info[:type] == 'ios'
177
+ output_path = Tempfile.new(['uncrushed_icon', '.png']).path
178
+ FIR::Parser::Pngcrush.uncrush_icon(origin_path, output_path)
179
+ origin_path = output_path if File.size(output_path) != 0
180
+ end
181
+
182
+ origin_path
183
+ end
184
+ end
185
+ end