pindo 5.13.9 → 5.13.10

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/git_handler.rb +247 -42
  3. data/lib/pindo/command/android/autobuild.rb +72 -30
  4. data/lib/pindo/command/android/autoresign.rb +23 -322
  5. data/lib/pindo/command/android/keystore.rb +7 -130
  6. data/lib/pindo/command/appstore/adhocbuild.rb +5 -14
  7. data/lib/pindo/command/appstore/autobuild.rb +64 -14
  8. data/lib/pindo/command/appstore/autoresign.rb +1 -3
  9. data/lib/pindo/command/ios/autobuild.rb +71 -53
  10. data/lib/pindo/command/ios/build.rb +8 -186
  11. data/lib/pindo/command/jps/media.rb +146 -0
  12. data/lib/pindo/command/jps/upload.rb +48 -20
  13. data/lib/pindo/command/jps.rb +1 -0
  14. data/lib/pindo/command/unity/autobuild.rb +99 -27
  15. data/lib/pindo/command/unity/packpush.rb +5 -8
  16. data/lib/pindo/command/utils/repoinit.rb +0 -2
  17. data/lib/pindo/command/utils/tag.rb +58 -26
  18. data/lib/pindo/command/utils.rb +0 -1
  19. data/lib/pindo/command/web/autobuild.rb +71 -37
  20. data/lib/pindo/command.rb +0 -56
  21. data/lib/pindo/config/build_info_manager.rb +7 -8
  22. data/lib/pindo/module/android/android_config_helper.rb +2 -11
  23. data/lib/pindo/module/appselect.rb +15 -41
  24. data/lib/pindo/module/appstore/itcapp_helper.rb +3 -6
  25. data/lib/pindo/module/build/build_helper.rb +28 -18
  26. data/lib/pindo/module/build/git_repo_helper.rb +284 -405
  27. data/lib/pindo/module/cert/pem_helper.rb +3 -6
  28. data/lib/pindo/module/pgyer/pgyerhelper.rb +193 -25
  29. data/lib/pindo/module/task/model/appstore/appstore_task.rb +5 -0
  30. data/lib/pindo/module/task/model/build/android_build_adhoc_task.rb +13 -187
  31. data/lib/pindo/module/task/model/build/android_build_dev_task.rb +36 -34
  32. data/lib/pindo/module/task/model/build/android_build_gplay_task.rb +13 -187
  33. data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +9 -6
  34. data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +9 -6
  35. data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +37 -32
  36. data/lib/pindo/module/task/model/build/web_build_dev_task.rb +7 -5
  37. data/lib/pindo/module/task/model/build_task.rb +8 -11
  38. data/lib/pindo/module/task/model/git/git_commit_task.rb +118 -0
  39. data/lib/pindo/module/task/model/git/git_tag_task.rb +125 -0
  40. data/lib/pindo/module/task/model/git_task.rb +75 -0
  41. data/lib/pindo/module/task/model/jps/jps_message_task.rb +178 -0
  42. data/lib/pindo/module/task/model/{resign → jps}/jps_resign_task.rb +13 -22
  43. data/lib/pindo/module/task/model/jps/jps_upload_media_task.rb +248 -0
  44. data/lib/pindo/module/task/model/jps/jps_upload_task.rb +38 -93
  45. data/lib/pindo/module/task/model/jps_task.rb +43 -0
  46. data/lib/pindo/module/task/model/resign/ipa_local_resign_task.rb +5 -0
  47. data/lib/pindo/module/task/model/unity/unity_config_task.rb +0 -4
  48. data/lib/pindo/module/task/model/unity/unity_export_task.rb +8 -7
  49. data/lib/pindo/module/task/model/unity/unity_update_task.rb +4 -3
  50. data/lib/pindo/module/task/model/unity/unity_yoo_asset_task.rb +8 -7
  51. data/lib/pindo/module/task/model/unity_task.rb +7 -2
  52. data/lib/pindo/module/task/pindo_task.rb +101 -1
  53. data/lib/pindo/module/task/task_manager.rb +29 -32
  54. data/lib/pindo/module/unity/nuget_helper.rb +7 -7
  55. data/lib/pindo/options/core/global_options_state.rb +96 -26
  56. data/lib/pindo/options/core/option_configuration.rb +3 -0
  57. data/lib/pindo/options/core/option_item.rb +36 -0
  58. data/lib/pindo/options/groups/build_options.rb +23 -6
  59. data/lib/pindo/options/groups/git_options.rb +115 -0
  60. data/lib/pindo/options/groups/jps_options.rb +7 -0
  61. data/lib/pindo/options/groups/option_group.rb +15 -0
  62. data/lib/pindo/options/groups/unity_options.rb +49 -0
  63. data/lib/pindo/options/options.rb +2 -0
  64. data/lib/pindo/version.rb +2 -2
  65. metadata +15 -11
  66. data/lib/pindo/base/githelper.rb +0 -686
  67. data/lib/pindo/base/pindocontext.rb +0 -602
  68. data/lib/pindo/command/utils/feishu.rb +0 -134
  69. data/lib/pindo/module/build/version_helper.rb +0 -146
  70. data/lib/pindo/module/task/model/git_tag_task.rb +0 -80
@@ -32,20 +32,23 @@ module Pindo
32
32
  # PindoTask 基类(简化版,所有任务在主线程中执行)
33
33
  class PindoTask
34
34
  attr_accessor :id, :name, :status, :error, :result
35
- attr_reader :type, :priority, :dependencies
35
+ attr_reader :type, :task_key, :priority, :dependencies, :data_dependencies
36
36
  attr_accessor :context, :metadata
37
37
  attr_reader :created_at, :started_at, :finished_at
38
38
  attr_accessor :retry_count # 剩余重试次数
39
39
  attr_reader :retry_mode, :retry_delay, :max_retry_count # max_retry_count: 初始最大重试次数
40
40
  attr_accessor :callbacks_setup # 标记回调是否已经设置
41
+ attr_accessor :task_manager # TaskManager 实例(依赖注入)
41
42
 
42
43
  def initialize(name, options = {})
43
44
  @id = SecureRandom.uuid
44
45
  @name = name
45
46
  @type = self.class.task_type
47
+ @task_key = self.class.task_key
46
48
  @priority = options[:priority] || TaskPriority::HIGH
47
49
  @status = TaskStatus::PENDING
48
50
  @dependencies = options[:dependencies] || []
51
+ @data_dependencies = options[:data_dependencies] || []
49
52
  @context = options[:context] || {}
50
53
  @metadata = options[:metadata] || {}
51
54
  @error = nil
@@ -73,6 +76,10 @@ module Pindo
73
76
  raise NotImplementedError, "Subclass must define task_type"
74
77
  end
75
78
 
79
+ def self.task_key
80
+ raise NotImplementedError, "Subclass must define task_key"
81
+ end
82
+
76
83
  # 默认重试配置
77
84
  def self.default_retry_mode
78
85
  RetryMode::IMMEDIATE
@@ -135,6 +142,93 @@ module Pindo
135
142
  @status == TaskStatus::CANCELLED
136
143
  end
137
144
 
145
+ # ========== 依赖任务数据获取 ==========
146
+
147
+ # 获取指定依赖任务
148
+ # @param dep_task_id [String] 依赖任务的 ID
149
+ # @return [PindoTask, nil] 依赖任务对象,如果不存在返回 nil
150
+ def get_dependency_task(dep_task_id)
151
+ return nil unless @task_manager
152
+ @task_manager.find_task(dep_task_id)
153
+ end
154
+
155
+ # 获取指定依赖任务的结果
156
+ # @param dep_task_id [String] 依赖任务的 ID
157
+ # @return [Hash, nil] 依赖任务的 result,如果任务不存在或未完成返回 nil
158
+ def get_dependency_result(dep_task_id)
159
+ dep_task = get_dependency_task(dep_task_id)
160
+ return nil unless dep_task
161
+ return nil unless dep_task.finished?
162
+ dep_task.result
163
+ end
164
+
165
+ # 获取所有依赖任务的结果(按依赖顺序)
166
+ # @return [Hash] key 为任务 ID,value 为任务结果
167
+ def get_all_dependencies_results
168
+ results = {}
169
+ @dependencies.each do |dep_id|
170
+ results[dep_id] = get_dependency_result(dep_id)
171
+ end
172
+ results
173
+ end
174
+
175
+ # ========== 任务数据参数传递 ==========
176
+
177
+ # 获取任务的数据参数(用于传递给其他任务)
178
+ # @return [Hash] 包含任务标识和参数的哈希
179
+ def data_param
180
+ {
181
+ task_id: @id,
182
+ task_type: @type,
183
+ task_key: @task_key,
184
+ task_name: @name,
185
+ task_param: build_task_param
186
+ }
187
+ end
188
+
189
+ # 获取指定数据依赖任务的数据参数
190
+ # @param task_id [String] 任务 ID
191
+ # @return [Hash, nil] 任务的数据参数,如果任务不存在或未完成返回 nil
192
+ def get_data_param(task_id)
193
+ dep_task = get_dependency_task(task_id)
194
+ return nil unless dep_task
195
+ return nil unless dep_task.finished? && dep_task.status == TaskStatus::SUCCESS
196
+ dep_task.data_param
197
+ end
198
+
199
+ # 获取所有数据依赖任务的数据参数
200
+ # @return [Array<Hash>] 数据参数数组
201
+ def get_all_data_params
202
+ @data_dependencies.map { |task_id| get_data_param(task_id) }.compact
203
+ end
204
+
205
+ # 根据 task_key 获取数据依赖任务的数据参数
206
+ # @param task_key [Symbol] 任务键
207
+ # @return [Hash, nil] 第一个匹配的任务数据参数
208
+ def get_data_param_by_key(task_key)
209
+ @data_dependencies.each do |task_id|
210
+ param = get_data_param(task_id)
211
+ return param if param && param[:task_key] == task_key
212
+ end
213
+ nil
214
+ end
215
+
216
+ # 获取所有指定 task_key 的数据参数
217
+ # @param task_key [Symbol] 任务键
218
+ # @return [Array<Hash>] 匹配的任务数据参数数组
219
+ def get_all_data_params_by_key(task_key)
220
+ get_all_data_params.select { |param| param[:task_key] == task_key }
221
+ end
222
+
223
+ # 获取主数据参数(第一个数据依赖任务的参数)
224
+ # @return [Hash, nil] 主数据参数
225
+ def primary_data_param
226
+ return nil if @data_dependencies.empty?
227
+ get_data_param(@data_dependencies.first)
228
+ end
229
+
230
+ # ========== 回调方法 ==========
231
+
138
232
  # 添加回调
139
233
  def on(event, &block)
140
234
  @callbacks[event] << block if @callbacks[event]
@@ -167,6 +261,12 @@ module Pindo
167
261
  raise NotImplementedError, "Subclass must implement do_work method"
168
262
  end
169
263
 
264
+ # 构建任务参数(子类重写以提供自定义参数)
265
+ # @return [Hash] 任务参数
266
+ def build_task_param
267
+ {} # 默认返回空哈希
268
+ end
269
+
170
270
  private
171
271
 
172
272
  # 内部执行逻辑
@@ -108,6 +108,15 @@ module Pindo
108
108
  task&.cancel
109
109
  end
110
110
 
111
+ # 查找任务(公共方法,供 PindoTask 获取依赖任务使用)
112
+ # @param task_id [String] 任务 ID
113
+ # @return [PindoTask, nil] 任务对象
114
+ def find_task(task_id)
115
+ all_tasks = @pending_queue + @completed_tasks
116
+ all_tasks << @current_task if @current_task
117
+ all_tasks.find { |t| t.id == task_id }
118
+ end
119
+
111
120
  private
112
121
 
113
122
  # 获取下一个可执行的任务
@@ -168,6 +177,9 @@ module Pindo
168
177
  def execute_task(task)
169
178
  @current_task = task
170
179
 
180
+ # 注入 TaskManager 实例(依赖注入)
181
+ task.task_manager = self
182
+
171
183
  # 设置任务进度回调
172
184
  setup_task_callbacks(task)
173
185
 
@@ -224,13 +236,6 @@ module Pindo
224
236
  Funlog.warning("任务 #{task.name} 因#{reason}而被取消")
225
237
  end
226
238
 
227
- # 查找任务
228
- def find_task(task_id)
229
- all_tasks = @pending_queue + @completed_tasks
230
- all_tasks << @current_task if @current_task
231
- all_tasks.find { |t| t.id == task_id }
232
- end
233
-
234
239
  # 输出任务执行计划
235
240
  def print_execution_plan
236
241
  # 按类型分组统计
@@ -242,37 +247,21 @@ module Pindo
242
247
  puts "=" * 60
243
248
 
244
249
  tasks_by_type.each do |type, tasks|
245
- type_name = get_type_display_name(type)
246
- puts " #{type_name}: #{tasks.count} 个任务"
250
+ # 直接从任务类获取显示名称
251
+ type_name = tasks.first&.class&.task_type_name || type.to_s.capitalize
252
+ puts "\n #{type_name}: #{tasks.count} 个任务"
253
+
254
+ # 显示该类型下的每个任务名称
255
+ tasks.each do |task|
256
+ puts " - #{task.name}"
257
+ end
247
258
  end
248
259
 
249
- puts " 总计: #{@pending_queue.count} 个任务"
260
+ puts "\n 总计: #{@pending_queue.count} 个任务"
250
261
  puts "=" * 60 + "\e[0m"
251
262
  puts "\n"
252
263
  end
253
264
 
254
- # 获取任务类型的显示名称
255
- def get_type_display_name(type)
256
- case type
257
- when :git_tag
258
- "Git仓库打标签"
259
- when :unity_config
260
- "Unity编译模式配置"
261
- when :unity_update
262
- "Unity工具库更新"
263
- when :unity_yoo_asset
264
- "Unity资源Yoo 打包"
265
- when :unity_export
266
- "Unity导出"
267
- when :build
268
- "编译构建"
269
- when :upload
270
- "上传发布"
271
- else
272
- type.to_s.capitalize
273
- end
274
- end
275
-
276
265
  # 输出任务执行头部
277
266
  def print_task_header(task)
278
267
  puts "\n"
@@ -293,6 +282,14 @@ module Pindo
293
282
  puts "\n"
294
283
  Funlog.error("任务失败: #{task.name}")
295
284
  Funlog.error("错误信息: #{error.message}")
285
+
286
+ # 打印堆栈信息用于调试(仅在 PINDO_DEBUG 模式下)
287
+ if ENV['PINDO_DEBUG'] && error.backtrace && !error.backtrace.empty?
288
+ puts "\n堆栈信息:"
289
+ error.backtrace.first(10).each do |line|
290
+ puts " #{line}"
291
+ end
292
+ end
296
293
  end
297
294
 
298
295
  # 输出任务底部分隔线
@@ -2,11 +2,11 @@ require 'fileutils'
2
2
  require 'json'
3
3
  require 'nokogiri'
4
4
  require 'open3'
5
+ require 'pindo/base/git_handler'
5
6
 
6
7
  module Pindo
7
8
  module Unity
8
9
  class NugetHelper
9
- extend Pindo::Githelper
10
10
 
11
11
  # ============================================
12
12
  # ID 格式转换
@@ -535,11 +535,11 @@ module Pindo
535
535
  default_message = "feat: 更新版本到 #{nuspec_version}"
536
536
 
537
537
  # 如果不是 Git 仓库,返回默认消息
538
- unless is_git_directory?(local_repo_dir: package_dir)
538
+ unless Pindo::GitHandler.is_git_directory?(local_repo_dir: package_dir)
539
539
  return default_message
540
540
  end
541
541
 
542
- git_root = git_root_directory(local_repo_dir: package_dir)
542
+ git_root = Pindo::GitHandler.git_root_directory(local_repo_dir: package_dir)
543
543
  unless git_root
544
544
  return default_message
545
545
  end
@@ -551,7 +551,7 @@ module Pindo
551
551
  Dir.chdir(git_root)
552
552
 
553
553
  # 获取所有 tags
554
- all_tags = git!(%W(-C #{git_root} tag -l)).split("\n")
554
+ all_tags = Pindo::GitHandler.git!(%W(-C #{git_root} tag -l)).split("\n")
555
555
 
556
556
  # 查找匹配当前版本的 tag(不区分大小写,支持 v 前缀)
557
557
  matching_tag = all_tags.find do |tag|
@@ -570,7 +570,7 @@ module Pindo
570
570
  Dir.chdir(git_root)
571
571
 
572
572
  # 获取所有 tags 并按版本号排序
573
- all_tags = git!(%W(-C #{git_root} tag -l)).split("\n")
573
+ all_tags = Pindo::GitHandler.git!(%W(-C #{git_root} tag -l)).split("\n")
574
574
  sorted_tags = all_tags.sort_by do |tag|
575
575
  version_str = tag.gsub(/^(v|V|release[\s_-]*)/i, '')
576
576
  version_str.split('.').map(&:to_i)
@@ -598,7 +598,7 @@ module Pindo
598
598
  # 获取最新 tag
599
599
  latest_tag = nil
600
600
  ["v", "V", "release", ""].each do |prefix|
601
- latest_tag = get_latest_version_tag(project_dir: git_root, tag_prefix: prefix)
601
+ latest_tag = Pindo::GitHandler.get_latest_version_tag(project_dir: git_root, tag_prefix: prefix)
602
602
  break if latest_tag
603
603
  end
604
604
 
@@ -615,7 +615,7 @@ module Pindo
615
615
 
616
616
  # 使用特殊分隔符来区分不同的 commits
617
617
  separator = "---COMMIT-SEPARATOR---"
618
- commits_raw = git!(%W(-C #{git_root} log #{git_range} --pretty=format:%B#{separator}))
618
+ commits_raw = Pindo::GitHandler.git!(%W(-C #{git_root} log #{git_range} --pretty=format:%B#{separator}))
619
619
 
620
620
  # 按分隔符拆分成单个 commits
621
621
  commits = commits_raw.split(separator).map(&:strip).reject(&:empty?)
@@ -11,6 +11,14 @@ module Pindo
11
11
  class GlobalOptionsState
12
12
  include Singleton
13
13
 
14
+ # 所有已知的 OptionGroup 模块(用于查找参数显示名称)
15
+ OPTION_GROUPS = [
16
+ -> { Pindo::Options::BuildOptions },
17
+ -> { Pindo::Options::JPSOptions },
18
+ -> { Pindo::Options::UnityOptions },
19
+ -> { Pindo::Options::GitOptions }
20
+ ].freeze
21
+
14
22
  def initialize
15
23
  # 运行时状态(内存)
16
24
  @current_command = nil # 当前命令名称
@@ -40,10 +48,23 @@ module Pindo
40
48
  end
41
49
 
42
50
  # 加载缓存的参数值(带用户确认)
51
+ # 环境变量 PINDO_OPTIONS_CACHE 控制缓存行为:
52
+ # - 1/true/force: 强制使用缓存,不询问用户
53
+ # - 0/false/disable: 禁用缓存,不询问用户
54
+ # - 其他/不设置: 默认行为,询问用户
43
55
  # @return [Hash] 缓存的参数值
44
56
  def load_cached_values
45
57
  return {} unless @current_directory && @current_command
46
58
 
59
+ # 检查缓存控制环境变量
60
+ cache_mode = ENV['PINDO_OPTIONS_CACHE']&.downcase
61
+
62
+ # 禁用缓存模式: 0, false, disable
63
+ if %w[0 false disable].include?(cache_mode)
64
+ log_verbose("PINDO_OPTIONS_CACHE=#{cache_mode},跳过缓存")
65
+ return {}
66
+ end
67
+
47
68
  cached_params = @cache_data.dig(@current_directory.to_sym, @current_command.to_sym)
48
69
 
49
70
  # 没有缓存数据,直接返回空Hash
@@ -52,17 +73,14 @@ module Pindo
52
73
  return {}
53
74
  end
54
75
 
55
- # 检查环境变量是否强制使用缓存
56
- force_build = ENV['PINDO_FORCE_BUILD']
57
-
58
- if force_build && !force_build.empty?
59
- # 自动使用缓存
60
- puts "\n检测到 PINDO_FORCE_BUILD 环境变量,自动使用缓存的参数"
76
+ # 强制使用缓存模式: 1, true, force
77
+ if %w[1 true force].include?(cache_mode)
78
+ puts "\n自动使用缓存的参数 (PINDO_OPTIONS_CACHE=#{cache_mode})"
61
79
  log_verbose("加载缓存参数: #{cached_params.inspect}")
62
80
  return cached_params
63
81
  end
64
82
 
65
- # 显示缓存的参数
83
+ # 默认模式:显示缓存的参数并询问用户
66
84
  display_cached_params(cached_params)
67
85
 
68
86
  # 询问用户是否使用缓存
@@ -82,6 +100,20 @@ module Pindo
82
100
  end
83
101
  end
84
102
 
103
+ # 参数显示顺序(优先级从高到低)
104
+ PARAM_DISPLAY_ORDER = [
105
+ # 1. 核心标识参数
106
+ :bundleid, :bundle_id, :bundle_name,
107
+ # 2. 构建配置
108
+ :build_type, :scheme,
109
+ # 3. JPS 相关
110
+ :proj, :upload, :send, :desc,
111
+ # 4. Unity 相关
112
+ :skipconfig, :skiplib, :skipyoo,
113
+ # 5. Git 相关
114
+ :ver_inc, :tag_type, :tag_pre, :release_branch
115
+ ].freeze
116
+
85
117
  # 显示缓存的参数
86
118
  def display_cached_params(cached_params)
87
119
  # 根据命令名显示友好的描述
@@ -99,10 +131,18 @@ module Pindo
99
131
  puts "\n检测到之前的参数 (#{group_desc}):"
100
132
  puts "────────────────────────────────────────"
101
133
 
102
- cached_params.each do |key, value|
134
+ # 按照预定义顺序排序参数
135
+ sorted_keys = cached_params.keys.sort_by do |key|
136
+ order_index = PARAM_DISPLAY_ORDER.index(key.to_sym)
137
+ order_index || PARAM_DISPLAY_ORDER.size # 未定义的参数排在最后
138
+ end
139
+
140
+ sorted_keys.each do |key|
141
+ value = cached_params[key]
103
142
  # 跳过内部字段
104
143
  next if key.to_s.start_with?('__')
105
144
  next if value.nil?
145
+ next unless is_cacheable?(key) # 跳过不可缓存的参数
106
146
 
107
147
  # 格式化显示参数
108
148
  key_name = format_param_name(key)
@@ -112,24 +152,28 @@ module Pindo
112
152
  puts "────────────────────────────────────────"
113
153
  end
114
154
 
115
- # 格式化参数名称
155
+ # 格式化参数名称(从 OptionItem 定义中查找显示名称)
156
+ # @param key [Symbol, String] 参数键名
157
+ # @return [String] 显示名称
116
158
  def format_param_name(key)
117
- case key.to_s
118
- when 'bundleid', 'bundle_id'
119
- 'Bundle ID'
120
- when 'build_type'
121
- '构建类型'
122
- when 'upload'
123
- '上传JPS'
124
- when 'send'
125
- '发送通知'
126
- when 'proj', 'project_name'
127
- '项目名称'
128
- when 'scheme'
129
- 'Scheme'
130
- else
131
- key.to_s
159
+ key_sym = key.to_sym
160
+
161
+ # 遍历所有已知的 OptionGroup,查找匹配的 OptionItem
162
+ OPTION_GROUPS.each do |group_proc|
163
+ begin
164
+ group = group_proc.call
165
+ if group.respond_to?(:all_options)
166
+ option_item = group.all_options[key_sym]
167
+ return option_item.display_name if option_item
168
+ end
169
+ rescue NameError
170
+ # 模块尚未加载,跳过
171
+ next
172
+ end
132
173
  end
174
+
175
+ # 如果没有找到匹配的 OptionItem,返回 key 本身
176
+ key.to_s
133
177
  end
134
178
 
135
179
  # 清除当前命令的缓存
@@ -235,10 +279,12 @@ module Pindo
235
279
  # 确保缓存数据结构存在
236
280
  @cache_data[@current_directory.to_sym] ||= {}
237
281
 
238
- # 提取当前参数值(排除 nil 值)
282
+ # 提取当前参数值(排除 nil 值和不可缓存的参数)
239
283
  current_params = {}
240
284
  @current_options.instance_variable_get(:@values).each do |key, value|
241
- current_params[key] = value unless value.nil?
285
+ next if value.nil?
286
+ next unless is_cacheable?(key) # 过滤不可缓存的参数
287
+ current_params[key] = value
242
288
  end
243
289
 
244
290
  # 保存到缓存
@@ -248,6 +294,30 @@ module Pindo
248
294
  save_cache_to_file_immediate
249
295
  end
250
296
 
297
+ # 判断参数是否可缓存
298
+ # @param key [Symbol, String] 参数键名
299
+ # @return [Boolean] 是否可缓存
300
+ def is_cacheable?(key)
301
+ key_sym = key.to_sym
302
+
303
+ # 遍历所有已知的 OptionGroup,查找匹配的 OptionItem
304
+ OPTION_GROUPS.each do |group_proc|
305
+ begin
306
+ group = group_proc.call
307
+ if group.respond_to?(:all_options)
308
+ option_item = group.all_options[key_sym]
309
+ return option_item.cacheable? if option_item
310
+ end
311
+ rescue NameError
312
+ # 模块尚未加载,跳过
313
+ next
314
+ end
315
+ end
316
+
317
+ # 如果没有找到匹配的 OptionItem,默认可缓存
318
+ true
319
+ end
320
+
251
321
  # 立即保存缓存数据到文件(内部方法)
252
322
  def save_cache_to_file_immediate
253
323
  begin
@@ -133,6 +133,9 @@ module Pindo
133
133
  # 应用 value_block(交互式获取值)
134
134
  # 优先级:命令行参数 > 缓存(已在 raw_values) > value_block > 默认值
135
135
  def apply_value_blocks
136
+ # 检测是否是 help 请求,跳过交互式选择
137
+ return if ARGV.include?('--help') || ARGV.include?('-h')
138
+
136
139
  @available_options.each do |item|
137
140
  # 只有当参数没有值时,才调用 value_block
138
141
  next if @values.key?(item.key) && !@values[item.key].nil?
@@ -5,12 +5,14 @@ module Pindo
5
5
 
6
6
  # 核心属性
7
7
  attr_accessor :key # Symbol: 参数键名
8
+ attr_accessor :name # String: 显示名称(用于缓存确认等场景)
8
9
  attr_accessor :description # String: 参数描述
9
10
  attr_accessor :type # Class: 数据类型 (String/Integer/Boolean)
10
11
  attr_accessor :env_name # String: 环境变量名
11
12
  attr_accessor :aliases # Array<Symbol>: 参数别名
12
13
  attr_accessor :default_value # Any: 默认值
13
14
  attr_accessor :optional # Boolean: 是否可选(默认为 true)
15
+ attr_accessor :cacheable # Boolean: 是否存入缓存(默认为 true)
14
16
  attr_accessor :verify_block # Proc: 自定义验证逻辑
15
17
  attr_accessor :value_block # Proc: 获取参数值的 block(交互式输入)
16
18
  attr_accessor :example # String: 使用示例
@@ -25,12 +27,14 @@ module Pindo
25
27
  raise ArgumentError, "key must be a Symbol" unless key.is_a?(Symbol)
26
28
 
27
29
  @key = key
30
+ @name = options[:name] # 显示名称,如果未设置则使用 key
28
31
  @description = options[:description] || options[:desc] || ""
29
32
  @type = options[:type] || String
30
33
  @env_name = options[:env_name]
31
34
  @aliases = options[:aliases] || []
32
35
  @default_value = options[:default_value] || options[:default]
33
36
  @optional = options.fetch(:optional, true)
37
+ @cacheable = options.fetch(:cacheable, false) # 默认不存入缓存
34
38
  @verify_block = options[:verify_block]
35
39
  @value_block = options[:value_block]
36
40
  @example = options[:example]
@@ -38,11 +42,21 @@ module Pindo
38
42
  validate_type!
39
43
  end
40
44
 
45
+ # 获取显示名称(优先使用 name,否则使用 key)
46
+ def display_name
47
+ @name || @key.to_s
48
+ end
49
+
41
50
  # 判断是否是 Boolean 类型
42
51
  def boolean?
43
52
  @type == Boolean || @type == :boolean
44
53
  end
45
54
 
55
+ # 判断是否需要存入缓存
56
+ def cacheable?
57
+ @cacheable
58
+ end
59
+
46
60
  # 从环境变量读取值
47
61
  # @return [String, nil] 环境变量的值
48
62
  def fetch_env_value
@@ -98,6 +112,28 @@ module Pindo
98
112
  [option_string, description_text]
99
113
  end
100
114
 
115
+ # 复制当前 OptionItem 并覆盖指定属性
116
+ # @param overrides [Hash] 要覆盖的属性
117
+ # @return [OptionItem] 新的 OptionItem 实例
118
+ # @example
119
+ # UnityOptions.select(:skipconfig).first.with(default_value: true)
120
+ def with(**overrides)
121
+ OptionItem.new(
122
+ key: overrides[:key] || @key,
123
+ name: overrides[:name] || @name,
124
+ description: overrides[:description] || @description,
125
+ type: overrides[:type] || @type,
126
+ env_name: overrides[:env_name] || @env_name,
127
+ aliases: overrides[:aliases] || @aliases,
128
+ default_value: overrides.key?(:default_value) ? overrides[:default_value] : @default_value,
129
+ optional: overrides.key?(:optional) ? overrides[:optional] : @optional,
130
+ cacheable: overrides.key?(:cacheable) ? overrides[:cacheable] : @cacheable,
131
+ verify_block: overrides[:verify_block] || @verify_block,
132
+ value_block: overrides[:value_block] || @value_block,
133
+ example: overrides[:example] || @example
134
+ )
135
+ end
136
+
101
137
  private
102
138
 
103
139
  # 验证类型是否合法
@@ -11,26 +11,42 @@ module Pindo
11
11
  def self.all_options
12
12
 
13
13
  @all_options ||= {
14
-
14
+
15
15
  bundleid: OptionItem.new(
16
16
  key: :bundleid,
17
- description: '指定打包的 Bundle ID',
17
+ name: 'Bundle ID',
18
+ description: '指定 iOS Bundle ID',
18
19
  type: String,
19
20
  env_name: 'PINDO_BUNDLE_ID',
20
- aliases: [:bundle_id, :package_name, :bundle_name],
21
21
  optional: true,
22
+ cacheable: true, # 存入文件缓存
22
23
  verify_block: proc do |value|
23
- # 支持 iOS 格式 (com.example.app) 和 Android 格式 (com.example.app)
24
- # iOS 可以包含大写字母、连字符和通配符(*)
25
24
  unless value =~ /^[a-zA-Z0-9\-\.\*]+$/
26
- raise "Bundle ID/Package Name 格式错误: #{value},应该类似 com.example.app 或 com.example.*"
25
+ raise "Bundle ID 格式错误: #{value},应该类似 com.example.app"
27
26
  end
28
27
  end,
29
28
  example: 'pindo ios autobuild --bundleid=com.example.app'
30
29
  ),
31
30
 
31
+ bundle_name: OptionItem.new(
32
+ key: :bundle_name,
33
+ name: 'Package Name',
34
+ description: '指定 Android Package Name',
35
+ type: String,
36
+ env_name: 'PINDO_BUNDLE_NAME',
37
+ optional: true,
38
+ cacheable: true, # 存入文件缓存
39
+ verify_block: proc do |value|
40
+ unless value =~ /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i
41
+ raise "Package Name 格式错误: #{value},应该类似 com.example.app"
42
+ end
43
+ end,
44
+ example: 'pindo android autobuild --bundle_name=com.example.app'
45
+ ),
46
+
32
47
  build_type: OptionItem.new(
33
48
  key: :build_type,
49
+ name: '构建类型',
34
50
  description: '指定构建类型(dev/adhoc/release)',
35
51
  type: String,
36
52
  env_name: 'PINDO_BUILD_TYPE',
@@ -47,6 +63,7 @@ module Pindo
47
63
 
48
64
  scheme: OptionItem.new(
49
65
  key: :scheme,
66
+ name: 'Scheme',
50
67
  description: '指定构建 Scheme',
51
68
  type: String,
52
69
  env_name: 'PINDO_SCHEME',