pindo 5.18.17 → 5.18.20

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.
@@ -352,7 +352,7 @@ module Pindo
352
352
  if local_exists && Pindo::GitHandler.is_tag_at_head?(git_root_dir: project_dir, tag_name: tag_name)
353
353
  if !remote_exists
354
354
  # 本地存在且在 HEAD,但远程不存在,推送到远程
355
- Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin #{tag_name}))
355
+ push_tag_with_verify(project_dir: project_dir, tag_name: tag_name)
356
356
  Funlog.instance.fancyinfo_success("推送 Tag 到远程: #{tag_name}")
357
357
  else
358
358
  Funlog.instance.fancyinfo_success("Tag 已存在且在 HEAD: #{tag_name}")
@@ -374,10 +374,48 @@ module Pindo
374
374
 
375
375
  # 创建并推送 tag
376
376
  Pindo::GitHandler.git!(%W(-C #{project_dir} tag #{tag_name}))
377
- Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin #{tag_name}))
377
+ push_tag_with_verify(project_dir: project_dir, tag_name: tag_name)
378
378
  Funlog.instance.fancyinfo_success("创建并推送 Tag: #{tag_name}")
379
379
  tag_name
380
380
  end
381
381
 
382
+ # 推送 tag 到远程并校验是否真正推送成功,失败则重试
383
+ # 校验方式为「远程 tag 指向的 commit == 本地 tag commit」,
384
+ # 一次 ls-remote 同时覆盖「存在性」与「指向正确性」
385
+ # @param project_dir [String] 仓库目录
386
+ # @param tag_name [String] tag 名称
387
+ # @param max_retries [Integer] 最大重试次数(默认 3)
388
+ # @return [Boolean] 是否推送成功(失败到达上限会 raise)
389
+ def push_tag_with_verify(project_dir:, tag_name:, max_retries: 3)
390
+ local_commit = Pindo::GitHandler.git!(%W(-C #{project_dir} rev-parse #{tag_name}^{commit})).strip
391
+
392
+ attempt = 0
393
+ loop do
394
+ attempt += 1
395
+ begin
396
+ Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin #{tag_name}))
397
+ rescue => e
398
+ first_line = e.message.lines.first&.strip
399
+ Funlog.instance.fancyinfo_warning("推送 Tag 失败(第 #{attempt}/#{max_retries} 次): #{first_line}")
400
+ end
401
+
402
+ # 校验远程 tag 指向的 commit 是否与本地一致,确认推送成功
403
+ remote_commit = Pindo::GitHandler.remote_tag_commit(local_repo_dir: project_dir, tag_name: tag_name)
404
+ if remote_commit && remote_commit == local_commit
405
+ Funlog.instance.fancyinfo_success("已确认 Tag 推送到远程: #{tag_name}")
406
+ 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
+ end
410
+
411
+ if attempt >= max_retries
412
+ raise Informative, "Tag 推送失败,已重试 #{max_retries} 次仍未在远程确认: #{tag_name}"
413
+ end
414
+
415
+ Funlog.instance.fancyinfo_update("远程未确认 Tag,#{attempt}s 后重试推送: #{tag_name}")
416
+ sleep(attempt)
417
+ end
418
+ end
419
+
382
420
  end
383
421
  end
@@ -12,13 +12,13 @@ module Pindo
12
12
  @project_obj = Xcodeproj::Project.open(proj_fullname)
13
13
  end
14
14
 
15
- def get_xcodeproj_icon_path()
16
-
15
+ # 查找工程的 AppIcon.appiconset 目录,找不到返回 nil(不抛异常)
16
+ def find_xcodeproj_icon_path()
17
17
  icon_path = nil
18
18
  select_target = @project_obj.targets.select { |target| target.respond_to?(:product_type) && target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application]) }.first
19
19
 
20
20
  project_dir = @project_obj.project_dir
21
-
21
+
22
22
  if !select_target.nil?
23
23
  file_refs = select_target.resources_build_phase.files_references.select { |file| file.display_name.include?("Assets.xcassets") } || []
24
24
  file_refs.each do |file_ref|
@@ -40,12 +40,33 @@ module Pindo
40
40
  end
41
41
 
42
42
  if icon_path.nil? || icon_path.empty? || !File.exist?(icon_path)
43
- raise Informative, "没有找到Xcode icon 目录"
43
+ return nil
44
44
  end
45
45
 
46
46
  return icon_path
47
47
  end
48
48
 
49
+ # 工程是否包含 AppIcon 目录(macOS 命令行工具等无 icon 工程返回 false)
50
+ def has_app_icon_dir?()
51
+ !find_xcodeproj_icon_path().nil?
52
+ end
53
+
54
+ # 工程是否为命令行工具(com.apple.product-type.tool)
55
+ def command_line_tool?()
56
+ tool_uti = Xcodeproj::Constants::PRODUCT_TYPE_UTI[:command_line_tool]
57
+ @project_obj.targets.any? do |target|
58
+ target.respond_to?(:product_type) && !target.product_type.nil? && target.product_type == tool_uti
59
+ end
60
+ end
61
+
62
+ def get_xcodeproj_icon_path()
63
+ icon_path = find_xcodeproj_icon_path()
64
+ if icon_path.nil?
65
+ raise Informative, "没有找到Xcode icon 目录"
66
+ end
67
+ return icon_path
68
+ end
69
+
49
70
  def install_icon_res(new_icon_dir:nil)
50
71
  icon_path = get_xcodeproj_icon_path()
51
72
  begin
@@ -356,7 +356,7 @@ module Pindo
356
356
  project.save
357
357
 
358
358
  if updated_targets.empty?
359
- Funlog.instance.fancyinfo_error("未找到需要更新的application target")
359
+ Funlog.instance.fancyinfo_warning("未找到需要更新的application target(如命令行工具工程),跳过版本更新")
360
360
  return false
361
361
  else
362
362
  Funlog.instance.fancyinfo_success("iOS版本更新完成!")
@@ -480,6 +480,12 @@ module Pindo
480
480
 
481
481
  puts "\n检测到项目 Icon URL: #{icon_url}"
482
482
 
483
+ # 命令行工具且无 AppIcon 目录时跳过 Icon 替换,视为成功
484
+ if XcodeResHelper.skip_icon_replacement?(proj_dir: project_dir)
485
+ Funlog.instance.fancyinfo_success("命令行工具工程无 AppIcon 目录,跳过 Icon 替换")
486
+ return true
487
+ end
488
+
483
489
  # 创建临时目录(所有 icon 处理都在此目录内完成)
484
490
  temp_icon_dir = File.join(project_dir, ".pindo_temp_ios_icon")
485
491
  icon_download_path = nil
@@ -699,8 +699,15 @@ module Pindo
699
699
  project_build_platform = project_obj.root_object.build_configuration_list.get_setting("SDKROOT")["Release"]
700
700
  is_macos = !project_build_platform.nil? && project_build_platform.eql?("macosx")
701
701
 
702
- # 根据平台查找输出文件
703
- if is_macos
702
+ # 命令行工具(com.apple.product-type.tool)产物为裸可执行文件,而非 .app/.ipa
703
+ tool_uti = Xcodeproj::Constants::PRODUCT_TYPE_UTI[:command_line_tool]
704
+ tool_target = project_obj.targets.find { |t| t.respond_to?(:product_type) && t.product_type == tool_uti }
705
+
706
+ # 根据工程类型查找输出文件
707
+ if tool_target
708
+ output_file = find_command_line_tool_output(build_dir: build_dir, target: tool_target)
709
+ puts "命令行工具: 查找可执行文件" if output_file
710
+ elsif is_macos
704
711
  # macOS 平台查找 .app 文件
705
712
  build_path = File.join(build_dir, "*.app")
706
713
  output_file = Dir.glob(build_path).select { |f| File.directory?(f) }.max_by { |f| File.mtime(f) }
@@ -715,6 +722,24 @@ module Pindo
715
722
  output_file
716
723
  end
717
724
 
725
+ # 查找命令行工具的输出可执行文件
726
+ # @param build_dir [String] 构建输出目录
727
+ # @param target [Xcodeproj::Project::Object::PBXNativeTarget] 命令行工具 target
728
+ # @return [String, nil] 可执行文件路径
729
+ def find_command_line_tool_output(build_dir:, target:)
730
+ product_name = target.build_configurations.first.build_settings['PRODUCT_NAME']
731
+ product_name = target.name if product_name.nil? || product_name.to_s.include?('$(')
732
+
733
+ # 优先取 gym 导出到 build 根目录的同名可执行文件
734
+ candidate = File.join(build_dir, product_name)
735
+ return candidate if File.file?(candidate)
736
+
737
+ # 回退:build 根目录下任意无扩展名的普通文件(排除 .dSYM 等目录)
738
+ Dir.glob(File.join(build_dir, "*")).find do |f|
739
+ File.file?(f) && File.extname(f).empty?
740
+ end
741
+ end
742
+
718
743
  # 获取 Gym 构建参数
719
744
  # @param project_fullname [String] 工程文件完整路径
720
745
  # @param icloud_id [String, nil] iCloud ID(可选)
@@ -735,8 +760,15 @@ module Pindo
735
760
  project_obj = Xcodeproj::Project.open(project_fullname)
736
761
 
737
762
  project_build_platform = project_obj.root_object.build_configuration_list.get_setting("SDKROOT")["Release"]
738
- main_target = project_obj.targets.select { |target| target.respond_to?(:product_type) && target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application]) }.first
739
- provisioning_profile_name = main_target.build_configurations.first.build_settings['PROVISIONING_PROFILE_SPECIFIER'].downcase
763
+ main_target = project_obj.targets.select { |target| target.respond_to?(:product_type) && !target.product_type.nil? && target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application]) }.first
764
+ # 命令行工具等无 application target 的工程,回退到第一个可用 native target
765
+ main_target ||= project_obj.targets.find { |target| target.respond_to?(:product_type) && !target.product_type.nil? }
766
+ if main_target.nil?
767
+ raise Informative, "未找到可编译的 target"
768
+ end
769
+ # 命令行工具通常无 provisioning profile,做 nil 保护
770
+ profile_specifier = main_target.build_configurations.first.build_settings['PROVISIONING_PROFILE_SPECIFIER']
771
+ provisioning_profile_name = profile_specifier.nil? ? "" : profile_specifier.downcase
740
772
 
741
773
  # 确定构建类型和 iCloud 环境
742
774
  build_type = "app-store"
@@ -114,6 +114,14 @@ module Pindo
114
114
  end
115
115
  end
116
116
 
117
+ # 是否应跳过 Icon 替换:工程既无 AppIcon 目录、又是命令行工具时才跳过
118
+ def skip_icon_replacement?(proj_dir:nil)
119
+ xcodeproj_file_name = Dir.glob(File.join(proj_dir, "/*.xcodeproj")).max_by {|f| File.mtime(f)}
120
+ return false if xcodeproj_file_name.nil?
121
+ xcodereshandler = XcodeResHandler.new(proj_fullname: xcodeproj_file_name)
122
+ !xcodereshandler.has_app_icon_dir? && xcodereshandler.command_line_tool?
123
+ end
124
+
117
125
  def install_icon(proj_dir:nil, new_icon_dir:nil)
118
126
  xcodeproj_file_name = Dir.glob(File.join(proj_dir, "/*.xcodeproj")).max_by {|f| File.mtime(f)}
119
127
  xcodereshandler = XcodeResHandler.new(proj_fullname: xcodeproj_file_name)
@@ -95,17 +95,26 @@ module Pindo
95
95
  end
96
96
 
97
97
  # 加载 all_bundleid_group 映射表
98
- # @return [Hash] group_name => bundle_id 的映射,如 {"fancyapptest" => "com.heroneverdie101.fancyapptest"}
98
+ # @return [Hash, nil] group_name => bundle_id 的映射,如 {"fancyapptest" => "com.heroneverdie101.fancyapptest"};加载失败返回 nil
99
99
  def self.load_bundleid_group
100
100
  config_file = File.join(pindo_env_configdir, 'bundleid_config.json')
101
- return {} unless File.exist?(config_file)
101
+ unless File.exist?(config_file)
102
+ puts "加载 bundleid_config.json 失败: 文件不存在 #{config_file},请运行 pindo setup 更新配置"
103
+ return nil
104
+ end
102
105
 
103
106
  begin
104
107
  config = JSON.parse(File.read(config_file))
105
- config['all_bundleid_group'] || {}
108
+ group = config['all_bundleid_group']
109
+ unless group.is_a?(Hash) && !group.empty?
110
+ puts "加载 bundleid_config.json 失败: all_bundleid_group 缺失或为空 #{config_file},请运行 pindo setup 更新配置"
111
+ return nil
112
+ end
113
+
114
+ group
106
115
  rescue => e
107
- puts "加载 bundleid_config.json 失败: #{e.message}"
108
- {}
116
+ puts "加载 bundleid_config.json 失败: #{e.message},请运行 pindo setup 更新配置"
117
+ nil
109
118
  end
110
119
  end
111
120
 
@@ -116,7 +125,15 @@ module Pindo
116
125
  return nil if group_name.nil? || group_name.empty?
117
126
 
118
127
  group = load_bundleid_group
119
- group[group_name]
128
+ return nil unless group
129
+
130
+ bundle_id = group[group_name]
131
+ if bundle_id.nil? || bundle_id.empty?
132
+ puts "解析 Bundle ID 失败: all_bundleid_group 中未找到 #{group_name},请运行 pindo setup 更新配置"
133
+ return nil
134
+ end
135
+
136
+ bundle_id
120
137
  end
121
138
 
122
139
  # 获取 pindo 环境配置目录
data/lib/pindo/version.rb CHANGED
@@ -6,7 +6,7 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.18.17"
9
+ VERSION = "5.18.20"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pindo
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.18.17
4
+ version: 5.18.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
@@ -526,7 +526,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
526
526
  - !ruby/object:Gem::Version
527
527
  version: 3.0.0
528
528
  requirements: []
529
- rubygems_version: 3.6.9
529
+ rubygems_version: 4.0.11
530
530
  specification_version: 4
531
531
  summary: easy work
532
532
  test_files: []