pod-binary-pack 1.0.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.
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require_relative '../push'
5
+
6
+ module Build
7
+ module Hosting
8
+ # 代码托管平台的仓库描述
9
+ # @!attribute id Gitee无数字ID概念,该字段仅GitLab使用
10
+ # @!attribute full_name 平台上的完整仓库路径,如 xxx/YYModel
11
+ # @!attribute namespace_path 命名空间路径,如 xxx
12
+ RepoInfo = Struct.new(:id, :full_name, :namespace_path, keyword_init: true)
13
+
14
+ # 代码托管平台抽象层
15
+ # GitLab/Gitee/不支持平台各自实现该接口,业务层不感知具体平台
16
+ class Provider
17
+ # @return [Push] 推送能力
18
+ def pusher
19
+ @pusher || Push.instance
20
+ end
21
+
22
+ attr_writer :pusher
23
+
24
+ # @return [Boolean] 当前平台的API能力是否可用
25
+ def available?
26
+ false
27
+ end
28
+
29
+ # 检查仓库是否存在,不存在则自动创建
30
+ # @param project_name [String] 组件/仓库名
31
+ # @param http_url [String] 仓库http地址
32
+ # @param private [Boolean] 是否私有仓库,固定默认私有
33
+ # @param description [String] 仓库描述
34
+ def create_project_if_need(project_name, http_url, private: true, description: nil)
35
+ raise NotImplementedError
36
+ end
37
+
38
+ # 查询仓库信息
39
+ # @return [Build::Hosting::RepoInfo, nil]
40
+ def find_project(pod_name, http_url, can_raise: true)
41
+ raise NotImplementedError
42
+ end
43
+
44
+ # 锁死(保护)发布分支
45
+ # @param repo_info [Build::Hosting::RepoInfo]
46
+ # @param branch [String] 分支名
47
+ # @param page_url [String] 仓库网页地址
48
+ # @return [String] 分支保护设置页地址
49
+ def lock_branch(repo_info, branch, page_url)
50
+ raise NotImplementedError
51
+ end
52
+
53
+ # 判断分支是否包含某次提交
54
+ # @return [Boolean]
55
+ def branch_contain_commit?(repo_info, branch, commit_sha)
56
+ raise NotImplementedError
57
+ end
58
+
59
+ # 合并组件分支到目标分支
60
+ def merge_branch(repo_module, target_branch)
61
+ raise NotImplementedError
62
+ end
63
+ end
64
+
65
+ # 不支持的平台(非Gitee/GitLab),所有平台能力静默跳过
66
+ class NullProvider < Provider
67
+ include Singleton
68
+
69
+ def available?
70
+ false
71
+ end
72
+
73
+ def create_project_if_need(project_name, http_url, private: true, description: nil)
74
+ Build.log.info("非受支持的代码托管平台(#{http_url}),跳过仓库检查和创建")
75
+ nil
76
+ end
77
+
78
+ def find_project(pod_name, http_url, can_raise: true)
79
+ nil
80
+ end
81
+
82
+ def lock_branch(repo_info, branch, page_url)
83
+ Build.log.info("非受支持的代码托管平台,跳过分支锁死: #{branch}")
84
+ nil
85
+ end
86
+
87
+ def branch_contain_commit?(repo_info, branch, commit_sha)
88
+ false
89
+ end
90
+
91
+ def merge_branch(repo_module, target_branch)
92
+ Build.log.info("非受支持的代码托管平台,不支持自动合并分支到#{target_branch}")
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require_relative 'hosting/provider'
5
+ require_relative 'hosting/gitee_client'
6
+ require_relative 'hosting/gitee_provider'
7
+ require_relative 'hosting/gitlab_provider'
8
+
9
+ module Build
10
+ # 代码托管平台工厂:根据仓库地址选择对应的平台Provider
11
+ module Hosting
12
+ # 从ssh(scp-like)或http地址中解析host
13
+ # git@gitee.com:owner/repo.git -> gitee.com
14
+ # https://gitee.com/owner/repo -> gitee.com
15
+ def self.host_of(url)
16
+ url = url.to_s
17
+ host =
18
+ if url.start_with?('git@')
19
+ url.split('@', 2).last.split(':', 2).first
20
+ else
21
+ begin
22
+ URI(url).host
23
+ rescue URI::Error
24
+ nil
25
+ end
26
+ end
27
+ host.to_s.downcase
28
+ end
29
+
30
+ # @param url [String] 仓库ssh或http地址
31
+ # @return [Build::Hosting::Provider]
32
+ def self.provider_for(url)
33
+ host = host_of(url)
34
+ if host.include?('gitee')
35
+ GiteeProvider.instance
36
+ elsif host.include?('gitlab')
37
+ GitlabProvider.instance
38
+ else
39
+ NullProvider.instance
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Build
4
+ # 统一构建日志,替代散落各处的 puts。
5
+ #
6
+ # 级别:
7
+ # step 阶段标题(⏩,前置空行),如 clone仓库、pod update、开始编译
8
+ # info 普通信息
9
+ # detail 细节说明/阶段性完成(-> 前缀);流程内各步骤完成统一用此级别
10
+ # success 成功提示(✅);仅用于整条命令/流程的最终关键结果,阶段步骤不要用,避免满屏✅
11
+ # warn 警告(⚠️,输出到 stderr,不中断流程)
12
+ # error 错误(❌,输出到 stderr;是否退出由调用方决定)
13
+ # debug 调试细节(命令回显/响应体等),仅 --verbose 或环境变量开启时输出
14
+ class Logger
15
+ DEBUG_ENV = 'POD_BINARY_PACK_VERBOSE'
16
+
17
+ attr_writer :debug_enabled
18
+
19
+ def initialize
20
+ @debug_enabled = !ENV.fetch(DEBUG_ENV, '').strip.empty?
21
+ end
22
+
23
+ def debug_enabled?
24
+ !!@debug_enabled
25
+ end
26
+
27
+ def step(message)
28
+ write($stdout, "\n⏩ #{message}")
29
+ end
30
+
31
+ def info(message = '')
32
+ write($stdout, message.to_s)
33
+ end
34
+
35
+ def detail(message)
36
+ write($stdout, "-> #{message}")
37
+ end
38
+
39
+ def success(message)
40
+ write($stdout, "✅ #{message}")
41
+ end
42
+
43
+ def warn(message)
44
+ write($stderr, "⚠️ #{message}")
45
+ end
46
+
47
+ def error(message)
48
+ write($stderr, "❌ #{message}")
49
+ end
50
+
51
+ def debug(message)
52
+ write($stdout, "· #{message}") if debug_enabled?
53
+ end
54
+
55
+ private
56
+
57
+ def write(io, message)
58
+ io.puts(message)
59
+ end
60
+ end
61
+
62
+ # @return [Build::Logger] 全局构建日志实例
63
+ def self.log
64
+ @log ||= Logger.new
65
+ end
66
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+ require 'json'
4
+
5
+ module Build
6
+ class Podspec
7
+ # @return [String] podspec文件路径
8
+ attr_accessor :path
9
+
10
+ # @return [JSON] 更新后的内容
11
+ attr_accessor :podspec_content
12
+
13
+ def initialize(path)
14
+ @path = path
15
+ @podspec_content = update_need_framework
16
+ end
17
+
18
+ private
19
+ # 添加必要的framework
20
+ def update_need_framework
21
+ code_path = File.dirname(self.path)
22
+
23
+ json = File.read(self.path)
24
+ podspec_content = JSON.parse(json)
25
+
26
+ need_adds = []
27
+
28
+ framework1 = "CoreServices"
29
+ contain1 = code_contain_framework(code_path, framework1)
30
+ if contain1
31
+ need_adds << framework1
32
+ Build.log.warn("源码podspec缺少依赖:s.frameworks '#{framework1}', 已为自动添加")
33
+ end
34
+
35
+ # 遍历添加
36
+ need_adds.each do |framework|
37
+ unless podspec_content["frameworks"]
38
+ podspec_content["frameworks"] = []
39
+ podspec_content["frameworks"] << framework
40
+ end
41
+
42
+ if podspec_content["frameworks"].class == String
43
+ origin = podspec_content["frameworks"]
44
+ if origin != framework
45
+ podspec_content["frameworks"] = [origin]
46
+ podspec_content["frameworks"] << framework
47
+ end
48
+ end
49
+
50
+ if podspec_content["frameworks"].class == Array
51
+ origin = podspec_content["frameworks"]
52
+ unless origin.include?(framework)
53
+ podspec_content["frameworks"] << framework
54
+ end
55
+ end
56
+ end
57
+
58
+ podspec_content
59
+
60
+ end
61
+
62
+ def code_contain_framework(code_path, framework)
63
+ files = Dir["#{code_path}/**/*"]
64
+
65
+ # 组件里所有依赖的其他组件
66
+ all_reference_pods = []
67
+ files.each do |file_path_str|
68
+ file_path = Pathname(file_path_str)
69
+ extend_name = file_path.extname
70
+ if not extend_name == ".h" and
71
+ not extend_name == ".m" and
72
+ not extend_name == ".mm" and
73
+ not extend_name == ".pch" and
74
+ not extend_name == ".c" and
75
+ not extend_name == ".cc" and
76
+ not extend_name == ".hpp" and
77
+ not extend_name == ".cpp"
78
+ next
79
+ end
80
+ reference_pods = pod_in_file(file_path)
81
+ all_reference_pods = all_reference_pods | reference_pods
82
+ end
83
+
84
+ all_reference_pods.include?(framework)
85
+ end
86
+
87
+ # 获取某个文件里引用的依赖库名字
88
+ # @param file_path 文件路径
89
+ # @return reference_pods
90
+ def pod_in_file(file_path)
91
+ reference_pods = []
92
+ if File.directory?(file_path)
93
+ return reference_pods
94
+ end
95
+
96
+ safe_readlines(file_path).each { |line|
97
+ # 兼容 #import <A/B.h> 与 #import<A/B.h>(无空格)两种写法,
98
+ # 无空格写法漏识别会导致framework依赖检测遗漏
99
+ match = line.match(/\A#\s*import\s*<([^>]+)>/)
100
+ next unless match
101
+
102
+ import_path = match[1]
103
+ next unless import_path.include?("/")
104
+
105
+ # 取第一段作为组件名,处理 #import <A/B/C.h> 多级子模块的情况
106
+ real_module_name = import_path.split("/").first
107
+
108
+ unless reference_pods.include?(real_module_name)
109
+ reference_pods << real_module_name
110
+ end
111
+ }
112
+ reference_pods
113
+ end
114
+
115
+ # 容错读取源码行:历史组件源码可能存在GBK等非UTF-8文件。
116
+ # 直接IO.readlines按默认编码读,后续start_with?/match遇到非法字节会抛
117
+ # invalid byte sequence中断整个打包流程;先按二进制读取再显式做一次
118
+ # 同编码转换替换非法字节
119
+ def safe_readlines(file_path)
120
+ content = File.read(file_path, mode: 'rb')
121
+ content.encode!('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: '?')
122
+ content.lines
123
+ end
124
+
125
+ end
126
+ end
data/lib/core/push.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'config'
4
+ require_relative 'repo_module'
5
+ require_relative 'repo_xcode_package'
6
+ require_relative 'repo_xcode_shell'
7
+ require 'singleton'
8
+
9
+ module Build
10
+ class Push
11
+ include Singleton
12
+
13
+ # @return [Object] 消息发送器
14
+ attr_accessor :sender
15
+
16
+ # @return [RepoModule] 打包的组件
17
+ attr_accessor :repo_module
18
+
19
+ # @return [RepoXCodeShell] 壳工程(即打包工程)
20
+ attr_accessor :repo_xcode_shell
21
+
22
+ def setup(sender = nil, repo_module = nil, repo_xcode_shell = nil)
23
+ @sender = sender
24
+ @repo_module = repo_module
25
+ @repo_xcode_shell = repo_xcode_shell
26
+
27
+ end
28
+
29
+ # 发送推送消息
30
+ # @param title [String] 推送标题
31
+ # @param summary [String] 推送简要描述
32
+ def push(title, summary = nil)
33
+
34
+ # 未初始化时 拿不到推送给谁 就不推 只打印
35
+ if not repo_module or not repo_module.last_commit.committer
36
+ Build.log.warn("未获取到仓库信息或提交者信息,不推送以下内容:")
37
+ Build.log.info(title)
38
+ Build.log.info(summary.to_s)
39
+ return
40
+ end
41
+
42
+ to = repo_module.last_commit.committer
43
+ content = create_push_content(summary)
44
+
45
+ Build.log.step("开始推送通知流程")
46
+ log_content = "#{title}\n#{content}\n"
47
+
48
+ # 发送渠道
49
+ Build.log.debug("即将推送给#{to}如下内容:\n#{log_content}")
50
+ if @sender
51
+ @sender.send_msg(to.upcase, title, content.to_s)
52
+ Build.log.detail("推送通知完毕")
53
+ else
54
+ Build.log.warn("未提供推送配置,不进行推送")
55
+ end
56
+
57
+ end
58
+
59
+ def create_push_content(summary = nil)
60
+
61
+ shell_http_url = repo_xcode_shell ? repo_xcode_shell.http_url : "null"
62
+ shell_branch = repo_xcode_shell ? repo_xcode_shell.branch : "null"
63
+ build_url = Build::Config.instance.build_url
64
+
65
+ content = ""
66
+ content += "🔹组件: #{repo_module.pod_name}\n"
67
+ content += "🔹版本: #{repo_module.pod_version_final}\n"
68
+ content += "🔹分支: #{repo_module.branch}\n"
69
+ content += "🔹提交: #{repo_module.last_commit.commit_msg}\n"
70
+ content += "🔹组件 地址: #{repo_module.http_url}\n"
71
+ content += "🔹壳工程地址: #{shell_http_url}\n"
72
+ content += "🔹壳工程分支: #{shell_branch}\n"
73
+ content += "🔹构建地址: #{build_url}\n"
74
+ content += summary ? "🔸摘要: #{summary}\n\n" : "\n"
75
+ content += "🔹#{Time.new.strftime("%Y-%m-%d %H:%M:%S")}\n"
76
+
77
+ end
78
+
79
+ end
80
+ end
data/lib/core/repo.rb ADDED
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+ require 'git'
3
+ require 'fileutils'
4
+ require_relative 'hosting'
5
+ require_relative 'commit'
6
+ require_relative 'push'
7
+ require_relative 'git_helper'
8
+ require_relative 'util/file_helper'
9
+
10
+ module Build
11
+ class Repo
12
+ attr_reader :page_url
13
+ attr_reader :http_url
14
+ attr_reader :ssh_url
15
+ attr_reader :branch
16
+
17
+ # @return [Build::Hosting::Provider] 当前仓库对应的代码托管平台能力
18
+ def hosting
19
+ @hosting ||= Hosting.provider_for(ssh_url)
20
+ end
21
+
22
+ attr_writer :hosting
23
+
24
+ # @return [Push] 推送能力
25
+ attr_accessor :pusher
26
+
27
+ # @return [String] 仓库提交时的用户名
28
+ attr_accessor :user_name
29
+
30
+ # @return [String] 仓库提交时的邮箱
31
+ attr_accessor :user_email
32
+
33
+ # @return [Pathname] 仓库所在本机的路径
34
+ attr_accessor :repo_path
35
+
36
+ # @return [String] 仓库所在本机的路径
37
+ attr_accessor :path
38
+
39
+ # @return [Commit] 最新的提交信息
40
+ attr_accessor :last_commit
41
+
42
+ # @return [Integer] 当前仓库的大小 KB
43
+ attr_accessor :size
44
+
45
+ # @return [String] 当前仓库的大小
46
+ attr_accessor :size_format
47
+
48
+ def initialize(path, ssh_url, branch = nil)
49
+ @ssh_url = ssh_url.to_s
50
+ @branch = branch.to_s
51
+ @path = path.to_s
52
+ @repo_path = Pathname.new(path.to_s)
53
+ @user_name = "jenkins"
54
+ @user_email = "jenkins@jenkins.com"
55
+ @last_commit = Commit.new
56
+ @pusher = Push.instance
57
+
58
+ __validate
59
+ end
60
+
61
+ public
62
+ # 根据url去clone
63
+ # @param only_head: only_head
64
+ def git_clone(only_head)
65
+ Build.log.info("(#{File.basename(self.path)})下载[#{self.ssh_url}] [#{self.branch}] [#{only_head ? "深度1" : "全量"}]")
66
+
67
+ # clone(git gem 5.x: depth/branch 均为 clone 的直接选项,不再支持嵌套 options:)
68
+ clone_options = {}
69
+ clone_options[:branch] = self.branch unless self.branch.to_s.empty?
70
+ clone_options[:depth] = 1 if only_head
71
+ begin
72
+ Git.clone(self.ssh_url, self.repo_path.to_s, **clone_options)
73
+ rescue Git::FailedError => e
74
+ # 常见克隆失败转成可读提示,避免直接抛git原始堆栈;
75
+ # git gem 5.x: e.result 为 CommandLine::Result,stderr 取不到时回退错误消息全文
76
+ stderr = if e.respond_to?(:result) && e.result.respond_to?(:stderr)
77
+ e.result.stderr.to_s
78
+ else
79
+ e.message.to_s
80
+ end
81
+ friendly =
82
+ if stderr.include?('not found in upstream origin')
83
+ "分支/tag '#{self.branch}' 在远端仓库中不存在,请检查分支名是否正确(区分大小写),或先在远端创建该分支/tag"
84
+ elsif stderr.include?('Repository not found') || stderr.include?('Permission denied') ||
85
+ stderr.include?('Could not read from remote')
86
+ "仓库不存在或当前账号无访问权限,请检查仓库地址与SSH公钥配置"
87
+ end
88
+ fatal_line = stderr.lines.find { |line| line.strip.start_with?('fatal:') }.to_s.strip
89
+ error = +"克隆仓库失败: #{self.ssh_url} (分支/tag: #{self.branch.to_s.empty? ? '默认' : self.branch})"
90
+ error << "\n-> #{friendly}" if friendly
91
+ error << "\n-> #{fatal_line}" unless fatal_line.empty?
92
+ pusher.push("❌ 打包失败", error)
93
+ Build.log.error(error)
94
+ Process.exit(-1)
95
+ end
96
+
97
+ repo = Git.open(self.repo_path)
98
+ repo.config_set('user.name', user_name)
99
+ repo.config_set('user.email', user_email)
100
+
101
+ Build.log.detail("下载完成")
102
+
103
+ # 打印最近提交记录(git gem 5.x: repo.log.execute)
104
+ log = repo.log(30).execute.first
105
+
106
+ if log
107
+ committer = log.committer
108
+ Build.log.detail("最近提交记录: [#{log.sha}] [#{log.message}] [#{committer.date}] [#{committer.name}] [#{committer.email}]")
109
+
110
+ last_commit.commit_id = log.sha
111
+ last_commit.commit_msg = log.message
112
+ last_commit.committer = committer.name
113
+ last_commit.commit_mail = committer.email
114
+ last_commit.commit_time = committer.date.strftime("%Y-%m-%d %H:%M:%S")
115
+
116
+ end
117
+ end
118
+
119
+ # 获取是否有变更
120
+ def git_status
121
+ GitHelper.status(self.path)
122
+ end
123
+
124
+ # 拉代码
125
+ def git_pull
126
+ GitHelper.pull(self.path)
127
+ end
128
+
129
+ # 添加到暂存区
130
+ def git_add_stag
131
+ GitHelper.add_stag(self.path)
132
+ end
133
+
134
+ # 仅暂存指定文件
135
+ def git_add_files(files)
136
+ GitHelper.add_files(self.path, files)
137
+ end
138
+
139
+ # 指定文件是否有改动
140
+ def git_file_changed?(file)
141
+ GitHelper.file_changed?(self.path, file)
142
+ end
143
+
144
+ # 提交代码
145
+ def git_commit(commit_msg = nil)
146
+ GitHelper.commit(self.path, commit_msg)
147
+ end
148
+
149
+ def git_push
150
+ GitHelper.push(self.path, self.branch)
151
+ end
152
+
153
+ def git_push_f
154
+ GitHelper.push_f(self.path, self.branch)
155
+ end
156
+
157
+ def git_push_tags
158
+ GitHelper.push_tags(self.path)
159
+ end
160
+
161
+ # 重置仓库
162
+ def git_reset_hard
163
+ GitHelper.reset_hard(self.path)
164
+ end
165
+
166
+ # 查询变更
167
+ def git_fetch
168
+ GitHelper.fetch(self.path)
169
+ end
170
+
171
+ # 检出分支
172
+ def git_checkout(checkout_branch)
173
+ GitHelper.checkout(self.path, checkout_branch)
174
+ end
175
+
176
+ # 删除本地标签
177
+ def git_tag_delete_local(tag)
178
+ GitHelper.tag_delete_local(self.path, tag)
179
+ end
180
+
181
+ # 删除远端标签
182
+ def git_tag_delete_origin(tag)
183
+ GitHelper.tag_delete_origin(path, tag)
184
+ end
185
+
186
+ # 本地仓库打标签
187
+ def git_tag_local(tag)
188
+ GitHelper.tag_local(self.path, tag)
189
+ end
190
+
191
+ # 远程仓库打标签
192
+ def git_tag_origin(tag)
193
+ GitHelper.tag_origin(self.path, tag)
194
+ end
195
+
196
+ # 丢弃修改并拉取代码
197
+ def discard_pull
198
+ current = Dir.pwd
199
+ Dir.chdir(self.path)
200
+ Build.log.info("丢弃修改并拉取代码")
201
+ git_add_stag
202
+ git_reset_hard
203
+ git_fetch
204
+ git_checkout(branch)
205
+ git_pull
206
+ Dir.chdir(current)
207
+ end
208
+
209
+ # 一条龙服务
210
+ # @return [Boolean] 本次是否真的产生并推送了新提交(无变更返回false)
211
+ def push_code
212
+ Build.log.step("推送产物(#{File.basename(self.path)})")
213
+
214
+ git_pull
215
+ if git_status
216
+ git_add_stag
217
+ git_commit(nil)
218
+ git_push
219
+ true
220
+ else
221
+ false
222
+ end
223
+ end
224
+
225
+ # 清空仓库(保留.git及隐藏文件,与原ls行为一致;Ruby原生实现避免空格文件名被shell切分)
226
+ def clear
227
+ current = Dir.pwd
228
+ Dir.chdir(self.path)
229
+ Build.log.debug("清空仓库(#{File.basename(self.path)})")
230
+ Dir.children('.').each do |entry|
231
+ next if entry.start_with?('.')
232
+ FileUtils.rm_rf(entry)
233
+ end
234
+ Dir.chdir(current)
235
+ end
236
+
237
+ def page_url
238
+ http_url.gsub(".git","")
239
+ end
240
+
241
+ def http_url
242
+ if ssh_url.start_with?"git@"
243
+ repo_url = ssh_url.gsub(":","/")
244
+ repo_url = repo_url.gsub("git@","http://")
245
+ return repo_url
246
+ end
247
+ ssh_url
248
+ end
249
+
250
+ def size
251
+ file_array = Array.new
252
+ FileHelper.recursion_files(self.path, file_array)
253
+
254
+ size = 0
255
+ file_array.each { |file|
256
+ size += File.size(file)
257
+ }
258
+
259
+ @size = size*0.001
260
+ end
261
+
262
+ def size_format
263
+ self.size > 1024 ? "#{(self.size * 0.001).round(1).to_s}MB": "#{self.size.round(0).to_s}KB"
264
+ end
265
+
266
+ private
267
+ # 参数校验
268
+ def __validate
269
+ if ssh_url.to_s.empty? or not ssh_url.to_s.start_with?("git@")
270
+ error = "repo初始化参数ssh_url必须不能为空, 且是ssh地址"
271
+ pusher.push("❌ 打包失败", error)
272
+ Process.exit(-1)
273
+ end
274
+
275
+ if path.to_s.empty?
276
+ error = "目标路径为空, 请先设置路径"
277
+ pusher.push("❌ 打包失败", error)
278
+ Process.exit(-1)
279
+ end
280
+
281
+ end
282
+
283
+ end
284
+ end