pindo 5.11.4 → 5.12.2

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/githelper.rb +16 -0
  3. data/lib/pindo/base/pindocontext.rb +13 -4
  4. data/lib/pindo/command/android/autobuild.rb +108 -185
  5. data/lib/pindo/command/android/build.rb +10 -2
  6. data/lib/pindo/command/ios/autobuild.rb +116 -213
  7. data/lib/pindo/command/ios/build.rb +12 -3
  8. data/lib/pindo/command/jps/upload.rb +253 -118
  9. data/lib/pindo/command/unity/autobuild.rb +297 -227
  10. data/lib/pindo/command/unity.rb +0 -3
  11. data/lib/pindo/command/utils/boss.rb +18 -15
  12. data/lib/pindo/command/utils/clearcert.rb +26 -18
  13. data/lib/pindo/command/utils/device.rb +28 -19
  14. data/lib/pindo/command/utils/feishu.rb +11 -4
  15. data/lib/pindo/command/utils/icon.rb +26 -20
  16. data/lib/pindo/command/utils/renewcert.rb +35 -29
  17. data/lib/pindo/command/utils/renewproj.rb +32 -25
  18. data/lib/pindo/command/utils/repoinit.rb +1 -1
  19. data/lib/pindo/command/utils/tag.rb +6 -180
  20. data/lib/pindo/command/utils/tgate.rb +34 -28
  21. data/lib/pindo/command/utils/xcassets.rb +30 -20
  22. data/lib/pindo/command/web/autobuild.rb +148 -128
  23. data/lib/pindo/module/android/android_build_helper.rb +0 -6
  24. data/lib/pindo/module/android/android_config_helper.rb +4 -26
  25. data/lib/pindo/module/build/build_helper.rb +18 -294
  26. data/lib/pindo/module/build/git_repo_helper.rb +530 -0
  27. data/lib/pindo/module/build/icon_downloader.rb +85 -0
  28. data/lib/pindo/module/pgyer/pgyerhelper.rb +16 -11
  29. data/lib/pindo/module/task/model/build/android_dev_build_task.rb +209 -0
  30. data/lib/pindo/module/task/model/build/android_release_build_task.rb +29 -0
  31. data/lib/pindo/module/task/model/build/ios_adhoc_build_task.rb +53 -0
  32. data/lib/pindo/module/task/model/build/ios_dev_build_task.rb +251 -0
  33. data/lib/pindo/module/task/model/build/ios_release_build_task.rb +53 -0
  34. data/lib/pindo/module/task/model/build/web_dev_build_task.rb +43 -0
  35. data/lib/pindo/module/task/model/build_task.rb +125 -301
  36. data/lib/pindo/module/task/model/git_tag_task.rb +80 -0
  37. data/lib/pindo/module/task/model/unity_export_task.rb +53 -41
  38. data/lib/pindo/module/task/model/upload_task.rb +149 -208
  39. data/lib/pindo/module/task/pindo_task.rb +135 -95
  40. data/lib/pindo/module/task/task_manager.rb +202 -352
  41. data/lib/pindo/module/unity/unity_helper.rb +7 -3
  42. data/lib/pindo/module/xcode/xcode_build_config.rb +4 -10
  43. data/lib/pindo/module/xcode/xcode_build_helper.rb +19 -0
  44. data/lib/pindo/version.rb +1 -1
  45. metadata +10 -4
  46. data/lib/pindo/command/unity/apk.rb +0 -185
  47. data/lib/pindo/command/unity/ipa.rb +0 -198
  48. data/lib/pindo/command/unity/web.rb +0 -163
@@ -0,0 +1,530 @@
1
+ require 'singleton'
2
+ require 'pindo/base/githelper'
3
+ require 'pindo/base/executable'
4
+ require 'pindo/base/pindocontext'
5
+ require 'highline/import'
6
+
7
+ module Pindo
8
+ class GitRepoHelper
9
+ include Singleton
10
+ include Pindo::Githelper
11
+
12
+ attr_accessor :temp_tag_decision
13
+
14
+ class << self
15
+ def share_instance
16
+ instance
17
+ end
18
+ end
19
+
20
+ # 主入口函数 - 创建并推送 Git 标签
21
+ # @param project_dir [String] 项目目录
22
+ # @param mode [String] 版本号增加模式 (major/minor/patch)
23
+ # @param force_retag [Boolean] 是否强制重新打tag
24
+ # @param custom_tag [String] 自定义tag版本号
25
+ # @param release_branch [String] 发布分支名称,默认为 'master'
26
+ def create_and_push_tag(project_dir:, mode: 'minor', force_retag: false, custom_tag: nil, release_branch: 'master')
27
+ puts project_dir
28
+
29
+ # 1. 验证 Git 仓库
30
+ unless is_git_directory?(local_repo_dir: project_dir)
31
+ raise Informative, "当前目录不是git仓库,请在git仓库目录下执行此命令"
32
+ end
33
+
34
+ # 获取git仓库根目录
35
+ root_dir = git_root_directory(local_repo_dir: project_dir)
36
+ if root_dir.nil?
37
+ raise Informative, "无法获取git仓库根目录"
38
+ end
39
+
40
+ # 2. 处理未提交的文件
41
+ process_need_add_files(project_dir: root_dir)
42
+
43
+ # 3. 获取当前分支
44
+ current_branch = git!(%W(-C #{root_dir} rev-parse --abbrev-ref HEAD)).strip
45
+ coding_branch = current_branch
46
+
47
+ # 4. 合并到发布分支
48
+ Funlog.instance.fancyinfo_start("开始合并到#{release_branch}分支")
49
+ merge_to_release_branch(
50
+ project_dir: root_dir,
51
+ release_branch: release_branch,
52
+ coding_branch: coding_branch
53
+ )
54
+
55
+ # 5. 添加版本标签并返回 tag 名称
56
+ new_tag = add_release_tag(
57
+ project_dir: root_dir,
58
+ increment_mode: mode,
59
+ force_retag: force_retag,
60
+ custom_tag: custom_tag
61
+ )
62
+
63
+ new_tag
64
+ end
65
+
66
+ # 检查并安装 git-cliff
67
+ # @param project_path [String] 项目路径
68
+ def check_check_and_install_cliff(project_path)
69
+ if is_git_directory?(local_repo_dir: project_path)
70
+ current_git_root_path = git_root_directory(local_repo_dir: project_path)
71
+ unless File.exist?(File.join(current_git_root_path, 'cliff.toml'))
72
+ add_git_cliffconfig(current_git_root_path)
73
+ end
74
+ end
75
+
76
+ begin
77
+ if !system('which git-cliff > /dev/null 2>&1')
78
+ puts "安装git-cliff..."
79
+ install_gitcliff()
80
+ end
81
+ rescue
82
+ Funlog.instance.fancyinfo_error("安装git-cliff出现错误")
83
+ return
84
+ end
85
+ end
86
+
87
+ # 写入 .gitignore 文件
88
+ # @param git_root_dir [String] Git 根目录
89
+ def write_gitignore(git_root_dir)
90
+ gitignore_path = File.join(git_root_dir, '.gitignore')
91
+
92
+ # 定义要添加的gitignore规则数组
93
+ ignore_rules = [
94
+ 'Temp',
95
+ 'Logs',
96
+ 'build_ios.log',
97
+ 'feishu.json',
98
+ 'CHANGELOG.md',
99
+ 'GoodPlatform/iOS/*',
100
+ 'GoodPlatform/Android/*',
101
+ 'GoodPlatform/BaseiOS/Unity/*',
102
+ 'GoodPlatform/BaseiOS/Pods/',
103
+ 'GoodPlatform/BaseiOS/build',
104
+ 'GoodPlatform/BaseiOS/config.json',
105
+ 'GoodPlatform/BaseiOS/Podfile.lock',
106
+ 'GoodPlatform/BaseAndroid/Unity/*',
107
+ 'GoodPlatform/BaseAndroid/build',
108
+ 'GoodPlatform/WebGL',
109
+ 'config.json',
110
+ 'Assets/Packages',
111
+ 'Assets/WebGLTemplates.meta',
112
+ 'Assets/WebGLTemplates/',
113
+ 'Packages/packages-lock.json'
114
+ ]
115
+
116
+ # 读取现有的gitignore内容
117
+ existing_lines = []
118
+ if File.exist?(gitignore_path)
119
+ existing_lines = File.readlines(gitignore_path).map(&:strip)
120
+ end
121
+
122
+ # 过滤出需要添加的规则(不存在的)
123
+ rules_to_add = ignore_rules.reject { |rule| existing_lines.include?(rule) }
124
+
125
+ # 如果有需要添加的规则,则添加
126
+ unless rules_to_add.empty?
127
+ File.open(gitignore_path, 'a') do |f|
128
+ # 检查是否已有Pindo标记,如果没有则添加
129
+ pindo_marker = '# Added by Pindo (pindo_common_ignore_1.0.0)'
130
+ f.puts("\n#{pindo_marker}") unless existing_lines.include?(pindo_marker.strip)
131
+
132
+ # 添加每条新规则
133
+ rules_to_add.each do |rule|
134
+ f.puts(rule)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ # 添加 git-cliff 配置
141
+ # @param project_path [String] 项目路径
142
+ def add_git_cliffconfig(project_path)
143
+ temp_dir = Dir.pwd
144
+ Funlog.instance.fancyinfo_start("添加日志变更git-cliff配置...")
145
+ if is_git_directory?(local_repo_dir: project_path)
146
+ current_git_root_path = git_root_directory(local_repo_dir: project_path)
147
+ Dir.chdir(current_git_root_path)
148
+ pindo_common_dir = clone_pindo_common_config_repo(force_delete:false)
149
+ if File.exist?(File.join(pindo_common_dir, 'cliff.toml'))
150
+ FileUtils.cp_r(File.join(pindo_common_dir, 'cliff.toml'), File.join(current_git_root_path, 'cliff.toml'))
151
+ end
152
+ Funlog.instance.fancyinfo_update("仓库添加git-cliff配置")
153
+ write_gitignore(current_git_root_path)
154
+ Funlog.instance.fancyinfo_update("仓库添加.gitignore")
155
+ Dir.chdir(current_git_root_path)
156
+ current_branch = git!(%W(-C #{current_git_root_path} rev-parse --abbrev-ref HEAD)).strip
157
+ git!(%W(-C #{current_git_root_path} add cliff.toml))
158
+ git!(%W(-C #{current_git_root_path} add .gitignore))
159
+ commit_message = "docs: 添加日志变更配置".encode('UTF-8')
160
+ git!(%W(-C #{current_git_root_path} commit -m #{commit_message}))
161
+ git!(%W(-C #{current_git_root_path} push origin #{current_branch}))
162
+ else
163
+ Funlog.instance.fancyinfo_error("当前目录不是git仓库,请在git仓库根目录下执行此命令")
164
+ Dir.chdir(temp_dir)
165
+ return
166
+ end
167
+ Funlog.instance.fancyinfo_success("日志变更git-cliff配置完成!")
168
+ Dir.chdir(temp_dir)
169
+ end
170
+
171
+ # 安装 git-cliff
172
+ def install_gitcliff
173
+ puts "\n检查git-cliff命令是否安装"
174
+ begin
175
+ if !system('which git-cliff > /dev/null 2>&1')
176
+ system('bash -c "$(curl -fsSL https://gitee.com/goodtools/env/raw/master/gitcliff_install.sh)"')
177
+ end
178
+ rescue
179
+ Funlog.instance.fancyinfo_error("安装git-cliff出现错误")
180
+ end
181
+ end
182
+
183
+ # 检查是否需要添加 Git 标签
184
+ # @param project_path [String] 项目路径
185
+ # @param auto_mode [Boolean] 是否自动模式
186
+ # @return [Array<Boolean, Array>] [是否需要添加tag, tag参数]
187
+ def check_is_need_add_tag?(project_path, auto_mode: false)
188
+ tag_action_parms = nil
189
+ is_need_add_tag = false
190
+
191
+ if is_git_directory?(local_repo_dir: project_path)
192
+ current_git_root_path = git_root_directory(local_repo_dir: project_path)
193
+ latest_tag = get_latest_version_tag(project_dir: current_git_root_path)
194
+
195
+ # 检查是否需要打 tag:
196
+ # 1. 最新 tag 不在 HEAD 上,或者
197
+ # 2. 仓库有未提交的更改
198
+ tag_not_at_head = !is_tag_at_head?(git_root_dir: current_git_root_path, tag_name: latest_tag)
199
+ has_uncommitted = has_uncommitted_changes?(git_root_dir: current_git_root_path)
200
+
201
+ if tag_not_at_head || has_uncommitted
202
+ # 提示用户需要打 tag 的原因
203
+ if has_uncommitted && !tag_not_at_head
204
+ puts "\n注意:仓库有未提交的更改,建议提交后再打 Tag"
205
+ end
206
+ # 检查环境变量
207
+ env_tag_decision = ENV['PINDO_TAG_DECISION']
208
+ if env_tag_decision && !env_tag_decision.empty?
209
+ case env_tag_decision.downcase
210
+ when 'new', 'new_tag', '1'
211
+ puts "\n环境变量指定:新增版本号,打新Tag"
212
+ puts
213
+ tag_action_parms = []
214
+ is_need_add_tag = true
215
+ # 保存到缓存
216
+ context = PindoContext.instance
217
+ decision = {
218
+ tag_action_parms: tag_action_parms,
219
+ is_need_add_tag: is_need_add_tag,
220
+ action: :new_tag,
221
+ description: "新增版本号,打新Tag"
222
+ }
223
+ context.set_selection(PindoContext::SelectionKey::TAG_DECISION, decision)
224
+ return is_need_add_tag, tag_action_parms
225
+ when 'retag', 'recreate', '2'
226
+ puts "\n环境变量指定:将上次的Tag删除重新打Tag"
227
+ puts
228
+ tag_action_parms = ['--retag']
229
+ is_need_add_tag = true
230
+ # 保存到缓存
231
+ context = PindoContext.instance
232
+ decision = {
233
+ tag_action_parms: tag_action_parms,
234
+ is_need_add_tag: is_need_add_tag,
235
+ action: :recreate_tag,
236
+ description: "将上次的Tag删除重新打Tag"
237
+ }
238
+ context.set_selection(PindoContext::SelectionKey::TAG_DECISION, decision)
239
+ return is_need_add_tag, tag_action_parms
240
+ when 'skip', 'no', 'none', '3'
241
+ puts "\n环境变量指定:不需要Tag继续编译"
242
+ puts
243
+ tag_action_parms = nil
244
+ is_need_add_tag = false
245
+ # 保存到缓存
246
+ context = PindoContext.instance
247
+ decision = {
248
+ tag_action_parms: tag_action_parms,
249
+ is_need_add_tag: is_need_add_tag,
250
+ action: :continue_without_tag,
251
+ description: "不需要Tag继续编译且上传"
252
+ }
253
+ context.set_selection(PindoContext::SelectionKey::TAG_DECISION, decision)
254
+ return is_need_add_tag, tag_action_parms
255
+ when 'exit', 'quit', '4'
256
+ Funlog.instance.fancyinfo_error("环境变量指定:终止退出编译!")
257
+ return false, nil
258
+ end
259
+ end
260
+
261
+ if auto_mode
262
+ # 在自动化模式或没有交互式终端时,默认选择新增版本号打新Tag
263
+ puts "检测到当前代码没有打Tag,在自动模式下将自动新增版本号并打新Tag"
264
+ tag_action_parms = []
265
+ is_need_add_tag = true
266
+ else
267
+ # 检查是否已有用户选择
268
+ context = PindoContext.instance
269
+ # 优先从持久化缓存读取,如果没有则从内存临时缓存读取
270
+ cached_decision = context.get_selection(PindoContext::SelectionKey::TAG_DECISION)
271
+
272
+ # 只有当持久化缓存中确实不存在该键时,才从临时缓存读取
273
+ if cached_decision.nil? && !context.has_selection?(PindoContext::SelectionKey::TAG_DECISION)
274
+ # 从 GitRepoHelper 的临时缓存读取
275
+ cached_decision = self.temp_tag_decision
276
+ end
277
+
278
+ if cached_decision && cached_decision.is_a?(Hash)
279
+ # 使用之前的选择
280
+ puts "\n使用之前的选择:#{cached_decision[:description]}"
281
+ tag_action_parms = cached_decision[:tag_action_parms]
282
+ is_need_add_tag = cached_decision[:is_need_add_tag]
283
+
284
+ # 如果是退出选择,则返回
285
+ if cached_decision[:action] == :exit
286
+ Funlog.instance.fancyinfo_error("终止退出编译!")
287
+ return false, nil
288
+ end
289
+ else
290
+ # 第一次询问
291
+ cli = HighLine.new
292
+ selected_action = nil
293
+ selected_description = nil
294
+
295
+ menu_options = {
296
+ "新增版本号,打新Tag" => -> {
297
+ tag_action_parms = []
298
+ selected_action = :new_tag
299
+ selected_description = "新增版本号,打新Tag"
300
+ is_need_add_tag = true
301
+ :new_tag
302
+ },
303
+ "将上次的Tag删除重新打Tag" => -> {
304
+ tag_action_parms = []
305
+ tag_action_parms << "--retag"
306
+ selected_action = :recreate_tag
307
+ selected_description = "将上次的Tag删除重新打Tag"
308
+ is_need_add_tag = true
309
+ :recreate_tag
310
+ },
311
+ "不需要Tag继续编译且上传,手动修改上传备注" => -> {
312
+ puts ""
313
+ tag_action_parms = nil
314
+ selected_action = :continue_without_tag
315
+ selected_description = "不需要Tag继续编译且上传"
316
+ is_need_add_tag = false
317
+ :continue_without_tag
318
+ },
319
+ "终止退出编译" => -> {
320
+ tag_action_parms = nil
321
+ selected_action = :exit
322
+ selected_description = "终止退出编译"
323
+ is_need_add_tag = false
324
+ Funlog.instance.fancyinfo_error("终止退出编译!")
325
+ :exit
326
+ }
327
+ }
328
+
329
+ result = cli.choose do |menu|
330
+ menu.header = "当前代码并没有打Tag,上传的Changelog需要Tag"
331
+ menu.prompt = "请选中打Tag的方式, 请输入选项(1/2/3...):"
332
+ menu_options.each do |option, action|
333
+ menu.choice(option) { action.call }
334
+ end
335
+ end
336
+
337
+ # 如果选择退出,直接返回
338
+ return false, nil if selected_action == :exit
339
+
340
+ # 保存用户选择
341
+ # 只有选择"新增版本号,打新Tag"时才保存到持久化缓存
342
+ if selected_action == :new_tag
343
+ # 保存到持久化缓存(文件)
344
+ context.set_selection(PindoContext::SelectionKey::TAG_DECISION, {
345
+ action: selected_action,
346
+ description: selected_description,
347
+ tag_action_parms: tag_action_parms,
348
+ is_need_add_tag: is_need_add_tag
349
+ })
350
+ # 清除临时缓存
351
+ self.temp_tag_decision = nil
352
+ else
353
+ # 不清空持久化缓存(避免将 nil 写入文件)
354
+ # 只使用临时缓存(仅在内存中,不会写入文件)
355
+ self.temp_tag_decision = {
356
+ action: selected_action,
357
+ description: selected_description,
358
+ tag_action_parms: tag_action_parms,
359
+ is_need_add_tag: is_need_add_tag
360
+ }
361
+ end
362
+ end
363
+ end
364
+ end
365
+ end
366
+
367
+ return [is_need_add_tag, tag_action_parms]
368
+ end
369
+
370
+ private
371
+
372
+ # 添加版本标签
373
+ # @param project_dir [String] 项目目录
374
+ # @param increment_mode [String] 版本号增加模式 (major/minor/patch)
375
+ # @param force_retag [Boolean] 是否强制重新打tag
376
+ # @param custom_tag [String] 自定义tag版本号
377
+ def add_release_tag(project_dir:, increment_mode: "minor", force_retag: false, custom_tag: nil)
378
+ raise ArgumentError, "项目目录不能为空" if project_dir.nil?
379
+
380
+ # 如果指定了自定义tag,直接使用
381
+ if custom_tag && !custom_tag.empty?
382
+ Funlog.instance.fancyinfo_start("使用指定的tag版本: #{custom_tag}")
383
+
384
+ # 确保tag有v前缀
385
+ new_tag = custom_tag.start_with?('v') ? custom_tag : "v#{custom_tag}"
386
+
387
+ # 检查tag是否已存在
388
+ existing_tags = git!(%W(-C #{project_dir} tag -l)).split("\n")
389
+ if existing_tags.include?(new_tag)
390
+ if force_retag
391
+ Funlog.instance.fancyinfo_update("tag #{new_tag} 已存在,强制重新打tag")
392
+ git!(%W(-C #{project_dir} tag -d #{new_tag}))
393
+ git!(%W(-C #{project_dir} push origin :refs/tags/#{new_tag}))
394
+ else
395
+ Funlog.instance.fancyinfo_success("tag #{new_tag} 已存在")
396
+ Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
397
+ return new_tag # 返回已存在的 tag
398
+ end
399
+ end
400
+
401
+ # 创建tag并推送
402
+ git!(%W(-C #{project_dir} tag #{new_tag}))
403
+ git!(%W(-C #{project_dir} push origin #{new_tag}))
404
+
405
+ Funlog.instance.fancyinfo_success("创建tag: #{new_tag}")
406
+ Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
407
+ return new_tag # 返回新创建的 tag
408
+ end
409
+
410
+ # 原有的自动递增逻辑
411
+ Funlog.instance.fancyinfo_start("开始创建初tag")
412
+ latest_tag = get_latest_version_tag(project_dir: project_dir)
413
+ if latest_tag.nil?
414
+ new_tag = create_next_version_tag(
415
+ project_dir: project_dir,
416
+ tag_prefix: "v",
417
+ increment_mode: increment_mode,
418
+ force_retag: false
419
+ )
420
+ Funlog.instance.fancyinfo_success("创建初始tag: #{new_tag}")
421
+ Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
422
+ return new_tag # 返回初始 tag
423
+ end
424
+
425
+ if is_tag_at_head?(git_root_dir: project_dir, tag_name: latest_tag)
426
+ Funlog.instance.fancyinfo_success("当前commit已有tag: #{latest_tag},无需重新打tag")
427
+ Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
428
+ return latest_tag # 返回已存在的 tag
429
+ end
430
+
431
+ Funlog.instance.fancyinfo_success("最近的上次tag是: #{latest_tag} ")
432
+ new_tag = create_next_version_tag(
433
+ project_dir: project_dir,
434
+ tag_prefix: "v",
435
+ increment_mode: increment_mode,
436
+ force_retag: force_retag
437
+ )
438
+
439
+ Funlog.instance.fancyinfo_success("当前仓库的tag: #{new_tag}")
440
+ Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
441
+
442
+ new_tag # 返回新创建的 tag
443
+ end
444
+
445
+ # 合并到发布分支
446
+ # @param project_dir [String] 项目目录
447
+ # @param release_branch [String] 发布分支名称
448
+ # @param coding_branch [String] 当前开发分支名称
449
+ def merge_to_release_branch(project_dir:, release_branch:, coding_branch:)
450
+ current_project_dir = project_dir
451
+ Funlog.instance.fancyinfo_start("开始合并到#{release_branch}分支")
452
+ if !coding_branch.eql?(release_branch)
453
+ coding_branch_commit_id = git!(%W(-C #{current_project_dir} rev-parse #{coding_branch})).strip
454
+ release_branch_commit_id = nil
455
+
456
+ if remote_branch_exists?(local_repo_dir: current_project_dir, branch: release_branch)
457
+ Funlog.instance.fancyinfo_update("存在#{release_branch}远程分支")
458
+ if local_branch_exists?(local_repo_dir: current_project_dir, branch: release_branch)
459
+ Funlog.instance.fancyinfo_update("存在#{release_branch}本地分支")
460
+ git!(%W(-C #{current_project_dir} checkout #{release_branch}))
461
+ else
462
+ Funlog.instance.fancyinfo_update("不存在#{release_branch}本地分支")
463
+ git!(%W(-C #{current_project_dir} checkout -b #{release_branch} origin/#{release_branch}))
464
+ end
465
+
466
+ git!(%W(-C #{current_project_dir} branch --set-upstream-to=origin/#{release_branch} #{release_branch}))
467
+ git!(%W(-C #{current_project_dir} fetch origin #{release_branch}))
468
+ git!(%W(-C #{current_project_dir} merge origin/#{release_branch}))
469
+
470
+ # 执行合并操作,捕获输出以便后续检查冲突
471
+ stdout, exit_status = Executable.capture_command('git', %W(-C #{current_project_dir} merge #{coding_branch}), :capture => :out)
472
+
473
+ # 检查是否有冲突(git merge 在有冲突时返回非0状态)
474
+ conflict_filelist = git!(%W(-C #{current_project_dir} diff --name-only --diff-filter=U --relative))
475
+ if !conflict_filelist.nil? && conflict_filelist.size > 0
476
+ puts "合并代码冲突, 冲突文件如下:"
477
+ raise Informative, "请手动处理冲突的文件!!!"
478
+ else
479
+ git!(%W(-C #{current_project_dir} push))
480
+ Funlog.instance.fancyinfo_success("代码已经合并到#{release_branch}分支")
481
+ # 获取 release_branch 的 commit ID(处理空分支情况)
482
+ begin
483
+ release_branch_commit_id = git!(%W(-C #{current_project_dir} rev-parse #{release_branch})).strip
484
+ rescue => e
485
+ # 分支可能存在但没有提交(空分支),此时使用 coding_branch 的 commit
486
+ Funlog.instance.fancyinfo_update("#{release_branch}分支为空或获取commit失败,将使用当前分支commit")
487
+ release_branch_commit_id = coding_branch_commit_id
488
+ end
489
+ end
490
+
491
+ else
492
+ if local_branch_exists?(local_repo_dir: current_project_dir, branch: release_branch)
493
+ Funlog.instance.fancyinfo_update("不存在#{release_branch}远程分支")
494
+ Funlog.instance.fancyinfo_update("存在#{release_branch}本地分支")
495
+ git!(%W(-C #{current_project_dir} checkout #{release_branch}))
496
+ git!(%W(-C #{current_project_dir} checkout -b #{release_branch}_temp))
497
+ git!(%W(-C #{current_project_dir} checkout #{coding_branch}))
498
+ git!(%W(-C #{current_project_dir} branch -D #{release_branch}))
499
+ else
500
+ Funlog.instance.fancyinfo_update("不存在#{release_branch}远程分支")
501
+ Funlog.instance.fancyinfo_update("不存在#{release_branch}本地分支")
502
+ end
503
+
504
+ git!(%W(-C #{current_project_dir} checkout -b #{release_branch}))
505
+ git!(%W(-C #{current_project_dir} push origin #{release_branch}))
506
+ git!(%W(-C #{current_project_dir} branch --set-upstream-to=origin/#{release_branch} #{release_branch}))
507
+
508
+ Funlog.instance.fancyinfo_success("代码已经合并到#{release_branch}分支")
509
+ # 获取 release_branch 的 commit ID(处理空分支情况)
510
+ begin
511
+ release_branch_commit_id = git!(%W(-C #{current_project_dir} rev-parse #{release_branch})).strip
512
+ rescue => e
513
+ # 新创建的分支,commit ID 应该与 coding_branch 相同
514
+ release_branch_commit_id = coding_branch_commit_id
515
+ end
516
+ end
517
+
518
+ git!(%W(-C #{current_project_dir} checkout #{coding_branch}))
519
+ if release_branch_commit_id && !release_branch_commit_id.eql?(coding_branch_commit_id)
520
+ git!(%W(-C #{current_project_dir} merge #{release_branch}))
521
+ Funlog.instance.fancyinfo_success("已将#{release_branch}合并到#{coding_branch}")
522
+ end
523
+ git!(%W(-C #{current_project_dir} push origin #{coding_branch}))
524
+ Funlog.instance.fancyinfo_success("已推送#{coding_branch}分支到远程")
525
+ else
526
+ Funlog.instance.fancyinfo_success("代码处于#{coding_branch}分支,无需合并")
527
+ end
528
+ end
529
+ end
530
+ end
@@ -0,0 +1,85 @@
1
+ require 'open-uri'
2
+ require 'openssl'
3
+ require 'fileutils'
4
+
5
+ module Pindo
6
+ module IconDownloader
7
+ # 带重试机制的文件下载
8
+ # @param url [String] 下载URL
9
+ # @param save_path [String] 保存路径
10
+ # @param max_retries [Integer] 最大重试次数,默认3次
11
+ # @param retry_delay [Integer] 重试间隔秒数,默认2秒
12
+ # @param options [Hash] URI.open 的选项
13
+ # @return [Boolean] 是否下载成功
14
+ def self.download_file_with_retry(url:, save_path:, max_retries: 3, retry_delay: 2, options: {})
15
+ return false if url.nil? || url.empty?
16
+ return false if save_path.nil? || save_path.empty?
17
+
18
+ # 默认选项
19
+ default_options = {
20
+ read_timeout: 30,
21
+ open_timeout: 10,
22
+ redirect: true,
23
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE
24
+ }
25
+ merged_options = default_options.merge(options)
26
+
27
+ # 确保目录存在
28
+ save_dir = File.dirname(save_path)
29
+ FileUtils.mkdir_p(save_dir) unless File.exist?(save_dir)
30
+
31
+ attempt = 0
32
+ last_error = nil
33
+
34
+ while attempt < max_retries
35
+ attempt += 1
36
+ begin
37
+ URI.open(url, merged_options) do |file|
38
+ File.binwrite(save_path, file.read)
39
+ end
40
+
41
+ # 验证文件已下载
42
+ if File.exist?(save_path) && File.size(save_path) > 0
43
+ return true
44
+ else
45
+ raise "下载的文件为空或不存在"
46
+ end
47
+ rescue => e
48
+ last_error = e
49
+ if attempt < max_retries
50
+ Funlog.instance.fancyinfo_error("下载失败 (第#{attempt}次): #{e.message},#{retry_delay}秒后重试...")
51
+ sleep(retry_delay)
52
+ end
53
+ end
54
+ end
55
+
56
+ # 所有重试都失败
57
+ Funlog.instance.fancyinfo_error("下载失败,已重试#{max_retries}次: #{last_error&.message}")
58
+ false
59
+ end
60
+
61
+ # 带重试机制的 Icon 下载
62
+ # @param icon_url [String] Icon的下载URL
63
+ # @param save_path [String] 保存路径
64
+ # @param max_retries [Integer] 最大重试次数,默认3次
65
+ # @return [Boolean] 是否下载成功
66
+ def self.download_icon_with_retry(icon_url:, save_path:, max_retries: 3)
67
+ Funlog.instance.fancyinfo_start("正在从 JPS 下载项目 Icon...")
68
+
69
+ success = download_file_with_retry(
70
+ url: icon_url,
71
+ save_path: save_path,
72
+ max_retries: max_retries,
73
+ retry_delay: 2
74
+ )
75
+
76
+ if success
77
+ Funlog.instance.fancyinfo_success("Icon 下载成功!")
78
+ else
79
+ Funlog.instance.fancyinfo_error("Icon 下载失败!")
80
+ end
81
+
82
+ success
83
+ end
84
+ end
85
+ end
@@ -98,6 +98,7 @@ module Pindo
98
98
  when 'ipa' then 'ipa_workflow'
99
99
  when 'apk' then 'apk_workflow'
100
100
  when 'zip' then 'webgl_workflow'
101
+ when 'app' then 'macos_workflow'
101
102
  else
102
103
  puts "[JPSConfig] 不支持的 package_type: #{package_type}"
103
104
  return result
@@ -1015,8 +1016,8 @@ module Pindo
1015
1016
  puts "使用项目配置文件: #{project_cliff_toml}" if ENV['DEBUG']
1016
1017
  cliff_config_cmd = "git-cliff -c \"#{project_cliff_toml}\" #{cliff_args} -o -"
1017
1018
  else
1018
- # 策略2: 使用 Pindo 默认配置文件(使用与 check_and_install_cliff 相同的方法)
1019
- pindo_common_dir = clone_pindo_common_config_repo(force_delete:false)
1019
+ # 策略2: 使用 Pindo 默认配置文件(直接从 Pindoconfig 获取目录,避免更新仓库)
1020
+ pindo_common_dir = Pindoconfig.instance.pindo_common_configdir
1020
1021
  pindo_cliff_toml = File.join(pindo_common_dir, 'cliff.toml')
1021
1022
  if File.exist?(pindo_cliff_toml)
1022
1023
  puts "使用 Pindo 默认配置文件: #{pindo_cliff_toml}" if ENV['DEBUG']
@@ -1038,19 +1039,22 @@ module Pindo
1038
1039
  end
1039
1040
  puts "git-cliff 输出成功" if ENV['DEBUG']
1040
1041
  else
1041
- puts "\n\e[31m错误: git-cliff 执行失败\e[0m"
1042
+ Funlog.warning("git-cliff 执行失败,使用默认描述")
1042
1043
  error_msg = stderr && !stderr.empty? ? stderr : stdout
1043
- puts "\e[31m错误信息: #{error_msg}\e[0m"
1044
- puts "\e[33m请检查 git-cliff 配置或联系管理员\e[0m"
1045
- raise Informative, "git-cliff 执行失败,无法生成版本描述"
1044
+ puts "错误信息: #{error_msg}" if ENV['DEBUG']
1045
+ # 使用默认描述而不是抛出异常
1046
+ description = "版本更新"
1046
1047
  end
1047
1048
  else
1048
1049
  # 没有找到任何配置文件
1049
- puts "\n\e[31m错误: 未找到 cliff.toml 配置文件\e[0m"
1050
- puts "\e[33m请确保:\e[0m"
1051
- puts "\e[36m 1. 项目根目录存在 cliff.toml 文件\e[0m"
1052
- puts "\e[36m 2. Pindo 工具目录存在默认配置文件\e[0m"
1053
- raise Informative, "缺少 cliff.toml 配置文件,无法生成版本描述"
1050
+ Funlog.warning("未找到 cliff.toml 配置文件,使用默认描述")
1051
+ if ENV['DEBUG']
1052
+ puts "请确保:"
1053
+ puts " 1. 项目根目录存在 cliff.toml 文件"
1054
+ puts " 2. 或 Pindo 工具目录存在默认配置文件"
1055
+ end
1056
+ # 使用默认描述而不是抛出异常
1057
+ description = "版本更新"
1054
1058
  end
1055
1059
 
1056
1060
  Dir.chdir(temp_dir)
@@ -1195,6 +1199,7 @@ module Pindo
1195
1199
  when 'ipa' then 'ipa_workflow'
1196
1200
  when 'apk' then 'apk_workflow'
1197
1201
  when 'zip' then 'webgl_workflow'
1202
+ when 'app' then 'macos_workflow'
1198
1203
  else raise Informative, "不支持的 package_type: #{package_type}"
1199
1204
  end
1200
1205