Class: Pindo::IosConfigParser

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/pindo/config/ios_config_parser.rb

Overview

iOS 配置解析器(单例模式)专门用于解析和处理 iOS 项目的 config.json 配置文件

使用示例:

parser = Pindo::IosConfigParser.instance
parser.load_config(config_file: 'path/to/config.json')

# 获取 Apple ID
apple_id = parser.apple_id

# 获取 Bundle ID 映射
bundle_id_map = parser.get_bundle_id_map

# 命令行参数覆盖
parser.override_apple_id('[email protected]')
parser.override_bundle_id('com.example.app')

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeIosConfigParser

Returns a new instance of IosConfigParser.



26
27
28
29
30
31
32
# File 'lib/pindo/config/ios_config_parser.rb', line 26

def initialize
  @config_file_path = nil
  @config_json = nil
  @config_data = {}
  @overrides = {}
  @app_repo_name = nil  # 保存原始 Bundle ID,用于配置仓库路径
end

Instance Attribute Details

#app_repo_nameObject (readonly)

Returns the value of attribute app_repo_name.



24
25
26
# File 'lib/pindo/config/ios_config_parser.rb', line 24

def app_repo_name
  @app_repo_name
end

#config_file_pathObject (readonly)

Returns the value of attribute config_file_path.



24
25
26
# File 'lib/pindo/config/ios_config_parser.rb', line 24

def config_file_path
  @config_file_path
end

#config_jsonObject (readonly)

Returns the value of attribute config_json.



24
25
26
# File 'lib/pindo/config/ios_config_parser.rb', line 24

def config_json
  @config_json
end

Instance Method Details

#app_id_iosString?

获取 App ID (Apple Store Connect)

Returns:

  • (String, nil)


249
250
251
# File 'lib/pindo/config/ios_config_parser.rb', line 249

def app_id_ios
  @config_data[:app_id_ios]
end

#app_nameString?

获取应用名称

Returns:

  • (String, nil)


237
238
239
# File 'lib/pindo/config/ios_config_parser.rb', line 237

def app_name
  @config_data[:app_name]
end

#app_versionString?

获取应用版本

Returns:

  • (String, nil)


243
244
245
# File 'lib/pindo/config/ios_config_parser.rb', line 243

def app_version
  @config_data[:app_version]
end

#apple_idString?

获取 Apple ID

Returns:

  • (String, nil)

    Apple 账号 ID



78
79
80
# File 'lib/pindo/config/ios_config_parser.rb', line 78

def apple_id
  @overrides[:apple_id] || @config_data[:apple_id]
end

#bundle_idString?

获取主应用 Bundle ID

Returns:

  • (String, nil)

    Bundle ID



104
105
106
# File 'lib/pindo/config/ios_config_parser.rb', line 104

def bundle_id
  @overrides[:bundle_id] || @config_data[:bundle_id]
end

#bundle_id_extensionString?

获取通用 Extension Bundle ID

Returns:

  • (String, nil)


140
141
142
# File 'lib/pindo/config/ios_config_parser.rb', line 140

def bundle_id_extension
  @config_data[:bundle_id_extension]
end

#bundle_id_extensionadString?

获取广告 Extension Bundle ID

Returns:

  • (String, nil)


164
165
166
# File 'lib/pindo/config/ios_config_parser.rb', line 164

def bundle_id_extensionad
  @config_data[:bundle_id_extensionad]
end

#bundle_id_extensionpornString?

获取 Porn Extension Bundle ID

Returns:

  • (String, nil)


170
171
172
# File 'lib/pindo/config/ios_config_parser.rb', line 170

def bundle_id_extensionporn
  @config_data[:bundle_id_extensionporn]
end

#bundle_id_imessageString?

获取 iMessage Extension Bundle ID

Returns:

  • (String, nil)


134
135
136
# File 'lib/pindo/config/ios_config_parser.rb', line 134

def bundle_id_imessage
  @config_data[:bundle_id_imessage]
end

#bundle_id_keyboardString?

获取 Keyboard Extension Bundle ID

Returns:

  • (String, nil)


128
129
130
# File 'lib/pindo/config/ios_config_parser.rb', line 128

def bundle_id_keyboard
  @config_data[:bundle_id_keyboard]
end

#bundle_id_pushcontentString?

获取 Push Content Extension Bundle ID

Returns:

  • (String, nil)


116
117
118
# File 'lib/pindo/config/ios_config_parser.rb', line 116

def bundle_id_pushcontent
  @config_data[:bundle_id_pushcontent]
end

#bundle_id_pushserviceString?

获取 Push Service Extension Bundle ID

Returns:

  • (String, nil)


122
123
124
# File 'lib/pindo/config/ios_config_parser.rb', line 122

def bundle_id_pushservice
  @config_data[:bundle_id_pushservice]
end

#bundle_id_siriString?

获取 Siri Extension Bundle ID

Returns:

  • (String, nil)


146
147
148
# File 'lib/pindo/config/ios_config_parser.rb', line 146

def bundle_id_siri
  @config_data[:bundle_id_siri]
end

#bundle_id_siriuiString?

获取 Siri UI Extension Bundle ID

Returns:

  • (String, nil)


152
153
154
# File 'lib/pindo/config/ios_config_parser.rb', line 152

def bundle_id_siriui
  @config_data[:bundle_id_siriui]
end

#bundle_id_watchappString?

获取 Watch App Bundle ID

Returns:

  • (String, nil)


176
177
178
# File 'lib/pindo/config/ios_config_parser.rb', line 176

def bundle_id_watchapp
  @config_data[:bundle_id_watchapp]
end

#bundle_id_watchapp_extensionString?

获取 Watch App Extension Bundle ID

Returns:

  • (String, nil)


182
183
184
# File 'lib/pindo/config/ios_config_parser.rb', line 182

def bundle_id_watchapp_extension
  @config_data[:bundle_id_watchapp_extension]
end

#bundle_id_widgetString?

获取 Widget Extension Bundle ID

Returns:

  • (String, nil)


158
159
160
# File 'lib/pindo/config/ios_config_parser.rb', line 158

def bundle_id_widget
  @config_data[:bundle_id_widget]
end

#clearObject

清除所有配置和覆盖



66
67
68
69
70
71
72
# File 'lib/pindo/config/ios_config_parser.rb', line 66

def clear
  @config_file_path = nil
  @config_json = nil
  @config_data = {}
  @overrides = {}
  @app_repo_name = nil
end

#company_nameString?

获取公司名称

Returns:

  • (String, nil)

    公司名称



90
91
92
# File 'lib/pindo/config/ios_config_parser.rb', line 90

def company_name
  @overrides[:company_name] || @config_data[:company_name]
end

#get_bundle_id_arrayArray<String>

获取 Bundle ID 数组(去除 nil 值)

Returns:

  • (Array<String>)

    Bundle ID 数组



215
216
217
# File 'lib/pindo/config/ios_config_parser.rb', line 215

def get_bundle_id_array
  get_bundle_id_map.values
end

#get_bundle_id_mapHash

获取所有 Bundle ID 的映射

Examples:

{
  "bundle_id" => "com.example.app",
  "bundle_id_pushcontent" => "com.example.app.content",
  "bundle_id_pushservice" => "com.example.app.service",
  ...
}

Returns:

  • (Hash)

    Bundle ID 映射,键为类型,值为 Bundle ID



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/pindo/config/ios_config_parser.rb', line 195

def get_bundle_id_map
  {
    "bundle_id" => bundle_id,
    "bundle_id_pushcontent" => bundle_id_pushcontent,
    "bundle_id_pushservice" => bundle_id_pushservice,
    "bundle_id_keyboard" => bundle_id_keyboard,
    "bundle_id_imessage" => bundle_id_imessage,
    "bundle_id_extension" => bundle_id_extension,
    "bundle_id_siri" => bundle_id_siri,
    "bundle_id_siriui" => bundle_id_siriui,
    "bundle_id_widget" => bundle_id_widget,
    "bundle_id_extensionad" => bundle_id_extensionad,
    "bundle_id_extensionporn" => bundle_id_extensionporn,
    "bundle_id_watchapp" => bundle_id_watchapp,
    "bundle_id_watchapp_extension" => bundle_id_watchapp_extension
  }.compact
end

#group_idString?

获取 App Group ID

Returns:

  • (String, nil)


223
224
225
# File 'lib/pindo/config/ios_config_parser.rb', line 223

def group_id
  @config_data[:group_id]
end

#icloud_idString?

获取 iCloud Container ID

Returns:

  • (String, nil)


229
230
231
# File 'lib/pindo/config/ios_config_parser.rb', line 229

def icloud_id
  @config_data[:icloud_id]
end

#ios_deployment_targetString?

获取 iOS 部署目标版本

Returns:

  • (String, nil)


269
270
271
# File 'lib/pindo/config/ios_config_parser.rb', line 269

def ios_deployment_target
  @config_data[:ios_deployment_target]
end

#load_config(config_file:) ⇒ Object

加载配置文件

Parameters:

  • config_file (String)

    config.json 文件路径

Raises:



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/pindo/config/ios_config_parser.rb', line 37

def load_config(config_file:)
  unless File.exist?(config_file)
    raise "配置文件不存在: #{config_file}"
  end

  @config_file_path = File.expand_path(config_file)

  begin
    @config_json = JSON.parse(File.read(@config_file_path))
  rescue JSON::ParserError => e
    raise "配置文件格式错误: #{e.message}"
  end

  # 解析配置
  parse_config_data

  # 检查配置版本
  check_config_version

  self
end

#loaded?Boolean

检查配置是否已加载

Returns:

  • (Boolean)


293
294
295
# File 'lib/pindo/config/ios_config_parser.rb', line 293

def loaded?
  !@config_json.nil?
end

#modify_config_with_adhoc(adhoc_config_dir:) ⇒ Boolean

合并 AdHoc 发布配置

  1. 替换所有 Bundle ID 为 AdHoc 配置的 Bundle ID(确保使用 AdHoc 证书)

  2. 合并第三方 SDK 配置(Facebook、AdMob、AppLovin)

  3. 合并 app_setting(排除主机和 URL 配置)

Parameters:

  • adhoc_config_dir (String)

    AdHoc 配置仓库目录

Returns:

  • (Boolean)

    是否成功



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/pindo/config/ios_config_parser.rb', line 305

def modify_config_with_adhoc(adhoc_config_dir:)
  return false unless loaded?
  return false if adhoc_config_dir.nil? || adhoc_config_dir.empty?

  begin
    # 1. 读取 AdHoc 配置
    adhoc_config_file = File.join(adhoc_config_dir, "config.json")
    unless File.exist?(adhoc_config_file)
      puts "AdHoc 配置文件不存在: #{adhoc_config_file}"
      return false
    end

    adhoc_config_json = JSON.parse(File.read(adhoc_config_file))

    # 1.5 替换 Apple ID(使用 AdHoc 配置的 Apple ID)
    if adhoc_config_json.dig('account_info', 'apple_acount_id')
      original_apple_id = @config_json.dig('account_info', 'apple_acount_id')
      adhoc_apple_id = adhoc_config_json['account_info']['apple_acount_id']

      if original_apple_id && adhoc_apple_id && original_apple_id != adhoc_apple_id
        @config_json['account_info']['apple_acount_id'] = adhoc_apple_id
        puts "  替换 Apple ID:"
        puts "    原始 Apple ID: #{original_apple_id}"
        puts "    AdHoc Apple ID: #{adhoc_apple_id}"
      end

      # 如果 AdHoc 配置包含 company_name,也一并替换
      if adhoc_config_json.dig('account_info', 'acount_company_name')
        @config_json['account_info']['acount_company_name'] = adhoc_config_json['account_info']['acount_company_name']
      end
    end

    # 2. 替换所有 Bundle ID(使用 AdHoc 配置的 Bundle ID)
    original_bundle_id = @config_json.dig('app_info', 'app_identifier')
    adhoc_bundle_id = adhoc_config_json.dig('app_info', 'app_identifier')

    if original_bundle_id && adhoc_bundle_id && original_bundle_id != adhoc_bundle_id
      puts "  替换 Bundle ID 前缀:"
      puts "    原始前缀: #{original_bundle_id}"
      puts "    AdHoc 前缀: #{adhoc_bundle_id}"

      # Bundle ID 字段映射表
      bundle_id_keys = [
        'app_identifier',              # 主应用
        'app_identifier_pushcontent',  # Push Content Extension
        'app_identifier_pushservice',  # Push Service Extension
        'app_identifier_keyboard',     # Keyboard Extension
        'app_identifier_imessage',     # iMessage Extension
        'app_identifier_extension',    # 通用 Extension
        'app_identifier_siri',         # Siri Extension
        'app_identifier_siriui',       # Siri UI Extension
        'app_identifier_widget',       # Widget Extension
        'app_identifier_extensionad',  # 广告 Extension
        'app_identifier_extensionporn',# Porn Extension
        'app_identifier_watchapp',     # Watch App
        'app_identifier_watchapp_extension' # Watch App Extension
      ]

      # 遍历所有 Bundle ID 字段并替换前缀
      bundle_id_keys.each do |key|
        current_value = @config_json.dig('app_info', key)
        next if current_value.nil? || current_value.empty?

        # 如果当前值以原始主 Bundle ID 开头,替换为 AdHoc Bundle ID
        if current_value.start_with?(original_bundle_id)
          # 计算后缀部分(如 .extension、.keyboard 等)
          suffix = current_value[original_bundle_id.length..-1]
          new_value = adhoc_bundle_id + suffix

          @config_json['app_info'][key] = new_value
          puts "#{key}: #{current_value}#{new_value}"
        end
      end
    end

    # 2.5 替换 App Group ID 和 iCloud Container ID
    if original_bundle_id && adhoc_bundle_id && original_bundle_id != adhoc_bundle_id
      # 处理 app_group_id (格式: group.com.example.app)
      if @config_json.dig('app_setting', 'app_group_id')
        current_group_id = @config_json['app_setting']['app_group_id']
        if current_group_id.include?(original_bundle_id)
          new_group_id = current_group_id.gsub(original_bundle_id, adhoc_bundle_id)
          @config_json['app_setting']['app_group_id'] = new_group_id
          puts "    ✓ app_group_id: #{current_group_id}#{new_group_id}"
        end
      end

      # 处理 app_icloud_id (格式: iCloud.com.example.app)
      if @config_json.dig('app_setting', 'app_icloud_id')
        current_icloud_id = @config_json['app_setting']['app_icloud_id']
        if current_icloud_id.include?(original_bundle_id)
          new_icloud_id = current_icloud_id.gsub(original_bundle_id, adhoc_bundle_id)
          @config_json['app_setting']['app_icloud_id'] = new_icloud_id
          puts "    ✓ app_icloud_id: #{current_icloud_id}#{new_icloud_id}"
        end
      end
    end

    # 3. 合并第三方 SDK 配置
    # Facebook 配置
    if adhoc_config_json.dig('app_info', 'facebook_app_id')
      @config_json['app_info']['facebook_app_id'] = adhoc_config_json['app_info']['facebook_app_id']
      @config_json['app_info']['facebook_client_token'] = adhoc_config_json['app_info']['facebook_client_token']
    end

    # AdMob 配置
    if @config_json.dig('app_info', 'admob_app_id') && adhoc_config_json.dig('app_info', 'admob_app_id')
      @config_json['app_info']['admob_app_id'] = adhoc_config_json['app_info']['admob_app_id']
    end

    # AppLovin 配置
    if @config_json.dig('app_info', 'applovin_app_id') && adhoc_config_json.dig('app_info', 'applovin_app_id')
      @config_json['app_info']['applovin_app_id'] = adhoc_config_json['app_info']['applovin_app_id']
    end

    # 4. 合并 app_setting(排除主机和 URL 配置)
    excluded_keys = ['kGUKeyAppClientHost', 'kGUKeyResBaseUrl', 'app_web_host', 'app_client_url']

    if @config_json['app_setting'] && adhoc_config_json['app_setting']
      @config_json['app_setting'].each_key do |key|
        next if excluded_keys.include?(key)

        if adhoc_config_json['app_setting'][key]
          @config_json['app_setting'][key] = adhoc_config_json['app_setting'][key]
        end
      end
    end

    # 5. 重新解析配置数据,同步更新 @config_data
    # 这样 bundle_id、bundle_id_pushcontent 等属性方法才能返回替换后的值
    parse_config_data

    puts "✅ 已替换 Apple ID、Bundle ID 并合并 AdHoc 配置(Facebook、AdMob、AppLovin)"
    true
  rescue => e
    puts "合并发布配置失败: #{e.message}"
    puts e.backtrace.join("\n")
    false
  end
end

#override_apple_id(value) ⇒ Object

覆盖 Apple ID(命令行参数)

Parameters:

  • value (String)

    Apple ID



84
85
86
# File 'lib/pindo/config/ios_config_parser.rb', line 84

def override_apple_id(value)
  @overrides[:apple_id] = value if value && !value.empty?
end

#override_bundle_id(value) ⇒ Object

覆盖主应用 Bundle ID(命令行参数)

Parameters:

  • value (String)

    Bundle ID



110
111
112
# File 'lib/pindo/config/ios_config_parser.rb', line 110

def override_bundle_id(value)
  @overrides[:bundle_id] = value if value && !value.empty?
end

#override_company_name(value) ⇒ Object

覆盖公司名称

Parameters:

  • value (String)

    公司名称



96
97
98
# File 'lib/pindo/config/ios_config_parser.rb', line 96

def override_company_name(value)
  @overrides[:company_name] = value if value && !value.empty?
end

打印所有配置信息(用于调试)



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/pindo/config/ios_config_parser.rb', line 449

def print_config_info
  puts "\n" + "=" * 80
  puts "iOS 配置信息"
  puts "=" * 80
  puts "配置文件: #{@config_file_path}"
  puts
  puts "【账号信息】"
  puts "  Apple ID: #{apple_id}"
  puts "  Company Name: #{company_name}"
  puts
  puts "【应用信息】"
  puts "  App Name: #{app_name}"
  puts "  App Version: #{app_version}"
  puts "  App ID (iOS): #{app_id_ios}"
  puts
  puts "【Bundle ID】"
  puts "  Main App: #{bundle_id}"
  get_bundle_id_map.each do |type, id|
    next if type == "bundle_id"
    puts "  #{type}: #{id}"
  end
  puts
  puts "【App Group & iCloud】"
  puts "  Group ID: #{group_id}"
  puts "  iCloud ID: #{icloud_id}"
  puts
  puts "【项目信息】"
  puts "  Project Name: #{project_name}"
  puts "  Xcode Build Type: #{xcode_build_type}"
  puts "  iOS Deployment Target: #{ios_deployment_target}"
  puts "=" * 80
  puts
end

#project_nameString?

获取项目名称

Returns:

  • (String, nil)


257
258
259
# File 'lib/pindo/config/ios_config_parser.rb', line 257

def project_name
  @config_data[:project_name]
end

#reloadObject

重新加载配置文件



60
61
62
63
# File 'lib/pindo/config/ios_config_parser.rb', line 60

def reload
  return unless @config_file_path
  load_config(config_file: @config_file_path)
end

#validate_required(*required_keys) ⇒ Object

验证必需的配置项是否存在

Parameters:

  • required_keys (Array<Symbol>)

    必需的配置键

Raises:



278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/pindo/config/ios_config_parser.rb', line 278

def validate_required(*required_keys)
  missing_keys = []

  required_keys.each do |key|
    value = send(key) rescue nil
    missing_keys << key if value.nil? || (value.respond_to?(:empty?) && value.empty?)
  end

  unless missing_keys.empty?
    raise "配置文件缺少必需项: #{missing_keys.join(', ')}"
  end
end

#xcode_build_typeString?

获取 Xcode 构建类型

Returns:

  • (String, nil)


263
264
265
# File 'lib/pindo/config/ios_config_parser.rb', line 263

def xcode_build_type
  @config_data[:xcode_build_type]
end