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,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Returns a copy of self with all blank keys removed.
|
5
|
+
#
|
6
|
+
# hash = { name: 'Rob', age: '', title: nil }
|
7
|
+
#
|
8
|
+
# hash.compact
|
9
|
+
# # => { name: 'Rob' }
|
10
|
+
def compact
|
11
|
+
delete_if { |_, v| v.is_a?(FalseClass) ? false : v.blank? }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns a new hash with all keys converted using the block operation.
|
15
|
+
#
|
16
|
+
# hash = { name: 'Rob', age: '28' }
|
17
|
+
#
|
18
|
+
# hash.transform_keys{ |key| key.to_s.upcase }
|
19
|
+
# # => {"NAME"=>"Rob", "AGE"=>"28"}
|
20
|
+
def transform_keys
|
21
|
+
return enum_for(:transform_keys) unless block_given?
|
22
|
+
result = self.class.new
|
23
|
+
each_key do |key|
|
24
|
+
result[yield(key)] = self[key]
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
30
|
+
# they respond to +to_sym+.
|
31
|
+
#
|
32
|
+
# hash = { 'name' => 'Rob', 'age' => '28' }
|
33
|
+
#
|
34
|
+
# hash.symbolize_keys
|
35
|
+
# # => {:name=>"Rob", :age=>"28"}
|
36
|
+
def symbolize_keys
|
37
|
+
transform_keys { |key| key.to_sym rescue key }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a new hash with all keys converted by the block operation.
|
41
|
+
# This includes the keys from the root hash and from all
|
42
|
+
# nested hashes and arrays.
|
43
|
+
#
|
44
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
45
|
+
#
|
46
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
47
|
+
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
48
|
+
def deep_transform_keys(&block)
|
49
|
+
_deep_transform_keys_in_object(self, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
53
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
54
|
+
# and from all nested hashes and arrays.
|
55
|
+
#
|
56
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
57
|
+
#
|
58
|
+
# hash.deep_symbolize_keys
|
59
|
+
# # => {:person=>{:name=>"Rob", :age=>"28"}}
|
60
|
+
def deep_symbolize_keys
|
61
|
+
deep_transform_keys { |key| key.to_sym rescue key }
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# support methods for deep transforming nested hashes and arrays
|
67
|
+
def _deep_transform_keys_in_object(object, &block)
|
68
|
+
case object
|
69
|
+
when Hash
|
70
|
+
object.each_with_object({}) do |(key, value), result|
|
71
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
72
|
+
end
|
73
|
+
when Array
|
74
|
+
object.map { |e| _deep_transform_keys_in_object(e, &block) }
|
75
|
+
else
|
76
|
+
object
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# Returns a hash with string keys that maps instance variable names without "@" to their
|
5
|
+
# corresponding values.
|
6
|
+
#
|
7
|
+
# class C
|
8
|
+
# def initialize(x, y)
|
9
|
+
# @x, @y = x, y
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
|
14
|
+
def instance_values
|
15
|
+
Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns an array of instance variable names as strings including "@".
|
19
|
+
#
|
20
|
+
# class C
|
21
|
+
# def initialize(x, y)
|
22
|
+
# @x, @y = x, y
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
|
27
|
+
def instance_variable_names
|
28
|
+
instance_variables.map { |var| var.to_s }
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class File
|
4
|
+
class << self
|
5
|
+
# A binary file is Mach-O dSYM
|
6
|
+
#
|
7
|
+
# @return [true, false]
|
8
|
+
def dsym?(file_path)
|
9
|
+
!(`file -b #{file_path}` =~ /dSYM/).nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
# A file is ASCII text
|
13
|
+
#
|
14
|
+
# @return [true, false]
|
15
|
+
def text?(file_path)
|
16
|
+
!(`file -b #{file_path}` =~ /text/).nil?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class String
|
22
|
+
# Convert String encoding to UTF-8
|
23
|
+
#
|
24
|
+
# @return string
|
25
|
+
def to_utf8
|
26
|
+
encode(Encoding.find('UTF-8'), invalid: :replace, undef: :replace, replace: '')
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module OS
|
4
|
+
class << self
|
5
|
+
|
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
|
+
|
22
|
+
def set_locale
|
23
|
+
system 'export LC_ALL=en_US.UTF-8'
|
24
|
+
system 'export LC_CTYPE=en_US.UTF-8'
|
25
|
+
system 'export LANG=en_US.UTF-8'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# Invokes the public method whose name goes as first argument just like
|
5
|
+
# +public_send+ does, except that if the receiver does not respond to it the
|
6
|
+
# call returns +nil+ rather than raising an exception.
|
7
|
+
#
|
8
|
+
# This method is defined to be able to write
|
9
|
+
#
|
10
|
+
# @person.try(:name)
|
11
|
+
#
|
12
|
+
# instead of
|
13
|
+
#
|
14
|
+
# @person.name if @person
|
15
|
+
#
|
16
|
+
# +try+ calls can be chained:
|
17
|
+
#
|
18
|
+
# @person.try(:spouse).try(:name)
|
19
|
+
#
|
20
|
+
# instead of
|
21
|
+
#
|
22
|
+
# @person.spouse.name if @person && @person.spouse
|
23
|
+
#
|
24
|
+
# +try+ will also return +nil+ if the receiver does not respond to the method:
|
25
|
+
#
|
26
|
+
# @person.try(:non_existing_method) #=> nil
|
27
|
+
#
|
28
|
+
# instead of
|
29
|
+
#
|
30
|
+
# @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil
|
31
|
+
#
|
32
|
+
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
|
33
|
+
# to the method:
|
34
|
+
#
|
35
|
+
# nil.try(:to_i) # => nil, rather than 0
|
36
|
+
#
|
37
|
+
# Arguments and blocks are forwarded to the method if invoked:
|
38
|
+
#
|
39
|
+
# @posts.try(:each_slice, 2) do |a, b|
|
40
|
+
# ...
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# The number of arguments in the signature must match. If the object responds
|
44
|
+
# to the method the call is attempted and +ArgumentError+ is still raised
|
45
|
+
# in case of argument mismatch.
|
46
|
+
#
|
47
|
+
# If +try+ is called without arguments it yields the receiver to a given
|
48
|
+
# block unless it is +nil+:
|
49
|
+
#
|
50
|
+
# @person.try do |p|
|
51
|
+
# ...
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# You can also call try with a block without accepting an argument, and the block
|
55
|
+
# will be instance_eval'ed instead:
|
56
|
+
#
|
57
|
+
# @person.try { upcase.truncate(50) }
|
58
|
+
#
|
59
|
+
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
|
60
|
+
# with instances of classes that do not have +Object+ among their ancestors,
|
61
|
+
# like direct subclasses of +BasicObject+. For example, using +try+ with
|
62
|
+
# +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
|
63
|
+
# the delegator itself.
|
64
|
+
def try(*a, &b)
|
65
|
+
try!(*a, &b) if a.empty? || respond_to?(a.first)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and
|
69
|
+
# does not implement the tried method.
|
70
|
+
|
71
|
+
def try!(*a, &b)
|
72
|
+
if a.empty? && block_given?
|
73
|
+
if b.arity.zero?
|
74
|
+
instance_eval(&b)
|
75
|
+
else
|
76
|
+
yield self
|
77
|
+
end
|
78
|
+
else
|
79
|
+
public_send(*a, &b)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class NilClass
|
85
|
+
# Calling +try+ on +nil+ always returns +nil+.
|
86
|
+
# It becomes especially helpful when navigating through associations that may return +nil+.
|
87
|
+
#
|
88
|
+
# nil.try(:name) # => nil
|
89
|
+
#
|
90
|
+
# Without +try+
|
91
|
+
# @person && @person.children.any? && @person.children.first.name
|
92
|
+
#
|
93
|
+
# With +try+
|
94
|
+
# @person.try(:children).try(:first).try(:name)
|
95
|
+
def try(*args)
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def try!(*args)
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
data/lib/fir/util.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative './util/http'
|
4
|
+
require_relative './util/config'
|
5
|
+
require_relative './util/parser/apk'
|
6
|
+
require_relative './util/parser/ipa'
|
7
|
+
require_relative './util/parser/pngcrush'
|
8
|
+
require_relative './util/login'
|
9
|
+
require_relative './util/me'
|
10
|
+
require_relative './util/info'
|
11
|
+
require_relative './util/build_common'
|
12
|
+
require_relative './util/build_ipa'
|
13
|
+
require_relative './util/build_apk'
|
14
|
+
require_relative './util/publish'
|
15
|
+
require_relative './util/mapping'
|
16
|
+
|
17
|
+
module FIR
|
18
|
+
module Util
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
include FIR::Http
|
23
|
+
include FIR::Config
|
24
|
+
include FIR::Login
|
25
|
+
include FIR::Me
|
26
|
+
include FIR::Info
|
27
|
+
include FIR::BuildCommon
|
28
|
+
include FIR::BuildIpa
|
29
|
+
include FIR::BuildApk
|
30
|
+
include FIR::Publish
|
31
|
+
include FIR::Mapping
|
32
|
+
|
33
|
+
attr_accessor :logger
|
34
|
+
|
35
|
+
def fetch_user_info(token)
|
36
|
+
get fir_api[:user_url], api_token: token
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch_user_uuid(token)
|
40
|
+
user_info = fetch_user_info(token)
|
41
|
+
user_info[:uuid]
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_file_exist(path)
|
45
|
+
return if File.file?(path)
|
46
|
+
|
47
|
+
logger.error 'File does not exist'
|
48
|
+
exit 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_supported_file(path)
|
52
|
+
return if APP_FILE_TYPE.include?(File.extname(path))
|
53
|
+
|
54
|
+
logger.error 'Unsupported file type'
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def check_token_cannot_be_blank(token)
|
59
|
+
return unless token.blank?
|
60
|
+
|
61
|
+
logger.error 'Token can not be blank'
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_logined
|
65
|
+
return unless current_token.blank?
|
66
|
+
|
67
|
+
logger.error 'Please use `fir login` first'
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def logger_info_blank_line
|
72
|
+
logger.info ''
|
73
|
+
end
|
74
|
+
|
75
|
+
def logger_info_dividing_line
|
76
|
+
logger.info '✈ -------------------------------------------- ✈'
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_rqrcode(string, png_file_path)
|
80
|
+
qrcode = ::RQRCode::QRCode.new(string.to_s)
|
81
|
+
qrcode.as_png(size: 500, border_modules: 2, file: png_file_path)
|
82
|
+
png_file_path
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FIR
|
4
|
+
module BuildApk
|
5
|
+
|
6
|
+
def build_apk(*args, options)
|
7
|
+
logger.warn "build 在 fir-cli 即将过期, 推荐使用 gradlew 打包 apk文件后 后再使用 fir 工具上传生成的apk 文件"
|
8
|
+
initialize_build_common_options(args, options)
|
9
|
+
set_flavor(options)
|
10
|
+
|
11
|
+
Dir.chdir(@build_dir)
|
12
|
+
@build_cmd = initialize_apk_build_cmd
|
13
|
+
|
14
|
+
logger_info_and_run_build_command
|
15
|
+
|
16
|
+
output_apk
|
17
|
+
publish_build_app(options) if options.publish?
|
18
|
+
|
19
|
+
logger_info_blank_line
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def set_flavor(options)
|
25
|
+
unless options.flavor.blank?
|
26
|
+
@flavor = options.flavor
|
27
|
+
unless @flavor =~ /^assemble(.+)/
|
28
|
+
@flavor = "assemble#{@flavor}Release"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize_apk_build_cmd
|
34
|
+
check_build_gradle_exist
|
35
|
+
|
36
|
+
cmd = "./gradlew build"
|
37
|
+
cmd = "./gradlew #{@flavor}" unless @flavor.blank?
|
38
|
+
cmd
|
39
|
+
end
|
40
|
+
|
41
|
+
def gradle_build_path
|
42
|
+
"#{@build_dir}/build/outputs/apk"
|
43
|
+
end
|
44
|
+
|
45
|
+
def prefix_gradle_build_path
|
46
|
+
"#{@build_dir}/app/build/outputs/apk"
|
47
|
+
end
|
48
|
+
|
49
|
+
def output_apk
|
50
|
+
@builded_apk ||= Dir["#{gradle_build_path}/*.apk"].find { |i| i =~ /release/ }
|
51
|
+
@builded_apk ||= Dir["#{prefix_gradle_build_path}/*.apk"].find { |i| i =~ /release/ }
|
52
|
+
@builded_apk ||= Dir["#{@build_dir}/*.apk"].find { |i| i =~ /release/ }
|
53
|
+
|
54
|
+
check_no_output_apk
|
55
|
+
|
56
|
+
apk_info = FIR.apk_info(@builded_apk)
|
57
|
+
@apk_name = @name.blank? ? "#{apk_info[:name]}-#{apk_info[:version]}-Build-#{apk_info[:build]}" : @name
|
58
|
+
|
59
|
+
@builded_app_path = "#{@output_path}/#{@apk_name}.apk"
|
60
|
+
FileUtils.cp(@builded_apk, @builded_app_path)
|
61
|
+
end
|
62
|
+
|
63
|
+
def check_no_output_apk
|
64
|
+
unless @builded_apk
|
65
|
+
logger.error 'Builded has no output apk'
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def check_build_gradle_exist
|
71
|
+
return if File.exist?("#{@build_dir}/build.gradle")
|
72
|
+
|
73
|
+
logger.error "The build.gradle isn't exit, please use gradle and edit"
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|