pindo 5.18.20 → 5.19.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 +4 -4
- data/lib/pindo/base/git_handler.rb +27 -54
- data/lib/pindo/command/repo/create.rb +1 -1
- data/lib/pindo/command/repo/search.rb +1 -1
- data/lib/pindo/command/utils/installskills.rb +2 -2
- data/lib/pindo/command/utils/renewproj.rb +0 -1
- data/lib/pindo/module/android/android_build_helper.rb +39 -16
- data/lib/pindo/module/android/gradle_helper.rb +8 -14
- data/lib/pindo/module/cert/cert_helper.rb +2 -2
- data/lib/pindo/module/pgyer/pgyerhelper.rb +18 -20
- data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -37
- data/lib/pindo/module/task/model/build/android_build_task.rb +40 -0
- data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +5 -5
- data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +5 -5
- data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +2 -2
- data/lib/pindo/module/task/model/build_task.rb +2 -0
- data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -1
- data/lib/pindo/module/task/model/git/git_tag_task.rb +2 -2
- data/lib/pindo/module/task/model/jps/jps_workflow_message_task.rb +8 -10
- data/lib/pindo/module/task/pindo_task.rb +2 -2
- data/lib/pindo/module/task/task_manager.rb +14 -6
- data/lib/pindo/module/unity/nuget_helper.rb +5 -20
- data/lib/pindo/module/utils/git_repo_helper.rb +40 -45
- data/lib/pindo/module/xcode/ipa_resign_helper.rb +33 -62
- data/lib/pindo/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db02bf00df627b07423ebd53475c538b60a4d5d8d567b2bb81a61390aaae73bc
|
|
4
|
+
data.tar.gz: 5252fb968e7bde86a095a78dce92599c9614061c1da9666e8cf2bea03927c8c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa95f45065b4a8635c4d12c5a4c1d3a95e678df97724103857e055d9088d8f8613fddef8786229163bbd8ad01c87242e396e5b79e1c5f4d92411b61192e3bde1
|
|
7
|
+
data.tar.gz: 931bd858133452f7cc5a3edafc60cbe7c4f56b5d0f8da6d720f24c23f7974cfe2c823e0bbc7e1d1a04bc8f99faa581ef7c52ae99b0676ec6cfc4e407411e3e05
|
|
@@ -86,8 +86,7 @@ module Pindo
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def add_branch(local_repo_dir: nil, branch: nil)
|
|
89
|
-
|
|
90
|
-
current=Dir.pwd
|
|
89
|
+
# 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
|
|
91
90
|
result = false
|
|
92
91
|
|
|
93
92
|
if !local_branch_exists?(local_repo_dir: local_repo_dir, branch: branch)
|
|
@@ -97,13 +96,11 @@ module Pindo
|
|
|
97
96
|
|
|
98
97
|
git_remote!(local_repo_dir, %W(-C #{local_repo_dir} push origin #{branch}:#{branch}))
|
|
99
98
|
|
|
100
|
-
Dir.chdir(current)
|
|
101
99
|
return result
|
|
102
100
|
end
|
|
103
101
|
|
|
104
102
|
def remove_branch(local_repo_dir: nil, branch: nil)
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
# 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
|
|
107
104
|
result = false
|
|
108
105
|
if File.exist?(local_repo_dir)
|
|
109
106
|
current_branch = git!(%W(-C #{local_repo_dir} rev-parse --abbrev-ref HEAD)).strip
|
|
@@ -120,51 +117,40 @@ module Pindo
|
|
|
120
117
|
else
|
|
121
118
|
result = false
|
|
122
119
|
end
|
|
123
|
-
Dir.chdir(current)
|
|
124
120
|
return result
|
|
125
121
|
end
|
|
126
122
|
|
|
127
123
|
|
|
128
124
|
def local_branch_exists?(local_repo_dir: nil, branch: nil)
|
|
129
|
-
|
|
125
|
+
# 命令改带 -C,去掉 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
|
|
130
126
|
result = false
|
|
131
127
|
|
|
132
128
|
if File.exist?(local_repo_dir)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
res_data = Executable.capture_command('git', %W(rev-parse --verify #{branch}), :capture => :out)
|
|
136
|
-
# puts "=====1"
|
|
137
|
-
# puts res_data
|
|
138
|
-
# res_data = git!(%W(-C #{local_repo_dir} --no-pager branch --list origin/#{branch} --no-color -r))
|
|
129
|
+
res_data = Executable.capture_command('git', %W(-C #{local_repo_dir} rev-parse --verify #{branch}), :capture => :out)
|
|
139
130
|
result = !res_data.nil? && !res_data.empty?
|
|
140
131
|
else
|
|
141
132
|
result = false
|
|
142
133
|
end
|
|
143
|
-
Dir.chdir(current)
|
|
144
134
|
return result
|
|
145
135
|
end
|
|
146
136
|
|
|
147
137
|
def remote_branch_exists?(local_repo_dir: nil, branch: nil)
|
|
148
|
-
|
|
138
|
+
# 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
|
|
149
139
|
result = false
|
|
150
140
|
if File.exist?(local_repo_dir)
|
|
151
|
-
Dir.chdir(local_repo_dir)
|
|
152
141
|
res_data = git_remote!(local_repo_dir, %W(-C #{local_repo_dir} ls-remote --heads origin refs/heads/#{branch}))
|
|
153
142
|
result = !res_data.nil? && !res_data.empty?
|
|
154
143
|
else
|
|
155
144
|
result = false
|
|
156
145
|
end
|
|
157
|
-
Dir.chdir(current)
|
|
158
146
|
return result
|
|
159
147
|
end
|
|
160
148
|
|
|
161
149
|
|
|
162
150
|
def add_tag(local_repo_dir: nil, tag_name: nil)
|
|
163
|
-
|
|
164
|
-
current=Dir.pwd
|
|
151
|
+
# 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
|
|
165
152
|
result = false
|
|
166
153
|
if File.exist?(local_repo_dir)
|
|
167
|
-
Dir.chdir(local_repo_dir)
|
|
168
154
|
if !local_tag_exists?(local_repo_dir: local_repo_dir, tag_name: tag_name)
|
|
169
155
|
git!(%W(-C #{local_repo_dir} tag #{tag_name}))
|
|
170
156
|
result = true
|
|
@@ -173,7 +159,6 @@ module Pindo
|
|
|
173
159
|
else
|
|
174
160
|
result = false
|
|
175
161
|
end
|
|
176
|
-
Dir.chdir(current)
|
|
177
162
|
return result
|
|
178
163
|
end
|
|
179
164
|
|
|
@@ -207,11 +192,9 @@ module Pindo
|
|
|
207
192
|
end
|
|
208
193
|
|
|
209
194
|
def remove_tag(local_repo_dir: nil, tag_name: nil)
|
|
210
|
-
|
|
195
|
+
# 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
|
|
211
196
|
result = false
|
|
212
|
-
current=Dir.pwd
|
|
213
197
|
if File.exist?(local_repo_dir)
|
|
214
|
-
Dir.chdir(local_repo_dir)
|
|
215
198
|
if local_tag_exists?(local_repo_dir: local_repo_dir, tag_name: tag_name)
|
|
216
199
|
git!(%W(-C #{local_repo_dir} tag -d #{tag_name}))
|
|
217
200
|
result = true
|
|
@@ -223,7 +206,6 @@ module Pindo
|
|
|
223
206
|
else
|
|
224
207
|
result = false
|
|
225
208
|
end
|
|
226
|
-
Dir.chdir(current)
|
|
227
209
|
return result
|
|
228
210
|
|
|
229
211
|
end
|
|
@@ -253,9 +235,7 @@ module Pindo
|
|
|
253
235
|
end
|
|
254
236
|
|
|
255
237
|
def git_latest_commit_id(local_repo_dir:nil)
|
|
256
|
-
|
|
257
|
-
current=Dir.pwd
|
|
258
|
-
|
|
238
|
+
# 命令已带 -C,无需 Dir.chdir(并发模式下 chdir 会污染全局 cwd)
|
|
259
239
|
unless File.exist?(File::join(local_repo_dir, ".git"))
|
|
260
240
|
return nil
|
|
261
241
|
end
|
|
@@ -263,16 +243,9 @@ module Pindo
|
|
|
263
243
|
commit_id = nil
|
|
264
244
|
|
|
265
245
|
if File.exist?(local_repo_dir)
|
|
266
|
-
Dir.chdir(local_repo_dir)
|
|
267
246
|
current_branch = git!(%W(-C #{local_repo_dir} rev-parse --abbrev-ref HEAD)).strip
|
|
268
|
-
# puts "current_branch: #{current_branch}"
|
|
269
|
-
|
|
270
|
-
# git log -n 1 --pretty=format:"commit %H"
|
|
271
|
-
# format_str = "commit %H"
|
|
272
|
-
# latest_log_info = git!(%W(-C #{current} log -n 1 --pretty=#{format_str} #{current_branch})).strip
|
|
273
247
|
commit_id = git!(%W(-C #{local_repo_dir} rev-parse #{current_branch})).strip
|
|
274
248
|
end
|
|
275
|
-
Dir.chdir(current)
|
|
276
249
|
|
|
277
250
|
return commit_id
|
|
278
251
|
end
|
|
@@ -362,9 +335,11 @@ module Pindo
|
|
|
362
335
|
rescue StandardError => e
|
|
363
336
|
Funlog.instance.fancyinfo_error("仓库#{local_repo_dir}更新失败!")
|
|
364
337
|
raise Informative, e.to_s
|
|
338
|
+
ensure
|
|
339
|
+
# 无论成功失败都还原 cwd,避免异常路径下污染全局工作目录
|
|
340
|
+
Dir.chdir(current)
|
|
365
341
|
end
|
|
366
342
|
|
|
367
|
-
Dir.chdir(current)
|
|
368
343
|
return local_repo_dir
|
|
369
344
|
end
|
|
370
345
|
|
|
@@ -464,12 +439,8 @@ module Pindo
|
|
|
464
439
|
end
|
|
465
440
|
|
|
466
441
|
def git_addpush_repo(path:nil, message:"res", commit_file_params:nil)
|
|
467
|
-
|
|
468
|
-
Dir.chdir(path)
|
|
469
|
-
|
|
470
|
-
# gitee.com 仓库自动去除代理
|
|
442
|
+
# gitee.com 仓库自动去除代理;is_gitee_repo? 命令已带 -C,无需 chdir
|
|
471
443
|
if is_gitee_repo?(local_repo_dir: path)
|
|
472
|
-
Dir.chdir(current)
|
|
473
444
|
return without_proxy { git_addpush_repo_impl(path: path, message: message, commit_file_params: commit_file_params) }
|
|
474
445
|
end
|
|
475
446
|
|
|
@@ -512,10 +483,10 @@ module Pindo
|
|
|
512
483
|
rescue => error
|
|
513
484
|
# puts(error.to_s)
|
|
514
485
|
raise Informative, "\n#{path}\n 仓库失败 !!!"
|
|
486
|
+
ensure
|
|
487
|
+
# 无论成功失败都还原 cwd,避免异常路径下污染全局工作目录
|
|
488
|
+
Dir.chdir(current)
|
|
515
489
|
end
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
Dir.chdir(current)
|
|
519
490
|
end
|
|
520
491
|
|
|
521
492
|
|
|
@@ -548,8 +519,11 @@ module Pindo
|
|
|
548
519
|
return nil
|
|
549
520
|
end
|
|
550
521
|
|
|
551
|
-
#
|
|
552
|
-
|
|
522
|
+
# git!/IO#readpartial 返回的是 ASCII-8BIT 字符串,含非 ASCII 字节(如中文路径、
|
|
523
|
+
# core.quotePath=false 时的原始多字节)会与下游 UTF-8 字面量拼接失败,这里统一
|
|
524
|
+
# 标成 UTF-8 并 scrub 无效字节,避免上层 `puts "...#{file}"` 抛 Encoding::CompatibilityError
|
|
525
|
+
raw_list = files_list.is_a?(String) ? files_list.split("\n") : Array(files_list)
|
|
526
|
+
raw_list.map { |f| f.dup.force_encoding('UTF-8').scrub }.reject(&:empty?)
|
|
553
527
|
rescue => e
|
|
554
528
|
Funlog.instance.fancyinfo_error("获取未提交文件列表失败: #{e.message}")
|
|
555
529
|
nil
|
|
@@ -624,14 +598,13 @@ module Pindo
|
|
|
624
598
|
puts
|
|
625
599
|
|
|
626
600
|
# 将文件列表分行并用红色显示
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
end
|
|
601
|
+
# 防御性编码归一化:git! 返回的是 ASCII-8BIT,与 UTF-8 字面量 " • " 拼接会抛
|
|
602
|
+
# Encoding::CompatibilityError;此处再兜一层,避免其它调用方传入 BINARY 字符串时崩溃
|
|
603
|
+
file_lines = files.is_a?(String) ? files.split("\n") : Array(files)
|
|
604
|
+
file_lines.each do |file|
|
|
605
|
+
next if file.nil? || file.empty?
|
|
606
|
+
safe_file = file.dup.force_encoding('UTF-8').scrub
|
|
607
|
+
puts " • #{safe_file}".red
|
|
635
608
|
end
|
|
636
609
|
|
|
637
610
|
puts
|
|
@@ -159,8 +159,8 @@ module Pindo
|
|
|
159
159
|
Funlog.fancyinfo_start("克隆仓库: #{url}...")
|
|
160
160
|
Funlog.info("仓库 URL: #{url}") if ENV['PINDO_DEBUG']
|
|
161
161
|
|
|
162
|
-
#
|
|
163
|
-
success = system("git clone
|
|
162
|
+
# 克隆仓库(数组参数形式,避免 URL 中的 shell 元字符注入)
|
|
163
|
+
success = system("git", "clone", url, temp_dir, "--quiet", err: File::NULL)
|
|
164
164
|
|
|
165
165
|
unless success && File.exist?(File.join(temp_dir, '.git'))
|
|
166
166
|
Funlog.fancyinfo_error("仓库克隆失败: #{url}")
|
|
@@ -142,7 +142,6 @@ module Pindo
|
|
|
142
142
|
new_project_dir = File.join(File::expand_path(current_dir + "/../") , temp_dir_name)
|
|
143
143
|
|
|
144
144
|
if File.exist?(new_project_dir)
|
|
145
|
-
system 'sh rm -rf ' + new_project_dir
|
|
146
145
|
FileUtils.rm_rf(new_project_dir)
|
|
147
146
|
end
|
|
148
147
|
FileUtils.mkdir(new_project_dir)
|
|
@@ -6,6 +6,7 @@ require_relative 'java_env_helper'
|
|
|
6
6
|
require_relative 'keystore_helper'
|
|
7
7
|
require_relative 'workflow_gradle_injector'
|
|
8
8
|
require 'fileutils'
|
|
9
|
+
require 'find'
|
|
9
10
|
|
|
10
11
|
module Pindo
|
|
11
12
|
class AndroidBuildHelper
|
|
@@ -24,6 +25,10 @@ module Pindo
|
|
|
24
25
|
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
25
26
|
raise ArgumentError, "项目目录不存在" unless File.directory?(project_dir)
|
|
26
27
|
|
|
28
|
+
# 构建前先清理 macOS 自动生成的 .DS_Store
|
|
29
|
+
# 避免其被带入 base / asset pack 模块的 assets/ 导致冲突,或引起 Gradle clean 在遍历删除时出现竞态
|
|
30
|
+
prune_ds_store_files!(project_dir)
|
|
31
|
+
|
|
27
32
|
# 设置 Java Home 环境变量
|
|
28
33
|
Pindo::JavaEnvHelper.setup_java_home_from_project(project_dir)
|
|
29
34
|
|
|
@@ -396,20 +401,19 @@ module Pindo
|
|
|
396
401
|
puts ' AAB 构建前先执行 Gradle clean'
|
|
397
402
|
gradle_clean_resilient!(project_path)
|
|
398
403
|
|
|
399
|
-
|
|
400
|
-
system(
|
|
404
|
+
# 数组参数 + chdir,避免 project_path 拼接进 shell
|
|
405
|
+
system("./gradlew", "--no-daemon", bundle_task, chdir: project_path)
|
|
401
406
|
end
|
|
402
407
|
|
|
403
408
|
# clean 失败(占用、.DS_Store 竞态等)时:停 Daemon → 再删 .DS_Store → 物理删除各模块 build/,与 Gradle clean 产物一致,不中断后续 bundle
|
|
404
409
|
private def gradle_clean_resilient!(project_path)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
return if system(cmd)
|
|
410
|
+
prune_ds_store_files!(project_path)
|
|
411
|
+
return if system("./gradlew", "--no-daemon", "clean", chdir: project_path)
|
|
408
412
|
|
|
409
413
|
puts ' ⚠ Gradle clean 失败,正在自动恢复:--stop、清理 .DS_Store、删除各模块 build/ ...'
|
|
410
|
-
system(
|
|
414
|
+
system("./gradlew", "--stop", chdir: project_path, out: File::NULL, err: File::NULL)
|
|
411
415
|
sleep 0.3
|
|
412
|
-
|
|
416
|
+
prune_ds_store_files!(project_path)
|
|
413
417
|
remove_android_module_build_dirs!(project_path)
|
|
414
418
|
end
|
|
415
419
|
|
|
@@ -445,18 +449,37 @@ module Pindo
|
|
|
445
449
|
puts " ⚠ 物理删除 build 目录时出现异常(将继续尝试 bundle): #{e.message}"
|
|
446
450
|
end
|
|
447
451
|
|
|
448
|
-
# macOS
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
452
|
+
# 清理 macOS 自动生成的 .DS_Store 文件
|
|
453
|
+
# 作用:
|
|
454
|
+
# 1) 避免被带入 base 与 asset pack 模块的 assets/ 造成打包冲突
|
|
455
|
+
# 2) 避免 Gradle clean 遍历删除 build 目录时 Finder 回写 .DS_Store 触发
|
|
456
|
+
# Unable to delete / New files were found 的竞态错误
|
|
457
|
+
private def prune_ds_store_files!(project_path)
|
|
458
|
+
return 0 unless project_path && File.directory?(project_path)
|
|
459
|
+
|
|
460
|
+
count = 0
|
|
461
|
+
Find.find(project_path) do |path|
|
|
462
|
+
next unless File.basename(path) == '.DS_Store'
|
|
463
|
+
next unless File.file?(path)
|
|
464
|
+
|
|
465
|
+
begin
|
|
466
|
+
File.delete(path)
|
|
467
|
+
count += 1
|
|
468
|
+
rescue StandardError
|
|
469
|
+
# 个别文件删除失败不影响整体流程
|
|
470
|
+
next
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
puts " 已清理 #{count} 个 .DS_Store 文件" if count.positive?
|
|
474
|
+
count
|
|
475
|
+
rescue StandardError => e
|
|
476
|
+
puts " ⚠ 清理 .DS_Store 异常:#{e.message}"
|
|
477
|
+
0
|
|
454
478
|
end
|
|
455
479
|
|
|
456
480
|
def build_so_library(project_path)
|
|
457
|
-
# 编译so库 (Thread Safe)
|
|
458
|
-
|
|
459
|
-
system(cmd)
|
|
481
|
+
# 编译so库 (Thread Safe);数组参数 + chdir,避免 project_path 拼接进 shell
|
|
482
|
+
system("./gradlew", "unityLibrary:BuildIl2CppTask", chdir: project_path)
|
|
460
483
|
end
|
|
461
484
|
|
|
462
485
|
def copy_so_files(source_path, target_path)
|
|
@@ -518,18 +518,16 @@ module Pindo
|
|
|
518
518
|
return false
|
|
519
519
|
end
|
|
520
520
|
|
|
521
|
-
# 3. 使用unzip -t测试ZIP
|
|
522
|
-
|
|
523
|
-
system(test_cmd)
|
|
521
|
+
# 3. 使用unzip -t测试ZIP文件完整性(数组参数,避免路径注入)
|
|
522
|
+
system("unzip", "-t", zip_file, out: File::NULL, err: File::NULL)
|
|
524
523
|
rescue => e
|
|
525
524
|
false
|
|
526
525
|
end
|
|
527
526
|
|
|
528
527
|
# 使用curl下载文件
|
|
529
528
|
def download_with_curl(url, output_path)
|
|
530
|
-
#
|
|
531
|
-
|
|
532
|
-
system(cmd)
|
|
529
|
+
# 数组参数形式,避免 URL/路径中的 shell 元字符注入
|
|
530
|
+
system("curl", "-L", "-o", output_path, url)
|
|
533
531
|
rescue => e
|
|
534
532
|
false
|
|
535
533
|
end
|
|
@@ -537,9 +535,8 @@ module Pindo
|
|
|
537
535
|
# 解压Gradle分发包
|
|
538
536
|
def extract_gradle_distribution(zip_file, extract_dir, gradle_version)
|
|
539
537
|
begin
|
|
540
|
-
# 使用unzip
|
|
541
|
-
|
|
542
|
-
if system(extract_cmd)
|
|
538
|
+
# 使用unzip命令解压(数组参数 + chdir,避免路径注入)
|
|
539
|
+
if system("unzip", "-q", zip_file, chdir: File.dirname(extract_dir))
|
|
543
540
|
# 设置执行权限
|
|
544
541
|
gradle_script = File.join(extract_dir, "bin/gradle")
|
|
545
542
|
if File.exist?(gradle_script)
|
|
@@ -577,11 +574,8 @@ module Pindo
|
|
|
577
574
|
|
|
578
575
|
# 使用官方Gradle命令生成wrapper
|
|
579
576
|
def generate_gradle_wrapper_official(project_path, gradle_version)
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
cmd = "gradle wrapper"
|
|
583
|
-
system(cmd)
|
|
584
|
-
end
|
|
577
|
+
# 数组参数 + chdir,避免 shell 解析
|
|
578
|
+
system("gradle", "wrapper", chdir: project_path)
|
|
585
579
|
rescue => e
|
|
586
580
|
false
|
|
587
581
|
end
|
|
@@ -165,9 +165,9 @@ module Pindo
|
|
|
165
165
|
puts "找到 #{identity_ids.size} 个证书:"
|
|
166
166
|
puts identity_ids
|
|
167
167
|
|
|
168
|
-
#
|
|
168
|
+
# 删除证书(数组参数形式,避免 identity_id 拼接进 shell)
|
|
169
169
|
identity_ids.each do |identity_id|
|
|
170
|
-
system
|
|
170
|
+
system("security", "delete-certificate", "-Z", identity_id)
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
# 清理 Provisioning Profiles
|
|
@@ -710,14 +710,10 @@ module Pindo
|
|
|
710
710
|
FileUtils.rm_rf(ipa_file_upload)
|
|
711
711
|
end
|
|
712
712
|
|
|
713
|
-
current_dir = Dir.pwd
|
|
714
713
|
if File.exist?(ipa_base_dir)
|
|
715
|
-
Dir.chdir(ipa_base_dir)
|
|
716
714
|
base_name = File.basename(mac_app_path)
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
system command
|
|
720
|
-
Dir.chdir(current_dir)
|
|
715
|
+
# 数组参数 + chdir,避免 shell 注入与 cwd 污染
|
|
716
|
+
system("zip", "-qry", ipa_file_upload, base_name, chdir: ipa_base_dir)
|
|
721
717
|
end
|
|
722
718
|
end
|
|
723
719
|
if !ipa_file_upload.nil? &&
|
|
@@ -738,19 +734,18 @@ module Pindo
|
|
|
738
734
|
rescue StandardError => e
|
|
739
735
|
|
|
740
736
|
end
|
|
741
|
-
current_project_dir = Dir.pwd
|
|
742
737
|
web_build_name = web_build_name.downcase.strip.gsub(/[\s\-_]/, '')
|
|
743
738
|
web_res_zip_fullname = File.join(web_zip_dir, web_build_name + "_" + web_build_version + ".zip")
|
|
744
739
|
if File.exist?(web_res_zip_fullname)
|
|
745
740
|
FileUtils.rm_rf(web_res_zip_fullname)
|
|
746
741
|
end
|
|
747
742
|
Zip::File.open(web_res_zip_fullname, Zip::File::CREATE) do |zipfile|
|
|
748
|
-
Dir.chdir
|
|
749
|
-
|
|
750
|
-
|
|
743
|
+
Dir.chdir(web_res_path) do
|
|
744
|
+
Dir.glob("**/*").reject {|fn| File.directory?(fn) }.each do |file|
|
|
745
|
+
zipfile.add(file.sub(web_res_path + '/', ''), file)
|
|
746
|
+
end
|
|
751
747
|
end
|
|
752
748
|
end
|
|
753
|
-
Dir.chdir(current_project_dir)
|
|
754
749
|
ipa_file_upload = web_res_zip_fullname
|
|
755
750
|
end
|
|
756
751
|
if !ipa_file_upload.nil? &&
|
|
@@ -767,12 +762,11 @@ module Pindo
|
|
|
767
762
|
FileUtils.rm_rf(windows_zip_fullname)
|
|
768
763
|
end
|
|
769
764
|
|
|
770
|
-
current_project_dir = Dir.pwd
|
|
771
765
|
Zip::File.open(windows_zip_fullname, Zip::File::CREATE) do |zipfile|
|
|
772
|
-
Dir.chdir
|
|
773
|
-
|
|
766
|
+
Dir.chdir(windows_build_dir) do
|
|
767
|
+
zipfile.add(windows_exe_name, windows_exe_name)
|
|
768
|
+
end
|
|
774
769
|
end
|
|
775
|
-
Dir.chdir(current_project_dir)
|
|
776
770
|
ipa_file_upload = windows_zip_fullname
|
|
777
771
|
end
|
|
778
772
|
unless !ipa_file_upload.nil? && File.exist?(ipa_file_upload)
|
|
@@ -800,14 +794,14 @@ module Pindo
|
|
|
800
794
|
end
|
|
801
795
|
|
|
802
796
|
Zip::File.open(server_zipfile_name, Zip::File::CREATE) do |zipfile|
|
|
803
|
-
Dir.chdir
|
|
804
|
-
|
|
805
|
-
|
|
797
|
+
Dir.chdir(server_file_directory) do
|
|
798
|
+
Dir.glob("**/*").reject {|fn| File.directory?(fn) }.each do |file|
|
|
799
|
+
zipfile.add(file.sub(server_file_directory + '/', ''), file)
|
|
800
|
+
end
|
|
806
801
|
end
|
|
807
802
|
end
|
|
808
803
|
|
|
809
804
|
addtach_file = server_zipfile_name
|
|
810
|
-
Dir.chdir current_project_dir
|
|
811
805
|
end
|
|
812
806
|
|
|
813
807
|
puts
|
|
@@ -1539,6 +1533,7 @@ module Pindo
|
|
|
1539
1533
|
if git_cliff_installed
|
|
1540
1534
|
temp_dir = Dir.pwd
|
|
1541
1535
|
Dir.chdir(current_git_root_path)
|
|
1536
|
+
begin
|
|
1542
1537
|
|
|
1543
1538
|
# 检查 HEAD 是否有 tag
|
|
1544
1539
|
head_tag = `git describe --exact-match --tags HEAD 2>/dev/null`.strip
|
|
@@ -1605,7 +1600,10 @@ module Pindo
|
|
|
1605
1600
|
description = "版本更新"
|
|
1606
1601
|
end
|
|
1607
1602
|
|
|
1608
|
-
|
|
1603
|
+
ensure
|
|
1604
|
+
# ensure 保证异常路径也还原 cwd,避免污染全局工作目录
|
|
1605
|
+
Dir.chdir(temp_dir)
|
|
1606
|
+
end
|
|
1609
1607
|
else
|
|
1610
1608
|
# git-cliff 未安装,提示用户并退出
|
|
1611
1609
|
puts "\n\e[31m错误: git-cliff 未安装\e[0m"
|
|
@@ -58,43 +58,7 @@ module Pindo
|
|
|
58
58
|
build_android_project
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
# 搜索 APK 和 AAB 文件
|
|
63
|
-
search_paths = []
|
|
64
|
-
search_paths.concat(TaskConfig::BUILD_OUTPUT_PATTERNS[:apk].map { |p| File.join(@project_path, p) })
|
|
65
|
-
search_paths.concat(TaskConfig::BUILD_OUTPUT_PATTERNS[:aab].map { |p| File.join(@project_path, p) })
|
|
66
|
-
|
|
67
|
-
package_files = []
|
|
68
|
-
search_paths.each do |pattern|
|
|
69
|
-
package_files.concat(Dir.glob(pattern))
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# 过滤测试包和未签名包
|
|
73
|
-
package_files.reject! do |f|
|
|
74
|
-
basename = File.basename(f).downcase
|
|
75
|
-
TaskConfig::EXCLUDED_PATTERNS.any? { |pattern| basename.include?(pattern) }
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
if package_files.any?
|
|
79
|
-
# 优先返回 AAB,其次 APK
|
|
80
|
-
aab_files = package_files.select { |f| f.end_with?(".aab") }
|
|
81
|
-
apk_files = package_files.select { |f| f.end_with?(".apk") }
|
|
82
|
-
|
|
83
|
-
latest_package = if aab_files.any?
|
|
84
|
-
aab_files.max_by { |f| File.mtime(f) }
|
|
85
|
-
elsif apk_files.any?
|
|
86
|
-
apk_files.max_by { |f| File.mtime(f) }
|
|
87
|
-
else
|
|
88
|
-
package_files.max_by { |f| File.mtime(f) }
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
puts " 找到 Android 包文件: #{latest_package}"
|
|
92
|
-
latest_package
|
|
93
|
-
else
|
|
94
|
-
puts " 警告: 未找到 APK/AAB 文件"
|
|
95
|
-
nil
|
|
96
|
-
end
|
|
97
|
-
end
|
|
61
|
+
# find_output 已上移到 AndroidBuildTask 基类(dev/adhoc/gplay 共用)
|
|
98
62
|
|
|
99
63
|
private
|
|
100
64
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative '../build_task'
|
|
2
|
+
require_relative '../../task_config'
|
|
2
3
|
|
|
3
4
|
module Pindo
|
|
4
5
|
module TaskSystem
|
|
@@ -12,6 +13,45 @@ module Pindo
|
|
|
12
13
|
def required_resources
|
|
13
14
|
(super || []) + [{ type: :gradle, directory: @project_path }]
|
|
14
15
|
end
|
|
16
|
+
|
|
17
|
+
# 查找构建产物(APK/AAB),三个子类(dev/adhoc/gplay)共用同一套 glob 逻辑
|
|
18
|
+
# 搜索路径与过滤规则与 pindo jps upload 查找 Android 包保持一致;优先 AAB,其次 APK
|
|
19
|
+
def find_output
|
|
20
|
+
search_paths = []
|
|
21
|
+
search_paths.concat(TaskConfig::BUILD_OUTPUT_PATTERNS[:apk].map { |p| File.join(@project_path, p) })
|
|
22
|
+
search_paths.concat(TaskConfig::BUILD_OUTPUT_PATTERNS[:aab].map { |p| File.join(@project_path, p) })
|
|
23
|
+
|
|
24
|
+
package_files = []
|
|
25
|
+
search_paths.each do |pattern|
|
|
26
|
+
package_files.concat(Dir.glob(pattern))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# 过滤测试包和未签名包
|
|
30
|
+
package_files.reject! do |f|
|
|
31
|
+
basename = File.basename(f).downcase
|
|
32
|
+
TaskConfig::EXCLUDED_PATTERNS.any? { |pattern| basename.include?(pattern) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if package_files.any?
|
|
36
|
+
# 优先返回 AAB,其次 APK
|
|
37
|
+
aab_files = package_files.select { |f| f.end_with?(".aab") }
|
|
38
|
+
apk_files = package_files.select { |f| f.end_with?(".apk") }
|
|
39
|
+
|
|
40
|
+
latest_package = if aab_files.any?
|
|
41
|
+
aab_files.max_by { |f| File.mtime(f) }
|
|
42
|
+
elsif apk_files.any?
|
|
43
|
+
apk_files.max_by { |f| File.mtime(f) }
|
|
44
|
+
else
|
|
45
|
+
package_files.max_by { |f| File.mtime(f) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
puts " 找到 Android 包文件: #{latest_package}"
|
|
49
|
+
latest_package
|
|
50
|
+
else
|
|
51
|
+
# 构建已执行但产物缺失,视为失败,不静默返回 nil
|
|
52
|
+
raise Informative, "构建完成但未找到 APK/AAB 文件"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
15
55
|
end
|
|
16
56
|
end
|
|
17
57
|
end
|
|
@@ -41,7 +41,7 @@ module Pindo
|
|
|
41
41
|
|
|
42
42
|
#准备构建
|
|
43
43
|
def prepare_build
|
|
44
|
-
Dir.chdir(@project_path)
|
|
44
|
+
# Dir.chdir(@project_path) # Removed for thread safety(下游均传入绝对路径)
|
|
45
45
|
|
|
46
46
|
# 0. 清理 Firebase Shell Script
|
|
47
47
|
cleanup_firebase_shell
|
|
@@ -98,8 +98,8 @@ module Pindo
|
|
|
98
98
|
puts " 找到 IPA 文件: #{latest_ipa}"
|
|
99
99
|
latest_ipa
|
|
100
100
|
else
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
# 构建已执行但产物缺失,视为失败,不静默返回 nil
|
|
102
|
+
raise Informative, "构建完成但未找到 IPA 文件 (#{build_path})"
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
|
|
@@ -289,7 +289,7 @@ module Pindo
|
|
|
289
289
|
puts " 处理 Quark/Swark..."
|
|
290
290
|
|
|
291
291
|
require 'pindo/module/xcode/xcode_swark_helper'
|
|
292
|
-
Dir.chdir(@project_path)
|
|
292
|
+
# Dir.chdir(@project_path) # Removed for thread safety(下游均传入绝对路径)
|
|
293
293
|
|
|
294
294
|
if xcode_build_type.include?("quark")
|
|
295
295
|
Pindo::XcodeSwarkHelper.quark_run(
|
|
@@ -377,7 +377,7 @@ module Pindo
|
|
|
377
377
|
|
|
378
378
|
# 编译 iOS 工程
|
|
379
379
|
def build_ios_project
|
|
380
|
-
Dir.chdir(@project_path)
|
|
380
|
+
# Dir.chdir(@project_path) # Removed for thread safety(下游均传入绝对路径)
|
|
381
381
|
|
|
382
382
|
# 使用 XcodeBuildHelper 进行构建
|
|
383
383
|
ipa_file = Pindo::XcodeBuildHelper.build_project(
|
|
@@ -40,7 +40,7 @@ module Pindo
|
|
|
40
40
|
|
|
41
41
|
#准备构建
|
|
42
42
|
def prepare_build
|
|
43
|
-
Dir.chdir(@project_path)
|
|
43
|
+
# Dir.chdir(@project_path) # Removed for thread safety(下游均传入绝对路径)
|
|
44
44
|
|
|
45
45
|
# 0. 清理 Firebase Shell Script
|
|
46
46
|
cleanup_firebase_shell
|
|
@@ -81,8 +81,8 @@ module Pindo
|
|
|
81
81
|
puts " 找到 IPA 文件: #{latest_ipa}"
|
|
82
82
|
latest_ipa
|
|
83
83
|
else
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
# 构建已执行但产物缺失,视为失败,不静默返回 nil
|
|
85
|
+
raise Informative, "构建完成但未找到 IPA 文件 (#{build_path})"
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -262,7 +262,7 @@ module Pindo
|
|
|
262
262
|
puts " 处理 Quark/Swark..."
|
|
263
263
|
|
|
264
264
|
require 'pindo/module/xcode/xcode_swark_helper'
|
|
265
|
-
Dir.chdir(@project_path)
|
|
265
|
+
# Dir.chdir(@project_path) # Removed for thread safety(下游均传入绝对路径)
|
|
266
266
|
|
|
267
267
|
if xcode_build_type.include?("quark")
|
|
268
268
|
Pindo::XcodeSwarkHelper.quark_run(
|
|
@@ -350,7 +350,7 @@ module Pindo
|
|
|
350
350
|
|
|
351
351
|
# 编译 iOS 工程
|
|
352
352
|
def build_ios_project
|
|
353
|
-
Dir.chdir(@project_path)
|
|
353
|
+
# Dir.chdir(@project_path) # Removed for thread safety(下游均传入绝对路径)
|
|
354
354
|
|
|
355
355
|
# 使用 XcodeBuildHelper 进行构建
|
|
356
356
|
ipa_file = Pindo::XcodeBuildHelper.build_project(
|
|
@@ -13,7 +13,7 @@ module Pindo
|
|
|
13
13
|
:git_commit
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
# 重试配置:失败后重试
|
|
16
|
+
# 重试配置:失败后重试 2 次(共 3 次尝试)
|
|
17
17
|
def self.default_retry_count
|
|
18
18
|
2
|
|
19
19
|
end
|
|
@@ -69,6 +69,16 @@ module Pindo
|
|
|
69
69
|
root_dir = @git_root_path
|
|
70
70
|
git_repo_helper = Pindo::GitRepoHelper.share_instance
|
|
71
71
|
|
|
72
|
+
# 0. 先拉取远程 tag,确保本地 tag 列表是最新的
|
|
73
|
+
# 否则本地 tag 落后于远程时(如远程被 CI/其他机器打了更高 tag),
|
|
74
|
+
# 后续基于本地 tag 计算的版本会偏低,导致本该新建的版本 tag 与远程已有 tag 冲突而被漏打。
|
|
75
|
+
# 尽力而为:网络不可达时仅告警,用本地 tag 降级计算,不阻断构建。
|
|
76
|
+
begin
|
|
77
|
+
Pindo::GitHandler.git_remote!(root_dir, %W(-C #{root_dir} fetch --tags --force origin))
|
|
78
|
+
rescue => e
|
|
79
|
+
Funlog.instance.fancyinfo_warning("拉取远程 tag 失败,将使用本地 tag 计算版本: #{e.message.lines.first&.strip}")
|
|
80
|
+
end
|
|
81
|
+
|
|
72
82
|
# 1. 如果 fixed_version 为 nil,尝试从 HEAD 的 tag 获取版本
|
|
73
83
|
# 必须在 check_gitignore 之前检查,因为 check_gitignore 可能创建提交导致 HEAD 移动
|
|
74
84
|
if (@fixed_version.nil? || @fixed_version.empty?) &&
|
|
@@ -12,7 +12,7 @@ module Pindo
|
|
|
12
12
|
:git_tag
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# 重试配置:失败后重试
|
|
15
|
+
# 重试配置:失败后重试 2 次(共 3 次尝试)
|
|
16
16
|
def self.default_retry_count
|
|
17
17
|
2
|
|
18
18
|
end
|
|
@@ -39,7 +39,7 @@ module Pindo
|
|
|
39
39
|
protected
|
|
40
40
|
|
|
41
41
|
def do_work
|
|
42
|
-
# 检查是否为 Git
|
|
42
|
+
# 检查是否为 Git 仓库,如果不是则直接成功返回(非 Git 仓库目录下跳过打 tag)
|
|
43
43
|
unless valid_git_repository?
|
|
44
44
|
Funlog.instance.fancyinfo_warning("当前目录不是Git仓库,跳过Git打Tag")
|
|
45
45
|
return {
|
|
@@ -15,6 +15,11 @@ module Pindo
|
|
|
15
15
|
:jps_workflow_message
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# 工作流消息失败重试 1 次(覆盖 JPSTask 默认的重试 3 次)
|
|
19
|
+
def self.default_retry_count
|
|
20
|
+
1
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
# 初始化工作流消息发送任务
|
|
19
24
|
# @param options [Hash] 选项
|
|
20
25
|
# @option options [String] :git_commit_id Git commit SHA(可选,从依赖任务获取)
|
|
@@ -70,17 +75,10 @@ module Pindo
|
|
|
70
75
|
workflow_id: @workflow_id
|
|
71
76
|
}
|
|
72
77
|
rescue => e
|
|
73
|
-
#
|
|
74
|
-
puts "
|
|
78
|
+
# 工作流消息是终端任务,失败需明确报错中断(不再伪造 success)
|
|
79
|
+
puts " ❌ JPS 工作流消息发送失败: #{e.message}"
|
|
75
80
|
puts " ⚠️ 错误堆栈: #{e.backtrace.first(3).join("\n ")}" if ENV['PINDO_DEBUG'] && e.backtrace
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# 消息发送失败不抛出异常,返回成功但带警告
|
|
79
|
-
{
|
|
80
|
-
success: true,
|
|
81
|
-
warning: e.message,
|
|
82
|
-
git_commit_id: @git_commit_id
|
|
83
|
-
}
|
|
81
|
+
raise Informative, "JPS 工作流消息发送失败: #{e.message}"
|
|
84
82
|
end
|
|
85
83
|
end
|
|
86
84
|
|
|
@@ -118,12 +118,19 @@ module Pindo
|
|
|
118
118
|
# 输出执行摘要
|
|
119
119
|
@reporter.print_execution_summary
|
|
120
120
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
# 失败闸门:FAILED / CANCELLED / 残留 PENDING(依赖阻塞等导致从未执行)都视为
|
|
122
|
+
# 「未成功完成」,统一抛异常确保进程非零退出码,避免任务没跑成却静默成功
|
|
123
|
+
completed = @queue.completed_snapshot
|
|
124
|
+
failed_tasks = completed.select { |t| t.status == TaskStatus::FAILED }
|
|
125
|
+
cancelled_tasks = completed.select { |t| t.status == TaskStatus::CANCELLED }
|
|
126
|
+
unfinished_tasks = @queue.pending_snapshot
|
|
127
|
+
|
|
128
|
+
if failed_tasks.any? || cancelled_tasks.any? || unfinished_tasks.any?
|
|
129
|
+
parts = []
|
|
130
|
+
parts << "失败: #{failed_tasks.map(&:name).join(', ')}" if failed_tasks.any?
|
|
131
|
+
parts << "取消: #{cancelled_tasks.map(&:name).join(', ')}" if cancelled_tasks.any?
|
|
132
|
+
parts << "未执行: #{unfinished_tasks.map(&:name).join(', ')}" if unfinished_tasks.any?
|
|
133
|
+
raise Informative, "存在未成功完成的任务 —— #{parts.join(';')}"
|
|
127
134
|
end
|
|
128
135
|
end
|
|
129
136
|
|
|
@@ -138,6 +145,7 @@ module Pindo
|
|
|
138
145
|
completed: completed.count,
|
|
139
146
|
success: completed.count { |t| t.status == TaskStatus::SUCCESS },
|
|
140
147
|
failed: completed.count { |t| t.status == TaskStatus::FAILED },
|
|
148
|
+
cancelled: completed.count { |t| t.status == TaskStatus::CANCELLED },
|
|
141
149
|
tasks: (pending + completed).map do |task|
|
|
142
150
|
{
|
|
143
151
|
id: task.id,
|
|
@@ -505,9 +505,8 @@ module Pindo
|
|
|
505
505
|
puts " 使用工具: nuget pack"
|
|
506
506
|
puts
|
|
507
507
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
success = system(cmd)
|
|
508
|
+
# 数组参数 + chdir,避免路径中的 shell 元字符注入
|
|
509
|
+
success = system("nuget", "pack", File.basename(nuspec_file), "-OutputDirectory", output_dir, chdir: package_dir)
|
|
511
510
|
|
|
512
511
|
unless success
|
|
513
512
|
raise Informative, "NuGet 打包失败,请检查 .nuspec 文件格式"
|
|
@@ -573,10 +572,8 @@ module Pindo
|
|
|
573
572
|
begin
|
|
574
573
|
# 查找是否存在与 nuspec 版本匹配的 tag(支持 v1.1.3, V1.1.3, 1.1.3 等格式)
|
|
575
574
|
matching_tag = nil
|
|
576
|
-
temp_dir = Dir.pwd
|
|
577
|
-
Dir.chdir(git_root)
|
|
578
575
|
|
|
579
|
-
# 获取所有 tags
|
|
576
|
+
# 获取所有 tags(命令已带 -C,无需 chdir)
|
|
580
577
|
all_tags = Pindo::GitHandler.git!(%W(-C #{git_root} tag -l)).split("\n")
|
|
581
578
|
|
|
582
579
|
# 查找匹配当前版本的 tag(不区分大小写,支持 v 前缀)
|
|
@@ -585,17 +582,12 @@ module Pindo
|
|
|
585
582
|
tag_version.downcase == nuspec_version.downcase
|
|
586
583
|
end
|
|
587
584
|
|
|
588
|
-
Dir.chdir(temp_dir)
|
|
589
|
-
|
|
590
585
|
# 确定获取哪个范围的 commits
|
|
591
586
|
git_range = if matching_tag
|
|
592
587
|
# 找到匹配的 tag,获取前一个 tag 到这个 tag 的 commits
|
|
593
588
|
puts "📝 找到版本 tag: #{matching_tag},生成该版本的 release notes..."
|
|
594
589
|
|
|
595
|
-
|
|
596
|
-
Dir.chdir(git_root)
|
|
597
|
-
|
|
598
|
-
# 获取所有 tags 并按版本号排序
|
|
590
|
+
# 获取所有 tags 并按版本号排序(命令已带 -C,无需 chdir)
|
|
599
591
|
all_tags = Pindo::GitHandler.git!(%W(-C #{git_root} tag -l)).split("\n")
|
|
600
592
|
sorted_tags = all_tags.sort_by do |tag|
|
|
601
593
|
version_str = tag.gsub(/^(v|V|release[\s_-]*)/i, '')
|
|
@@ -609,8 +601,6 @@ module Pindo
|
|
|
609
601
|
prev_tag = sorted_tags[tag_index - 1]
|
|
610
602
|
end
|
|
611
603
|
|
|
612
|
-
Dir.chdir(temp_dir)
|
|
613
|
-
|
|
614
604
|
if prev_tag
|
|
615
605
|
"#{prev_tag}..#{matching_tag}"
|
|
616
606
|
else
|
|
@@ -635,10 +625,7 @@ module Pindo
|
|
|
635
625
|
end
|
|
636
626
|
end
|
|
637
627
|
|
|
638
|
-
# 获取 commit messages(包含完整的 body)
|
|
639
|
-
temp_dir = Dir.pwd
|
|
640
|
-
Dir.chdir(git_root)
|
|
641
|
-
|
|
628
|
+
# 获取 commit messages(包含完整的 body;命令已带 -C,无需 chdir)
|
|
642
629
|
# 使用特殊分隔符来区分不同的 commits
|
|
643
630
|
separator = "---COMMIT-SEPARATOR---"
|
|
644
631
|
commits_raw = Pindo::GitHandler.git!(%W(-C #{git_root} log #{git_range} --pretty=format:%B#{separator}))
|
|
@@ -646,8 +633,6 @@ module Pindo
|
|
|
646
633
|
# 按分隔符拆分成单个 commits
|
|
647
634
|
commits = commits_raw.split(separator).map(&:strip).reject(&:empty?)
|
|
648
635
|
|
|
649
|
-
Dir.chdir(temp_dir)
|
|
650
|
-
|
|
651
636
|
# 过滤只保留符合规范的 commits(支持带 scope 和不带 scope 格式)
|
|
652
637
|
# 匹配格式: feat:, feat(scope):, fix:, fix(api): 等
|
|
653
638
|
conventional_commit_regex = /^(feat|fix|docs|doc|perf|refactor|style|test|chore|revert)(\([^)]+\))?:/i
|
|
@@ -330,53 +330,34 @@ module Pindo
|
|
|
330
330
|
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
331
331
|
raise ArgumentError, "tag名称不能为空" if tag_name.nil? || tag_name.empty?
|
|
332
332
|
|
|
333
|
+
local_exists = Pindo::GitHandler.local_tag_exists?(local_repo_dir: project_dir, tag_name: tag_name)
|
|
334
|
+
|
|
335
|
+
# 建本地 tag 是纯本地操作,不依赖远程可达,网络故障也能留下本地 tag;
|
|
336
|
+
# 删远程残留与推送一律交给 push_tag_with_verify,推送失败时本地 tag 仍保留,可事后手动补推。
|
|
333
337
|
case tag_type
|
|
334
338
|
when Pindo::CreateTagType::NONE
|
|
339
|
+
# 不创建 tag
|
|
335
340
|
Funlog.instance.fancyinfo_success("跳过创建 Tag")
|
|
336
|
-
|
|
337
|
-
when Pindo::CreateTagType::RECREATE
|
|
338
|
-
#
|
|
339
|
-
if Pindo::GitHandler.remote_tag_exists?(local_repo_dir: project_dir, tag_name: tag_name)
|
|
340
|
-
Funlog.instance.fancyinfo_update("删除远程 Tag: #{tag_name}")
|
|
341
|
-
Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin :refs/tags/#{tag_name}))
|
|
342
|
-
end
|
|
343
|
-
if Pindo::GitHandler.local_tag_exists?(local_repo_dir: project_dir, tag_name: tag_name)
|
|
344
|
-
Funlog.instance.fancyinfo_update("删除本地 Tag: #{tag_name}")
|
|
345
|
-
Pindo::GitHandler.git!(%W(-C #{project_dir} tag -d #{tag_name}))
|
|
346
|
-
end
|
|
347
|
-
when Pindo::CreateTagType::NEW
|
|
348
|
-
local_exists = Pindo::GitHandler.local_tag_exists?(local_repo_dir: project_dir, tag_name: tag_name)
|
|
349
|
-
remote_exists = Pindo::GitHandler.remote_tag_exists?(local_repo_dir: project_dir, tag_name: tag_name)
|
|
350
|
-
|
|
351
|
-
# 如果 tag 在 HEAD 上,不需要重新创建
|
|
341
|
+
nil
|
|
342
|
+
when Pindo::CreateTagType::NEW, Pindo::CreateTagType::RECREATE
|
|
343
|
+
# 本地 tag 已在 HEAD:无需重建,仅确保推送到远程(RECREATE 在 HEAD 原地重建也是空转,同样适用)
|
|
352
344
|
if local_exists && Pindo::GitHandler.is_tag_at_head?(git_root_dir: project_dir, tag_name: tag_name)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
Funlog.instance.
|
|
345
|
+
push_tag_with_verify(project_dir: project_dir, tag_name: tag_name)
|
|
346
|
+
tag_name
|
|
347
|
+
else
|
|
348
|
+
# 本地 tag 不存在或不在 HEAD:删旧重建后推送
|
|
349
|
+
if local_exists
|
|
350
|
+
Funlog.instance.fancyinfo_update("删除本地旧 Tag: #{tag_name}")
|
|
351
|
+
Pindo::GitHandler.git!(%W(-C #{project_dir} tag -d #{tag_name}))
|
|
359
352
|
end
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if remote_exists
|
|
365
|
-
Funlog.instance.fancyinfo_update("删除远程 Tag: #{tag_name}")
|
|
366
|
-
Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin :refs/tags/#{tag_name}))
|
|
367
|
-
end
|
|
368
|
-
if local_exists
|
|
369
|
-
Funlog.instance.fancyinfo_update("删除本地 Tag: #{tag_name}")
|
|
370
|
-
Pindo::GitHandler.git!(%W(-C #{project_dir} tag -d #{tag_name}))
|
|
353
|
+
Pindo::GitHandler.git!(%W(-C #{project_dir} tag #{tag_name}))
|
|
354
|
+
Funlog.instance.fancyinfo_success("已创建本地 Tag: #{tag_name}")
|
|
355
|
+
push_tag_with_verify(project_dir: project_dir, tag_name: tag_name)
|
|
356
|
+
tag_name
|
|
371
357
|
end
|
|
372
|
-
|
|
358
|
+
else
|
|
359
|
+
raise ArgumentError, "未知的 tag_type: #{tag_type}"
|
|
373
360
|
end
|
|
374
|
-
|
|
375
|
-
# 创建并推送 tag
|
|
376
|
-
Pindo::GitHandler.git!(%W(-C #{project_dir} tag #{tag_name}))
|
|
377
|
-
push_tag_with_verify(project_dir: project_dir, tag_name: tag_name)
|
|
378
|
-
Funlog.instance.fancyinfo_success("创建并推送 Tag: #{tag_name}")
|
|
379
|
-
tag_name
|
|
380
361
|
end
|
|
381
362
|
|
|
382
363
|
# 推送 tag 到远程并校验是否真正推送成功,失败则重试
|
|
@@ -393,23 +374,37 @@ module Pindo
|
|
|
393
374
|
loop do
|
|
394
375
|
attempt += 1
|
|
395
376
|
begin
|
|
377
|
+
# 远程若残留指向不同 commit 的同名 tag,先删除再推送,避免 non-fast-forward 被拒
|
|
378
|
+
remote_commit = Pindo::GitHandler.remote_tag_commit(local_repo_dir: project_dir, tag_name: tag_name)
|
|
379
|
+
if remote_commit == local_commit
|
|
380
|
+
Funlog.instance.fancyinfo_success("已确认 Tag 推送到远程: #{tag_name}")
|
|
381
|
+
return true
|
|
382
|
+
elsif remote_commit
|
|
383
|
+
Funlog.instance.fancyinfo_update("删除远程残留 Tag(远程 #{remote_commit[0..7]} ≠ 本地 #{local_commit[0..7]}): #{tag_name}")
|
|
384
|
+
Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin :refs/tags/#{tag_name}))
|
|
385
|
+
end
|
|
386
|
+
|
|
396
387
|
Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin #{tag_name}))
|
|
397
388
|
rescue => e
|
|
398
389
|
first_line = e.message.lines.first&.strip
|
|
399
390
|
Funlog.instance.fancyinfo_warning("推送 Tag 失败(第 #{attempt}/#{max_retries} 次): #{first_line}")
|
|
400
391
|
end
|
|
401
392
|
|
|
402
|
-
# 校验远程 tag 指向的 commit
|
|
403
|
-
remote_commit =
|
|
393
|
+
# 校验远程 tag 指向的 commit 是否与本地一致,确认推送成功(探测失败按未确认处理,不中断重试)
|
|
394
|
+
remote_commit = begin
|
|
395
|
+
Pindo::GitHandler.remote_tag_commit(local_repo_dir: project_dir, tag_name: tag_name)
|
|
396
|
+
rescue
|
|
397
|
+
nil
|
|
398
|
+
end
|
|
404
399
|
if remote_commit && remote_commit == local_commit
|
|
405
400
|
Funlog.instance.fancyinfo_success("已确认 Tag 推送到远程: #{tag_name}")
|
|
406
401
|
return true
|
|
407
|
-
elsif remote_commit
|
|
408
|
-
Funlog.instance.fancyinfo_warning("远程 Tag 指向不同 commit(远程 #{remote_commit[0..7]} ≠ 本地 #{local_commit[0..7]}),疑似残留旧 Tag: #{tag_name}")
|
|
409
402
|
end
|
|
410
403
|
|
|
411
404
|
if attempt >= max_retries
|
|
412
|
-
|
|
405
|
+
# 本地 tag 已创建,仅远程未确认 —— 保留本地 tag,提示可手动补推
|
|
406
|
+
Funlog.instance.fancyinfo_warning("本地 Tag 已创建并保留,可稍后手动执行: git push origin #{tag_name}")
|
|
407
|
+
raise Informative, "Tag 推送失败,已重试 #{max_retries} 次仍未在远程确认: #{tag_name}(本地 Tag 已保留,可手动补推)"
|
|
413
408
|
end
|
|
414
409
|
|
|
415
410
|
Funlog.instance.fancyinfo_update("远程未确认 Tag,#{attempt}s 后重试推送: #{tag_name}")
|
|
@@ -86,10 +86,10 @@ module Pindo
|
|
|
86
86
|
tmp_dir = File.join(ipa_dir, tmp_time)
|
|
87
87
|
FileUtils.mkdir(tmp_dir) unless File.exist?(tmp_dir)
|
|
88
88
|
|
|
89
|
-
# 解压 IPA
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
# 解压 IPA(数组参数避免注入;失败立即报错,不静默继续)
|
|
90
|
+
unless system("unzip", "-q", resign_ipa_full_name, "-d", tmp_dir)
|
|
91
|
+
raise Informative, "IPA 解压失败: #{resign_ipa_full_name}"
|
|
92
|
+
end
|
|
93
93
|
|
|
94
94
|
payload_path = File.join(tmp_dir, "Payload")
|
|
95
95
|
if File.exist?(payload_path)
|
|
@@ -129,14 +129,13 @@ module Pindo
|
|
|
129
129
|
# 修改 Info.plist 中的 Bundle ID
|
|
130
130
|
modify_ipa_info_plist(modify_content_path: modify_content_path, bundle_id: new_bundle_id)
|
|
131
131
|
|
|
132
|
-
# 重新打包 IPA
|
|
133
|
-
current_dir = Dir.pwd
|
|
132
|
+
# 重新打包 IPA(数组参数 + chdir,避免 shell;* 由 Ruby 展开;失败立即报错)
|
|
134
133
|
if File.exist?(payload_path)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
# 排除点文件,对齐原 shell `*` 语义(避免把 .DS_Store 等打进 IPA 根目录)
|
|
135
|
+
entries = Dir.children(tmp_dir).reject { |e| e.start_with?('.') }
|
|
136
|
+
unless system("zip", "-qry", resign_ipa_full_name, *entries, chdir: tmp_dir)
|
|
137
|
+
raise Informative, "IPA 重新打包失败: #{resign_ipa_full_name}"
|
|
138
|
+
end
|
|
140
139
|
end
|
|
141
140
|
|
|
142
141
|
# 清理临时目录
|
|
@@ -160,24 +159,21 @@ module Pindo
|
|
|
160
159
|
|
|
161
160
|
# 确保 PlistBuddy 可用
|
|
162
161
|
unless File.exist?("/usr/local/bin/PlistBuddy")
|
|
163
|
-
|
|
164
|
-
system command
|
|
162
|
+
system("ln", "-s", "/usr/libexec/PlistBuddy", "/usr/local/bin/PlistBuddy")
|
|
165
163
|
end
|
|
166
164
|
|
|
167
|
-
# 读取旧的 Bundle ID
|
|
168
|
-
old_bundle_id_command = '/usr/local/bin/PlistBuddy -c "Print :CFBundleIdentifier" ' + main_info_plist
|
|
169
|
-
puts old_bundle_id_command
|
|
165
|
+
# 读取旧的 Bundle ID(数组参数,避免路径拼接进 shell)
|
|
170
166
|
old_bundle_id = ""
|
|
171
|
-
IO.popen(
|
|
172
|
-
old_bundle_id = old_bundle_id.strip
|
|
167
|
+
IO.popen(["/usr/local/bin/PlistBuddy", "-c", "Print :CFBundleIdentifier", main_info_plist]) { |f| old_bundle_id = f.gets }
|
|
168
|
+
old_bundle_id = old_bundle_id.to_s.strip
|
|
173
169
|
puts "旧 Bundle ID: #{old_bundle_id}"
|
|
174
170
|
|
|
175
|
-
# 修改主应用的 Bundle ID
|
|
171
|
+
# 修改主应用的 Bundle ID(失败立即报错)
|
|
176
172
|
new_main_bundle_id = bundle_id
|
|
177
173
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
unless system("/usr/local/bin/PlistBuddy", "-c", "Set :CFBundleIdentifier #{new_main_bundle_id}", main_info_plist)
|
|
175
|
+
raise Informative, "修改主应用 Bundle ID 失败: #{main_info_plist}"
|
|
176
|
+
end
|
|
181
177
|
|
|
182
178
|
# 如果 Bundle ID 发生变化,更新 URL Schemes
|
|
183
179
|
if old_bundle_id != new_main_bundle_id
|
|
@@ -193,10 +189,8 @@ module Pindo
|
|
|
193
189
|
appex_path = File.join(plugin_path, file)
|
|
194
190
|
tmp_info_plist = File.join(appex_path, "Info.plist")
|
|
195
191
|
|
|
196
|
-
old_bundle_id_command = '/usr/local/bin/PlistBuddy -c "Print :CFBundleIdentifier" ' + tmp_info_plist
|
|
197
|
-
puts old_bundle_id_command
|
|
198
192
|
old_plugin_bundle_id = ""
|
|
199
|
-
IO.popen(
|
|
193
|
+
IO.popen(["/usr/local/bin/PlistBuddy", "-c", "Print :CFBundleIdentifier", tmp_info_plist]) { |f| old_plugin_bundle_id = f.gets }
|
|
200
194
|
puts "old_bundle_id = #{old_plugin_bundle_id}"
|
|
201
195
|
|
|
202
196
|
# 根据插件类型修改 Bundle ID
|
|
@@ -219,9 +213,9 @@ module Pindo
|
|
|
219
213
|
plugin_types.each do |suffix|
|
|
220
214
|
if old_bundle_id.include?(suffix)
|
|
221
215
|
new_plugin_bundle_id = new_base_bundle_id + suffix
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
216
|
+
unless system("/usr/local/bin/PlistBuddy", "-c", "Set :CFBundleIdentifier #{new_plugin_bundle_id}", plist_path)
|
|
217
|
+
puts " ⚠ 修改插件 Bundle ID 失败: #{plist_path}"
|
|
218
|
+
end
|
|
225
219
|
break
|
|
226
220
|
end
|
|
227
221
|
end
|
|
@@ -322,49 +316,26 @@ module Pindo
|
|
|
322
316
|
puts " 旧 Scheme: #{old_scheme} (基于 #{old_bundle_id})"
|
|
323
317
|
puts " 新 Scheme: #{new_scheme} (基于 #{new_bundle_id})"
|
|
324
318
|
|
|
325
|
-
# 将 binary plist 转换为 XML
|
|
326
|
-
|
|
327
|
-
unless system(convert_command)
|
|
319
|
+
# 将 binary plist 转换为 XML 格式(数组参数,避免 plist_path 拼接进 shell)
|
|
320
|
+
unless system("plutil", "-convert", "xml1", plist_path, err: File::NULL)
|
|
328
321
|
puts " ✗ 转换 plist 为 XML 失败"
|
|
329
322
|
return
|
|
330
323
|
end
|
|
331
324
|
|
|
332
|
-
#
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
unless scheme_exists
|
|
325
|
+
# 用 Ruby 文件操作做字面量替换,彻底规避 sed/grep 的 shell 注入
|
|
326
|
+
content = File.read(plist_path)
|
|
327
|
+
unless content.include?(old_scheme)
|
|
337
328
|
puts " ✓ 没有找到需要更新的 URL Scheme"
|
|
338
|
-
|
|
339
|
-
convert_back_command = "plutil -convert binary1 \"#{plist_path}\" 2>/dev/null"
|
|
340
|
-
system(convert_back_command)
|
|
329
|
+
system("plutil", "-convert", "binary1", plist_path, err: File::NULL)
|
|
341
330
|
return
|
|
342
331
|
end
|
|
343
332
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
temp_file = "#{plist_path}.tmp"
|
|
347
|
-
sed_command = "sed 's/#{old_scheme}/#{new_scheme}/g' \"#{plist_path}\" > \"#{temp_file}\" && mv \"#{temp_file}\" \"#{plist_path}\""
|
|
333
|
+
replace_count = content.scan(old_scheme).size
|
|
334
|
+
File.write(plist_path, content.gsub(old_scheme, new_scheme))
|
|
348
335
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
replace_count = `#{count_command}`.strip.to_i
|
|
353
|
-
|
|
354
|
-
# 转换回 binary 格式
|
|
355
|
-
convert_back_command = "plutil -convert binary1 \"#{plist_path}\" 2>/dev/null"
|
|
356
|
-
system(convert_back_command)
|
|
357
|
-
|
|
358
|
-
puts " ✓ 已替换 #{replace_count} 处 URL Scheme: #{old_scheme} → #{new_scheme}"
|
|
359
|
-
else
|
|
360
|
-
puts " ✗ 替换 URL Scheme 失败"
|
|
361
|
-
# 清理临时文件
|
|
362
|
-
FileUtils.rm_f(temp_file) if File.exist?(temp_file)
|
|
363
|
-
|
|
364
|
-
# 转换回 binary 格式(即使失败也要转回)
|
|
365
|
-
convert_back_command = "plutil -convert binary1 \"#{plist_path}\" 2>/dev/null"
|
|
366
|
-
system(convert_back_command)
|
|
367
|
-
end
|
|
336
|
+
# 转换回 binary 格式
|
|
337
|
+
system("plutil", "-convert", "binary1", plist_path, err: File::NULL)
|
|
338
|
+
puts " ✓ 已替换 #{replace_count} 处 URL Scheme: #{old_scheme} → #{new_scheme}"
|
|
368
339
|
end
|
|
369
340
|
|
|
370
341
|
end
|
data/lib/pindo/version.rb
CHANGED