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,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
|