Merge branch 'release/1.4.6'

This commit is contained in:
NaixSpirit
2016-02-21 21:06:02 +08:00
16 changed files with 412 additions and 217 deletions

View File

@@ -1,5 +1,10 @@
## 更新记录
### fir-cli 1.4.6
- 上传增加设置密码及公开访问权限的参数, [Issue #62](https://github.com/FIRHQ/fir-cli/issues/62)
- 打包增加指定 `exportProvisioningProfile` 参数, [Issue #59](https://github.com/FIRHQ/fir-cli/issues/59)
- 增加 http retry 机制, [Issue #65](https://github.com/FIRHQ/fir-cli/issues/65)
### fir-cli 1.4.5
- 增加 Android flavor 打包(感谢 [msdx](https://github.com/msdx) 的热心帮助)
- `$ fir ba <project dir> -f <flavor>`

View File

@@ -120,7 +120,7 @@ fir publish 命令可以轻松发布应用到 fir.im, 支持 ipa 和 apk 文件.
$ fir publish path/to/application -T YOUR_FIR_TOKEN
```
如果需要上传 changelog, 自定义 short 地址, 上传符号表, 生成二维码等功能, 可以使用 `fir publish -h`查看相应的帮助
如果需要上传 changelog, 自定义 short 地址, 设置密码, 设置公开访问权限, 上传符号表, 生成二维码等功能, 可以使用 `fir publish -h`查看相应的帮助
### fir login 使用说明

View File

@@ -27,7 +27,8 @@ Gem::Specification.new do |spec|
/_/ /___/_/ |_| \____/_____/___/
## 更新记录
### fir-cli 1.4.5
### fir-cli 1.4.6
- 增加上传时候设置密码及公开访问权限
- 增加 Android flavor 打包(感谢 [msdx](https://github.com/msdx) 的热心帮助)
- `$ fir ba <project dir> -f <flavor>`
- 详细更新记录, 请查看: https://github.com/FIRHQ/fir-cli/blob/master/CHANGELOG

View File

@@ -20,7 +20,7 @@ module FIR
$ fir bi <project dir> [-c <changelog> -P <bughd project id> -M -p -Q -T <your api token>]
$ fir bi <git ssh url> [-B develop -c <changelog> -P <bughd project id> -M -p -Q -T <your api token>]
$ fir bi <git ssh url> [-B develop -c <changelog> -f <profile> -P <bughd project id> -M -p -Q -T <your api token>]
$ fir bi <workspace dir> -w -S <scheme name> [-C <configuration>] [-t <target name>] [-o <ipa output dir>] [settings] [-c <changelog>] [-p -Q -T <your api token>]
LONGDESC
@@ -30,6 +30,7 @@ module FIR
method_option :scheme, type: :string, aliases: '-S', desc: 'Set the scheme NAME if build workspace'
method_option :configuration, type: :string, aliases: '-C', desc: 'Use the build configuration NAME for building each target'
method_option :target, type: :string, aliases: '-t', desc: 'Build the target specified by targetname'
method_option :profile, type: :string, aliases: '-f', desc: 'Set the export provisioning profile'
method_option :output, type: :string, aliases: '-o', desc: 'IPA output path, the default is: BUILD_DIR/fir_build_ipa'
method_option :publish, type: :boolean, aliases: '-p', desc: 'true/false if publish to fir.im'
method_option :short, type: :string, aliases: '-s', desc: 'Set custom short link if publish to fir.im'
@@ -90,11 +91,15 @@ module FIR
$ fir p <app file path> [-c <changelog> -s <custom short link> -Q -T <your api token>]
$ fir p <app file path> [-c <changelog> -s <custom short link> --password=123456 -o false -Q -T <your api token>]
$ fir p <app file path> [-c <changelog> -s <custom short link> -m <mapping file path> -P <bughd project id> -Q -T <your api token>]
LONGDESC
map 'p' => :publish
method_option :short, type: :string, aliases: '-s', desc: 'Set custom short link'
method_option :changelog, type: :string, aliases: '-c', desc: 'Set changelog'
method_option :password, type: :string, aliases: '-p', desc: 'Set password for app'
method_option :open, type: :boolean, aliases: '-o', desc: 'true/false if open for everyone, the default is: true', default: true
method_option :qrcode, type: :boolean, aliases: '-Q', desc: 'Generate qrcode'
method_option :mappingfile, type: :string, aliases: '-m', desc: 'App mapping file'
method_option :proj, type: :string, aliases: '-P', desc: 'Project id in BugHD.com if upload app mapping file'

View File

@@ -1,5 +1,9 @@
# encoding: utf-8
require_relative './patches/blank'
require_relative './patches/concern'
require_relative './patches/hash'
require_relative './patches/instance_variables'
require_relative './patches/native_patch'
require_relative './patches/os_patch'
require_relative './patches/try'

131
lib/fir/patches/blank.rb Normal file
View File

@@ -0,0 +1,131 @@
# encoding: utf-8
class Object
# An object is blank if it's false, empty, or a whitespace string.
# For example, '', ' ', +nil+, [], and {} are all blank.
#
# This simplifies
#
# address.nil? || address.empty?
#
# to
#
# address.blank?
#
# @return [true, false]
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
# An object is present if it's not blank.
#
# @return [true, false]
def present?
!blank?
end
# Returns the receiver if it's present otherwise returns +nil+.
# <tt>object.presence</tt> is equivalent to
#
# object.present? ? object : nil
#
# For example, something like
#
# state = params[:state] if params[:state].present?
# country = params[:country] if params[:country].present?
# region = state || country || 'US'
#
# becomes
#
# region = params[:state].presence || params[:country].presence || 'US'
#
# @return [Object]
def presence
self if present?
end
end
class NilClass
# +nil+ is blank:
#
# nil.blank? # => true
#
# @return [true]
def blank?
true
end
end
class FalseClass
# +false+ is blank:
#
# false.blank? # => true
#
# @return [true]
def blank?
true
end
end
class TrueClass
# +true+ is not blank:
#
# true.blank? # => false
#
# @return [false]
def blank?
false
end
end
class Array
# An array is blank if it's empty:
#
# [].blank? # => true
# [1,2,3].blank? # => false
#
# @return [true, false]
alias_method :blank?, :empty?
end
class Hash
# A hash is blank if it's empty:
#
# {}.blank? # => true
# { key: 'value' }.blank? # => false
#
# @return [true, false]
alias_method :blank?, :empty?
end
class String
BLANK_RE = /\A[[:space:]]*\z/
# A string is blank if it's empty or contains whitespaces only:
#
# ''.blank? # => true
# ' '.blank? # => true
# "\t\n\r".blank? # => true
# ' blah '.blank? # => false
#
# Unicode whitespace is supported:
#
# "\u00a0".blank? # => true
#
# @return [true, false]
def blank?
BLANK_RE === self
end
end
class Numeric #:nodoc:
# No number is blank:
#
# 1.blank? # => false
# 0.blank? # => false
#
# @return [false]
def blank?
false
end
end

79
lib/fir/patches/hash.rb Normal file
View File

@@ -0,0 +1,79 @@
# encoding: utf-8
class Hash
# Returns a copy of self with all blank keys removed.
#
# hash = { name: 'Rob', age: '', title: nil }
#
# hash.compact
# # => { name: 'Rob' }
def compact
delete_if { |_, v| v.is_a?(FalseClass) ? false : v.blank? }
end
# Returns a new hash with all keys converted using the block operation.
#
# hash = { name: 'Rob', age: '28' }
#
# hash.transform_keys{ |key| key.to_s.upcase }
# # => {"NAME"=>"Rob", "AGE"=>"28"}
def transform_keys
return enum_for(:transform_keys) unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
end
result
end
# Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
#
# hash = { 'name' => 'Rob', 'age' => '28' }
#
# hash.symbolize_keys
# # => {:name=>"Rob", :age=>"28"}
def symbolize_keys
transform_keys { |key| key.to_sym rescue key }
end
# Returns a new hash with all keys converted by the block operation.
# This includes the keys from the root hash and from all
# nested hashes and arrays.
#
# hash = { person: { name: 'Rob', age: '28' } }
#
# hash.deep_transform_keys{ |key| key.to_s.upcase }
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
def deep_transform_keys(&block)
_deep_transform_keys_in_object(self, &block)
end
# Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+. This includes the keys from the root hash
# and from all nested hashes and arrays.
#
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
#
# hash.deep_symbolize_keys
# # => {:person=>{:name=>"Rob", :age=>"28"}}
def deep_symbolize_keys
deep_transform_keys { |key| key.to_sym rescue key }
end
private
# support methods for deep transforming nested hashes and arrays
def _deep_transform_keys_in_object(object, &block)
case object
when Hash
object.each_with_object({}) do |(key, value), result|
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
end
when Array
object.map { |e| _deep_transform_keys_in_object(e, &block) }
else
object
end
end
end

View File

@@ -0,0 +1,30 @@
# encoding: utf-8
class Object
# Returns a hash with string keys that maps instance variable names without "@" to their
# corresponding values.
#
# class C
# def initialize(x, y)
# @x, @y = x, y
# end
# end
#
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
def instance_values
Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
end
# Returns an array of instance variable names as strings including "@".
#
# class C
# def initialize(x, y)
# @x, @y = x, y
# end
# end
#
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
def instance_variable_names
instance_variables.map { |var| var.to_s }
end
end

View File

@@ -1,203 +1,5 @@
# encoding: utf-8
class Object
# An object is blank if it's false, empty, or a whitespace string.
# For example, '', ' ', +nil+, [], and {} are all blank.
#
# This simplifies
#
# address.nil? || address.empty?
#
# to
#
# address.blank?
#
# @return [true, false]
def blank?
respond_to?(:empty?) ? empty? : !self
end
# An object is present if it's not blank.
#
# @return [true, false]
def present?
!blank?
end
# Returns the receiver if it's present otherwise returns +nil+.
# <tt>object.presence</tt> is equivalent to
#
# object.present? ? object : nil
#
# For example, something like
#
# state = params[:state] if params[:state].present?
# country = params[:country] if params[:country].present?
# region = state || country || 'US'
#
# becomes
#
# region = params[:state].presence || params[:country].presence || 'US'
#
# @return [Object]
def presence
self if present?
end
end
class NilClass
# +nil+ is blank:
#
# nil.blank? # => true
#
# @return [true]
def blank?
true
end
end
class FalseClass
# +false+ is blank:
#
# false.blank? # => true
#
# @return [true]
def blank?
true
end
end
class TrueClass
# +true+ is not blank:
#
# true.blank? # => false
#
# @return [false]
def blank?
false
end
end
class Array
# An array is blank if it's empty:
#
# [].blank? # => true
# [1,2,3].blank? # => false
#
# @return [true, false]
alias_method :blank?, :empty?
end
class Hash
# A hash is blank if it's empty:
#
# {}.blank? # => true
# { key: 'value' }.blank? # => false
#
# @return [true, false]
alias_method :blank?, :empty?
end
class String
BLANK_RE = /\A[[:space:]]*\z/
# A string is blank if it's empty or contains whitespaces only:
#
# ''.blank? # => true
# ' '.blank? # => true
# "\t\n\r".blank? # => true
# ' blah '.blank? # => false
#
# Unicode whitespace is supported:
#
# "\u00a0".blank? # => true
#
# @return [true, false]
def blank?
BLANK_RE === self
end
end
class Numeric #:nodoc:
# No number is blank:
#
# 1.blank? # => false
# 0.blank? # => false
#
# @return [false]
def blank?
false
end
end
class Hash
# Returns a new hash with all keys converted using the block operation.
#
# hash = { name: 'Rob', age: '28' }
#
# hash.transform_keys{ |key| key.to_s.upcase }
# # => {"NAME"=>"Rob", "AGE"=>"28"}
def transform_keys
return enum_for(:transform_keys) unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
end
result
end
# Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
#
# hash = { 'name' => 'Rob', 'age' => '28' }
#
# hash.symbolize_keys
# # => {:name=>"Rob", :age=>"28"}
def symbolize_keys
transform_keys { |key| key.to_sym rescue key }
end
# Returns a new hash with all keys converted by the block operation.
# This includes the keys from the root hash and from all
# nested hashes and arrays.
#
# hash = { person: { name: 'Rob', age: '28' } }
#
# hash.deep_transform_keys{ |key| key.to_s.upcase }
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
def deep_transform_keys(&block)
_deep_transform_keys_in_object(self, &block)
end
# Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+. This includes the keys from the root hash
# and from all nested hashes and arrays.
#
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
#
# hash.deep_symbolize_keys
# # => {:person=>{:name=>"Rob", :age=>"28"}}
def deep_symbolize_keys
deep_transform_keys { |key| key.to_sym rescue key }
end
private
# support methods for deep transforming nested hashes and arrays
def _deep_transform_keys_in_object(object, &block)
case object
when Hash
object.each_with_object({}) do |(key, value), result|
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
end
when Array
object.map { |e| _deep_transform_keys_in_object(e, &block) }
else
object
end
end
end
class File
class << self
# A binary file is Mach-O dSYM

102
lib/fir/patches/try.rb Normal file
View File

@@ -0,0 +1,102 @@
# encoding: utf-8
class Object
# Invokes the public method whose name goes as first argument just like
# +public_send+ does, except that if the receiver does not respond to it the
# call returns +nil+ rather than raising an exception.
#
# This method is defined to be able to write
#
# @person.try(:name)
#
# instead of
#
# @person.name if @person
#
# +try+ calls can be chained:
#
# @person.try(:spouse).try(:name)
#
# instead of
#
# @person.spouse.name if @person && @person.spouse
#
# +try+ will also return +nil+ if the receiver does not respond to the method:
#
# @person.try(:non_existing_method) #=> nil
#
# instead of
#
# @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil
#
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
# to the method:
#
# nil.try(:to_i) # => nil, rather than 0
#
# Arguments and blocks are forwarded to the method if invoked:
#
# @posts.try(:each_slice, 2) do |a, b|
# ...
# end
#
# The number of arguments in the signature must match. If the object responds
# to the method the call is attempted and +ArgumentError+ is still raised
# in case of argument mismatch.
#
# If +try+ is called without arguments it yields the receiver to a given
# block unless it is +nil+:
#
# @person.try do |p|
# ...
# end
#
# You can also call try with a block without accepting an argument, and the block
# will be instance_eval'ed instead:
#
# @person.try { upcase.truncate(50) }
#
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
# with instances of classes that do not have +Object+ among their ancestors,
# like direct subclasses of +BasicObject+. For example, using +try+ with
# +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
# the delegator itself.
def try(*a, &b)
try!(*a, &b) if a.empty? || respond_to?(a.first)
end
# Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and
# does not implement the tried method.
def try!(*a, &b)
if a.empty? && block_given?
if b.arity.zero?
instance_eval(&b)
else
yield self
end
else
public_send(*a, &b)
end
end
end
class NilClass
# Calling +try+ on +nil+ always returns +nil+.
# It becomes especially helpful when navigating through associations that may return +nil+.
#
# nil.try(:name) # => nil
#
# Without +try+
# @person && @person.children.any? && @person.children.first.name
#
# With +try+
# @person.try(:children).try(:first).try(:name)
def try(*args)
nil
end
def try!(*args)
nil
end
end

View File

@@ -24,11 +24,13 @@ module FIR
@configuration = options[:configuration]
@target_name = options[:target]
@scheme_name = options[:scheme]
@profile_name = options[:profile]
build_cmd = 'xcodebuild build -sdk iphoneos'
build_cmd += initialize_xcode_build_path(options)
build_cmd += " -configuration '#{@configuration}'" unless @configuration.blank?
build_cmd += " -target '#{@target_name}'" unless @target_name.blank?
build_cmd += " -exportProvisioningProfile '#{@profile_name}'" unless @profile_name.blank?
build_cmd += " #{ipa_custom_settings(args)} 2>&1"
build_cmd
end

View File

@@ -5,6 +5,7 @@ module FIR
DEFAULT_TIMEOUT = 300
def get(url, params = {})
tries = 5
begin
res = ::RestClient::Request.execute(
method: :get,
@@ -13,8 +14,14 @@ module FIR
headers: default_headers.merge(params: params)
)
rescue => e
logger.error e.message.to_s + ' - ' + e.response.to_s
exit 1
logger.error e.message.to_s
if tries > 0
logger.info "Retry in #{tries} times......"
tries -= 1
retry
else
exit 1
end
end
JSON.parse(res.body.force_encoding('UTF-8'), symbolize_names: true)
@@ -22,6 +29,7 @@ module FIR
%w(post patch put).each do |method|
define_method method do |url, query|
tries = 5
begin
res = ::RestClient::Request.execute(
method: method.to_sym,
@@ -31,8 +39,14 @@ module FIR
headers: default_headers
)
rescue => e
logger.error e.message.to_s + ' - ' + e.response.to_s
exit 1
logger.error e.message.to_s
if tries > 0
logger.info "Retry in #{tries} times......"
tries -= 1
retry
else
exit 1
end
end
JSON.parse(res.body.force_encoding('UTF-8'), symbolize_names: true)

View File

@@ -102,12 +102,13 @@ module FIR
end
def update_app_info
return if @short.blank?
update_info = { short: @short, passwd: @passwd, is_opened: @is_opened }.compact
logger.info 'Updating app info......'
return if update_info.blank?
patch fir_api[:app_url] + "/#{@app_id}", short: @short,
api_token: @token
logger.info "Updating app info......"
patch fir_api[:app_url] + "/#{@app_id}", update_info.merge(api_token: @token)
end
def fetch_uploading_info
@@ -157,6 +158,8 @@ module FIR
@token = options[:token] || current_token
@changelog = read_changelog(options[:changelog]).to_s.to_utf8
@short = options[:short].to_s
@passwd = options[:password].to_s
@is_opened = !!options[:open]
@export_qrcode = !!options[:qrcode]
end

View File

@@ -1,5 +1,5 @@
# encoding: utf-8
module FIR
VERSION = '1.4.5'
VERSION = '1.4.6'
end

View File

@@ -2,14 +2,30 @@
class PublishTest < Minitest::Test
def test_publish
options = {
token: default_token,
changelog: "test from fir-cli #{Time.now.to_i}",
qrcode: true
def setup
@options = {
token: default_token,
changelog: "test from fir-cli #{Time.now.to_i}"
}
end
assert FIR.publish(default_ipa, options)
assert FIR.publish(default_apk, options)
def test_simple_publish
assert FIR.publish(default_ipa, @options)
assert FIR.publish(default_apk, @options)
end
def test_update_app_info
short = SecureRandom.hex[3..9]
passwd = SecureRandom.hex[0..9]
is_opened = (rand(100) % 2) == 0
update_info = { short: short, password: passwd, open: is_opened }
FIR.publish(default_ipa, @options.merge(update_info))
info = FIR.fetch_app_info
assert_equal short, info[:short]
assert_equal passwd, info[:passwd]
assert_equal is_opened, info[:is_opened]
end
end

View File

@@ -5,6 +5,7 @@ CodeClimate::TestReporter.start
require 'minitest/autorun'
require 'ostruct'
require 'securerandom'
require 'fir'
FIR.logger = Logger.new(STDOUT)