pindo 5.17.4 → 5.18.3

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/git_handler.rb +120 -38
  3. data/lib/pindo/command/android/autobuild.rb +92 -31
  4. data/lib/pindo/command/appstore/adhocbuild.rb +1 -1
  5. data/lib/pindo/command/appstore/autobuild.rb +1 -1
  6. data/lib/pindo/command/appstore/autoresign.rb +1 -1
  7. data/lib/pindo/command/appstore/updateid.rb +229 -0
  8. data/lib/pindo/command/appstore.rb +1 -0
  9. data/lib/pindo/command/ios/autobuild.rb +70 -33
  10. data/lib/pindo/command/ios/podpush.rb +1 -1
  11. data/lib/pindo/command/unity/autobuild.rb +38 -18
  12. data/lib/pindo/command/utils/allcopyconfig.rb +144 -0
  13. data/lib/pindo/command/utils/copyconfig.rb +207 -0
  14. data/lib/pindo/command/utils/icon.rb +2 -2
  15. data/lib/pindo/command/utils/renewbundleid.rb +199 -0
  16. data/lib/pindo/command/utils/renewcert.rb +56 -54
  17. data/lib/pindo/command/utils.rb +3 -0
  18. data/lib/pindo/command/web/autobuild.rb +10 -8
  19. data/lib/pindo/config/build_info_manager.rb +1 -3
  20. data/lib/pindo/module/android/android_build_helper.rb +198 -33
  21. data/lib/pindo/module/android/android_config_helper.rb +305 -88
  22. data/lib/pindo/module/android/android_project_helper.rb +124 -14
  23. data/lib/pindo/module/android/android_res_helper.rb +349 -51
  24. data/lib/pindo/module/android/keystore_helper.rb +611 -295
  25. data/lib/pindo/module/android/workflow_gradle_injector.rb +702 -0
  26. data/lib/pindo/module/appselect.rb +4 -4
  27. data/lib/pindo/module/appstore/bundleid_helper.rb +204 -14
  28. data/lib/pindo/module/build/build_helper.rb +76 -10
  29. data/lib/pindo/module/build/git_repo_helper.rb +4 -4
  30. data/lib/pindo/module/cert/mode/base_cert_operator.rb +12 -6
  31. data/lib/pindo/module/pgyer/pgyerhelper.rb +124 -39
  32. data/lib/pindo/module/task/model/build/android_build_dev_task.rb +64 -6
  33. data/lib/pindo/module/task/model/git/git_commit_task.rb +70 -54
  34. data/lib/pindo/module/task/model/git/git_tag_task.rb +13 -9
  35. data/lib/pindo/module/task/model/jps/jps_upload_task.rb +110 -3
  36. data/lib/pindo/module/task/model/unity/unity_export_task.rb +2 -1
  37. data/lib/pindo/module/task/model/unity/unity_update_task.rb +2 -1
  38. data/lib/pindo/module/task/model/unity/unity_yoo_asset_task.rb +2 -1
  39. data/lib/pindo/module/task/model/unity_task.rb +2 -1
  40. data/lib/pindo/module/unity/unity_helper.rb +13 -10
  41. data/lib/pindo/module/unity/unity_proc_helper.rb +27 -2
  42. data/lib/pindo/module/xcode/applovin_xcode_helper.rb +6 -2
  43. data/lib/pindo/module/xcode/res/xcode_res_constant.rb +72 -0
  44. data/lib/pindo/module/xcode/res/xcode_res_handler.rb +3 -3
  45. data/lib/pindo/module/xcode/xcode_build_config.rb +46 -17
  46. data/lib/pindo/module/xcode/xcode_build_helper.rb +186 -25
  47. data/lib/pindo/module/xcode/xcode_project_helper.rb +1 -1
  48. data/lib/pindo/module/xcode/xcode_res_helper.rb +32 -16
  49. data/lib/pindo/options/groups/build_options.rb +5 -5
  50. data/lib/pindo/options/groups/git_options.rb +7 -5
  51. data/lib/pindo/options/groups/unity_options.rb +11 -0
  52. data/lib/pindo/options/helpers/bundleid_selector.rb +25 -0
  53. data/lib/pindo/options/helpers/git_constants.rb +7 -6
  54. data/lib/pindo/version.rb +3 -3
  55. metadata +12 -7
@@ -95,7 +95,7 @@ module Pindo
95
95
 
96
96
  project_fullname = Dir.glob(File.join(project_dir, "/*.xcodeproj")).max_by {|f| File.mtime(f)}
97
97
  project_obj = Xcodeproj::Project.open(project_fullname)
98
- select_target = project_obj.targets.select { |target| target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application]) }.first
98
+ select_target = project_obj.targets.select { |target| target.respond_to?(:product_type) && target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application]) }.first
99
99
  file_ref = select_target.resources_build_phase.files_references.select { |file| file.display_name.include?("GoogleService-Info.plist") }.first
100
100
 
101
101
  if !file_ref.nil?
@@ -113,10 +113,10 @@ module Pindo
113
113
  end
114
114
  end
115
115
 
116
- # 获取 Target 名称映射
116
+ # 获取扩展 Target 名称后缀到 config key 的映射
117
+ # 注意:MainTarget 不在此映射中,主应用 target 直接使用 app_identifier
117
118
  def get_target_name_map
118
119
  return {
119
- "MainTarget" => "bundle_id",
120
120
  "Content" => "bundle_id_pushcontent",
121
121
  "Service" => "bundle_id_pushservice",
122
122
  "Keyboard" => "bundle_id_keyboard",
@@ -143,24 +143,47 @@ module Pindo
143
143
  end
144
144
 
145
145
  project_obj.targets.each do |target|
146
+ # 跳过非 native target(如 PBXAggregateTarget)
147
+ next unless target.respond_to?(:product_type)
146
148
 
147
149
  exe_binary_name = config_json['app_info']['app_display_name']
148
150
  exe_binary_name = exe_binary_name.gsub(/ /, '');
149
151
  exe_binary_name = exe_binary_name.gsub(/\'/, '');
150
152
 
153
+ target_name_map = get_target_name_map
154
+
155
+ # 根据 target 类型确定 bundle_id
156
+ target_bundle_id = nil
157
+ if target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application])
158
+ # 主应用 target:使用 app_identifier
159
+ target_bundle_id = config_json['app_info']['app_identifier']
160
+ else
161
+ # 扩展 target:从 target_name_map 查找对应的 bundle_id
162
+ # target_name_map 的 value 是 bundle_id_* 格式,config_json 中实际字段名是 app_identifier_*
163
+ target_name_map.each do |suffix, config_key|
164
+ if target.name.to_s.end_with?(suffix)
165
+ json_key = config_key.sub('bundle_id_', 'app_identifier_')
166
+ target_bundle_id = config_json['app_info'][json_key]
167
+ break
168
+ end
169
+ end
170
+ end
171
+
151
172
  target.build_configurations.each do |config|
152
173
  config.build_settings['CURRENT_PROJECT_VERSION'] = config_json['app_info']['app_build_version']
153
174
  config.build_settings['MARKETING_VERSION'] = config_json['app_info']['app_version']
154
175
  config.build_settings['INFOPLIST_KEY_CFBundleDisplayName'] = config_json['app_info']['app_display_name']
176
+ if target_bundle_id && !target_bundle_id.empty? && !target_bundle_id.include?("*")
177
+ config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = target_bundle_id
178
+ end
155
179
  end
156
180
 
157
- target_name_map = get_target_name_map
158
- if target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application])
181
+ if target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application])
159
182
  target.build_configurations.each do |config|
160
183
  config.build_settings['PRODUCT_NAME'] = exe_binary_name
161
184
  config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = ios_deployment_targe
162
185
  end
163
- elsif
186
+ else
164
187
  target_name_map.each do |k, v|
165
188
  if target.name.to_s.end_with?(k)
166
189
  target.build_configurations.each do |config|
@@ -321,33 +344,171 @@ module Pindo
321
344
 
322
345
  end
323
346
 
347
+ # 解析 INFOPLIST_FILE 路径,处理 Xcode 变量和引号
348
+ # @param project_dir [String] 项目目录
349
+ # @param raw_path [String] INFOPLIST_FILE 原始值
350
+ # @param target [Xcodeproj::Project::Object::PBXNativeTarget, nil] target(用于解析 TARGET_NAME 等变量)
351
+ # @return [String, nil] 解析后的绝对路径,文件不存在时返回 nil
352
+ def resolve_info_plist_path(project_dir, raw_path, target: nil)
353
+ return nil if raw_path.nil? || raw_path.empty?
354
+
355
+ # 去除首尾引号
356
+ resolved = raw_path.gsub(/\A["']|["']\z/, '')
357
+
358
+ # 解析 Xcode 变量
359
+ resolved = resolved
360
+ .gsub('$(SRCROOT)', project_dir)
361
+ .gsub('${SRCROOT}', project_dir)
362
+ .gsub('$(PROJECT_DIR)', project_dir)
363
+ .gsub('${PROJECT_DIR}', project_dir)
364
+
365
+ # 解析 target 相关变量
366
+ if target
367
+ target_name = target.name.to_s
368
+ resolved = resolved
369
+ .gsub('$(TARGET_NAME)', target_name)
370
+ .gsub('${TARGET_NAME}', target_name)
371
+
372
+ # PRODUCT_NAME 从 build settings 获取,降级到 TARGET_NAME
373
+ product_name = target.build_configurations.first.build_settings['PRODUCT_NAME'] || target_name
374
+ # PRODUCT_NAME 本身可能是 $(TARGET_NAME),递归替换
375
+ product_name = product_name
376
+ .gsub('$(TARGET_NAME)', target_name)
377
+ .gsub('${TARGET_NAME}', target_name)
378
+ resolved = resolved
379
+ .gsub('$(PRODUCT_NAME)', product_name)
380
+ .gsub('${PRODUCT_NAME}', product_name)
381
+ end
382
+
383
+ # 如果仍有未解析的变量,尝试去掉变量部分做 glob 匹配
384
+ if resolved.match?(/\$[\({]/)
385
+ # 将未解析的变量替换为通配符,尝试 glob 查找
386
+ glob_pattern = resolved.gsub(/\$[\({][^)\}]+[\)}]/, '*')
387
+ glob_path = File.absolute_path?(glob_pattern) ? glob_pattern : File.join(project_dir, glob_pattern)
388
+ matches = Dir.glob(glob_path)
389
+ return matches.first if matches.size == 1
390
+ if matches.size > 1
391
+ Funlog.instance.fancyinfo_warning("INFOPLIST_FILE 变量解析后匹配到多个文件: #{matches.join(', ')},跳过")
392
+ end
393
+ return nil
394
+ end
395
+
396
+ # 如果是相对路径,拼接 project_dir
397
+ abs_path = File.absolute_path?(resolved) ? resolved : File.join(project_dir, resolved)
398
+ File.exist?(abs_path) ? abs_path : nil
399
+ end
400
+
401
+ # 确保 Info.plist 文件存在,不存在时自动创建并配置 Xcode 工程
402
+ # 仅在需要写入复杂配置(URL Schemes 等)时调用
403
+ # @param project_dir [String] 项目目录
404
+ # @param target [Xcodeproj::Project::Object::PBXNativeTarget] Xcode target
405
+ # @param project_obj [Xcodeproj::Project] Xcode 工程对象
406
+ # @return [String, nil] Info.plist 文件路径
407
+ def ensure_info_plist_exists(project_dir:, target:, project_obj:)
408
+ # 1. 尝试从 build settings 获取已有路径
409
+ raw_path = target.build_configurations.first.build_settings['INFOPLIST_FILE']
410
+ existing_path = resolve_info_plist_path(project_dir, raw_path, target: target)
411
+ return existing_path if existing_path
412
+
413
+ # 2. 检查是否是 GENERATE_INFOPLIST_FILE=YES 的合法模式
414
+ generate_flag = target.build_configurations.first.build_settings['GENERATE_INFOPLIST_FILE']
415
+ is_generated_mode = (generate_flag.to_s.upcase == 'YES') && (raw_path.nil? || raw_path.empty?)
416
+
417
+ # 3. 创建 Info.plist(GENERATE_INFOPLIST_FILE 模式下作为补充 plist,Xcode 会合并)
418
+ target_name = target.name.to_s
419
+ # 优先使用原始 INFOPLIST_FILE 路径(用已知变量替换后)创建,避免覆盖工程配置
420
+ if raw_path && !raw_path.empty?
421
+ # 用 resolve 中相同的变量替换逻辑解析路径(不检查文件是否存在)
422
+ resolved_raw = raw_path
423
+ .gsub(/\A["']|["']\z/, '')
424
+ .gsub('$(SRCROOT)', project_dir).gsub('${SRCROOT}', project_dir)
425
+ .gsub('$(PROJECT_DIR)', project_dir).gsub('${PROJECT_DIR}', project_dir)
426
+ .gsub('$(TARGET_NAME)', target_name).gsub('${TARGET_NAME}', target_name)
427
+ product_name = target.build_configurations.first.build_settings['PRODUCT_NAME'] || target_name
428
+ product_name = product_name.gsub('$(TARGET_NAME)', target_name).gsub('${TARGET_NAME}', target_name)
429
+ resolved_raw = resolved_raw.gsub('$(PRODUCT_NAME)', product_name).gsub('${PRODUCT_NAME}', product_name)
430
+
431
+ # 如果仍含未解析变量,降级到 target_name/Info.plist
432
+ if resolved_raw.match?(/\$[\({]/)
433
+ relative_plist_path = "#{target_name}/Info.plist"
434
+ elsif File.absolute_path?(resolved_raw)
435
+ # 绝对路径转为相对路径
436
+ relative_plist_path = resolved_raw.sub(/\A#{Regexp.escape(project_dir)}\//, '')
437
+ else
438
+ relative_plist_path = resolved_raw
439
+ end
440
+ else
441
+ relative_plist_path = "#{target_name}/Info.plist"
442
+ end
443
+ info_plist_path = File.join(project_dir, relative_plist_path)
444
+
445
+ if is_generated_mode
446
+ Funlog.instance.fancyinfo_warning("Target #{target_name} 使用 GENERATE_INFOPLIST_FILE 模式,创建补充 Info.plist 用于复杂配置")
447
+ else
448
+ Funlog.instance.fancyinfo_warning("Target #{target_name} 未找到 Info.plist,自动创建: #{relative_plist_path}")
449
+ end
450
+
451
+ # 3.1 创建目录和最小 Info.plist
452
+ FileUtils.mkdir_p(File.dirname(info_plist_path))
453
+ empty_plist = {}
454
+ Xcodeproj::Plist.write_to_path(empty_plist, info_plist_path)
455
+
456
+ # 3.2 设置 build settings
457
+ target.build_configurations.each do |config|
458
+ config.build_settings['INFOPLIST_FILE'] = relative_plist_path
459
+ # 保持 GENERATE_INFOPLIST_FILE = YES,Xcode 自动合并
460
+ config.build_settings['GENERATE_INFOPLIST_FILE'] ||= 'YES'
461
+ end
462
+
463
+ # 3.3 将文件添加到 Xcode 工程引用,保持与实际相对路径一致
464
+ plist_dir = File.dirname(relative_plist_path)
465
+ plist_name = File.basename(relative_plist_path)
466
+ target_group =
467
+ if plist_dir == "." || plist_dir.empty?
468
+ project_obj.main_group.find_subpath(target_name, true)
469
+ else
470
+ project_obj.main_group.find_subpath(plist_dir, true)
471
+ end
472
+
473
+ unless target_group.files.any? { |f| f.path == plist_name } ||
474
+ project_obj.files.any? { |f| f.path == relative_plist_path }
475
+ target_group.new_file(plist_name)
476
+ Funlog.instance.fancyinfo_success("已将 Info.plist 添加到 Xcode 工程: #{relative_plist_path}")
477
+ end
478
+
479
+ project_obj.save
480
+
481
+ info_plist_path
482
+ end
483
+
324
484
  def modify_info_plist(project_dir:nil, proj_name:nil, config_json:nil)
325
485
 
326
486
  ## Main Info.plist
327
487
  proj_fullname = File.join(project_dir, proj_name) + ".xcodeproj"
328
488
  project_obj = Xcodeproj::Project.open(proj_fullname)
329
489
 
330
- info_plist_path = nil
331
490
  project_obj.targets.each do |target|
332
- temp_info = target.build_configurations.first.build_settings['INFOPLIST_FILE']
333
- if !temp_info.nil?
334
- info_plist_path = File.join(project_dir, temp_info)
335
- end
336
-
337
- # if target.product_type.to_s.eql?("com.apple.product-type.application") && !File.exist?(info_plist_path)
338
- # info_plist_array = Dir.glob(File.join(project_dir, target.name, "**", "Info.plist"))
339
- # if info_plist_array.size == 1
340
- # info_plist_path = info_plist_array.first
341
- # else
342
- # raise Informative, "Missing Target #{target.name.to_s} Info.plist !!! Modify Info.plist Error !!!"
343
- # end
344
- # end
345
-
346
- if target.product_type.to_s.eql?("com.apple.product-type.application") && (info_plist_path.nil? || !File.exist?(info_plist_path))
347
- raise Informative, "Target #{target.name.to_s} 没有找到Info.plist, 修改Info.plist出错了!!"
491
+ # 跳过非 native target(如 PBXAggregateTarget)
492
+ next unless target.respond_to?(:product_type)
493
+
494
+ is_app_target = target.product_type.to_s.eql?("com.apple.product-type.application")
495
+
496
+ # 主应用 target:确保 Info.plist 存在(需要写入 URL Schemes 等复杂配置)
497
+ # 非主应用 target:仅在已有 Info.plist 时修改,不强制创建
498
+ if is_app_target
499
+ info_plist_path = ensure_info_plist_exists(
500
+ project_dir: project_dir,
501
+ target: target,
502
+ project_obj: project_obj
503
+ )
504
+ else
505
+ raw_path = target.build_configurations.first.build_settings['INFOPLIST_FILE']
506
+ info_plist_path = resolve_info_plist_path(project_dir, raw_path, target: target)
348
507
  end
349
508
 
350
- if target.product_type.to_s.eql?("com.apple.product-type.application") then
509
+ next if info_plist_path.nil?
510
+
511
+ if is_app_target
351
512
  modify_maintarget_info_plist(plist_file_name:info_plist_path, config_json:config_json, target_name:proj_name)
352
513
  end
353
514
 
@@ -513,7 +674,7 @@ module Pindo
513
674
  project_obj = Xcodeproj::Project.open(project_fullname)
514
675
 
515
676
  project_build_platform = project_obj.root_object.build_configuration_list.get_setting("SDKROOT")["Release"]
516
- main_target = project_obj.targets.select { |target| target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application]) }.first
677
+ main_target = project_obj.targets.select { |target| target.respond_to?(:product_type) && target.product_type.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application]) }.first
517
678
  provisioning_profile_name = main_target.build_configurations.first.build_settings['PROVISIONING_PROFILE_SPECIFIER'].downcase
518
679
 
519
680
  # 确定构建类型和 iCloud 环境
@@ -125,7 +125,7 @@ module Pindo
125
125
  config.build_settings['CODE_SIGN_IDENTITY'] = "iPhone Developer"
126
126
  config.build_settings['DEVELOPMENT_TEAM'] = "9WX2E4VC26"
127
127
  config.build_settings['PROVISIONING_PROFILE'] = ""
128
- config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = bundle_id unless bundle_id.eql?("com.heroneverdie101.*")
128
+ config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = bundle_id unless bundle_id.include?("*")
129
129
  config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = "match Development " + bundle_id
130
130
  # config.build_settings['TARGETED_DEVICE_FAMILY'] = "1,2"
131
131
  if is_extention
@@ -8,23 +8,38 @@ module Pindo
8
8
 
9
9
  class XcodeResHelper
10
10
  class << self
11
+ def macos_project?(proj_dir)
12
+ xcodeproj_file_name = Dir.glob(File.join(proj_dir, "*.xcodeproj")).max_by { |f| File.mtime(f) }
13
+ return false if xcodeproj_file_name.nil?
14
+
15
+ project_obj = Xcodeproj::Project.open(xcodeproj_file_name)
16
+ sdkroot_settings = project_obj.root_object.build_configuration_list.get_setting("SDKROOT")
17
+ sdkroot = sdkroot_settings.values.compact.first
18
+ !sdkroot.nil? && sdkroot.eql?("macosx")
19
+ end
20
+
21
+ def image_dimensions(image_data)
22
+ width, height = image_data.fetch("size").split("x").map(&:to_f)
23
+ scale = image_data.fetch("scale", "1x").to_s.chomp("x").to_f
24
+
25
+ [
26
+ (width * scale).to_i,
27
+ (height * scale).to_i
28
+ ]
29
+ end
11
30
 
12
31
  def create_icon(icon_name:nil, new_icon_dir:nil, xcode_icon_json:nil)
13
32
  if !File.exist?(icon_name)
14
33
  raise Informative, "文件不存在:#{icon_name}"
15
34
  end
16
35
  xcode_icon_json["images"].each do |image_data|
17
- width,height = image_data["size"].split("x")
36
+ width, height = image_dimensions(image_data)
18
37
  image_name = image_data["filename"]
19
- iNum = image_name.split("@").last.chomp(".png").chomp("x").to_f
20
- width = width.to_f * iNum
21
- height = height.to_f * iNum
22
-
23
- width = width.to_i
24
- height = height.to_i
38
+ next if image_name.nil? || image_name.empty?
25
39
 
26
40
  command = [
27
41
  'sips',
42
+ '-s', 'format', 'png',
28
43
  '--matchTo', '/System/Library/ColorSync/Profiles/sRGB Profile.icc',
29
44
  '-z', width.to_s, height.to_s,
30
45
  icon_name,
@@ -48,14 +63,9 @@ module Pindo
48
63
  raise Informative, "文件不存在:#{image_icon_name} or #{icon_name}"
49
64
  end
50
65
  xcode_icon_json["images"].each do |image_data|
51
-
52
- height, width = image_data["size"].split("x")
66
+ width, height = image_dimensions(image_data)
53
67
  image_name = image_data["filename"]
54
- iNum = image_name.split("@").last.chomp(".png").chomp("x").to_f
55
- width = width.to_f * iNum
56
- height = height.to_f * iNum
57
- width = width.to_i
58
- height = height.to_i
68
+ next if image_name.nil? || image_name.empty?
59
69
 
60
70
  icon_ori_name = image_icon_name
61
71
  if width.to_s.eql?(height.to_s)
@@ -64,6 +74,7 @@ module Pindo
64
74
 
65
75
  command = [
66
76
  'sips',
77
+ '-s', 'format', 'png',
67
78
  '--matchTo', '/System/Library/ColorSync/Profiles/sRGB Profile.icc',
68
79
  '-z', width.to_s, height.to_s,
69
80
  icon_ori_name,
@@ -77,13 +88,18 @@ module Pindo
77
88
  end
78
89
  end
79
90
 
80
- def create_icons(icon_name:nil, new_icon_dir:nil)
91
+ def create_icons(icon_name:nil, new_icon_dir:nil, proj_dir:nil)
81
92
  begin
82
93
  FileUtils.mkdir_p(new_icon_dir)
83
94
  rescue => e
84
95
  puts e
85
96
  end
86
- create_icon(icon_name: icon_name, new_icon_dir: new_icon_dir, xcode_icon_json: XcodoeResConst.xcode_ios_icon_json)
97
+ xcode_icon_json = if !proj_dir.nil? && macos_project?(proj_dir)
98
+ XcodoeResConst.xcode_macos_icon_json
99
+ else
100
+ XcodoeResConst.xcode_ios_icon_json
101
+ end
102
+ create_icon(icon_name: icon_name, new_icon_dir: new_icon_dir, xcode_icon_json: xcode_icon_json)
87
103
  icon_dir = File.dirname(icon_name)
88
104
  imessage_icon = File.join(icon_dir, "icon1024_768.png")
89
105
  new_imessage_icon_dir = File.join(new_icon_dir, "imessage")
@@ -19,13 +19,13 @@ module Pindo
19
19
  type: String,
20
20
  env_name: 'PINDO_BUNDLE_ID',
21
21
  optional: true,
22
- cacheable: true, # 存入文件缓存
22
+ cacheable: false, # 从 JPSBuildConfig.json 获取,不需要缓存
23
23
  verify_block: proc do |value|
24
24
  unless value =~ /^[a-zA-Z0-9\-\.\*]+$/
25
25
  raise "Bundle ID 格式错误: #{value},应该类似 com.example.app"
26
26
  end
27
27
  end,
28
- example: 'pindo ios autobuild --bundleid=com.example.app'
28
+ example: 'pindo ios autobuild --bundleid=com.example.*'
29
29
  ),
30
30
 
31
31
  bundle_name: OptionItem.new(
@@ -35,14 +35,14 @@ module Pindo
35
35
  type: String,
36
36
  env_name: 'PINDO_BUNDLE_NAME',
37
37
  optional: true,
38
- cacheable: true, # 存入文件缓存
38
+ cacheable: false, # 从 JPSBuildConfig.json 获取,不需要缓存
39
39
  verify_block: proc do |value|
40
40
  # 允许标准格式(com.example.app)或带通配符格式(com.example.*)
41
- unless value =~ /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_\*]*)+$/i
41
+ unless value =~ /\A[a-z][a-z0-9_]*(\.(?:[a-z][a-z0-9_]*|\*))+\z/i
42
42
  raise "Package Name 格式错误: #{value},应该类似 com.example.app 或 com.example.*"
43
43
  end
44
44
  end,
45
- example: 'pindo android autobuild --bundle_name=com.example.app'
45
+ example: 'pindo android autobuild --bundle_name=com.example.*'
46
46
  ),
47
47
 
48
48
  build_type: OptionItem.new(
@@ -81,19 +81,19 @@ module Pindo
81
81
  git_commit: OptionItem.new(
82
82
  key: :git_commit,
83
83
  name: 'Git 未提交处理方式',
84
- description: '处理未提交文件的方式: commit/skip/reset/stash/exit',
84
+ description: '处理未提交文件的方式: commit/skip-with-tag/skip-without-tag/reset/stash/exit',
85
85
  type: String,
86
86
  env_name: 'PINDO_GIT_COMMIT',
87
87
  default_value: nil,
88
88
  optional: true,
89
89
  cacheable: false,
90
90
  verify_block: proc do |value|
91
- allowed = %w[commit skip reset delete stash exit resetde discard]
91
+ allowed = %w[commit skip-with-tag skip-without-tag reset delete stash exit resetde discard]
92
92
  unless allowed.include?(value.to_s.downcase)
93
93
  raise "git_commit 值错误: #{value}, 必须是 #{allowed.join(', ')} 之一"
94
94
  end
95
95
  end,
96
- example: 'pindo unity autobuild --git-commit=skip'
96
+ example: 'pindo unity autobuild --git-commit=skip-with-tag'
97
97
  )
98
98
  }
99
99
  end
@@ -122,8 +122,10 @@ module Pindo
122
122
  case value.to_s.downcase
123
123
  when 'commit', 'comm'
124
124
  Pindo::UncommittedFilesProcessType::COMMIT
125
- when 'skip', 'none', 'dont'
126
- Pindo::UncommittedFilesProcessType::SKIP
125
+ when 'skip-with-tag'
126
+ Pindo::UncommittedFilesProcessType::SKIP_WITH_TAG
127
+ when 'skip-without-tag'
128
+ Pindo::UncommittedFilesProcessType::SKIP_WITHOUT_TAG
127
129
  when 'reset', 'delete', 'resetde', 'discard'
128
130
  Pindo::UncommittedFilesProcessType::RESET
129
131
  when 'stash'
@@ -41,6 +41,17 @@ module Pindo
41
41
  default_value: false,
42
42
  optional: true,
43
43
  example: 'pindo unity autobuild --skipyoo'
44
+ ),
45
+
46
+ kill_unity: OptionItem.new(
47
+ key: :kill_unity,
48
+ name: '自动关闭Unity',
49
+ description: '自动关闭检测到的Unity进程(不询问用户确认)',
50
+ type: OptionItem::Boolean,
51
+ env_name: 'PINDO_KILL_UNITY',
52
+ default_value: false,
53
+ optional: true,
54
+ example: 'pindo unity autobuild --kill-unity'
44
55
  )
45
56
  }
46
57
  end
@@ -94,6 +94,31 @@ module Pindo
94
94
  bundleids
95
95
  end
96
96
 
97
+ # 加载 all_bundleid_group 映射表
98
+ # @return [Hash] group_name => bundle_id 的映射,如 {"fancyapptest" => "com.heroneverdie101.fancyapptest"}
99
+ def self.load_bundleid_group
100
+ config_file = File.join(pindo_env_configdir, 'bundleid_config.json')
101
+ return {} unless File.exist?(config_file)
102
+
103
+ begin
104
+ config = JSON.parse(File.read(config_file))
105
+ config['all_bundleid_group'] || {}
106
+ rescue => e
107
+ puts "加载 bundleid_config.json 失败: #{e.message}"
108
+ {}
109
+ end
110
+ end
111
+
112
+ # 通过 group name 查找真实 bundle ID
113
+ # @param group_name [String] 组名(如 "fancyapptest"、"wildcarddevelopmentid")
114
+ # @return [String, nil] 真实 bundle ID(如 "com.heroneverdie101.fancyapptest"),未找到返回 nil
115
+ def self.resolve_bundleid_by_group_name(group_name)
116
+ return nil if group_name.nil? || group_name.empty?
117
+
118
+ group = load_bundleid_group
119
+ group[group_name]
120
+ end
121
+
97
122
  # 获取 pindo 环境配置目录
98
123
  def self.pindo_env_configdir
99
124
  require_relative '../../config/pindoconfig'
@@ -18,11 +18,12 @@ module Pindo
18
18
  # Git 未提交文件处理类型枚举
19
19
  # 用于控制构建前未提交文件的处理方式
20
20
  module UncommittedFilesProcessType
21
- COMMIT = :commit # 自动提交所有更改
22
- SKIP = :skip # 跳过处理,继续执行
23
- RESET = :reset # 丢弃所有更改,重置到远程
24
- STASH = :stash # 保存到 stash 区域
25
- EXIT = :exit # 退出程序,要求手动处理
26
- NONE = :none # 没有需要提交的内容
21
+ COMMIT = :commit # 自动提交所有更改
22
+ SKIP_WITH_TAG = :skip_with_tag # 不提交,但合并分支并打 tag
23
+ SKIP_WITHOUT_TAG = :skip_without_tag # 不提交,不合并分支,不打 tag
24
+ RESET = :reset # 丢弃所有更改,重置到远程
25
+ STASH = :stash # 保存到 stash 区域
26
+ EXIT = :exit # 退出程序,要求手动处理
27
+ NONE = :none # 没有需要提交的内容
27
28
  end
28
29
  end
data/lib/pindo/version.rb CHANGED
@@ -6,13 +6,13 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.17.4"
9
+ VERSION = "5.18.3"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
13
13
  VERSION_INFO_FILE = File.expand_path('~/.pindo/version_info.yml')
14
14
  CHECK_INTERVAL = 5 * 60 * 60 # 5小时检查一次
15
- CONFIG_MIN_VERSION = '1.5.0' # 硬编码的配置版本要求
15
+ CONFIG_MIN_VERSION = '1.7.0' # 硬编码的配置版本要求
16
16
 
17
17
  class << self
18
18
  # 主版本检查方法(保持向后兼容)
@@ -557,4 +557,4 @@ module Pindo
557
557
  end
558
558
  end
559
559
  end
560
- end
560
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pindo
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.17.4
4
+ version: 5.18.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: claide
@@ -109,20 +109,20 @@ dependencies:
109
109
  requirements:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
- version: '2.0'
112
+ version: '2.2'
113
113
  - - ">="
114
114
  - !ruby/object:Gem::Version
115
- version: 2.0.4
115
+ version: 2.2.0
116
116
  type: :runtime
117
117
  prerelease: false
118
118
  version_requirements: !ruby/object:Gem::Requirement
119
119
  requirements:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
- version: '2.0'
122
+ version: '2.2'
123
123
  - - ">="
124
124
  - !ruby/object:Gem::Version
125
- version: 2.0.4
125
+ version: 2.2.0
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rqrcode
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -304,6 +304,7 @@ files:
304
304
  - lib/pindo/command/appstore/quswauth.rb
305
305
  - lib/pindo/command/appstore/screenshots.rb
306
306
  - lib/pindo/command/appstore/tag.rb
307
+ - lib/pindo/command/appstore/updateid.rb
307
308
  - lib/pindo/command/appstore/upload.rb
308
309
  - lib/pindo/command/env.rb
309
310
  - lib/pindo/command/env/dreamstudio.rb
@@ -348,14 +349,17 @@ files:
348
349
  - lib/pindo/command/unity/packinit.rb
349
350
  - lib/pindo/command/unity/packpush.rb
350
351
  - lib/pindo/command/utils.rb
352
+ - lib/pindo/command/utils/allcopyconfig.rb
351
353
  - lib/pindo/command/utils/boss.rb
352
354
  - lib/pindo/command/utils/clearcert.rb
355
+ - lib/pindo/command/utils/copyconfig.rb
353
356
  - lib/pindo/command/utils/decrypt.rb
354
357
  - lib/pindo/command/utils/device.rb
355
358
  - lib/pindo/command/utils/encrypt.rb
356
359
  - lib/pindo/command/utils/fabric.rb
357
360
  - lib/pindo/command/utils/icon.rb
358
361
  - lib/pindo/command/utils/installskills.rb
362
+ - lib/pindo/command/utils/renewbundleid.rb
359
363
  - lib/pindo/command/utils/renewcert.rb
360
364
  - lib/pindo/command/utils/renewproj.rb
361
365
  - lib/pindo/command/utils/repoinit.rb
@@ -379,6 +383,7 @@ files:
379
383
  - lib/pindo/module/android/gradle_helper.rb
380
384
  - lib/pindo/module/android/java_env_helper.rb
381
385
  - lib/pindo/module/android/keystore_helper.rb
386
+ - lib/pindo/module/android/workflow_gradle_injector.rb
382
387
  - lib/pindo/module/appselect.rb
383
388
  - lib/pindo/module/appstore/appstore_iap_tier.json
384
389
  - lib/pindo/module/appstore/appstore_in_app_purchase.rb
@@ -511,7 +516,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
511
516
  - !ruby/object:Gem::Version
512
517
  version: 3.0.0
513
518
  requirements: []
514
- rubygems_version: 3.6.3
519
+ rubygems_version: 4.0.3
515
520
  specification_version: 4
516
521
  summary: easy work
517
522
  test_files: []