fir-cli-x 1.7.2.1
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 +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 ))})"
|
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
|