fir-cli-x 1.7.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +8 -0
- data/.dockerignore +2 -0
- data/.flow-plugin.yml +14 -0
- data/.gitignore +27 -0
- data/.travis.yml +23 -0
- data/CHANGELOG +194 -0
- data/Dockerfile +12 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +10 -0
- data/bin/console +11 -0
- data/bin/fir +14 -0
- data/bin/setup +7 -0
- data/doc/help.md +34 -0
- data/doc/info.md +44 -0
- data/doc/install.md +67 -0
- data/doc/login.md +19 -0
- data/doc/publish.md +35 -0
- data/doc/upgrade.md +7 -0
- data/fir-cli.gemspec +52 -0
- data/fir.sh +46 -0
- data/install.sh +210 -0
- data/lib/fir-cli.rb +3 -0
- data/lib/fir.rb +28 -0
- data/lib/fir/api.yml +7 -0
- data/lib/fir/cli.rb +181 -0
- data/lib/fir/patches.rb +10 -0
- data/lib/fir/patches/blank.rb +131 -0
- data/lib/fir/patches/concern.rb +146 -0
- data/lib/fir/patches/default_headers.rb +9 -0
- data/lib/fir/patches/hash.rb +79 -0
- data/lib/fir/patches/instance_variables.rb +30 -0
- data/lib/fir/patches/native_patch.rb +28 -0
- data/lib/fir/patches/os_patch.rb +28 -0
- data/lib/fir/patches/try.rb +102 -0
- data/lib/fir/util.rb +86 -0
- data/lib/fir/util/build_apk.rb +77 -0
- data/lib/fir/util/build_common.rb +93 -0
- data/lib/fir/util/build_ipa.rb +11 -0
- data/lib/fir/util/config.rb +43 -0
- data/lib/fir/util/http.rb +23 -0
- data/lib/fir/util/info.rb +38 -0
- data/lib/fir/util/login.rb +17 -0
- data/lib/fir/util/mapping.rb +98 -0
- data/lib/fir/util/me.rb +19 -0
- data/lib/fir/util/parser/apk.rb +46 -0
- data/lib/fir/util/parser/bin/pngcrush +0 -0
- data/lib/fir/util/parser/common.rb +24 -0
- data/lib/fir/util/parser/ipa.rb +188 -0
- data/lib/fir/util/parser/pngcrush.rb +23 -0
- data/lib/fir/util/publish.rb +253 -0
- data/lib/fir/version.rb +5 -0
- data/lib/fir/xcode_wrapper.sh +29 -0
- data/lib/fir_cli.rb +3 -0
- data/test/build_ipa_test.rb +17 -0
- data/test/cases/test_apk.apk +0 -0
- data/test/cases/test_apk_txt +1 -0
- data/test/cases/test_ipa.ipa +0 -0
- data/test/cases/test_ipa_dsym +0 -0
- data/test/info_test.rb +36 -0
- data/test/login_test.rb +12 -0
- data/test/mapping_test.rb +18 -0
- data/test/me_test.rb +17 -0
- data/test/publish_test.rb +44 -0
- data/test/test_helper.rb +98 -0
- metadata +273 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FIR
|
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,253 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module FIR
|
3
|
+
module Publish
|
4
|
+
def publish(*args, options)
|
5
|
+
initialize_publish_options(args, options)
|
6
|
+
check_supported_file_and_token
|
7
|
+
|
8
|
+
logger_info_publishing_message
|
9
|
+
|
10
|
+
@app_info = send("#{@file_type}_info", @file_path, full_info: true)
|
11
|
+
@user_info = fetch_user_info(@token)
|
12
|
+
@uploading_info = fetch_uploading_info
|
13
|
+
@app_id = @uploading_info[:id]
|
14
|
+
|
15
|
+
upload_app
|
16
|
+
|
17
|
+
logger_info_dividing_line
|
18
|
+
logger_info_app_short_and_qrcode(options)
|
19
|
+
|
20
|
+
dingtalk_notifier(options)
|
21
|
+
upload_mapping_file_with_publish(options)
|
22
|
+
logger_info_blank_line
|
23
|
+
clean_files
|
24
|
+
end
|
25
|
+
|
26
|
+
def logger_info_publishing_message
|
27
|
+
user_info = fetch_user_info(@token)
|
28
|
+
|
29
|
+
email = user_info.fetch(:email, '')
|
30
|
+
name = user_info.fetch(:name, '')
|
31
|
+
|
32
|
+
logger.info "Publishing app via #{name}<#{email}>......."
|
33
|
+
logger_info_dividing_line
|
34
|
+
end
|
35
|
+
|
36
|
+
def upload_app
|
37
|
+
@icon_cert = @uploading_info[:cert][:icon]
|
38
|
+
@binary_cert = @uploading_info[:cert][:binary]
|
39
|
+
|
40
|
+
upload_app_icon unless @app_info[:icons].blank?
|
41
|
+
@app_uploaded_callback_data = upload_app_binary
|
42
|
+
logger.info "App id is #{@app_id}"
|
43
|
+
logger.info "Release id is #{@app_uploaded_callback_data[:release_id]}"
|
44
|
+
upload_device_info
|
45
|
+
update_app_info
|
46
|
+
fetch_app_info
|
47
|
+
end
|
48
|
+
|
49
|
+
%w[binary icon].each do |word|
|
50
|
+
define_method("upload_app_#{word}") do
|
51
|
+
upload_file(word)
|
52
|
+
storage = ENV['SOTRAGE_NAME'] || 'qiniu'
|
53
|
+
post("#{fir_api[:base_url]}/auth/#{storage}/callback", send("#{word}_information"))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def upload_file(postfix)
|
58
|
+
logger.info "Uploading app #{postfix}......"
|
59
|
+
url = @uploading_info[:cert][postfix.to_sym][:upload_url]
|
60
|
+
info = send("uploading_#{postfix}_info")
|
61
|
+
logger.debug "url = #{url}, info = #{info}"
|
62
|
+
uploaded_info = post(url, info.merge(manual_callback: true),
|
63
|
+
params_to_json: false,
|
64
|
+
header: nil)
|
65
|
+
rescue StandardError
|
66
|
+
logger.error "Uploading app #{postfix} failed"
|
67
|
+
exit 1
|
68
|
+
end
|
69
|
+
|
70
|
+
def uploading_icon_info
|
71
|
+
large_icon_path = @app_info[:icons].max_by { |f| File.size(f) }
|
72
|
+
@uncrushed_icon_path = convert_icon(large_icon_path)
|
73
|
+
{
|
74
|
+
key: @icon_cert[:key],
|
75
|
+
token: @icon_cert[:token],
|
76
|
+
file: File.new(@uncrushed_icon_path, 'rb'),
|
77
|
+
'x:is_converted' => '1'
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def icon_information
|
82
|
+
{
|
83
|
+
key: @icon_cert[:key],
|
84
|
+
token: @icon_cert[:token],
|
85
|
+
origin: 'fir-cli',
|
86
|
+
parent_id: @app_id,
|
87
|
+
fsize: File.size(@uncrushed_icon_path),
|
88
|
+
fname: 'blob'
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def binary_information
|
93
|
+
{
|
94
|
+
build: @app_info[:build],
|
95
|
+
fname: File.basename(@file_path),
|
96
|
+
key: @binary_cert[:key],
|
97
|
+
name: @app_info[:display_name] || @app_info[:name],
|
98
|
+
origin: 'fir-cli',
|
99
|
+
parent_id: @app_id,
|
100
|
+
release_tag: 'develop',
|
101
|
+
fsize: File.size(@file_path),
|
102
|
+
release_type: @app_info[:release_type],
|
103
|
+
distribution_name: @app_info[:distribution_name],
|
104
|
+
token: @binary_cert[:token],
|
105
|
+
version: @app_info[:version],
|
106
|
+
changelog: @changelog,
|
107
|
+
user_id: @user_info[:id]
|
108
|
+
}.reject { |x| x.nil? || x == '' }
|
109
|
+
end
|
110
|
+
|
111
|
+
def uploading_binary_info
|
112
|
+
{
|
113
|
+
key: @binary_cert[:key],
|
114
|
+
token: @binary_cert[:token],
|
115
|
+
file: File.new(@file_path, 'rb'),
|
116
|
+
# Custom variables
|
117
|
+
'x:name' => @app_info[:display_name] || @app_info[:name],
|
118
|
+
'x:build' => @app_info[:build],
|
119
|
+
'x:version' => @app_info[:version],
|
120
|
+
'x:changelog' => @changelog,
|
121
|
+
'x:release_type' => @app_info[:release_type],
|
122
|
+
'x:distribution_name' => @app_info[:distribution_name]
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
def upload_device_info
|
127
|
+
return if @app_info[:devices].blank?
|
128
|
+
|
129
|
+
logger.info 'Updating devices info......'
|
130
|
+
|
131
|
+
post fir_api[:udids_url], key: @binary_cert[:key],
|
132
|
+
udids: @app_info[:devices].join(','),
|
133
|
+
api_token: @token
|
134
|
+
end
|
135
|
+
|
136
|
+
def update_app_info
|
137
|
+
update_info = { short: @short, passwd: @passwd, is_opened: @is_opened }.compact
|
138
|
+
|
139
|
+
return if update_info.blank?
|
140
|
+
|
141
|
+
logger.info 'Updating app info......'
|
142
|
+
|
143
|
+
patch fir_api[:app_url] + "/#{@app_id}", update_info.merge(api_token: @token)
|
144
|
+
end
|
145
|
+
|
146
|
+
def fetch_uploading_info
|
147
|
+
logger.info "Fetching #{@app_info[:identifier]}@fir.im uploading info......"
|
148
|
+
logger.info "Uploading app: #{@app_info[:name]}-#{@app_info[:version]}(Build #{@app_info[:build]})"
|
149
|
+
|
150
|
+
post fir_api[:app_url], type: @app_info[:type],
|
151
|
+
bundle_id: @app_info[:identifier],
|
152
|
+
manual_callback: true,
|
153
|
+
api_token: @token
|
154
|
+
end
|
155
|
+
|
156
|
+
def fetch_release_id
|
157
|
+
get "#{fir_api[:base_url]}/apps/#{@app_id}/releases/find_release_by_key", api_token: @token, key: @binary_cert[:key]
|
158
|
+
end
|
159
|
+
|
160
|
+
def fetch_app_info
|
161
|
+
logger.info 'Fetch app info from fir.im'
|
162
|
+
|
163
|
+
@fir_app_info = get(fir_api[:app_url] + "/#{@app_id}", api_token: @token)
|
164
|
+
write_app_info(id: @fir_app_info[:id], short: @fir_app_info[:short], name: @fir_app_info[:name])
|
165
|
+
@fir_app_info
|
166
|
+
end
|
167
|
+
|
168
|
+
def upload_mapping_file_with_publish(options)
|
169
|
+
return if !options[:mappingfile] || !options[:proj]
|
170
|
+
|
171
|
+
logger_info_blank_line
|
172
|
+
|
173
|
+
mapping options[:mappingfile], proj: options[:proj],
|
174
|
+
build: @app_info[:build],
|
175
|
+
version: @app_info[:version],
|
176
|
+
token: @token
|
177
|
+
end
|
178
|
+
|
179
|
+
def logger_info_app_short_and_qrcode(options)
|
180
|
+
@download_url = "#{fir_api[:domain]}/#{@fir_app_info[:short]}"
|
181
|
+
@download_url += "?release_id=#{@app_uploaded_callback_data[:release_id]}" if !!options[:need_release_id]
|
182
|
+
|
183
|
+
logger.info "Published succeed: #{@download_url}"
|
184
|
+
|
185
|
+
@qrcode_path = "#{File.dirname(@file_path)}/fir-#{@app_info[:name]}.png"
|
186
|
+
FIR.generate_rqrcode(@download_url, @qrcode_path)
|
187
|
+
|
188
|
+
logger.info "Local qrcode file: #{@qrcode_path}" if @export_qrcode
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def clean_files
|
194
|
+
File.delete(@qrcode_path) unless @export_qrcode
|
195
|
+
end
|
196
|
+
|
197
|
+
def dingtalk_notifier(options)
|
198
|
+
if options[:dingtalk_access_token]
|
199
|
+
title = "#{@app_info[:name]}-#{@app_info[:version]}(Build #{@app_info[:build]})"
|
200
|
+
payload = {
|
201
|
+
"msgtype": 'markdown',
|
202
|
+
"markdown": {
|
203
|
+
"title": "#{title} uploaded",
|
204
|
+
"text": "#{title} uploaded at #{Time.now}\nurl: #{@download_url}\n ![app二维码](data:image/png;base64,#{Base64.strict_encode64(File.read(open(@qrcode_path)))})"
|
205
|
+
}
|
206
|
+
}
|
207
|
+
url = "https://oapi.dingtalk.com/robot/send?access_token=#{options[:dingtalk_access_token]}"
|
208
|
+
DefaultRest.post(url, payload)
|
209
|
+
end
|
210
|
+
rescue StandardError => e
|
211
|
+
logger.warn "Dingtalk send error #{e.message}"
|
212
|
+
end
|
213
|
+
|
214
|
+
def initialize_publish_options(args, options)
|
215
|
+
@file_path = File.absolute_path(args.first.to_s)
|
216
|
+
@file_type = File.extname(@file_path).delete('.')
|
217
|
+
@token = options[:token] || current_token
|
218
|
+
@changelog = read_changelog(options[:changelog]).to_s.to_utf8
|
219
|
+
@short = options[:short].to_s
|
220
|
+
@passwd = options[:password].to_s
|
221
|
+
@is_opened = @passwd.blank? ? options[:open] : false
|
222
|
+
@export_qrcode = !!options[:qrcode]
|
223
|
+
end
|
224
|
+
|
225
|
+
def read_changelog(changelog)
|
226
|
+
return if changelog.blank?
|
227
|
+
|
228
|
+
File.exist?(changelog) ? File.read(changelog) : changelog
|
229
|
+
end
|
230
|
+
|
231
|
+
def check_supported_file_and_token
|
232
|
+
check_file_exist(@file_path)
|
233
|
+
check_supported_file(@file_path)
|
234
|
+
check_token_cannot_be_blank(@token)
|
235
|
+
fetch_user_info(@token)
|
236
|
+
end
|
237
|
+
|
238
|
+
def convert_icon(origin_path)
|
239
|
+
# 兼容性不太好, 蔽掉转化图标
|
240
|
+
return origin_path
|
241
|
+
|
242
|
+
logger.info "Converting app's icon......"
|
243
|
+
|
244
|
+
if @app_info[:type] == 'ios'
|
245
|
+
output_path = Tempfile.new(['uncrushed_icon', '.png']).path
|
246
|
+
FIR::Parser::Pngcrush.uncrush_icon(origin_path, output_path)
|
247
|
+
origin_path = output_path if File.size(output_path) != 0
|
248
|
+
end
|
249
|
+
|
250
|
+
origin_path
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
data/lib/fir/version.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
#!/bin/bash --login
|
4
|
+
|
5
|
+
which rvm > /dev/null
|
6
|
+
|
7
|
+
if [[ $? -eq 0 ]]; then
|
8
|
+
echo "RVM detected, forcing to use system ruby since xcodebuild cause error"
|
9
|
+
[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"
|
10
|
+
rvm use system
|
11
|
+
fi
|
12
|
+
|
13
|
+
if which rbenv > /dev/null; then
|
14
|
+
echo "rbenv detected, removing env variables since xcodebuild cause error"
|
15
|
+
rbenv shell system
|
16
|
+
fi
|
17
|
+
|
18
|
+
shell_session_update() { :; }
|
19
|
+
|
20
|
+
unset RUBYLIB
|
21
|
+
unset RUBYOPT
|
22
|
+
unset BUNDLE_BIN_PATH
|
23
|
+
unset _ORIGINAL_GEM_PATH
|
24
|
+
unset BUNDLE_GEMFILE
|
25
|
+
unset GEM_HOME
|
26
|
+
unset GEM_PATH
|
27
|
+
|
28
|
+
set -x
|
29
|
+
xcodebuild "$@"
|
data/lib/fir_cli.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class BuildAppTest < Minitest::Test
|
4
|
+
|
5
|
+
def test_build_app
|
6
|
+
if ENV['BUILD_TEST']
|
7
|
+
options = OpenStruct.new
|
8
|
+
options.send('publish?=', true)
|
9
|
+
|
10
|
+
assert FIR.build_ipa(default_ipa_project, options)
|
11
|
+
assert FIR.build_apk(default_apk_project, options)
|
12
|
+
|
13
|
+
assert FIR.build_ipa(default_ipa_git_url, options)
|
14
|
+
assert FIR.build_apk(default_apk_git_url, options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
this is a text
|
Binary file
|
Binary file
|
data/test/info_test.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class InfoTest < Minitest::Test
|
4
|
+
|
5
|
+
def test_apk_info
|
6
|
+
info = FIR.apk_info(default_apk, full_info: true)
|
7
|
+
|
8
|
+
assert_equal 'android', info[:type]
|
9
|
+
assert_equal 'com.bughd.myapplication', info[:identifier]
|
10
|
+
assert_equal 'My Application', info[:name]
|
11
|
+
assert_equal '1', info[:build]
|
12
|
+
assert_equal '1.0', info[:version]
|
13
|
+
|
14
|
+
assert_equal true, File.exist?(info[:icons].first)
|
15
|
+
|
16
|
+
assert FIR.info(default_apk, {})
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_ipa_info
|
20
|
+
info = FIR.ipa_info(default_ipa, full_info: true)
|
21
|
+
|
22
|
+
assert_equal 'ios', info[:type]
|
23
|
+
assert_equal 'im.fir.build-ipa', info[:identifier]
|
24
|
+
assert_equal 'build_ipa', info[:name]
|
25
|
+
assert_equal '1', info[:build]
|
26
|
+
assert_equal '1.0', info[:version]
|
27
|
+
|
28
|
+
# Only for OSX
|
29
|
+
# assert_equal nil, info[:display_name]
|
30
|
+
# assert_equal default_device_udid, info[:devices].first
|
31
|
+
# assert_equal 'adhoc', info[:release_type]
|
32
|
+
# assert_equal default_distribution_name, info[:distribution_name]
|
33
|
+
|
34
|
+
assert FIR.info(default_ipa, {})
|
35
|
+
end
|
36
|
+
end
|
data/test/login_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class MappingTest < Minitest::Test
|
4
|
+
|
5
|
+
def test_mapping
|
6
|
+
options = {
|
7
|
+
token: default_token,
|
8
|
+
version: '1.1',
|
9
|
+
build: '1'
|
10
|
+
}
|
11
|
+
|
12
|
+
if ENV['MAPPING_TEST']
|
13
|
+
assert FIR.mapping(default_dsym_mapping, options.merge(proj: default_bughd_project_ios_id))
|
14
|
+
assert FIR.mapping(default_txt_mapping, options.merge(proj: default_bughd_project_android_id))
|
15
|
+
assert FIR.mapping(bigger_txt_mapping, options.merge(proj: default_bughd_project_android_id))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/test/me_test.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class MeTest < Minitest::Test
|
4
|
+
|
5
|
+
def test_me
|
6
|
+
user_info = FIR.fetch_user_info(default_token)
|
7
|
+
|
8
|
+
FIR.write_config(email: user_info.fetch(:email, ''), token: default_token)
|
9
|
+
FIR.reload_config
|
10
|
+
|
11
|
+
me_info = FIR.fetch_user_info(FIR.current_token)
|
12
|
+
|
13
|
+
assert_equal default_email, me_info.fetch(:email, '')
|
14
|
+
|
15
|
+
assert FIR.me
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class PublishTest < Minitest::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@options = {
|
7
|
+
token: default_token,
|
8
|
+
changelog: "test from fir-cli #{Time.now.to_i}"
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_simple_publish
|
13
|
+
assert FIR.publish(default_ipa, @options)
|
14
|
+
assert FIR.publish(default_apk, @options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_update_app_info
|
18
|
+
short = SecureRandom.hex[3..9]
|
19
|
+
is_opened = (rand(100) % 2) == 0
|
20
|
+
|
21
|
+
update_info = { short: short, open: is_opened }
|
22
|
+
FIR.publish(default_ipa, @options.merge(update_info))
|
23
|
+
|
24
|
+
info = FIR.fetch_app_info
|
25
|
+
|
26
|
+
assert_equal short, info[:short]
|
27
|
+
assert_equal is_opened, info[:is_opened]
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_update_app_passwd
|
31
|
+
short = SecureRandom.hex[3..9]
|
32
|
+
is_opened = (rand(100) % 2) == 0
|
33
|
+
passwd = SecureRandom.hex[0..9]
|
34
|
+
|
35
|
+
update_info = { short: short, password: passwd, open: is_opened }
|
36
|
+
FIR.publish(default_ipa, @options.merge(update_info))
|
37
|
+
|
38
|
+
info = FIR.fetch_app_info
|
39
|
+
|
40
|
+
assert_equal short, info[:short]
|
41
|
+
assert_equal passwd, info[:passwd]
|
42
|
+
assert_equal false, info[:is_opened]
|
43
|
+
end
|
44
|
+
end
|