escalator_ios 1.2.0
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/README.md +35 -0
- data/bin/escalator +10 -0
- data/escalator_ios.gemspec +27 -0
- data/lib/escalator/archive.rb +122 -0
- data/lib/escalator/combine.rb +116 -0
- data/lib/escalator/command/archive.rb +84 -0
- data/lib/escalator/command/combine.rb +116 -0
- data/lib/escalator/command/confuse.rb +86 -0
- data/lib/escalator/command/resign.rb +138 -0
- data/lib/escalator/command/setup.rb +91 -0
- data/lib/escalator/command/upload.rb +95 -0
- data/lib/escalator/command.rb +46 -0
- data/lib/escalator/confuse.rb +101 -0
- data/lib/escalator/group+.rb +13 -0
- data/lib/escalator/resign.rb +271 -0
- data/lib/escalator/setup.rb +79 -0
- data/lib/escalator/throw.rb +23 -0
- data/lib/escalator/upload.rb +57 -0
- data/lib/escalator.rb +25 -0
- data/resources/ExportOptions.plist +20 -0
- data/resources/IconInfo.plist +31 -0
- data/resources/zsh_plugin/escalator/_escalator +127 -0
- metadata +178 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
module Escalator
|
2
|
+
|
3
|
+
class Command
|
4
|
+
|
5
|
+
class Resign < Command
|
6
|
+
|
7
|
+
class Help < CLAide::Help
|
8
|
+
|
9
|
+
def message
|
10
|
+
[
|
11
|
+
formatted_error_message,
|
12
|
+
"请执行: escalator resign --help".ansi.green
|
13
|
+
].compact.join("\n\n").insert 0, "\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :ipaPaths
|
19
|
+
|
20
|
+
attr_reader :key_id
|
21
|
+
|
22
|
+
attr_reader :issuer_id
|
23
|
+
|
24
|
+
attr_reader :keyfile_path
|
25
|
+
|
26
|
+
attr_reader :apple_id
|
27
|
+
|
28
|
+
attr_reader :user_password
|
29
|
+
|
30
|
+
attr_reader :bundle_identifier
|
31
|
+
|
32
|
+
attr_reader :short_version
|
33
|
+
|
34
|
+
attr_reader :bundle_version
|
35
|
+
|
36
|
+
attr_reader :bundle_name
|
37
|
+
|
38
|
+
attr_reader :display_name
|
39
|
+
|
40
|
+
attr_reader :executable_name
|
41
|
+
|
42
|
+
attr_reader :iconAssets_path
|
43
|
+
|
44
|
+
self.command = "resign"
|
45
|
+
|
46
|
+
self.summary = "重签 IPA 安装包"
|
47
|
+
|
48
|
+
self.description = <<-DESC
|
49
|
+
利用新的 #{"证书".ansi.yellow} 和 #{"配置".ansi.yellow} 对当前 IPA 安装包进行重签名并修改相应信息
|
50
|
+
|
51
|
+
Appstore Connect 用户访问密钥申请:
|
52
|
+
|
53
|
+
#{"https://appstoreconnect.apple.com => 登录 => 用户和访问 => 密钥".ansi.blue}
|
54
|
+
|
55
|
+
`IPAPATH` 要重签名的 IPA 路径, 支持同一账号处理多个 IPA
|
56
|
+
|
57
|
+
`--iconAssets-path=path` 参数若存在, 务必将 ipa 对应 Xcode 工程下所有被引用的 .xcassets 文件的拷贝到 IPA 所在的目录下, 若不提供 APP 将丢失所有老资源
|
58
|
+
DESC
|
59
|
+
|
60
|
+
self.arguments = [
|
61
|
+
CLAide::Argument.new('IPAPATH', true, false)
|
62
|
+
]
|
63
|
+
|
64
|
+
def self.options
|
65
|
+
[
|
66
|
+
["--key-id=id", "id 为 Appstore Connect 用户访问密钥的 keyId"],
|
67
|
+
["--issuer-id=id", "id 为 Appstore Connect 用户访问密钥的 issuerId"],
|
68
|
+
["--keyfile-path=path", "path 为 Appstore Connect 用户访问密钥的 .p8 文件保存路径"],
|
69
|
+
["--user-password=password", "password 为当前电脑用户的访问密码"],
|
70
|
+
["--bundle-identifier=identifier", "identifier 为新的唯一标识符"],
|
71
|
+
["--short-version=version", "version 为新的版本号"],
|
72
|
+
["--bundle-version=version", "version 为新的 BuildID, 同一账号下处理多 IPA, 会自动叠加"],
|
73
|
+
["--apple-id=id", "id 为对应的苹果开发者账户, 如果含有 APP GROUP 推荐添加此字段, 默认: 无"],
|
74
|
+
["--bundle-name=name", "name 为新的包名, 默认: 不变"],
|
75
|
+
["--display-name=name", "name 为新的 APP 名称, 默认: 不变"],
|
76
|
+
["--executable-name=name", "name 为新的二进制文件名, 默认: 不变"],
|
77
|
+
["--iconAssets-path=path", "path 为包含新 AppIcon 的 .xcassets 资源文件路径, 默认: 无"]
|
78
|
+
].concat super
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize argv
|
82
|
+
@ipaPaths = argv.arguments!.select{ |arg| !arg.empty? }
|
83
|
+
@key_id = argv.option "key-id", ""
|
84
|
+
@issuer_id = argv.option "issuer-id", ""
|
85
|
+
@keyfile_path = argv.option "keyfile-path", ""
|
86
|
+
@apple_id = argv.option "apple-id", ""
|
87
|
+
@user_password = argv.option "user-password", ""
|
88
|
+
@bundle_identifier = argv.option "bundle-identifier", ""
|
89
|
+
@short_version = argv.option "short-version", ""
|
90
|
+
@bundle_version = argv.option "bundle-version", ""
|
91
|
+
@bundle_name = argv.option "bundle-name", ""
|
92
|
+
@display_name = argv.option "display-name", ""
|
93
|
+
@executable_name = argv.option "executable-name", ""
|
94
|
+
@iconAssets_path = argv.option "iconAssets-path", ""
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate!
|
99
|
+
super
|
100
|
+
if ipaPaths.empty?
|
101
|
+
help! "未检测到 IPAPATH 参数"
|
102
|
+
end
|
103
|
+
if key_id.empty?
|
104
|
+
help! "未检测到 --key-id 参数"
|
105
|
+
end
|
106
|
+
if issuer_id.empty?
|
107
|
+
help! "未检测到 --issuer-id 参数"
|
108
|
+
end
|
109
|
+
if keyfile_path.empty?
|
110
|
+
help! "未检测到 --keyfile-path 参数"
|
111
|
+
end
|
112
|
+
if user_password.empty?
|
113
|
+
help! "未检测到 --user-password 参数"
|
114
|
+
end
|
115
|
+
if bundle_identifier.empty?
|
116
|
+
help! "未检测到 --bundle-identifier 参数"
|
117
|
+
end
|
118
|
+
if short_version.empty?
|
119
|
+
help! "未检测到 --short-version 参数"
|
120
|
+
end
|
121
|
+
if bundle_version.empty?
|
122
|
+
help! "未检测到 --bundle-version 参数"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def help!(error_message = nil)
|
127
|
+
invoked_command_class.help!(error_message, Help)
|
128
|
+
end
|
129
|
+
|
130
|
+
def run
|
131
|
+
Escalator::Resign.run self
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Escalator
|
2
|
+
|
3
|
+
class Command
|
4
|
+
|
5
|
+
class Setup < Command
|
6
|
+
|
7
|
+
class Help < CLAide::Help
|
8
|
+
|
9
|
+
def message
|
10
|
+
[
|
11
|
+
formatted_error_message,
|
12
|
+
"请执行: escalator setup --help".ansi.green
|
13
|
+
].compact.join("\n\n").insert 0, "\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :commands
|
19
|
+
|
20
|
+
attr_reader :output_path
|
21
|
+
|
22
|
+
attr_reader :set_zshcompletion
|
23
|
+
|
24
|
+
attr_reader :custom_plugins
|
25
|
+
|
26
|
+
self.command = "setup"
|
27
|
+
|
28
|
+
self.summary = "生成 组合命令执行需要的参数模版"
|
29
|
+
|
30
|
+
self.description = <<-DESC
|
31
|
+
导出组合命令执行必须需的, 全局配置参数模版 和 独立配置参数模版
|
32
|
+
|
33
|
+
独立参数模版, 在包含 #{"resign".ansi.green} 命令时才会导出
|
34
|
+
|
35
|
+
独立参数模版, 需放置在对应输入项目的根路径下, 会覆盖全局配置中对应的参数
|
36
|
+
|
37
|
+
`COMMAND` 要组合的命令, 支持 #{"confuse archive resign upload".ansi.green} 命令
|
38
|
+
DESC
|
39
|
+
|
40
|
+
self.arguments = [
|
41
|
+
CLAide::Argument.new('COMMAND', true, true)
|
42
|
+
]
|
43
|
+
|
44
|
+
def self.options
|
45
|
+
[
|
46
|
+
["--output-path=path", "path 为参数模版导出路径"],
|
47
|
+
["--set-zshcompletion", "会忽略其他参数, 只设置 oh-my-zsh 的命令补全插件到 custom/plugins 中, zshrc 配置插件, 请自己手动更新, 默认: 否"]
|
48
|
+
].concat super
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize argv
|
52
|
+
@commands = argv.arguments!.uniq
|
53
|
+
@output_path = argv.option "output-path"
|
54
|
+
@set_zshcompletion = argv.flag? "set-zshcompletion", false
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate!
|
59
|
+
super
|
60
|
+
if set_zshcompletion
|
61
|
+
if ENV["ZSH"].empty?
|
62
|
+
help! "未检测到 oh-my-zsh, 请先进行安装"
|
63
|
+
end
|
64
|
+
@custom_plugins = "#{ENV["ZSH"]}/custom/plugins/"
|
65
|
+
return
|
66
|
+
end
|
67
|
+
if commands.empty?
|
68
|
+
help! "未检测到 COMMAND 参数"
|
69
|
+
end
|
70
|
+
cmds = (commands - (%w(confuse archive resign upload) & commands))
|
71
|
+
if !cmds.empty?
|
72
|
+
help! "检测到 非法参数: #{cmds.join ", "}"
|
73
|
+
end
|
74
|
+
if !output_path
|
75
|
+
help! "未检测到 --output-path 参数"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def help!(error_message = nil)
|
80
|
+
invoked_command_class.help!(error_message, Help)
|
81
|
+
end
|
82
|
+
|
83
|
+
def run
|
84
|
+
Escalator::Setup.run self
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Escalator
|
2
|
+
|
3
|
+
class Command
|
4
|
+
|
5
|
+
class Upload < Command
|
6
|
+
|
7
|
+
class Help < CLAide::Help
|
8
|
+
|
9
|
+
def message
|
10
|
+
[
|
11
|
+
formatted_error_message,
|
12
|
+
"请执行: escalator upload --help".ansi.green
|
13
|
+
].compact.join("\n\n").insert 0, "\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :ipaPaths
|
19
|
+
|
20
|
+
attr_reader :key_id
|
21
|
+
|
22
|
+
attr_reader :issuer_id
|
23
|
+
|
24
|
+
attr_reader :keyfile_path
|
25
|
+
|
26
|
+
attr_reader :bundle_identifier
|
27
|
+
|
28
|
+
self.command = "upload"
|
29
|
+
|
30
|
+
self.summary = "上传 IPA 安装包"
|
31
|
+
|
32
|
+
self.description = <<-DESC
|
33
|
+
利用 Appstore Connect 用户访问密钥, 上传 ipa 安装包
|
34
|
+
|
35
|
+
Appstore Connect 用户访问密钥申请:
|
36
|
+
|
37
|
+
#{"https://appstoreconnect.apple.com => 登录 => 用户和访问 => 密钥".ansi.blue}
|
38
|
+
|
39
|
+
`IPAPATH` 要上传的 IPA 路径, 支持同一账号上传多个 IPA
|
40
|
+
DESC
|
41
|
+
|
42
|
+
self.arguments = [
|
43
|
+
CLAide::Argument.new('IPAPATH', true, true)
|
44
|
+
]
|
45
|
+
|
46
|
+
def self.options
|
47
|
+
[
|
48
|
+
["--key-id=id", "id 为 Appstore Connect 用户访问密钥的 keyId"],
|
49
|
+
["--issuer-id=id", "id 为 Appstore Connect 用户访问密钥的 issuerId"],
|
50
|
+
["--keyfile-path=path", "path 为 Appstore Connect 用户访问密钥的 .p8 文件保存路径"],
|
51
|
+
["--bundle-identifier=identifier", "identifier 为上传账户下对应唯一标识符"]
|
52
|
+
].concat super
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize argv
|
56
|
+
@ipaPaths = argv.arguments!.select { |arg| !arg.empty? }
|
57
|
+
@key_id = argv.option "key-id", ""
|
58
|
+
@issuer_id = argv.option "issuer-id", ""
|
59
|
+
@keyfile_path = argv.option "keyfile-path", ""
|
60
|
+
@bundle_identifier = argv.option "bundle-identifier", ""
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate!
|
65
|
+
super
|
66
|
+
if ipaPaths.empty?
|
67
|
+
help! "未检测到 IPAPATH 参数"
|
68
|
+
end
|
69
|
+
if key_id.empty?
|
70
|
+
help! "未检测到 --key-id 参数"
|
71
|
+
end
|
72
|
+
if issuer_id.empty?
|
73
|
+
help! "未检测到 --issuer-id 参数"
|
74
|
+
end
|
75
|
+
if keyfile_path.empty?
|
76
|
+
help! "未检测到 --keyfile-path 参数"
|
77
|
+
end
|
78
|
+
if bundle_identifier.empty?
|
79
|
+
help! "未检测到 --bundle-identifier 参数"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def help!(error_message = nil)
|
84
|
+
invoked_command_class.help!(error_message, Help)
|
85
|
+
end
|
86
|
+
|
87
|
+
def run
|
88
|
+
Escalator::Upload.run self
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Escalator
|
2
|
+
|
3
|
+
class Command < CLAide::Command
|
4
|
+
|
5
|
+
require_relative "command/confuse"
|
6
|
+
require_relative "command/archive"
|
7
|
+
require_relative "command/resign"
|
8
|
+
require_relative "command/upload"
|
9
|
+
require_relative "command/combine"
|
10
|
+
require_relative "command/setup"
|
11
|
+
|
12
|
+
self.abstract_command = true
|
13
|
+
|
14
|
+
self.version = VERSION
|
15
|
+
|
16
|
+
self.command = "escalator"
|
17
|
+
|
18
|
+
self.description = <<-DESC
|
19
|
+
本工具专为 testflight 上架多马甲包而生
|
20
|
+
|
21
|
+
旨在取代马甲包代码频繁修改, 上传安装包繁杂的人工流程
|
22
|
+
|
23
|
+
主要功能包含: 代码混淆, 项目打包, ipa重签名, ipa上传
|
24
|
+
DESC
|
25
|
+
|
26
|
+
DEFAULT_ROOT_OPTIONS = [
|
27
|
+
['--version', '仅输出工具版本号, 默认: 否'],
|
28
|
+
]
|
29
|
+
|
30
|
+
DEFAULT_OPTIONS = [
|
31
|
+
['--verbose', '输出更多调试信息, 默认: 否'],
|
32
|
+
['--no-ansi', '关闭高亮输出模式, 默认: 否'],
|
33
|
+
['--help', '仅输出指定命令的帮助文档, 默认: 否'],
|
34
|
+
]
|
35
|
+
|
36
|
+
def self.options
|
37
|
+
if root_command?
|
38
|
+
DEFAULT_ROOT_OPTIONS + DEFAULT_OPTIONS
|
39
|
+
else
|
40
|
+
DEFAULT_OPTIONS
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Escalator
|
2
|
+
|
3
|
+
class Confuse
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def run command
|
8
|
+
@command = command
|
9
|
+
Throw.note "Begin confuse #{File.basename File.dirname command.project_path} ..."
|
10
|
+
prepareContext
|
11
|
+
handleProject
|
12
|
+
project_path
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
EXTNAMES = ["h", "hpp", "pch", "c", "m", "mm", "cpp", "swift", "xib", "storyboard"]
|
18
|
+
|
19
|
+
attr_accessor :command
|
20
|
+
|
21
|
+
attr_accessor :keywords
|
22
|
+
|
23
|
+
attr_accessor :project_path
|
24
|
+
|
25
|
+
def prepareContext
|
26
|
+
Throw.note "Prepare confuse context ..."
|
27
|
+
@keywords = []
|
28
|
+
timestamp = Time.now.to_i.to_s
|
29
|
+
command.keywords.each { |keyword|
|
30
|
+
part1 = (Random.rand(25) + 65).chr
|
31
|
+
part2 = OpenSSL::Digest.hexdigest("SHA1", keyword + timestamp)[0,7].upcase
|
32
|
+
text1 = part1 + part2
|
33
|
+
text2 = text1.sub(/[A-Z]*[0-9a-z]/) { |match| match.downcase }
|
34
|
+
@keywords << OpenStruct.new(:first => text1,
|
35
|
+
:second => text2,
|
36
|
+
:value => keyword,
|
37
|
+
:regexp => Regexp.new(keyword, Regexp::IGNORECASE))
|
38
|
+
}
|
39
|
+
if command.verbose?
|
40
|
+
puts keywords.map { |keyword|
|
41
|
+
"#{keyword.value} ==> first: #{keyword.first} second: #{keyword.second}\n"
|
42
|
+
}
|
43
|
+
end
|
44
|
+
source_path = File.expand_path("../", command.project_path)
|
45
|
+
output_path = File.expand_path("./", command.output_path)
|
46
|
+
project_name = File.basename(command.project_path)
|
47
|
+
target_path = "#{output_path}/#{File.basename(source_path)}"
|
48
|
+
@project_path = "#{target_path}/#{project_name}"
|
49
|
+
FileUtils.mkdir_p output_path
|
50
|
+
if File.exist? target_path
|
51
|
+
FileUtils.rm_rf target_path
|
52
|
+
end
|
53
|
+
FileUtils.cp_r source_path, output_path
|
54
|
+
end
|
55
|
+
|
56
|
+
def handleProject
|
57
|
+
project = Xcodeproj::Project.open project_path
|
58
|
+
keywords.each { |keyword|
|
59
|
+
Throw.note "Begin Confuse #{keyword.value} ..."
|
60
|
+
handleGroup project.groups.first, keyword
|
61
|
+
}
|
62
|
+
project.save project_path
|
63
|
+
if command.show_output?
|
64
|
+
system "open #{command.output_path}"
|
65
|
+
end
|
66
|
+
Throw.note "Confuse Success!"
|
67
|
+
end
|
68
|
+
|
69
|
+
def handleGroup group, keyword
|
70
|
+
group.anyGroups.each { |subGroup|
|
71
|
+
handleGroup subGroup, keyword
|
72
|
+
}
|
73
|
+
group.files.each { |file|
|
74
|
+
if command.verbose?
|
75
|
+
puts "Confusing #{keyword.value} for #{file.path} ..."
|
76
|
+
end
|
77
|
+
if file.path.include? keyword.value
|
78
|
+
dir = File.dirname(file.real_path)
|
79
|
+
name = file.path.gsub keyword.regexp, keyword.first
|
80
|
+
FileUtils.mv file.real_path, "#{dir}/#{name}"
|
81
|
+
file.set_path name
|
82
|
+
end
|
83
|
+
handleFile file, keyword
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def handleFile file, keyword
|
88
|
+
if EXTNAMES.include? file.path.split(".").last
|
89
|
+
content = File.read file.real_path
|
90
|
+
content = content.gsub(keyword.regexp) { |match|
|
91
|
+
match == keyword.value ? keyword.first : keyword.second
|
92
|
+
}
|
93
|
+
File.write file.real_path, content
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|