pindo 5.5.2 → 5.5.4
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/pindocontext.rb +16 -2
- data/lib/pindo/command/dev/tag.rb +42 -7
- data/lib/pindo/command/unity/initpack.rb +101 -0
- data/lib/pindo/command/unity/pack.rb +86 -0
- data/lib/pindo/command/unity/upload.rb +263 -0
- data/lib/pindo/command/unity.rb +3 -0
- data/lib/pindo/module/build/buildhelper.rb +15 -5
- data/lib/pindo/module/unity/nugethelper.rb +571 -0
- data/lib/pindo/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9b78c1d33a386cb6ca94c86474093c790548e29964bc4a20eeb74e987ed8a66
|
4
|
+
data.tar.gz: 603656976a10b41c86d8f6c3a125601b8120cf9b0c064b3bbd66b1cc22dc0f4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7adaf27919be676327ad4942df3212bf67c18b5c3f550bb8ac70c15bd4d5b3167b0fc5372b5008edf74326af9a768227329634c6b427ece338cb0cc04d12ba69
|
7
|
+
data.tar.gz: 4b2256dd2a74d7bf251507a0a0335ca8528021f5471dbe27dd5afcb9c21c7b1c504724d633083b753c3c57a70b4cfcf6d968b84e7efb52989f9a9534ee8d66f4
|
@@ -217,6 +217,20 @@ module Pindo
|
|
217
217
|
|
218
218
|
private
|
219
219
|
|
220
|
+
# 递归将Hash的键从字符串转换为符号
|
221
|
+
def symbolize_keys(obj)
|
222
|
+
case obj
|
223
|
+
when Hash
|
224
|
+
obj.each_with_object({}) do |(key, value), result|
|
225
|
+
result[key.to_sym] = symbolize_keys(value)
|
226
|
+
end
|
227
|
+
when Array
|
228
|
+
obj.map { |item| symbolize_keys(item) }
|
229
|
+
else
|
230
|
+
obj
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
220
234
|
# 确保缓存目录存在
|
221
235
|
def ensure_cache_dir
|
222
236
|
cache_dir = File.expand_path('~/.pindo/cache')
|
@@ -306,7 +320,7 @@ module Pindo
|
|
306
320
|
@memory_selections[proj_path][cmd] ||= {}
|
307
321
|
selections.each do |key, value|
|
308
322
|
symbol_key = key.to_sym
|
309
|
-
@memory_selections[proj_path][cmd][symbol_key] = value
|
323
|
+
@memory_selections[proj_path][cmd][symbol_key] = symbolize_keys(value)
|
310
324
|
end
|
311
325
|
end
|
312
326
|
end
|
@@ -332,7 +346,7 @@ module Pindo
|
|
332
346
|
@memory_selections[proj_path][cmd] ||= {}
|
333
347
|
selections.each do |key, value|
|
334
348
|
symbol_key = key.to_sym
|
335
|
-
@memory_selections[proj_path][cmd][symbol_key] = value
|
349
|
+
@memory_selections[proj_path][cmd][symbol_key] = symbolize_keys(value)
|
336
350
|
end
|
337
351
|
end
|
338
352
|
end
|
@@ -38,18 +38,21 @@ module Pindo
|
|
38
38
|
def self.options
|
39
39
|
[
|
40
40
|
['--mode', '指定版本号增加模式(major/minor/patch),默认minor'],
|
41
|
-
['--retag', '强制重新打最新的tag']
|
41
|
+
['--retag', '强制重新打最新的tag'],
|
42
|
+
['--tag', '直接指定tag版本号(如 1.1.3),会自动添加v前缀']
|
42
43
|
].concat(super)
|
43
44
|
end
|
44
45
|
|
45
46
|
def initialize(argv)
|
46
47
|
@mode = argv.option('mode') || 'minor'
|
47
48
|
@force_retag = argv.flag?('retag', false)
|
48
|
-
|
49
|
-
|
49
|
+
@custom_tag = argv.option('tag')
|
50
|
+
|
51
|
+
# 如果指定了自定义tag,不需要验证mode
|
52
|
+
unless @custom_tag || ['major', 'minor', 'patch'].include?(@mode)
|
50
53
|
raise Informative, "mode参数必须是 major, minor 或 patch"
|
51
54
|
end
|
52
|
-
|
55
|
+
|
53
56
|
super
|
54
57
|
@additional_args = argv.remainder!
|
55
58
|
end
|
@@ -89,13 +92,45 @@ module Pindo
|
|
89
92
|
add_release_tag(
|
90
93
|
project_dir: root_dir,
|
91
94
|
increment_mode: @mode,
|
92
|
-
force_retag: @force_retag
|
95
|
+
force_retag: @force_retag,
|
96
|
+
custom_tag: @custom_tag
|
93
97
|
)
|
94
98
|
end
|
95
99
|
|
96
|
-
def add_release_tag(project_dir: nil, increment_mode: "minor", force_retag: false)
|
100
|
+
def add_release_tag(project_dir: nil, increment_mode: "minor", force_retag: false, custom_tag: nil)
|
97
101
|
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
98
|
-
|
102
|
+
|
103
|
+
# 如果指定了自定义tag,直接使用
|
104
|
+
if custom_tag && !custom_tag.empty?
|
105
|
+
Funlog.instance.fancyinfo_start("使用指定的tag版本: #{custom_tag}")
|
106
|
+
|
107
|
+
# 确保tag有v前缀
|
108
|
+
new_tag = custom_tag.start_with?('v') ? custom_tag : "v#{custom_tag}"
|
109
|
+
|
110
|
+
# 检查tag是否已存在
|
111
|
+
existing_tags = git!(%W(-C #{project_dir} tag -l)).split("\n")
|
112
|
+
if existing_tags.include?(new_tag)
|
113
|
+
if force_retag
|
114
|
+
Funlog.instance.fancyinfo_update("tag #{new_tag} 已存在,强制重新打tag")
|
115
|
+
git!(%W(-C #{project_dir} tag -d #{new_tag}))
|
116
|
+
git!(%W(-C #{project_dir} push origin :refs/tags/#{new_tag}))
|
117
|
+
else
|
118
|
+
Funlog.instance.fancyinfo_success("tag #{new_tag} 已存在")
|
119
|
+
Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
|
120
|
+
return
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# 创建tag并推送
|
125
|
+
git!(%W(-C #{project_dir} tag #{new_tag}))
|
126
|
+
git!(%W(-C #{project_dir} push origin #{new_tag}))
|
127
|
+
|
128
|
+
Funlog.instance.fancyinfo_success("创建tag: #{new_tag}")
|
129
|
+
Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
|
130
|
+
return
|
131
|
+
end
|
132
|
+
|
133
|
+
# 原有的自动递增逻辑
|
99
134
|
Funlog.instance.fancyinfo_start("开始创建初tag")
|
100
135
|
latest_tag = get_latest_version_tag(project_dir: project_dir)
|
101
136
|
if latest_tag.nil?
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
require 'pindo/module/unity/nugethelper'
|
4
|
+
|
5
|
+
module Pindo
|
6
|
+
class Command
|
7
|
+
class Unity < Command
|
8
|
+
class Initpack < Unity
|
9
|
+
|
10
|
+
self.summary = '初始化 Unity Package 的 NuGet 打包配置'
|
11
|
+
|
12
|
+
self.description = <<-DESC
|
13
|
+
初始化并同步 package.json 和 .nuspec 文件,确保信息一致。
|
14
|
+
|
15
|
+
支持4种情况:
|
16
|
+
|
17
|
+
A) 只有 package.json → 自动创建 .nuspec
|
18
|
+
|
19
|
+
B) 只有 .nuspec → 自动创建 package.json
|
20
|
+
|
21
|
+
C) 同时存在 → 以 .nuspec 为准同步到 package.json
|
22
|
+
|
23
|
+
D) 都不存在 → 报错提示
|
24
|
+
|
25
|
+
ID 格式规则:
|
26
|
+
|
27
|
+
• package.json 的 name:始终全小写
|
28
|
+
|
29
|
+
• .nuspec 的 id:始终驼峰
|
30
|
+
|
31
|
+
使用示例:
|
32
|
+
|
33
|
+
$ pindo unity initpack
|
34
|
+
DESC
|
35
|
+
|
36
|
+
def self.arguments
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.options
|
41
|
+
[].concat(super)
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(argv)
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
package_dir = Dir.pwd
|
50
|
+
|
51
|
+
puts "=" * 60
|
52
|
+
puts "🔧 Unity Package 初始化"
|
53
|
+
puts "=" * 60
|
54
|
+
puts
|
55
|
+
|
56
|
+
# 检测文件存在情况
|
57
|
+
status = Pindo::Unity::NugetHelper.detect_files(package_dir)
|
58
|
+
|
59
|
+
puts "文件检测:"
|
60
|
+
puts " package.json: #{status[:has_package_json] ? '✅' : '❌'}"
|
61
|
+
puts " .nuspec: #{status[:has_nuspec] ? '✅' : '❌'}"
|
62
|
+
if status[:nuspec_file]
|
63
|
+
puts " 路径: #{File.basename(status[:nuspec_file])}"
|
64
|
+
end
|
65
|
+
puts
|
66
|
+
|
67
|
+
# 根据4种情况处理
|
68
|
+
case [status[:has_package_json], status[:has_nuspec]]
|
69
|
+
when [true, false]
|
70
|
+
puts "📝 情况A:只有 package.json"
|
71
|
+
puts " → 创建 .nuspec(ID 使用驼峰格式)"
|
72
|
+
puts
|
73
|
+
Pindo::Unity::NugetHelper.handle_case_a(package_dir)
|
74
|
+
when [false, true]
|
75
|
+
puts "📝 情况B:只有 .nuspec"
|
76
|
+
puts " → 创建 package.json(name 使用全小写)"
|
77
|
+
puts
|
78
|
+
Pindo::Unity::NugetHelper.handle_case_b(package_dir, status[:nuspec_file])
|
79
|
+
when [true, true]
|
80
|
+
puts "📝 情况C:同时存在两个文件"
|
81
|
+
puts " → 以 .nuspec 为准同步到 package.json"
|
82
|
+
puts
|
83
|
+
Pindo::Unity::NugetHelper.handle_case_c(package_dir, status[:nuspec_file])
|
84
|
+
when [false, false]
|
85
|
+
puts "❌ 情况D:都不存在"
|
86
|
+
Pindo::Unity::NugetHelper.handle_case_d(package_dir)
|
87
|
+
end
|
88
|
+
|
89
|
+
puts
|
90
|
+
puts "=" * 60
|
91
|
+
puts "✅ 初始化完成!"
|
92
|
+
puts "=" * 60
|
93
|
+
puts
|
94
|
+
puts "下一步:"
|
95
|
+
puts " 运行 pindo unity pack 进行打包"
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
require 'pindo/module/unity/nugethelper'
|
4
|
+
|
5
|
+
module Pindo
|
6
|
+
class Command
|
7
|
+
class Unity < Command
|
8
|
+
class Pack < Unity
|
9
|
+
|
10
|
+
self.summary = '打包 Unity Package 为 NuGet 格式'
|
11
|
+
|
12
|
+
self.description = <<-DESC
|
13
|
+
将 Unity Package 打包成 .nupkg 文件。
|
14
|
+
|
15
|
+
前提条件:
|
16
|
+
|
17
|
+
需要先运行 pindo unity initpack 初始化配置文件
|
18
|
+
|
19
|
+
使用示例:
|
20
|
+
|
21
|
+
$ pindo unity pack
|
22
|
+
DESC
|
23
|
+
|
24
|
+
def self.arguments
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.options
|
29
|
+
[].concat(super)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(argv)
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
package_dir = Dir.pwd
|
38
|
+
|
39
|
+
puts "=" * 60
|
40
|
+
puts "📦 Unity Package 打包"
|
41
|
+
puts "=" * 60
|
42
|
+
puts
|
43
|
+
|
44
|
+
# 先执行 initpack 命令进行检查和同步
|
45
|
+
puts "🔍 执行初始化检查..."
|
46
|
+
initpack_cmd = Pindo::Command::Unity::Initpack.new(CLAide::ARGV.new([]))
|
47
|
+
initpack_cmd.run
|
48
|
+
puts
|
49
|
+
|
50
|
+
# 获取 nuspec 文件路径
|
51
|
+
status = Pindo::Unity::NugetHelper.detect_files(package_dir)
|
52
|
+
nuspec_file = status[:nuspec_file]
|
53
|
+
|
54
|
+
# 生成 Release Notes(从 Git 提交历史)
|
55
|
+
puts "📝 生成 Release Notes..."
|
56
|
+
release_notes = Pindo::Unity::NugetHelper.generate_release_notes_from_git(package_dir)
|
57
|
+
|
58
|
+
if release_notes && !release_notes.empty?
|
59
|
+
# 更新 .nuspec 文件
|
60
|
+
Pindo::Unity::NugetHelper.update_nuspec_release_notes(nuspec_file, release_notes)
|
61
|
+
puts "✅ 已更新 .nuspec 的 releaseNotes 字段"
|
62
|
+
puts
|
63
|
+
else
|
64
|
+
puts "⚠️ 未生成 Release Notes,将使用 .nuspec 中已有的内容"
|
65
|
+
puts
|
66
|
+
end
|
67
|
+
|
68
|
+
# 执行打包
|
69
|
+
nupkg_file = Pindo::Unity::NugetHelper.pack_nupkg(package_dir)
|
70
|
+
|
71
|
+
puts
|
72
|
+
puts "=" * 60
|
73
|
+
puts "✅ 打包完成!"
|
74
|
+
puts "=" * 60
|
75
|
+
puts
|
76
|
+
puts "生成文件:"
|
77
|
+
puts " #{nupkg_file}"
|
78
|
+
puts
|
79
|
+
puts "下一步:"
|
80
|
+
puts " 运行 pindo unity upload 上传到 JPS"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'highline/import'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'json'
|
4
|
+
require 'jpsclient'
|
5
|
+
require 'pindo/module/unity/nugethelper'
|
6
|
+
|
7
|
+
module Pindo
|
8
|
+
class Command
|
9
|
+
class Unity < Command
|
10
|
+
class Upload < Unity
|
11
|
+
|
12
|
+
self.summary = '上传 NuGet 包到 JPS'
|
13
|
+
|
14
|
+
self.description = <<-DESC
|
15
|
+
上传打包好的 .nupkg 文件到 JPS 的 Nuget 项目。
|
16
|
+
|
17
|
+
支持功能:
|
18
|
+
|
19
|
+
* 自动查找当前目录的 .nupkg 文件
|
20
|
+
|
21
|
+
* 指定文件路径上传
|
22
|
+
|
23
|
+
* 上传前用户确认
|
24
|
+
|
25
|
+
使用示例:
|
26
|
+
|
27
|
+
$ pindo unity upload # 自动查找并上传
|
28
|
+
|
29
|
+
$ pindo unity upload path/to/package.nupkg # 指定文件上传
|
30
|
+
|
31
|
+
$ pindo unity upload --nupkg=path/to/file # 使用选项指定
|
32
|
+
DESC
|
33
|
+
|
34
|
+
def self.arguments
|
35
|
+
[
|
36
|
+
CLAide::Argument.new('path/to/package.nupkg', false),
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.options
|
41
|
+
[
|
42
|
+
['--nupkg', '指定要上传的 .nupkg 文件路径'],
|
43
|
+
].concat(super)
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(argv)
|
47
|
+
@args_nupkg_file = argv.shift_argument
|
48
|
+
@nupkg_file = argv.option('nupkg')
|
49
|
+
|
50
|
+
if !@nupkg_file.nil?
|
51
|
+
@args_nupkg_file = @nupkg_file
|
52
|
+
end
|
53
|
+
if @args_nupkg_file && !@args_nupkg_file.empty?
|
54
|
+
@args_nupkg_file = @args_nupkg_file.strip.gsub(/\"/, '')
|
55
|
+
end
|
56
|
+
|
57
|
+
super(argv)
|
58
|
+
@additional_args = argv.remainder!
|
59
|
+
end
|
60
|
+
|
61
|
+
def run
|
62
|
+
package_dir = Dir.pwd
|
63
|
+
|
64
|
+
puts "=" * 60
|
65
|
+
puts "🚀 上传到 JPS Nuget 项目"
|
66
|
+
puts "=" * 60
|
67
|
+
puts
|
68
|
+
|
69
|
+
# 如果没有指定文件或文件不存在,则查找
|
70
|
+
if @args_nupkg_file.nil? || !File.exist?(@args_nupkg_file)
|
71
|
+
# 在当前目录查找 .nupkg 文件
|
72
|
+
@args_nupkg_file = Pindo::Unity::NugetHelper.find_nupkg_file(package_dir)
|
73
|
+
|
74
|
+
# 如果找到文件,询问用户是否上传
|
75
|
+
if !@args_nupkg_file.nil?
|
76
|
+
answer = agree("需要上传的文件是: #{@args_nupkg_file} ?(Y/n)")
|
77
|
+
if !answer
|
78
|
+
@args_nupkg_file = nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# 如果还是没有文件,让用户手动输入
|
83
|
+
if @args_nupkg_file.nil? || !File.exist?(@args_nupkg_file)
|
84
|
+
@args_nupkg_file = ask('需要上传的文件:') || nil
|
85
|
+
if @args_nupkg_file
|
86
|
+
@args_nupkg_file = @args_nupkg_file.strip.gsub(/\\ /, ' ')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# 验证文件存在
|
92
|
+
if @args_nupkg_file.nil? || !File.exist?(@args_nupkg_file)
|
93
|
+
raise Informative, "未找到 .nupkg 文件"
|
94
|
+
end
|
95
|
+
|
96
|
+
puts
|
97
|
+
puts "上传文件:"
|
98
|
+
puts " #{File.basename(@args_nupkg_file)}"
|
99
|
+
puts " 大小: #{sprintf('%.2f', File.size(@args_nupkg_file) / 1024.0 / 1024.0)} MB"
|
100
|
+
puts
|
101
|
+
|
102
|
+
# 上传到 JPS
|
103
|
+
upload_to_jps_nuget(@args_nupkg_file)
|
104
|
+
|
105
|
+
puts
|
106
|
+
puts "=" * 60
|
107
|
+
puts "✅ 上传完成!"
|
108
|
+
puts "=" * 60
|
109
|
+
puts
|
110
|
+
|
111
|
+
# 上传成功后自动打 tag
|
112
|
+
create_git_tag_from_nuspec(package_dir)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def upload_to_jps_nuget(nupkg_file)
|
118
|
+
# 1. 登录 JPS
|
119
|
+
config_file = File.join(
|
120
|
+
Pindoconfig.instance.pindo_common_configdir,
|
121
|
+
"jps_client_config.json"
|
122
|
+
)
|
123
|
+
|
124
|
+
puts "🔐 登录 JPS..."
|
125
|
+
jps_client = JPSClient::Client.new(config_file: config_file)
|
126
|
+
login_success = jps_client.do_login(force_login: false)
|
127
|
+
|
128
|
+
unless login_success
|
129
|
+
raise Informative, "JPS 登录失败,请检查配置"
|
130
|
+
end
|
131
|
+
|
132
|
+
puts "✅ 登录成功"
|
133
|
+
puts
|
134
|
+
|
135
|
+
# 2. 查询 Nuget 项目
|
136
|
+
nuget_project = find_nuget_project(jps_client)
|
137
|
+
puts
|
138
|
+
|
139
|
+
# 3. 上传文件到存储(使用 nuget-resource bucket)
|
140
|
+
puts "📤 上传文件到存储..."
|
141
|
+
|
142
|
+
# 修改配置使用 nuget-resource bucket
|
143
|
+
config_json = jps_client.config_json
|
144
|
+
config_json['upload_config']['bucket_name'] = 'nuget-resource'
|
145
|
+
|
146
|
+
upload_client = JPSClient::UploadClient.new(jps_client)
|
147
|
+
file_url = upload_client.upload_file(binary_file: nupkg_file)
|
148
|
+
|
149
|
+
if file_url.nil?
|
150
|
+
raise Informative, "文件上传失败"
|
151
|
+
end
|
152
|
+
|
153
|
+
puts "✅ 文件上传成功"
|
154
|
+
puts
|
155
|
+
|
156
|
+
# 4. 提交到 JPS 项目
|
157
|
+
puts "📋 提交到 JPS 项目..."
|
158
|
+
|
159
|
+
# 读取 package.json 获取包信息
|
160
|
+
package_info = JSON.parse(File.read(File.join(Dir.pwd, "package.json")))
|
161
|
+
|
162
|
+
result = jps_client.upload_project_package(
|
163
|
+
projectId: nuget_project['id'],
|
164
|
+
params: {
|
165
|
+
packageUrl: file_url,
|
166
|
+
attachFileUrls: []
|
167
|
+
}
|
168
|
+
)
|
169
|
+
|
170
|
+
if result && result['code'] == 200
|
171
|
+
puts "✅ 提交成功"
|
172
|
+
print_upload_result(result, package_info)
|
173
|
+
else
|
174
|
+
error_msg = result['message'] || result['msg'] || '未知错误'
|
175
|
+
raise Informative, "提交失败: #{error_msg}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def find_nuget_project(jps_client)
|
180
|
+
puts "🔍 查询 Nuget 项目..."
|
181
|
+
|
182
|
+
result = jps_client.get_project_list(params: {})
|
183
|
+
|
184
|
+
unless result && result['code'] == 200
|
185
|
+
raise Informative, "获取项目列表失败"
|
186
|
+
end
|
187
|
+
|
188
|
+
projects = result['data'] || []
|
189
|
+
|
190
|
+
if projects.empty?
|
191
|
+
raise Informative, "JPS 中没有任何项目,请联系管理员创建"
|
192
|
+
end
|
193
|
+
|
194
|
+
nuget_project = projects.find { |p| p['projectName']&.downcase == 'nuget' }
|
195
|
+
|
196
|
+
if nuget_project.nil?
|
197
|
+
puts
|
198
|
+
puts "❌ 未找到名为 'nuget' 的项目"
|
199
|
+
puts
|
200
|
+
puts "可用项目列表:"
|
201
|
+
projects.each do |proj|
|
202
|
+
puts " - #{proj['projectName']} (ID: #{proj['id']})"
|
203
|
+
end
|
204
|
+
puts
|
205
|
+
raise Informative, "请联系管理员在 JPS 中创建名为 'nuget' 的项目"
|
206
|
+
end
|
207
|
+
|
208
|
+
puts "✅ 找到项目: #{nuget_project['projectName']} (ID: #{nuget_project['id']})"
|
209
|
+
|
210
|
+
nuget_project
|
211
|
+
end
|
212
|
+
|
213
|
+
def print_upload_result(result, package_info)
|
214
|
+
puts
|
215
|
+
puts "━" * 60
|
216
|
+
puts "📦 上传信息"
|
217
|
+
puts "━" * 60
|
218
|
+
puts "包名称: #{package_info['displayName']}"
|
219
|
+
puts "包ID: #{package_info['name']}"
|
220
|
+
puts "版本: #{package_info['version']}"
|
221
|
+
|
222
|
+
if result['data'] && result['data']['id']
|
223
|
+
puts "JPS ID: #{result['data']['id']}"
|
224
|
+
end
|
225
|
+
|
226
|
+
puts "━" * 60
|
227
|
+
end
|
228
|
+
|
229
|
+
def create_git_tag_from_nuspec(package_dir)
|
230
|
+
puts "🏷️ 创建 Git Tag..."
|
231
|
+
|
232
|
+
# 获取 .nuspec 文件并解析版本
|
233
|
+
nuspec_file = Pindo::Unity::NugetHelper.find_nuspec_file(package_dir)
|
234
|
+
unless nuspec_file
|
235
|
+
puts "⚠️ 未找到 .nuspec 文件,跳过创建 tag"
|
236
|
+
return
|
237
|
+
end
|
238
|
+
|
239
|
+
nuspec_info = Pindo::Unity::NugetHelper.parse_nuspec(nuspec_file)
|
240
|
+
version = nuspec_info['version']
|
241
|
+
|
242
|
+
unless version && !version.empty?
|
243
|
+
puts "⚠️ .nuspec 中未找到版本号,跳过创建 tag"
|
244
|
+
return
|
245
|
+
end
|
246
|
+
|
247
|
+
puts "版本号: #{version}"
|
248
|
+
puts
|
249
|
+
|
250
|
+
# 调用 pindo dev tag 命令,使用 --tag 参数
|
251
|
+
begin
|
252
|
+
tag_cmd = Pindo::Command::Dev::Tag.new(CLAide::ARGV.new(["--tag=#{version}"]))
|
253
|
+
tag_cmd.run
|
254
|
+
puts "✅ Git Tag 创建成功"
|
255
|
+
rescue => e
|
256
|
+
puts "⚠️ 创建 Git Tag 失败: #{e.message}"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
data/lib/pindo/command/unity.rb
CHANGED
@@ -142,16 +142,20 @@ module Pindo
|
|
142
142
|
# 在自动化模式或没有交互式终端时,默认选择新增版本号打新Tag
|
143
143
|
puts "检测到当前代码没有打Tag,在自动模式下将自动新增版本号并打新Tag"
|
144
144
|
tag_action_parms = []
|
145
|
-
is_need_add_tag =
|
145
|
+
is_need_add_tag = true # 自动模式下需要打tag
|
146
146
|
else
|
147
147
|
# 检查是否已有用户选择
|
148
148
|
context = PindoContext.instance
|
149
149
|
build_helper = BuildHelper.share_instance
|
150
150
|
# 优先从持久化缓存读取,如果没有则从内存临时缓存读取
|
151
|
-
cached_decision = context.get_selection(PindoContext::SelectionKey::TAG_DECISION)
|
152
|
-
build_helper.temp_tag_decision
|
151
|
+
cached_decision = context.get_selection(PindoContext::SelectionKey::TAG_DECISION)
|
153
152
|
|
154
|
-
|
153
|
+
# 只有当持久化缓存中确实不存在该键时,才从临时缓存读取
|
154
|
+
if cached_decision.nil? && !context.has_selection?(PindoContext::SelectionKey::TAG_DECISION)
|
155
|
+
cached_decision = build_helper.temp_tag_decision
|
156
|
+
end
|
157
|
+
|
158
|
+
if cached_decision && cached_decision.is_a?(Hash)
|
155
159
|
# 使用之前的选择
|
156
160
|
puts "\n使用之前的选择:#{cached_decision[:description]}"
|
157
161
|
tag_action_parms = cached_decision[:tag_action_parms]
|
@@ -172,6 +176,7 @@ module Pindo
|
|
172
176
|
tag_action_parms = []
|
173
177
|
selected_action = :new_tag
|
174
178
|
selected_description = "新增版本号,打新Tag"
|
179
|
+
is_need_add_tag = true # 明确设置需要打tag
|
175
180
|
:new_tag
|
176
181
|
},
|
177
182
|
"将上次的Tag删除重新打Tag" => -> {
|
@@ -179,17 +184,22 @@ module Pindo
|
|
179
184
|
tag_action_parms << "--retag"
|
180
185
|
selected_action = :recreate_tag
|
181
186
|
selected_description = "将上次的Tag删除重新打Tag"
|
187
|
+
is_need_add_tag = true # 明确设置需要打tag
|
182
188
|
:recreate_tag
|
183
189
|
},
|
184
190
|
"不需要Tag继续编译且上传,手动修改上传备注" => -> {
|
185
191
|
puts ""
|
192
|
+
tag_action_parms = nil # 明确设置为nil
|
186
193
|
selected_action = :continue_without_tag
|
187
194
|
selected_description = "不需要Tag继续编译且上传"
|
195
|
+
is_need_add_tag = false # 明确设置不需要打tag
|
188
196
|
:continue_without_tag
|
189
197
|
},
|
190
198
|
"终止退出编译" => -> {
|
199
|
+
tag_action_parms = nil # 明确设置为nil
|
191
200
|
selected_action = :exit
|
192
201
|
selected_description = "终止退出编译"
|
202
|
+
is_need_add_tag = false # 明确设置不需要打tag
|
193
203
|
raise Informative, "终止退出编译!"
|
194
204
|
:exit
|
195
205
|
}
|
@@ -203,7 +213,7 @@ module Pindo
|
|
203
213
|
end
|
204
214
|
end
|
205
215
|
|
206
|
-
is_need_add_tag
|
216
|
+
# is_need_add_tag 已在各个选项中明确设置,无需重新计算
|
207
217
|
|
208
218
|
# 保存用户选择
|
209
219
|
build_helper = BuildHelper.share_instance
|
@@ -0,0 +1,571 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module Pindo
|
7
|
+
module Unity
|
8
|
+
class NugetHelper
|
9
|
+
extend Pindo::Githelper
|
10
|
+
|
11
|
+
# ============================================
|
12
|
+
# ID 格式转换
|
13
|
+
# ============================================
|
14
|
+
|
15
|
+
# 驼峰转全小写(用于 package.json)
|
16
|
+
def self.to_lowercase_id(id)
|
17
|
+
id.downcase
|
18
|
+
end
|
19
|
+
|
20
|
+
# 保持驼峰(用于 .nuspec)
|
21
|
+
def self.to_camelcase_id(id)
|
22
|
+
# 如果 id 已经是驼峰,保持原样
|
23
|
+
# 如果是全小写,转为驼峰
|
24
|
+
if id == id.downcase
|
25
|
+
# 简单的转换:按点分割,每段首字母大写
|
26
|
+
id.split('.').map do |part|
|
27
|
+
part.split(/(?=[A-Z])/).map(&:capitalize).join
|
28
|
+
end.join('.')
|
29
|
+
else
|
30
|
+
id # 保持原有的驼峰格式
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# ============================================
|
35
|
+
# 文件检测相关
|
36
|
+
# ============================================
|
37
|
+
|
38
|
+
# 查找 .nuspec 文件
|
39
|
+
def self.find_nuspec_file(package_dir)
|
40
|
+
# 优先根据 package.json 的 displayName 查找
|
41
|
+
if File.exist?(File.join(package_dir, "package.json"))
|
42
|
+
package_info = parse_package_json(package_dir)
|
43
|
+
display_name = package_info['displayName']
|
44
|
+
expected_nuspec = File.join(package_dir, "#{display_name}.nuspec")
|
45
|
+
return expected_nuspec if File.exist?(expected_nuspec)
|
46
|
+
end
|
47
|
+
|
48
|
+
# 查找任意 .nuspec 文件
|
49
|
+
nuspec_files = Dir.glob(File.join(package_dir, "*.nuspec"))
|
50
|
+
nuspec_files.first
|
51
|
+
end
|
52
|
+
|
53
|
+
# 检测文件存在情况
|
54
|
+
def self.detect_files(package_dir)
|
55
|
+
has_package_json = File.exist?(File.join(package_dir, "package.json"))
|
56
|
+
nuspec_file = find_nuspec_file(package_dir)
|
57
|
+
has_nuspec = !nuspec_file.nil?
|
58
|
+
|
59
|
+
{
|
60
|
+
has_package_json: has_package_json,
|
61
|
+
has_nuspec: has_nuspec,
|
62
|
+
nuspec_file: nuspec_file
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# ============================================
|
67
|
+
# 初始化和同步(4种情况)
|
68
|
+
# ============================================
|
69
|
+
|
70
|
+
# 情况A:只有 package.json,创建 .nuspec
|
71
|
+
def self.handle_case_a(package_dir)
|
72
|
+
package_info = parse_package_json(package_dir)
|
73
|
+
validate_package_json(package_info)
|
74
|
+
|
75
|
+
# 确保 package.json 的 name 是全小写
|
76
|
+
package_info['name'] = to_lowercase_id(package_info['name'])
|
77
|
+
update_package_json_name(package_dir, package_info['name'])
|
78
|
+
|
79
|
+
nuspec_path = File.join(package_dir, "#{package_info['displayName']}.nuspec")
|
80
|
+
|
81
|
+
# 生成 .nuspec,ID 使用驼峰格式
|
82
|
+
nuspec_id = to_camelcase_id(package_info['name'])
|
83
|
+
generate_nuspec_from_package_json(package_info, nuspec_path, nuspec_id)
|
84
|
+
|
85
|
+
puts "✅ 根据 package.json 创建了 #{package_info['displayName']}.nuspec"
|
86
|
+
puts " package.json name: #{package_info['name']} (全小写)"
|
87
|
+
puts " .nuspec id: #{nuspec_id} (驼峰)"
|
88
|
+
end
|
89
|
+
|
90
|
+
# 情况B:只有 .nuspec,创建 package.json
|
91
|
+
def self.handle_case_b(package_dir, nuspec_file)
|
92
|
+
nuspec_info = parse_nuspec(nuspec_file)
|
93
|
+
validate_nuspec(nuspec_info)
|
94
|
+
|
95
|
+
package_json_path = File.join(package_dir, "package.json")
|
96
|
+
|
97
|
+
# package.json 的 name 使用全小写
|
98
|
+
package_json_name = to_lowercase_id(nuspec_info['id'])
|
99
|
+
generate_package_json_from_nuspec(nuspec_info, package_json_path, package_json_name)
|
100
|
+
|
101
|
+
puts "✅ 根据 #{File.basename(nuspec_file)} 创建了 package.json"
|
102
|
+
puts " .nuspec id: #{nuspec_info['id']} (驼峰)"
|
103
|
+
puts " package.json name: #{package_json_name} (全小写)"
|
104
|
+
end
|
105
|
+
|
106
|
+
# 情况C:同时存在,以 .nuspec 为准同步到 package.json
|
107
|
+
def self.handle_case_c(package_dir, nuspec_file)
|
108
|
+
package_info = parse_package_json(package_dir)
|
109
|
+
nuspec_info = parse_nuspec(nuspec_file)
|
110
|
+
|
111
|
+
conflicts = check_conflicts(package_info, nuspec_info)
|
112
|
+
|
113
|
+
if conflicts.empty?
|
114
|
+
puts "✅ package.json 和 .nuspec 信息一致"
|
115
|
+
|
116
|
+
# 即使一致,也要确保 ID 格式正确
|
117
|
+
expected_package_name = to_lowercase_id(nuspec_info['id'])
|
118
|
+
expected_nuspec_id = nuspec_info['id'] # 保持驼峰
|
119
|
+
|
120
|
+
if package_info['name'] != expected_package_name
|
121
|
+
puts "🔧 修正 package.json name 格式:"
|
122
|
+
puts " #{package_info['name']} → #{expected_package_name}"
|
123
|
+
update_package_json_name(package_dir, expected_package_name)
|
124
|
+
end
|
125
|
+
else
|
126
|
+
puts "⚠️ 检测到不一致,将以 .nuspec 为准同步到 package.json:"
|
127
|
+
conflicts.each { |msg| puts " - #{msg}" }
|
128
|
+
puts
|
129
|
+
|
130
|
+
# 以 .nuspec 为准,更新 package.json
|
131
|
+
update_package_json_from_nuspec(nuspec_info, package_dir)
|
132
|
+
puts "✅ 已同步 package.json"
|
133
|
+
puts " .nuspec id: #{nuspec_info['id']} (驼峰)"
|
134
|
+
puts " package.json name: #{to_lowercase_id(nuspec_info['id'])} (全小写)"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# 情况D:都不存在,报错
|
139
|
+
def self.handle_case_d(package_dir)
|
140
|
+
raise Informative, <<~ERROR
|
141
|
+
当前目录不是有效的 Unity Package:
|
142
|
+
- 缺少 package.json 文件
|
143
|
+
- 缺少 .nuspec 文件
|
144
|
+
|
145
|
+
请确保至少存在其中一个文件。
|
146
|
+
ERROR
|
147
|
+
end
|
148
|
+
|
149
|
+
# ============================================
|
150
|
+
# 解析和验证
|
151
|
+
# ============================================
|
152
|
+
|
153
|
+
# 解析 package.json
|
154
|
+
def self.parse_package_json(package_dir)
|
155
|
+
file_path = File.join(package_dir, "package.json")
|
156
|
+
JSON.parse(File.read(file_path))
|
157
|
+
end
|
158
|
+
|
159
|
+
# 解析 .nuspec
|
160
|
+
def self.parse_nuspec(nuspec_file)
|
161
|
+
doc = Nokogiri::XML(File.read(nuspec_file))
|
162
|
+
doc.remove_namespaces!
|
163
|
+
metadata = doc.at_xpath('//metadata')
|
164
|
+
|
165
|
+
raise Informative, ".nuspec 文件格式错误:未找到 metadata 节点" if metadata.nil?
|
166
|
+
|
167
|
+
{
|
168
|
+
'id' => metadata.at_xpath('id')&.text,
|
169
|
+
'version' => metadata.at_xpath('version')&.text,
|
170
|
+
'title' => metadata.at_xpath('title')&.text,
|
171
|
+
'description' => metadata.at_xpath('description')&.text,
|
172
|
+
'releaseNotes' => metadata.at_xpath('releaseNotes')&.text,
|
173
|
+
'authors' => metadata.at_xpath('authors')&.text,
|
174
|
+
'projectUrl' => metadata.at_xpath('projectUrl')&.text,
|
175
|
+
'readme' => metadata.at_xpath('readme')&.text,
|
176
|
+
'license' => metadata.at_xpath('license')&.text
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
# 检查冲突(忽略 ID 大小写差异)
|
181
|
+
def self.check_conflicts(package_info, nuspec_info)
|
182
|
+
conflicts = []
|
183
|
+
|
184
|
+
# ID 对比(都转为小写比较)
|
185
|
+
if to_lowercase_id(package_info['name']) != to_lowercase_id(nuspec_info['id'])
|
186
|
+
conflicts << "ID不一致: package.json(#{package_info['name']}) vs .nuspec(#{nuspec_info['id']})"
|
187
|
+
end
|
188
|
+
|
189
|
+
# 版本对比
|
190
|
+
if package_info['version'] != nuspec_info['version']
|
191
|
+
conflicts << "版本不一致: package.json(#{package_info['version']}) vs .nuspec(#{nuspec_info['version']})"
|
192
|
+
end
|
193
|
+
|
194
|
+
# displayName/title 对比
|
195
|
+
if package_info['displayName'] != nuspec_info['title']
|
196
|
+
conflicts << "标题不一致: package.json displayName(#{package_info['displayName']}) vs .nuspec title(#{nuspec_info['title']})"
|
197
|
+
end
|
198
|
+
|
199
|
+
# releaseNotes 对比
|
200
|
+
pkg_notes = package_info['releaseNotes'] || package_info['changelogUrl'] || ""
|
201
|
+
nuspec_notes = nuspec_info['releaseNotes'] || ""
|
202
|
+
if !pkg_notes.empty? && !nuspec_notes.empty? && pkg_notes != nuspec_notes
|
203
|
+
conflicts << "发布说明不一致"
|
204
|
+
end
|
205
|
+
|
206
|
+
conflicts
|
207
|
+
end
|
208
|
+
|
209
|
+
# 验证 package.json
|
210
|
+
def self.validate_package_json(package_info)
|
211
|
+
required_fields = ['name', 'version', 'displayName']
|
212
|
+
missing = required_fields.select { |field| package_info[field].nil? || package_info[field].empty? }
|
213
|
+
|
214
|
+
unless missing.empty?
|
215
|
+
raise Informative, "package.json 缺少必需字段: #{missing.join(', ')}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# 验证 .nuspec
|
220
|
+
def self.validate_nuspec(nuspec_info)
|
221
|
+
required_fields = ['id', 'version', 'title', 'description', 'releaseNotes', 'readme', 'projectUrl']
|
222
|
+
missing = required_fields.select { |field| nuspec_info[field].nil? || nuspec_info[field].empty? }
|
223
|
+
|
224
|
+
unless missing.empty?
|
225
|
+
raise Informative, ".nuspec 缺少必需字段: #{missing.join(', ')}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# ============================================
|
230
|
+
# 生成和更新文件
|
231
|
+
# ============================================
|
232
|
+
|
233
|
+
# 生成 .nuspec(从 package.json)
|
234
|
+
def self.generate_nuspec_from_package_json(package_info, nuspec_path, nuspec_id)
|
235
|
+
# 提取 projectUrl
|
236
|
+
project_url = ''
|
237
|
+
if package_info['repository'].is_a?(Hash)
|
238
|
+
project_url = package_info['repository']['url'] || ''
|
239
|
+
elsif package_info['repository'].is_a?(String)
|
240
|
+
project_url = package_info['repository']
|
241
|
+
end
|
242
|
+
project_url = package_info['homepage'] if project_url.empty? && package_info['homepage']
|
243
|
+
|
244
|
+
# 确保必需字段有值
|
245
|
+
description = package_info['description'] || package_info['displayName'] || 'Unity Package'
|
246
|
+
release_notes = package_info['releaseNotes'] || package_info['changelogUrl'] || 'No release notes'
|
247
|
+
|
248
|
+
nuspec_content = <<~XML
|
249
|
+
<?xml version="1.0" encoding="utf-8"?>
|
250
|
+
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
251
|
+
<metadata>
|
252
|
+
<id>#{nuspec_id}</id>
|
253
|
+
<version>#{package_info['version']}</version>
|
254
|
+
<title>#{package_info['displayName']}</title>
|
255
|
+
<authors>#{package_info['author'] || 'Unity Package'}</authors>
|
256
|
+
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
257
|
+
<license type="expression">MIT</license>
|
258
|
+
<projectUrl>#{project_url}</projectUrl>
|
259
|
+
<description>#{description}</description>
|
260
|
+
<releaseNotes>#{release_notes}</releaseNotes>
|
261
|
+
<readme>README.md</readme>
|
262
|
+
</metadata>
|
263
|
+
<files>
|
264
|
+
<file src="**" exclude="build/**;.git/**;*.nuspec;*.nupkg" target="content" />
|
265
|
+
</files>
|
266
|
+
</package>
|
267
|
+
XML
|
268
|
+
|
269
|
+
File.write(nuspec_path, nuspec_content)
|
270
|
+
end
|
271
|
+
|
272
|
+
# 生成 package.json(从 .nuspec)
|
273
|
+
def self.generate_package_json_from_nuspec(nuspec_info, package_json_path, package_json_name)
|
274
|
+
package_content = {
|
275
|
+
"name" => package_json_name,
|
276
|
+
"version" => nuspec_info['version'],
|
277
|
+
"displayName" => nuspec_info['title'],
|
278
|
+
"description" => nuspec_info['description'],
|
279
|
+
"unity" => "2020.3",
|
280
|
+
"releaseNotes" => nuspec_info['releaseNotes']
|
281
|
+
}
|
282
|
+
|
283
|
+
# 添加 repository 字段(如果有 projectUrl)
|
284
|
+
if nuspec_info['projectUrl'] && !nuspec_info['projectUrl'].empty?
|
285
|
+
package_content["repository"] = {
|
286
|
+
"type" => "git",
|
287
|
+
"url" => nuspec_info['projectUrl']
|
288
|
+
}
|
289
|
+
end
|
290
|
+
|
291
|
+
File.write(package_json_path, JSON.pretty_generate(package_content))
|
292
|
+
end
|
293
|
+
|
294
|
+
# 更新 package.json(从 .nuspec)
|
295
|
+
def self.update_package_json_from_nuspec(nuspec_info, package_dir)
|
296
|
+
package_json_path = File.join(package_dir, "package.json")
|
297
|
+
|
298
|
+
# 读取现有 package.json(保留其他字段)
|
299
|
+
package_info = parse_package_json(package_dir)
|
300
|
+
|
301
|
+
# 更新关键字段
|
302
|
+
package_info['name'] = to_lowercase_id(nuspec_info['id'])
|
303
|
+
package_info['version'] = nuspec_info['version']
|
304
|
+
package_info['displayName'] = nuspec_info['title']
|
305
|
+
package_info['description'] = nuspec_info['description']
|
306
|
+
package_info['releaseNotes'] = nuspec_info['releaseNotes']
|
307
|
+
|
308
|
+
# 更新 repository 字段(如果有 projectUrl)
|
309
|
+
if nuspec_info['projectUrl'] && !nuspec_info['projectUrl'].empty?
|
310
|
+
package_info['repository'] = {
|
311
|
+
"type" => "git",
|
312
|
+
"url" => nuspec_info['projectUrl']
|
313
|
+
}
|
314
|
+
end
|
315
|
+
|
316
|
+
File.write(package_json_path, JSON.pretty_generate(package_info))
|
317
|
+
end
|
318
|
+
|
319
|
+
# 更新 package.json 的 name 字段
|
320
|
+
def self.update_package_json_name(package_dir, new_name)
|
321
|
+
package_json_path = File.join(package_dir, "package.json")
|
322
|
+
package_info = parse_package_json(package_dir)
|
323
|
+
package_info['name'] = new_name
|
324
|
+
File.write(package_json_path, JSON.pretty_generate(package_info))
|
325
|
+
end
|
326
|
+
|
327
|
+
# ============================================
|
328
|
+
# NuGet 打包
|
329
|
+
# ============================================
|
330
|
+
|
331
|
+
# 检测 NuGet 工具(Unity Package 只使用 nuget 命令)
|
332
|
+
def self.check_nuget_tool
|
333
|
+
if system("nuget help > /dev/null 2>&1")
|
334
|
+
return true
|
335
|
+
end
|
336
|
+
|
337
|
+
raise Informative, <<~ERROR
|
338
|
+
未找到 nuget 命令,请安装 NuGet CLI:
|
339
|
+
|
340
|
+
macOS:
|
341
|
+
brew install nuget
|
342
|
+
|
343
|
+
Linux:
|
344
|
+
sudo apt install nuget # Ubuntu/Debian
|
345
|
+
sudo yum install nuget # CentOS/RHEL
|
346
|
+
|
347
|
+
Windows:
|
348
|
+
choco install nuget.commandline
|
349
|
+
或下载 nuget.exe: https://www.nuget.org/downloads
|
350
|
+
ERROR
|
351
|
+
end
|
352
|
+
|
353
|
+
# 执行打包(Unity Package 使用 nuget pack 命令)
|
354
|
+
def self.pack_nupkg(package_dir, output_dir = nil)
|
355
|
+
check_nuget_tool
|
356
|
+
|
357
|
+
nuspec_file = find_nuspec_file(package_dir)
|
358
|
+
if nuspec_file.nil?
|
359
|
+
raise Informative, "未找到 .nuspec 文件,请先运行 pindo unity initpack"
|
360
|
+
end
|
361
|
+
|
362
|
+
output_dir ||= File.join(package_dir, "build")
|
363
|
+
FileUtils.mkdir_p(output_dir)
|
364
|
+
|
365
|
+
puts "📦 开始打包 Unity Package..."
|
366
|
+
puts " .nuspec: #{File.basename(nuspec_file)}"
|
367
|
+
puts " 输出目录: #{output_dir}"
|
368
|
+
puts " 使用工具: nuget pack"
|
369
|
+
puts
|
370
|
+
|
371
|
+
cmd = "cd \"#{package_dir}\" && nuget pack \"#{File.basename(nuspec_file)}\" -OutputDirectory \"#{output_dir}\""
|
372
|
+
|
373
|
+
success = system(cmd)
|
374
|
+
|
375
|
+
unless success
|
376
|
+
raise Informative, "NuGet 打包失败,请检查 .nuspec 文件格式"
|
377
|
+
end
|
378
|
+
|
379
|
+
nupkg_files = Dir.glob(File.join(output_dir, "*.nupkg"))
|
380
|
+
if nupkg_files.empty?
|
381
|
+
raise Informative, "打包完成但未找到 .nupkg 文件"
|
382
|
+
end
|
383
|
+
|
384
|
+
nupkg_file = nupkg_files.max_by { |f| File.mtime(f) }
|
385
|
+
puts "✅ 打包成功: #{File.basename(nupkg_file)}"
|
386
|
+
puts " 路径: #{nupkg_file}"
|
387
|
+
|
388
|
+
nupkg_file
|
389
|
+
end
|
390
|
+
|
391
|
+
# ============================================
|
392
|
+
# 查找 .nupkg 文件
|
393
|
+
# ============================================
|
394
|
+
|
395
|
+
def self.find_nupkg_file(package_dir)
|
396
|
+
# 优先在 build 目录查找
|
397
|
+
build_dir = File.join(package_dir, "build")
|
398
|
+
if File.directory?(build_dir)
|
399
|
+
nupkg_files = Dir.glob(File.join(build_dir, "*.nupkg"))
|
400
|
+
unless nupkg_files.empty?
|
401
|
+
return nupkg_files.max_by { |f| File.mtime(f) }
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# 如果 build 目录没有,在当前目录查找
|
406
|
+
nupkg_files = Dir.glob(File.join(package_dir, "*.nupkg"))
|
407
|
+
|
408
|
+
if nupkg_files.empty?
|
409
|
+
return nil
|
410
|
+
end
|
411
|
+
|
412
|
+
nupkg_files.max_by { |f| File.mtime(f) }
|
413
|
+
end
|
414
|
+
|
415
|
+
# ============================================
|
416
|
+
# 生成 Release Notes(从 Git 提交历史)
|
417
|
+
# ============================================
|
418
|
+
|
419
|
+
def self.generate_release_notes_from_git(package_dir)
|
420
|
+
# 获取 .nuspec 文件并解析版本(用于默认消息)
|
421
|
+
nuspec_file = find_nuspec_file(package_dir)
|
422
|
+
return nil if nuspec_file.nil?
|
423
|
+
|
424
|
+
nuspec_info = parse_nuspec(nuspec_file)
|
425
|
+
nuspec_version = nuspec_info['version']
|
426
|
+
return nil if nuspec_version.nil? || nuspec_version.empty?
|
427
|
+
|
428
|
+
# 默认消息(带 feat: 标签)
|
429
|
+
default_message = "feat: 更新版本到 #{nuspec_version}"
|
430
|
+
|
431
|
+
# 如果不是 Git 仓库,返回默认消息
|
432
|
+
unless is_git_directory?(local_repo_dir: package_dir)
|
433
|
+
return default_message
|
434
|
+
end
|
435
|
+
|
436
|
+
git_root = git_root_directory(local_repo_dir: package_dir)
|
437
|
+
unless git_root
|
438
|
+
return default_message
|
439
|
+
end
|
440
|
+
|
441
|
+
begin
|
442
|
+
# 查找是否存在与 nuspec 版本匹配的 tag(支持 v1.1.3, V1.1.3, 1.1.3 等格式)
|
443
|
+
matching_tag = nil
|
444
|
+
temp_dir = Dir.pwd
|
445
|
+
Dir.chdir(git_root)
|
446
|
+
|
447
|
+
# 获取所有 tags
|
448
|
+
all_tags = git!(%W(-C #{git_root} tag -l)).split("\n")
|
449
|
+
|
450
|
+
# 查找匹配当前版本的 tag(不区分大小写,支持 v 前缀)
|
451
|
+
matching_tag = all_tags.find do |tag|
|
452
|
+
tag_version = tag.gsub(/^(v|V|release[\s_-]*)/i, '')
|
453
|
+
tag_version.downcase == nuspec_version.downcase
|
454
|
+
end
|
455
|
+
|
456
|
+
Dir.chdir(temp_dir)
|
457
|
+
|
458
|
+
# 确定获取哪个范围的 commits
|
459
|
+
git_range = if matching_tag
|
460
|
+
# 找到匹配的 tag,获取前一个 tag 到这个 tag 的 commits
|
461
|
+
puts "📝 找到版本 tag: #{matching_tag},生成该版本的 release notes..."
|
462
|
+
|
463
|
+
temp_dir = Dir.pwd
|
464
|
+
Dir.chdir(git_root)
|
465
|
+
|
466
|
+
# 获取所有 tags 并按版本号排序
|
467
|
+
all_tags = git!(%W(-C #{git_root} tag -l)).split("\n")
|
468
|
+
sorted_tags = all_tags.sort_by do |tag|
|
469
|
+
version_str = tag.gsub(/^(v|V|release[\s_-]*)/i, '')
|
470
|
+
version_str.split('.').map(&:to_i)
|
471
|
+
end
|
472
|
+
|
473
|
+
# 找到当前 tag 的索引
|
474
|
+
tag_index = sorted_tags.index(matching_tag)
|
475
|
+
prev_tag = nil
|
476
|
+
if tag_index && tag_index > 0
|
477
|
+
prev_tag = sorted_tags[tag_index - 1]
|
478
|
+
end
|
479
|
+
|
480
|
+
Dir.chdir(temp_dir)
|
481
|
+
|
482
|
+
if prev_tag
|
483
|
+
"#{prev_tag}..#{matching_tag}"
|
484
|
+
else
|
485
|
+
# 如果是第一个 tag,获取所有历史
|
486
|
+
matching_tag
|
487
|
+
end
|
488
|
+
else
|
489
|
+
# 没有找到匹配的 tag,获取最新 tag 到 HEAD 的 commits
|
490
|
+
puts "📝 未找到版本 #{nuspec_version} 的 tag,生成最新 tag 到 HEAD 的 release notes..."
|
491
|
+
|
492
|
+
# 获取最新 tag
|
493
|
+
latest_tag = nil
|
494
|
+
["v", "V", "release", ""].each do |prefix|
|
495
|
+
latest_tag = get_latest_version_tag(project_dir: git_root, tag_prefix: prefix)
|
496
|
+
break if latest_tag
|
497
|
+
end
|
498
|
+
|
499
|
+
if latest_tag
|
500
|
+
"#{latest_tag}..HEAD"
|
501
|
+
else
|
502
|
+
"HEAD"
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
# 获取 commit messages(包含完整的 body)
|
507
|
+
temp_dir = Dir.pwd
|
508
|
+
Dir.chdir(git_root)
|
509
|
+
|
510
|
+
# 使用特殊分隔符来区分不同的 commits
|
511
|
+
separator = "---COMMIT-SEPARATOR---"
|
512
|
+
commits_raw = git!(%W(-C #{git_root} log #{git_range} --pretty=format:%B#{separator}))
|
513
|
+
|
514
|
+
# 按分隔符拆分成单个 commits
|
515
|
+
commits = commits_raw.split(separator).map(&:strip).reject(&:empty?)
|
516
|
+
|
517
|
+
Dir.chdir(temp_dir)
|
518
|
+
|
519
|
+
# 过滤只保留符合规范的 commits(feat:, fix:, docs:, perf:, refactor:, style:, test:, chore:, revert:)
|
520
|
+
valid_prefixes = ['feat:', 'fix:', 'docs:', 'doc:', 'perf:', 'refactor:', 'style:', 'test:', 'chore:', 'revert:']
|
521
|
+
filtered_commits = commits.select do |commit|
|
522
|
+
valid_prefixes.any? { |prefix| commit.downcase.start_with?(prefix) }
|
523
|
+
end
|
524
|
+
|
525
|
+
# 如果没有符合规范的 commits,返回默认消息
|
526
|
+
if filtered_commits.empty?
|
527
|
+
return default_message
|
528
|
+
end
|
529
|
+
|
530
|
+
# 返回每条 commit,用换行分隔(不添加空行)
|
531
|
+
filtered_commits.join("\n")
|
532
|
+
|
533
|
+
rescue => e
|
534
|
+
puts "⚠️ 生成 release notes 失败: #{e.message}"
|
535
|
+
# 返回默认消息
|
536
|
+
default_message
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
# 更新 .nuspec 文件的 releaseNotes
|
541
|
+
def self.update_nuspec_release_notes(nuspec_file, release_notes)
|
542
|
+
return unless File.exist?(nuspec_file)
|
543
|
+
return if release_notes.nil? || release_notes.empty?
|
544
|
+
|
545
|
+
doc = Nokogiri::XML(File.read(nuspec_file))
|
546
|
+
|
547
|
+
# 保存命名空间信息
|
548
|
+
namespaces = doc.collect_namespaces
|
549
|
+
|
550
|
+
# 移除命名空间以便查找
|
551
|
+
doc.remove_namespaces!
|
552
|
+
metadata = doc.at_xpath('//metadata')
|
553
|
+
|
554
|
+
return unless metadata
|
555
|
+
|
556
|
+
release_notes_node = metadata.at_xpath('releaseNotes')
|
557
|
+
if release_notes_node
|
558
|
+
release_notes_node.content = release_notes
|
559
|
+
else
|
560
|
+
# 如果不存在,创建节点
|
561
|
+
new_node = Nokogiri::XML::Node.new('releaseNotes', doc)
|
562
|
+
new_node.content = release_notes
|
563
|
+
metadata.add_child(new_node)
|
564
|
+
end
|
565
|
+
|
566
|
+
File.write(nuspec_file, doc.to_xml)
|
567
|
+
end
|
568
|
+
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
data/lib/pindo/version.rb
CHANGED
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.5.
|
4
|
+
version: 5.5.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- wade
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 2025-09-28 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: claide
|
@@ -400,7 +400,10 @@ files:
|
|
400
400
|
- lib/pindo/command/unity.rb
|
401
401
|
- lib/pindo/command/unity/apk.rb
|
402
402
|
- lib/pindo/command/unity/autobuild.rb
|
403
|
+
- lib/pindo/command/unity/initpack.rb
|
403
404
|
- lib/pindo/command/unity/ipa.rb
|
405
|
+
- lib/pindo/command/unity/pack.rb
|
406
|
+
- lib/pindo/command/unity/upload.rb
|
404
407
|
- lib/pindo/command/unity/web.rb
|
405
408
|
- lib/pindo/command/utils.rb
|
406
409
|
- lib/pindo/command/utils/boss.rb
|
@@ -440,6 +443,7 @@ files:
|
|
440
443
|
- lib/pindo/module/cert/provisioninghelper.rb
|
441
444
|
- lib/pindo/module/cert/xcodecerthelper.rb
|
442
445
|
- lib/pindo/module/pgyer/pgyerhelper.rb
|
446
|
+
- lib/pindo/module/unity/nugethelper.rb
|
443
447
|
- lib/pindo/module/webserver/brotli_file_handler.rb
|
444
448
|
- lib/pindo/module/webserver/preview/preview.css
|
445
449
|
- lib/pindo/module/webserver/preview/preview.html
|
@@ -475,7 +479,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
475
479
|
- !ruby/object:Gem::Version
|
476
480
|
version: '0'
|
477
481
|
requirements: []
|
478
|
-
rubygems_version: 3.6.
|
482
|
+
rubygems_version: 3.6.3
|
479
483
|
specification_version: 3
|
480
484
|
summary: easy work
|
481
485
|
test_files: []
|