pindo 5.20.0 → 5.20.1
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.
- checksums.yaml +4 -4
- data/lib/pindo/base/git_handler.rb +52 -7
- data/lib/pindo/command/jps/apptest.rb +6 -3
- data/lib/pindo/command/jps/bind.rb +6 -2
- data/lib/pindo/command/jps/config.rb +354 -0
- data/lib/pindo/command/jps/download.rb +3 -2
- data/lib/pindo/command/jps/media.rb +6 -2
- data/lib/pindo/command/jps/upload.rb +6 -2
- data/lib/pindo/command/jps.rb +1 -0
- data/lib/pindo/module/pgyer/pgyerhelper.rb +259 -100
- data/lib/pindo/module/source_files_guard.rb +83 -0
- data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +3 -3
- data/lib/pindo/module/task/model/git/git_commit_task.rb +12 -2
- data/lib/pindo/module/task/model/git/git_tag_task.rb +66 -10
- data/lib/pindo/options/groups/jps_options.rb +43 -0
- data/lib/pindo/version.rb +1 -1
- metadata +3 -1
|
@@ -28,6 +28,9 @@ module Pindo
|
|
|
28
28
|
def initialize(config_file: nil)
|
|
29
29
|
@force_login = false
|
|
30
30
|
@has_login = false
|
|
31
|
+
# 项目列表分页进度:下一个待拉取的页码、是否已拉完所有页
|
|
32
|
+
@projects_next_page = 1
|
|
33
|
+
@projects_fully_loaded = false
|
|
31
34
|
# PgyerHelper 提供默认配置路径
|
|
32
35
|
@config_file = config_file || File.join(File::expand_path(Pindoconfig.instance.pindo_common_configdir), "jps_client_config.json")
|
|
33
36
|
end
|
|
@@ -177,7 +180,7 @@ module Pindo
|
|
|
177
180
|
# @param working_directory [String] 工作目录
|
|
178
181
|
# @param manage_type [String] 管理类型(默认为空,"git" 时筛选 git 工作流)
|
|
179
182
|
# @return [Hash] 选择的工作流信息
|
|
180
|
-
def select_workflow_for_project(project_id:, package_type:, working_directory:, manage_type: "")
|
|
183
|
+
def select_workflow_for_project(project_id:, package_type:, working_directory:, manage_type: "", workflow_name: nil)
|
|
181
184
|
# 1. 从 JPS API 获取可用工作流列表
|
|
182
185
|
Funlog.instance.fancyinfo_start("正在获取可用工作流...")
|
|
183
186
|
|
|
@@ -241,8 +244,23 @@ module Pindo
|
|
|
241
244
|
else package_type
|
|
242
245
|
end
|
|
243
246
|
|
|
244
|
-
# 非 git
|
|
245
|
-
|
|
247
|
+
# 非 git 模式:过滤结果即为候选。若为空则交由下方“没有找到匹配工作流”报错,
|
|
248
|
+
# 不再回退到全部工作流——否则会把其它类型的工作流误当作请求类型(写错 workflow_key)
|
|
249
|
+
display_workflows = filtered_workflows
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# 2.5 若指定了 workflow_name,按名称精确匹配(忽略大小写与首尾空格)确定性选择。
|
|
253
|
+
# 只接受完整精确匹配,不做子串模糊匹配,避免 "Production" 误命中 "Legacy Production Upload"。
|
|
254
|
+
if workflow_name && !workflow_name.to_s.strip.empty? && !display_workflows.empty?
|
|
255
|
+
key_name = workflow_name.to_s.strip.downcase
|
|
256
|
+
name_matched = display_workflows.select do |w|
|
|
257
|
+
(w['tabName'] || w['tab_name']).to_s.strip.downcase == key_name
|
|
258
|
+
end
|
|
259
|
+
if name_matched.empty?
|
|
260
|
+
available = display_workflows.map { |w| w['tabName'] || w['tab_name'] }.join('、')
|
|
261
|
+
raise Informative, "未找到名称精确匹配 \"#{workflow_name}\" 的 #{type_desc} 工作流(需与 tab 名完全一致),可选: #{available}"
|
|
262
|
+
end
|
|
263
|
+
display_workflows = name_matched
|
|
246
264
|
end
|
|
247
265
|
|
|
248
266
|
# 3. 根据匹配数量决定选择方式
|
|
@@ -463,7 +481,7 @@ module Pindo
|
|
|
463
481
|
return app_info_obj, workflow_info
|
|
464
482
|
end
|
|
465
483
|
|
|
466
|
-
def prepare_upload(working_directory:nil, conf:nil, package_type:nil, manage_type: "", bundle_name: nil)
|
|
484
|
+
def prepare_upload(working_directory:nil, conf:nil, package_type:nil, manage_type: "", bundle_name: nil, proj_name: nil, workflow_name: nil)
|
|
467
485
|
working_directory ||= Dir.pwd
|
|
468
486
|
|
|
469
487
|
if ENV['PINDO_DEBUG']
|
|
@@ -474,6 +492,12 @@ module Pindo
|
|
|
474
492
|
puts " manage_type: #{manage_type.inspect}"
|
|
475
493
|
end
|
|
476
494
|
|
|
495
|
+
# 是否需要选择/保存工作流:空串 package_type/manage_type 视为“无工作流需求”(如 download 仅要项目)
|
|
496
|
+
wants_workflow = !package_type.to_s.strip.empty? || !manage_type.to_s.strip.empty?
|
|
497
|
+
# 是否显式指定了项目名(--proj)/工作流名(--workflow_name):命令行参数优先于已有配置
|
|
498
|
+
has_explicit_proj = !proj_name.to_s.strip.empty?
|
|
499
|
+
has_explicit_workflow_name = !workflow_name.to_s.strip.empty?
|
|
500
|
+
|
|
477
501
|
# 确定配置文件路径:显式指定 > 工作目录自动检测 > 工程目录自动检测
|
|
478
502
|
config_path = if conf && !conf.empty?
|
|
479
503
|
File.expand_path(conf)
|
|
@@ -483,8 +507,19 @@ module Pindo
|
|
|
483
507
|
|
|
484
508
|
puts "[PINDO_DEBUG] 使用配置文件: #{config_path}" if ENV['PINDO_DEBUG']
|
|
485
509
|
|
|
486
|
-
#
|
|
487
|
-
|
|
510
|
+
# 显式 --workflow_name 但未给 --proj 时,从已有配置回填项目名,
|
|
511
|
+
# 使 --workflow_name 能在正确项目内按名重选工作流,而不是被旧配置里的工作流静默覆盖
|
|
512
|
+
if wants_workflow && has_explicit_workflow_name && !has_explicit_proj && File.exist?(config_path)
|
|
513
|
+
existing_cfg = (JSON.parse(File.read(config_path)) rescue nil)
|
|
514
|
+
existing_proj = existing_cfg && existing_cfg['project_name']
|
|
515
|
+
if existing_proj && !existing_proj.to_s.strip.empty?
|
|
516
|
+
proj_name = existing_proj
|
|
517
|
+
has_explicit_proj = true
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# 尝试从配置文件加载(显式 --proj/--workflow_name 时跳过,保证命令行参数优先于已有配置)
|
|
522
|
+
if wants_workflow && !has_explicit_proj && !has_explicit_workflow_name
|
|
488
523
|
config_result = parse_jps_build_config(
|
|
489
524
|
config_file: config_path,
|
|
490
525
|
package_type: package_type,
|
|
@@ -539,29 +574,35 @@ module Pindo
|
|
|
539
574
|
app_info_obj = nil
|
|
540
575
|
if login
|
|
541
576
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
577
|
+
# 非交互:显式指定项目名(--proj)时直接解析,跳过项目选择菜单
|
|
578
|
+
if proj_name && !proj_name.to_s.strip.empty?
|
|
579
|
+
resolved_proj_name = proj_name.to_s.strip
|
|
580
|
+
app_info_obj = find_app_info_with_obj_list(proj_name: resolved_proj_name, strict: true)
|
|
581
|
+
if app_info_obj.nil?
|
|
582
|
+
raise Informative, "JPS 上未找到项目: #{resolved_proj_name},请检查项目名称"
|
|
583
|
+
end
|
|
584
|
+
PindoUserLocalConfig.instance.write_last_work_project(proj_name: app_info_obj["projectName"])
|
|
585
|
+
@proj_name = resolved_proj_name
|
|
586
|
+
|
|
587
|
+
if wants_workflow
|
|
588
|
+
workflow_info = select_workflow_for_project(
|
|
589
|
+
project_id: app_info_obj["id"],
|
|
590
|
+
package_type: package_type,
|
|
591
|
+
working_directory: working_directory,
|
|
592
|
+
manage_type: manage_type,
|
|
593
|
+
workflow_name: workflow_name
|
|
594
|
+
)
|
|
595
|
+
save_jps_build_config(
|
|
596
|
+
working_directory: working_directory,
|
|
597
|
+
app_info_obj: app_info_obj,
|
|
598
|
+
workflow_info: workflow_info,
|
|
599
|
+
package_type: package_type,
|
|
600
|
+
manage_type: manage_type,
|
|
601
|
+
conf: conf
|
|
602
|
+
)
|
|
603
|
+
return app_info_obj, workflow_info
|
|
562
604
|
else
|
|
563
|
-
|
|
564
|
-
return app_info_obj
|
|
605
|
+
return app_info_obj
|
|
565
606
|
end
|
|
566
607
|
end
|
|
567
608
|
|
|
@@ -662,13 +703,14 @@ module Pindo
|
|
|
662
703
|
# 记录上次上传的项目
|
|
663
704
|
PindoUserLocalConfig.instance.write_last_work_project(proj_name:app_info_obj["projectName"])
|
|
664
705
|
|
|
665
|
-
#
|
|
666
|
-
if
|
|
706
|
+
# 如果需要工作流,选择工作流并保存配置
|
|
707
|
+
if wants_workflow
|
|
667
708
|
workflow_info = select_workflow_for_project(
|
|
668
709
|
project_id: app_info_obj["id"],
|
|
669
710
|
package_type: package_type,
|
|
670
711
|
working_directory: working_directory,
|
|
671
|
-
manage_type: manage_type
|
|
712
|
+
manage_type: manage_type,
|
|
713
|
+
workflow_name: workflow_name
|
|
672
714
|
)
|
|
673
715
|
|
|
674
716
|
save_jps_build_config(
|
|
@@ -676,7 +718,8 @@ module Pindo
|
|
|
676
718
|
app_info_obj: app_info_obj,
|
|
677
719
|
workflow_info: workflow_info,
|
|
678
720
|
package_type: package_type,
|
|
679
|
-
manage_type: manage_type
|
|
721
|
+
manage_type: manage_type,
|
|
722
|
+
conf: conf
|
|
680
723
|
)
|
|
681
724
|
|
|
682
725
|
@proj_name = upload_proj_name
|
|
@@ -1357,41 +1400,108 @@ module Pindo
|
|
|
1357
1400
|
result&.dig("data")
|
|
1358
1401
|
end
|
|
1359
1402
|
|
|
1360
|
-
|
|
1403
|
+
PROJECT_PAGE_SIZE = 100
|
|
1404
|
+
# 安全上限:最多翻 200 页(约 2 万项目),防止服务端异常(如忽略 pageNo 恒返满页)导致死循环
|
|
1405
|
+
MAX_PROJECT_PAGES = 200
|
|
1406
|
+
|
|
1407
|
+
def find_app_info_with_obj_list(proj_name:nil, bundle_id:nil, strict: false)
|
|
1361
1408
|
|
|
1362
1409
|
if proj_name.nil? && bundle_id.nil?
|
|
1363
1410
|
return nil
|
|
1364
1411
|
end
|
|
1365
1412
|
|
|
1366
|
-
Funlog.instance.fancyinfo_start("
|
|
1367
|
-
|
|
1368
|
-
get_app_list_in_pgyer()
|
|
1413
|
+
Funlog.instance.fancyinfo_start("正在查找 JPS 项目...")
|
|
1369
1414
|
|
|
1370
1415
|
key_proj_name = proj_name&.downcase&.strip&.gsub(/[\s\-_]/, '')
|
|
1371
1416
|
key_bundle_id = bundle_id&.downcase&.strip
|
|
1372
1417
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1418
|
+
# 在给定列表中查找匹配对象:bundle_id 精确优先,其次项目名归一化匹配。
|
|
1419
|
+
# strict 且归一化撞到多个时优先完整精确匹配,仍多个则报错要求精确名。
|
|
1420
|
+
matcher = lambda do |list|
|
|
1421
|
+
obj = nil
|
|
1422
|
+
if key_bundle_id && !key_bundle_id.empty?
|
|
1423
|
+
obj = list.find { |it| it["bundleId"].to_s.downcase.strip.eql?(key_bundle_id) }
|
|
1379
1424
|
end
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1425
|
+
if obj.nil? && key_proj_name && !key_proj_name.empty?
|
|
1426
|
+
matches = list.select do |it|
|
|
1427
|
+
it["projectName"].to_s.downcase.strip.gsub(/[\s\-_]/, '').eql?(key_proj_name)
|
|
1428
|
+
end
|
|
1429
|
+
if strict && matches.size > 1
|
|
1430
|
+
key_exact = proj_name.to_s.downcase.strip
|
|
1431
|
+
exact = matches.select { |it| it["projectName"].to_s.downcase.strip.eql?(key_exact) }
|
|
1432
|
+
if exact.size == 1
|
|
1433
|
+
obj = exact.first
|
|
1434
|
+
else
|
|
1435
|
+
names = matches.map { |it| it["projectName"] }.join('、')
|
|
1436
|
+
raise Informative, "项目名 \"#{proj_name}\" 匹配到多个 JPS 项目: #{names},请使用精确名称"
|
|
1437
|
+
end
|
|
1438
|
+
else
|
|
1439
|
+
obj = matches.first
|
|
1440
|
+
end
|
|
1387
1441
|
end
|
|
1442
|
+
obj
|
|
1388
1443
|
end
|
|
1389
1444
|
|
|
1390
|
-
|
|
1445
|
+
# strict 需完整列表才能判定唯一;其余边拉边找、命中即早停
|
|
1446
|
+
list = ensure_projects_loaded(require_full: strict) { |acc| !matcher.call(acc).nil? }
|
|
1447
|
+
app_info_obj = matcher.call(list)
|
|
1448
|
+
|
|
1449
|
+
Funlog.instance.fancyinfo_success("JPS 项目查找完成")
|
|
1391
1450
|
|
|
1392
1451
|
app_info_obj
|
|
1393
1452
|
end
|
|
1394
1453
|
|
|
1454
|
+
# 逐页(每页 PROJECT_PAGE_SIZE=100)拉取 JPS 项目列表并累积到进程缓存。
|
|
1455
|
+
# - require_full=false:每拉一页调用 stop_when(累积列表),命中即停止继续拉取(早停)
|
|
1456
|
+
# - require_full=true :拉完所有页(strict 判唯一等需要完整列表的场景)
|
|
1457
|
+
# 已缓存的页会复用并从上次进度续拉;全部页拉完仍未命中即返回完整列表,由调用方判定“不存在”。
|
|
1458
|
+
#
|
|
1459
|
+
# @param require_full [Boolean] 是否必须拉完所有页
|
|
1460
|
+
# @yield [Array] 当前累积的项目列表;返回真值表示已命中,可停止续拉
|
|
1461
|
+
# @return [Array] 累积到的项目列表
|
|
1462
|
+
def ensure_projects_loaded(require_full: false, &stop_when)
|
|
1463
|
+
accumulated = Pindoconfig.instance.get_pgyerapps_info_list() || []
|
|
1464
|
+
return accumulated if @projects_fully_loaded
|
|
1465
|
+
if !require_full && stop_when && stop_when.call(accumulated)
|
|
1466
|
+
return accumulated
|
|
1467
|
+
end
|
|
1468
|
+
|
|
1469
|
+
page_no = @projects_next_page ||= 1
|
|
1470
|
+
loop do
|
|
1471
|
+
if page_no > MAX_PROJECT_PAGES
|
|
1472
|
+
Funlog.instance.fancyinfo_warning("项目分页已达安全上限 #{MAX_PROJECT_PAGES} 页,停止继续拉取")
|
|
1473
|
+
@projects_fully_loaded = true
|
|
1474
|
+
break
|
|
1475
|
+
end
|
|
1476
|
+
|
|
1477
|
+
res = @pgyer_client.get_project_list(params: { pageNo: page_no, pageSize: PROJECT_PAGE_SIZE })
|
|
1478
|
+
data = res && res["data"]
|
|
1479
|
+
|
|
1480
|
+
if data.nil?
|
|
1481
|
+
# 首页即无数据 → 网络/登录异常;非首页则视为已到末尾
|
|
1482
|
+
if page_no == 1 && accumulated.empty?
|
|
1483
|
+
err = res && (res["msg"] || res["code"])
|
|
1484
|
+
raise Informative, "JPS网络数据异常或未登录,请检查网络或重新登录(pindo jps login)#{err ? ":#{err}" : ""}"
|
|
1485
|
+
end
|
|
1486
|
+
@projects_fully_loaded = true
|
|
1487
|
+
break
|
|
1488
|
+
end
|
|
1489
|
+
|
|
1490
|
+
accumulated += data
|
|
1491
|
+
Pindoconfig.instance.set_pgyerapps_info_list(app_info_list: accumulated)
|
|
1492
|
+
@projects_next_page = (page_no += 1)
|
|
1493
|
+
|
|
1494
|
+
if data.size < PROJECT_PAGE_SIZE
|
|
1495
|
+
# 不足一页 → 已是最后一页
|
|
1496
|
+
@projects_fully_loaded = true
|
|
1497
|
+
break
|
|
1498
|
+
end
|
|
1499
|
+
break if !require_full && stop_when && stop_when.call(accumulated)
|
|
1500
|
+
end
|
|
1501
|
+
|
|
1502
|
+
accumulated
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1395
1505
|
def wildcard_android_bundle_name?(bundle_name)
|
|
1396
1506
|
return false if bundle_name.nil? || bundle_name.empty?
|
|
1397
1507
|
|
|
@@ -1438,27 +1548,11 @@ module Pindo
|
|
|
1438
1548
|
return version_item_obj
|
|
1439
1549
|
end
|
|
1440
1550
|
|
|
1551
|
+
# 拉取(累积缓存中的)全部 JPS 项目列表——分页拉完所有页,去除旧的单页 1000 上限。
|
|
1441
1552
|
def get_app_list_in_pgyer()
|
|
1553
|
+
app_info_list = ensure_projects_loaded(require_full: true)
|
|
1442
1554
|
|
|
1443
|
-
|
|
1444
|
-
pageNo:1,
|
|
1445
|
-
pageSize:1000
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
app_info_list = Pindoconfig.instance.get_pgyerapps_info_list()
|
|
1449
|
-
if app_info_list.nil?
|
|
1450
|
-
res_data = @pgyer_client.get_project_list(params:params)
|
|
1451
|
-
if res_data && !res_data["data"].nil? && res_data["data"].size > 0
|
|
1452
|
-
app_info_list = res_data["data"]
|
|
1453
|
-
Pindoconfig.instance.set_pgyerapps_info_list(app_info_list:app_info_list)
|
|
1454
|
-
elsif res_data
|
|
1455
|
-
error_msg = res_data["msg"] || "未知错误"
|
|
1456
|
-
error_code = res_data["code"]
|
|
1457
|
-
Funlog.instance.fancyinfo_error("拉取项目列表失败: [#{error_code}] #{error_msg}")
|
|
1458
|
-
end
|
|
1459
|
-
end
|
|
1460
|
-
|
|
1461
|
-
if app_info_list.nil?
|
|
1555
|
+
if app_info_list.nil? || app_info_list.empty?
|
|
1462
1556
|
Funlog.instance.fancyinfo_error("拉取app信息列表失败!")
|
|
1463
1557
|
raise Informative, "JPS网络数据异常,请检查网络连接或重新登录(pindo jps login)"
|
|
1464
1558
|
end
|
|
@@ -1935,8 +2029,97 @@ module Pindo
|
|
|
1935
2029
|
end
|
|
1936
2030
|
end
|
|
1937
2031
|
|
|
2032
|
+
# 一次性把多个工作流写入 JPSBuildConfig.json(单次原子写盘),供 pindo jps config 多类型配置使用。
|
|
2033
|
+
# 同一项目下已有的其它工作流键会保留;项目变更则整份重建。写入是原子的,
|
|
2034
|
+
# 中途不会留下半成品配置。
|
|
2035
|
+
#
|
|
2036
|
+
# @param working_directory [String] 工作目录
|
|
2037
|
+
# @param app_info_obj [Hash] 已选定的 JPS 项目信息对象
|
|
2038
|
+
# @param entries [Array<Hash>] 形如 [{ package_type:, manage_type:, workflow_info: }]
|
|
2039
|
+
# @param conf [String, nil] 显式配置文件路径(--conf),优先于自动定位
|
|
2040
|
+
# @return [Hash, nil] 写入后的完整配置
|
|
2041
|
+
def save_jps_workflows(working_directory:, app_info_obj:, entries:, conf: nil)
|
|
2042
|
+
return if working_directory.nil? || app_info_obj.nil? || entries.nil? || entries.empty?
|
|
2043
|
+
|
|
2044
|
+
config_file = resolve_jps_config_file(working_directory: working_directory, conf: conf)
|
|
2045
|
+
new_project_id = app_info_obj["id"]
|
|
2046
|
+
|
|
2047
|
+
# 仅当项目 ID 相同才保留已有其它工作流键;否则整份重建
|
|
2048
|
+
config = {}
|
|
2049
|
+
if File.exist?(config_file)
|
|
2050
|
+
existing = (JSON.parse(File.read(config_file)) rescue nil)
|
|
2051
|
+
config = existing if existing.is_a?(Hash) && existing['project_id'] == new_project_id
|
|
2052
|
+
end
|
|
2053
|
+
|
|
2054
|
+
config['project_name'] = app_info_obj["projectName"]
|
|
2055
|
+
config['project_scheme'] = app_info_obj["scheme"] ||
|
|
2056
|
+
app_info_obj["projectName"].to_s.downcase.strip.gsub(/[\s\-_]/, '')
|
|
2057
|
+
config['project_id'] = new_project_id
|
|
2058
|
+
config['bundle_group_name'] = app_info_obj["bundleId"]
|
|
2059
|
+
config['wildcard_bundle_group_name'] = app_info_obj["wildcardBundleId"]
|
|
2060
|
+
|
|
2061
|
+
entries.each do |entry|
|
|
2062
|
+
workflow_key = jps_workflow_key(package_type: entry[:package_type], manage_type: entry[:manage_type].to_s)
|
|
2063
|
+
config[workflow_key] = build_workflow_data(
|
|
2064
|
+
workflow_info: entry[:workflow_info],
|
|
2065
|
+
package_type: entry[:package_type]
|
|
2066
|
+
)
|
|
2067
|
+
end
|
|
2068
|
+
|
|
2069
|
+
atomic_write(config_file, JSON.pretty_generate(config) + "\n")
|
|
2070
|
+
puts "已写入 JPSBuildConfig.json(#{entries.size} 个工作流)"
|
|
2071
|
+
config
|
|
2072
|
+
end
|
|
2073
|
+
|
|
1938
2074
|
private
|
|
1939
2075
|
|
|
2076
|
+
# 原子写文件:写同目录临时文件再 rename,避免半写状态。
|
|
2077
|
+
# rename 会以普通文件替换符号链接(不写穿其目标),符合“不跟随符号链接”的预期。
|
|
2078
|
+
def atomic_write(path, content)
|
|
2079
|
+
dir = File.dirname(path)
|
|
2080
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
|
2081
|
+
tmp = File.join(dir, ".#{File.basename(path)}.#{Process.pid}.tmp")
|
|
2082
|
+
begin
|
|
2083
|
+
File.write(tmp, content)
|
|
2084
|
+
File.rename(tmp, path)
|
|
2085
|
+
ensure
|
|
2086
|
+
File.delete(tmp) if File.exist?(tmp)
|
|
2087
|
+
end
|
|
2088
|
+
end
|
|
2089
|
+
|
|
2090
|
+
# 定位 JPSBuildConfig.json:显式 --conf 优先,否则按工作目录自动定位
|
|
2091
|
+
def resolve_jps_config_file(working_directory:, conf: nil)
|
|
2092
|
+
if conf && !conf.to_s.strip.empty?
|
|
2093
|
+
File.expand_path(conf)
|
|
2094
|
+
else
|
|
2095
|
+
Pindo::BuildHelper.share_instance.determine_jps_config_path(working_directory)
|
|
2096
|
+
end
|
|
2097
|
+
end
|
|
2098
|
+
|
|
2099
|
+
# package_type/manage_type → JPSBuildConfig.json 中的 workflow_key
|
|
2100
|
+
def jps_workflow_key(package_type:, manage_type: "")
|
|
2101
|
+
return 'git_workflow' if manage_type == 'git'
|
|
2102
|
+
case package_type
|
|
2103
|
+
when 'ipa' then 'ipa_workflow'
|
|
2104
|
+
when 'apk' then 'apk_workflow'
|
|
2105
|
+
when 'zip' then 'webgl_workflow'
|
|
2106
|
+
when 'mac' then 'macos_workflow'
|
|
2107
|
+
when 'exe' then 'win_workflow'
|
|
2108
|
+
else raise Informative, "不支持的 package_type: #{package_type}"
|
|
2109
|
+
end
|
|
2110
|
+
end
|
|
2111
|
+
|
|
2112
|
+
# 构建单个工作流的写入结构
|
|
2113
|
+
def build_workflow_data(workflow_info:, package_type:)
|
|
2114
|
+
{
|
|
2115
|
+
"workflow_id" => workflow_info[:workflow_id],
|
|
2116
|
+
"tab_name" => workflow_info[:tab_name],
|
|
2117
|
+
"package_type" => package_type,
|
|
2118
|
+
"package_name" => workflow_info[:package_name],
|
|
2119
|
+
"manage_type" => workflow_info[:manage_type] || ""
|
|
2120
|
+
}
|
|
2121
|
+
end
|
|
2122
|
+
|
|
1940
2123
|
# 验证工作流配置是否有效
|
|
1941
2124
|
#
|
|
1942
2125
|
# @param workflow [Hash] 工作流配置对象
|
|
@@ -1985,11 +2168,7 @@ module Pindo
|
|
|
1985
2168
|
workflow_key => workflow_data
|
|
1986
2169
|
}
|
|
1987
2170
|
|
|
1988
|
-
|
|
1989
|
-
config_dir = File.dirname(config_file)
|
|
1990
|
-
FileUtils.mkdir_p(config_dir) unless File.directory?(config_dir)
|
|
1991
|
-
|
|
1992
|
-
File.write(config_file, JSON.pretty_generate(new_config))
|
|
2171
|
+
atomic_write(config_file, JSON.pretty_generate(new_config))
|
|
1993
2172
|
puts "已创建 JPSBuildConfig.json"
|
|
1994
2173
|
end
|
|
1995
2174
|
|
|
@@ -2000,28 +2179,14 @@ module Pindo
|
|
|
2000
2179
|
# @param app_info_obj [Hash] 项目信息对象
|
|
2001
2180
|
# @param workflow_info [Hash] 工作流信息
|
|
2002
2181
|
# @param package_type [String] 包类型 ("ipa" | "apk" | "zip")
|
|
2003
|
-
def save_jps_build_config(working_directory:, app_info_obj:, workflow_info:, package_type:, manage_type: "")
|
|
2182
|
+
def save_jps_build_config(working_directory:, app_info_obj:, workflow_info:, package_type:, manage_type: "", conf: nil)
|
|
2004
2183
|
return if working_directory.nil? || app_info_obj.nil? || workflow_info.nil?
|
|
2005
2184
|
|
|
2006
|
-
# 1.
|
|
2007
|
-
config_file =
|
|
2185
|
+
# 1. 确定配置文件路径:显式 --conf 优先,否则按工作目录自动定位
|
|
2186
|
+
config_file = resolve_jps_config_file(working_directory: working_directory, conf: conf)
|
|
2008
2187
|
|
|
2009
2188
|
# 2. 根据 manage_type 或 package_type 确定 workflow_key
|
|
2010
|
-
workflow_key =
|
|
2011
|
-
if manage_type == "git"
|
|
2012
|
-
# manage_type 为 git 时,使用 git_workflow
|
|
2013
|
-
workflow_key = 'git_workflow'
|
|
2014
|
-
else
|
|
2015
|
-
# 按 package_type 确定 workflow_key
|
|
2016
|
-
workflow_key = case package_type
|
|
2017
|
-
when 'ipa' then 'ipa_workflow'
|
|
2018
|
-
when 'apk' then 'apk_workflow'
|
|
2019
|
-
when 'zip' then 'webgl_workflow'
|
|
2020
|
-
when 'mac' then 'macos_workflow'
|
|
2021
|
-
when 'exe' then 'win_workflow'
|
|
2022
|
-
else raise Informative, "不支持的 package_type: #{package_type}"
|
|
2023
|
-
end
|
|
2024
|
-
end
|
|
2189
|
+
workflow_key = jps_workflow_key(package_type: package_type, manage_type: manage_type.to_s)
|
|
2025
2190
|
|
|
2026
2191
|
# 3. 准备新的项目基础信息
|
|
2027
2192
|
new_project_id = app_info_obj["id"]
|
|
@@ -2032,13 +2197,7 @@ module Pindo
|
|
|
2032
2197
|
app_info_obj["projectName"].to_s.downcase.strip.gsub(/[\s\-_]/, '')
|
|
2033
2198
|
|
|
2034
2199
|
# 4. 准备新的工作流信息
|
|
2035
|
-
new_workflow_data =
|
|
2036
|
-
"workflow_id" => workflow_info[:workflow_id],
|
|
2037
|
-
"tab_name" => workflow_info[:tab_name],
|
|
2038
|
-
"package_type" => package_type,
|
|
2039
|
-
"package_name" => workflow_info[:package_name],
|
|
2040
|
-
"manage_type" => workflow_info[:manage_type] || ""
|
|
2041
|
-
}
|
|
2200
|
+
new_workflow_data = build_workflow_data(workflow_info: workflow_info, package_type: package_type)
|
|
2042
2201
|
|
|
2043
2202
|
# 5. 配置文件不存在 → 创建新配置
|
|
2044
2203
|
if !File.exist?(config_file)
|
|
@@ -2105,7 +2264,7 @@ module Pindo
|
|
|
2105
2264
|
existing_config[workflow_key] = new_workflow_data
|
|
2106
2265
|
existing_config["bundle_group_name"] = new_bundle_group_name
|
|
2107
2266
|
existing_config["wildcard_bundle_group_name"] = new_wildcard_bundle_group_name
|
|
2108
|
-
|
|
2267
|
+
atomic_write(config_file, JSON.pretty_generate(existing_config))
|
|
2109
2268
|
puts "已更新 #{workflow_key}"
|
|
2110
2269
|
return
|
|
2111
2270
|
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'pindo/module/build/build_helper'
|
|
2
|
+
|
|
3
|
+
module Pindo
|
|
4
|
+
# autobuild 会注入配置的固定源码清单。路径中的工程名、Target 名和 Module 名用 glob 匹配。
|
|
5
|
+
module BuildSourceFilesGuard
|
|
6
|
+
IOS_PATTERNS = [
|
|
7
|
+
'config.json',
|
|
8
|
+
'Podfile.lock',
|
|
9
|
+
'**/*.xcodeproj/project.pbxproj',
|
|
10
|
+
'**/Info.plist',
|
|
11
|
+
'**/*.entitlements',
|
|
12
|
+
'**/AppIcon.appiconset',
|
|
13
|
+
'**/iMessage App Icon.stickersiconset'
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
ANDROID_PATTERNS = [
|
|
17
|
+
'build.gradle',
|
|
18
|
+
'build.gradle.kts',
|
|
19
|
+
'settings.gradle',
|
|
20
|
+
'settings.gradle.kts',
|
|
21
|
+
'google-services.json',
|
|
22
|
+
'**/build.gradle',
|
|
23
|
+
'**/build.gradle.kts',
|
|
24
|
+
'**/google-services.json',
|
|
25
|
+
'**/src/main/AndroidManifest.xml',
|
|
26
|
+
'**/src/main/res'
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
EXCLUDED_PATH_COMPONENTS = %w[
|
|
30
|
+
.git
|
|
31
|
+
.gradle
|
|
32
|
+
.build
|
|
33
|
+
Pods
|
|
34
|
+
build
|
|
35
|
+
DerivedData
|
|
36
|
+
GoodPlatform
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
def for_project(project_path)
|
|
41
|
+
case Pindo::BuildHelper.share_instance.project_type(project_path)
|
|
42
|
+
when :ios
|
|
43
|
+
ios(project_path)
|
|
44
|
+
when :android
|
|
45
|
+
android(project_path)
|
|
46
|
+
else
|
|
47
|
+
unity(project_path)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ios(project_path)
|
|
52
|
+
source_paths(project_path, IOS_PATTERNS)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def android(project_path)
|
|
56
|
+
source_paths(project_path, ANDROID_PATTERNS)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Unity 平台工程导出到已忽略的 GoodPlatform,不提交 Unity 源码配置。
|
|
60
|
+
def unity(_project_path)
|
|
61
|
+
[]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def source_paths(project_path, patterns)
|
|
67
|
+
project_dir = File.expand_path(project_path)
|
|
68
|
+
project_prefix = "#{project_dir}#{File::SEPARATOR}"
|
|
69
|
+
candidates = patterns.flat_map { |pattern| Dir.glob(File.join(project_dir, pattern)) }
|
|
70
|
+
|
|
71
|
+
candidates
|
|
72
|
+
.select { |path| path.start_with?(project_prefix) }
|
|
73
|
+
.reject { |path| excluded_source_path?(project_dir, path) }
|
|
74
|
+
.uniq
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def excluded_source_path?(project_dir, path)
|
|
78
|
+
relative_path = path.delete_prefix("#{project_dir}#{File::SEPARATOR}")
|
|
79
|
+
!(relative_path.split(File::SEPARATOR) & EXCLUDED_PATH_COMPONENTS).empty?
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -143,14 +143,14 @@ module Pindo
|
|
|
143
143
|
)
|
|
144
144
|
|
|
145
145
|
# 下载并替换 Icon
|
|
146
|
-
if @app_info_obj && @app_info_obj["
|
|
146
|
+
if @app_info_obj && @app_info_obj["packageProjectIcon"] && !@app_info_obj["packageProjectIcon"].empty?
|
|
147
147
|
icon_replace_success = Pindo::XcodeBuildConfig.download_and_replace_icon_from_url(
|
|
148
148
|
project_dir: @project_path,
|
|
149
|
-
icon_url: @app_info_obj["
|
|
149
|
+
icon_url: @app_info_obj["packageProjectIcon"]
|
|
150
150
|
)
|
|
151
151
|
|
|
152
152
|
unless icon_replace_success
|
|
153
|
-
raise Informative, "Icon 下载或替换失败,请检查 Icon URL 是否有效: #{@app_info_obj["
|
|
153
|
+
raise Informative, "Icon 下载或替换失败,请检查 Icon URL 是否有效: #{@app_info_obj["packageProjectIcon"]}"
|
|
154
154
|
end
|
|
155
155
|
else
|
|
156
156
|
Funlog.warning("未配置 Icon URL,跳过 Icon 替换")
|
|
@@ -40,9 +40,8 @@ module Pindo
|
|
|
40
40
|
@process_type = options[:process_type] || Pindo::UncommittedFilesProcessType::SKIP_WITH_TAG # 默认跳过但打tag
|
|
41
41
|
@commit_message = options[:commit_message] || 'chore(build): pindo 构建产生提交' # 默认提交消息
|
|
42
42
|
|
|
43
|
-
options[:project_path] = project_path
|
|
44
43
|
# 调用基类初始化(基类会设置 release_branch, ver_inc, tag_type, tag_pre 等通用属性)
|
|
45
|
-
super("Git仓库文件提交检查", options)
|
|
44
|
+
super("Git仓库文件提交检查", options.merge(project_path: project_path))
|
|
46
45
|
end
|
|
47
46
|
|
|
48
47
|
def validate
|
|
@@ -54,6 +53,13 @@ module Pindo
|
|
|
54
53
|
|
|
55
54
|
protected
|
|
56
55
|
|
|
56
|
+
def build_task_param
|
|
57
|
+
param = super
|
|
58
|
+
return param if param.empty?
|
|
59
|
+
|
|
60
|
+
param.merge(process_type: @process_type)
|
|
61
|
+
end
|
|
62
|
+
|
|
57
63
|
def do_work
|
|
58
64
|
# 检查是否为 Git 仓库,如果不是则直接成功返回
|
|
59
65
|
unless valid_git_repository?
|
|
@@ -159,6 +165,10 @@ module Pindo
|
|
|
159
165
|
# 5. 获取当前分支
|
|
160
166
|
coding_branch = get_current_branch_name
|
|
161
167
|
|
|
168
|
+
# 5.1 解析发布分支:仓库无 master 时按 master>main>唯一远程分支>当前分支 探测,
|
|
169
|
+
# 避免默认 'master' 与实际主分支(如 main)不一致导致同步/checkout 失败
|
|
170
|
+
@release_branch = Pindo::GitHandler.resolve_release_branch(local_repo_dir: root_dir, preferred: @release_branch)
|
|
171
|
+
|
|
162
172
|
# 6. 检查并推送本地提交到远程(确保本地和远程同步)
|
|
163
173
|
Pindo::GitHandler.check_unpushed_commits(project_dir: root_dir, branch: coding_branch)
|
|
164
174
|
|