pindo 5.5.3 → 5.5.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 698722ba7d446bce8be6df5d15039b7c1528349d6d43ab95db8224a0baf2743c
4
- data.tar.gz: 56f32768dfdde34bf203c2529a31bb2454e2612a29e2e9a1a165634d586157db
3
+ metadata.gz: c3c6d7ad7866c8d8f92e5a817db829bf51daf67408a0876b83687b25826c1fcb
4
+ data.tar.gz: 974a6c7c9be87170be834820ead3344e2c59ead93c63d832401ed80412f6353d
5
5
  SHA512:
6
- metadata.gz: 81add8a3f5b1fd16f6746bcea99e3ca2a7d6bb65c7d0f58b7eca0c03a9fc88a42adc8401dda0ffd7200a7daafab28aa4065c4e14447016861ae94f99da341e14
7
- data.tar.gz: 20228ff9d9c68f846c442d35e7fc96e011f3e9c501ad95db2130c7212a624fe082c395a7f282b007eed88fcd5a9ee4300566fbbf579b1d6ecf3d451e1c679ce6
6
+ metadata.gz: 416e9a546f37f30a966db52da1b4922f75ece53e67b52f33085d3723d543f82abdedeffe025d637fc455d9766e387c0f10d6fd5a9a83e4077c9aff794b235710
7
+ data.tar.gz: ac874183dbc82659b9bf653d28e8456edffc8fa61cb42d4abfd9c98a84f516ad3fe5dc7f5130b68dc36734210ac5fbf90d27f35634214e981964221203edfb78
@@ -1,6 +1,7 @@
1
1
  require 'fileutils'
2
2
  require 'pindo/base/executable'
3
3
  require 'etc'
4
+ require 'claide'
4
5
 
5
6
  module Pindo
6
7
  module Githelper
@@ -487,12 +488,27 @@ module Pindo
487
488
  private
488
489
 
489
490
  def display_file_status(branch, files)
490
- puts "========================================"
491
- puts "\n当前所在分支:#{branch}有下面这些文件没有提交:"
491
+ puts "" * 60
492
+ puts "⚠️ 警告:检测到未提交的文件".red
493
+ puts "═" * 60
492
494
  puts
493
- puts "\n#{files}\n"
495
+ puts "当前所在分支: #{branch}".red
496
+ puts "以下文件尚未提交到 Git:".red
494
497
  puts
495
- puts "========================================"
498
+
499
+ # 将文件列表分行并用红色显示
500
+ if files.is_a?(String)
501
+ files.split("\n").each do |file|
502
+ puts " • #{file}".red unless file.empty?
503
+ end
504
+ else
505
+ files.each do |file|
506
+ puts " • #{file}".red unless file.empty?
507
+ end
508
+ end
509
+
510
+ puts
511
+ puts "═" * 60
496
512
  end
497
513
 
498
514
  def process_user_choice(project_dir, branch)
@@ -501,8 +517,8 @@ module Pindo
501
517
  menu_choice = "None"
502
518
 
503
519
  cli.choose do |menu|
504
- menu.header = "对以上未提交的文件, 有下列处理方式"
505
- menu.prompt = "请选择如何处理(1/2/3...):"
520
+ menu.header = "对以上未提交的文件, 有下列处理方式".red
521
+ menu.prompt = "请选择如何处理(1/2/3...):".yellow
506
522
  process_types.each do |item|
507
523
  menu.choice(item) { |choice| menu_choice = choice }
508
524
  end
@@ -11,7 +11,7 @@ module Pindo
11
11
  # 命令的详细说明,包含用法示例
12
12
  self.description = <<-DESC
13
13
  发布代码,添加tag,添加发布信息,并合并到发布分支。
14
-
14
+
15
15
  支持功能:
16
16
 
17
17
  * 处理未提交的文件
@@ -28,6 +28,8 @@ module Pindo
28
28
 
29
29
  $ pindo dev tag --mode=patch # 使用patch模式创建tag
30
30
 
31
+ $ pindo dev tag --tag=1.1.3 # 直接指定版本号创建tag(会自动添加v前缀)
32
+
31
33
  $ pindo dev tag --retag # 强制重新打tag
32
34
  DESC
33
35
 
@@ -38,18 +40,21 @@ module Pindo
38
40
  def self.options
39
41
  [
40
42
  ['--mode', '指定版本号增加模式(major/minor/patch),默认minor'],
41
- ['--retag', '强制重新打最新的tag']
43
+ ['--retag', '强制重新打最新的tag'],
44
+ ['--tag', '直接指定tag版本号(如 1.1.3),会自动添加v前缀']
42
45
  ].concat(super)
43
46
  end
44
47
 
45
48
  def initialize(argv)
46
49
  @mode = argv.option('mode') || 'minor'
47
50
  @force_retag = argv.flag?('retag', false)
48
-
49
- unless ['major', 'minor', 'patch'].include?(@mode)
51
+ @custom_tag = argv.option('tag')
52
+
53
+ # 如果指定了自定义tag,不需要验证mode
54
+ unless @custom_tag || ['major', 'minor', 'patch'].include?(@mode)
50
55
  raise Informative, "mode参数必须是 major, minor 或 patch"
51
56
  end
52
-
57
+
53
58
  super
54
59
  @additional_args = argv.remainder!
55
60
  end
@@ -89,13 +94,45 @@ module Pindo
89
94
  add_release_tag(
90
95
  project_dir: root_dir,
91
96
  increment_mode: @mode,
92
- force_retag: @force_retag
97
+ force_retag: @force_retag,
98
+ custom_tag: @custom_tag
93
99
  )
94
100
  end
95
101
 
96
- def add_release_tag(project_dir: nil, increment_mode: "minor", force_retag: false)
102
+ def add_release_tag(project_dir: nil, increment_mode: "minor", force_retag: false, custom_tag: nil)
97
103
  raise ArgumentError, "项目目录不能为空" if project_dir.nil?
98
-
104
+
105
+ # 如果指定了自定义tag,直接使用
106
+ if custom_tag && !custom_tag.empty?
107
+ Funlog.instance.fancyinfo_start("使用指定的tag版本: #{custom_tag}")
108
+
109
+ # 确保tag有v前缀
110
+ new_tag = custom_tag.start_with?('v') ? custom_tag : "v#{custom_tag}"
111
+
112
+ # 检查tag是否已存在
113
+ existing_tags = git!(%W(-C #{project_dir} tag -l)).split("\n")
114
+ if existing_tags.include?(new_tag)
115
+ if force_retag
116
+ Funlog.instance.fancyinfo_update("tag #{new_tag} 已存在,强制重新打tag")
117
+ git!(%W(-C #{project_dir} tag -d #{new_tag}))
118
+ git!(%W(-C #{project_dir} push origin :refs/tags/#{new_tag}))
119
+ else
120
+ Funlog.instance.fancyinfo_success("tag #{new_tag} 已存在")
121
+ Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
122
+ return
123
+ end
124
+ end
125
+
126
+ # 创建tag并推送
127
+ git!(%W(-C #{project_dir} tag #{new_tag}))
128
+ git!(%W(-C #{project_dir} push origin #{new_tag}))
129
+
130
+ Funlog.instance.fancyinfo_success("创建tag: #{new_tag}")
131
+ Funlog.instance.fancyinfo_success("仓库路径: #{project_dir}")
132
+ return
133
+ end
134
+
135
+ # 原有的自动递增逻辑
99
136
  Funlog.instance.fancyinfo_start("开始创建初tag")
100
137
  latest_tag = get_latest_version_tag(project_dir: project_dir)
101
138
  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,361 @@
1
+ require 'highline/import'
2
+ require 'fileutils'
3
+ require 'json'
4
+ require 'jpsclient'
5
+ require 'claide'
6
+ require 'pindo/module/unity/nugethelper'
7
+
8
+ module Pindo
9
+ class Command
10
+ class Unity < Command
11
+ class Upload < Unity
12
+ include Pindo::Githelper
13
+ extend Pindo::Executable
14
+ executable :git
15
+
16
+ self.summary = '上传 NuGet 包到 JPS'
17
+
18
+ self.description = <<-DESC
19
+ 上传打包好的 .nupkg 文件到 JPS 的 Nuget 项目。
20
+
21
+ 支持功能:
22
+
23
+ * 自动查找当前目录的 .nupkg 文件
24
+
25
+ * 指定文件路径上传
26
+
27
+ * 上传前用户确认
28
+
29
+ 使用示例:
30
+
31
+ $ pindo unity upload # 自动查找并上传
32
+
33
+ $ pindo unity upload path/to/package.nupkg # 指定文件上传
34
+
35
+ $ pindo unity upload --nupkg=path/to/file # 使用选项指定
36
+ DESC
37
+
38
+ def self.arguments
39
+ [
40
+ CLAide::Argument.new('path/to/package.nupkg', false),
41
+ ]
42
+ end
43
+
44
+ def self.options
45
+ [
46
+ ['--nupkg', '指定要上传的 .nupkg 文件路径'],
47
+ ].concat(super)
48
+ end
49
+
50
+ def initialize(argv)
51
+ @args_nupkg_file = argv.shift_argument
52
+ @nupkg_file = argv.option('nupkg')
53
+
54
+ if !@nupkg_file.nil?
55
+ @args_nupkg_file = @nupkg_file
56
+ end
57
+ if @args_nupkg_file && !@args_nupkg_file.empty?
58
+ @args_nupkg_file = @args_nupkg_file.strip.gsub(/\"/, '')
59
+ end
60
+
61
+ super(argv)
62
+ @additional_args = argv.remainder!
63
+ end
64
+
65
+ def run
66
+ package_dir = Dir.pwd
67
+
68
+ puts "=" * 60
69
+ puts "🚀 上传到 JPS Nuget 项目"
70
+ puts "=" * 60
71
+ puts
72
+
73
+ # 如果没有指定文件或文件不存在,则查找
74
+ if @args_nupkg_file.nil? || !File.exist?(@args_nupkg_file)
75
+ # 在当前目录查找 .nupkg 文件
76
+ @args_nupkg_file = Pindo::Unity::NugetHelper.find_nupkg_file(package_dir)
77
+
78
+ # 如果找到文件,询问用户是否上传
79
+ if !@args_nupkg_file.nil?
80
+ answer = agree("需要上传的文件是: #{@args_nupkg_file} ?(Y/n)")
81
+ if !answer
82
+ @args_nupkg_file = nil
83
+ end
84
+ end
85
+
86
+ # 如果还是没有文件,让用户手动输入
87
+ if @args_nupkg_file.nil? || !File.exist?(@args_nupkg_file)
88
+ @args_nupkg_file = ask('需要上传的文件:') || nil
89
+ if @args_nupkg_file
90
+ @args_nupkg_file = @args_nupkg_file.strip.gsub(/\\ /, ' ')
91
+ end
92
+ end
93
+ end
94
+
95
+ # 验证文件存在
96
+ if @args_nupkg_file.nil? || !File.exist?(@args_nupkg_file)
97
+ raise Informative, "未找到 .nupkg 文件"
98
+ end
99
+
100
+ puts
101
+ puts "上传文件:"
102
+ puts " #{File.basename(@args_nupkg_file)}"
103
+ puts " 大小: #{sprintf('%.2f', File.size(@args_nupkg_file) / 1024.0 / 1024.0)} MB"
104
+ puts
105
+
106
+ # 上传到 JPS
107
+ upload_to_jps_nuget(@args_nupkg_file)
108
+
109
+ puts
110
+ puts "=" * 60
111
+ puts "✅ 上传完成!"
112
+ puts "=" * 60
113
+ puts
114
+
115
+ # 上传成功后自动打 tag
116
+ create_git_tag_from_nuspec(package_dir)
117
+ end
118
+
119
+ private
120
+
121
+ def upload_to_jps_nuget(nupkg_file)
122
+ # 1. 登录 JPS
123
+ config_file = File.join(
124
+ Pindoconfig.instance.pindo_common_configdir,
125
+ "jps_client_config.json"
126
+ )
127
+
128
+ puts "🔐 登录 JPS..."
129
+ jps_client = JPSClient::Client.new(config_file: config_file)
130
+ login_success = jps_client.do_login(force_login: false)
131
+
132
+ unless login_success
133
+ raise Informative, "JPS 登录失败,请检查配置"
134
+ end
135
+
136
+ puts "✅ 登录成功"
137
+ puts
138
+
139
+ # 2. 查询 Nuget 项目
140
+ nuget_project = find_nuget_project(jps_client)
141
+ puts
142
+
143
+ # 3. 上传文件到存储(使用 nuget-resource bucket)
144
+ puts "📤 上传文件到存储..."
145
+
146
+ # 修改配置使用 nuget-resource bucket
147
+ config_json = jps_client.config_json
148
+ config_json['upload_config']['bucket_name'] = 'nuget-resource'
149
+
150
+ upload_client = JPSClient::UploadClient.new(jps_client)
151
+ file_url = upload_client.upload_file(binary_file: nupkg_file)
152
+
153
+ if file_url.nil?
154
+ raise Informative, "文件上传失败"
155
+ end
156
+
157
+ puts "✅ 文件上传成功"
158
+ puts
159
+
160
+ # 4. 提交到 JPS 项目
161
+ puts "📋 提交到 JPS 项目..."
162
+
163
+ # 读取 package.json 获取包信息
164
+ package_info = JSON.parse(File.read(File.join(Dir.pwd, "package.json")))
165
+
166
+ result = jps_client.upload_project_package(
167
+ projectId: nuget_project['id'],
168
+ params: {
169
+ packageUrl: file_url,
170
+ attachFileUrls: []
171
+ }
172
+ )
173
+
174
+ if result && result['code'] == 200
175
+ puts "✅ 提交成功"
176
+ print_upload_result(result, package_info)
177
+ else
178
+ error_msg = result['message'] || result['msg'] || '未知错误'
179
+ raise Informative, "提交失败: #{error_msg}"
180
+ end
181
+ end
182
+
183
+ def find_nuget_project(jps_client)
184
+ puts "🔍 查询 Nuget 项目..."
185
+
186
+ result = jps_client.get_project_list(params: {})
187
+
188
+ unless result && result['code'] == 200
189
+ raise Informative, "获取项目列表失败"
190
+ end
191
+
192
+ projects = result['data'] || []
193
+
194
+ if projects.empty?
195
+ raise Informative, "JPS 中没有任何项目,请联系管理员创建"
196
+ end
197
+
198
+ nuget_project = projects.find { |p| p['projectName']&.downcase == 'nuget' }
199
+
200
+ if nuget_project.nil?
201
+ puts
202
+ puts "❌ 未找到名为 'nuget' 的项目"
203
+ puts
204
+ puts "可用项目列表:"
205
+ projects.each do |proj|
206
+ puts " - #{proj['projectName']} (ID: #{proj['id']})"
207
+ end
208
+ puts
209
+ raise Informative, "请联系管理员在 JPS 中创建名为 'nuget' 的项目"
210
+ end
211
+
212
+ puts "✅ 找到项目: #{nuget_project['projectName']} (ID: #{nuget_project['id']})"
213
+
214
+ nuget_project
215
+ end
216
+
217
+ def print_upload_result(result, package_info)
218
+ puts
219
+ puts "━" * 60
220
+ puts "📦 上传信息"
221
+ puts "━" * 60
222
+ puts "包名称: #{package_info['displayName']}"
223
+ puts "包ID: #{package_info['name']}"
224
+ puts "版本: #{package_info['version']}"
225
+
226
+ if result['data'] && result['data']['id']
227
+ puts "JPS ID: #{result['data']['id']}"
228
+ end
229
+
230
+ puts "━" * 60
231
+ end
232
+
233
+ def create_git_tag_from_nuspec(package_dir)
234
+ puts "🏷️ 创建 Git Tag..."
235
+
236
+ # 检查并更新 .gitignore 文件
237
+ ensure_build_directory_ignored(package_dir)
238
+
239
+ # 检查是否有未提交的文件
240
+ check_uncommitted_files(package_dir)
241
+
242
+ # 获取 .nuspec 文件并解析版本
243
+ nuspec_file = Pindo::Unity::NugetHelper.find_nuspec_file(package_dir)
244
+ unless nuspec_file
245
+ puts "⚠️ 未找到 .nuspec 文件,跳过创建 tag"
246
+ return
247
+ end
248
+
249
+ nuspec_info = Pindo::Unity::NugetHelper.parse_nuspec(nuspec_file)
250
+ version = nuspec_info['version']
251
+
252
+ unless version && !version.empty?
253
+ puts "⚠️ .nuspec 中未找到版本号,跳过创建 tag"
254
+ return
255
+ end
256
+
257
+ puts "版本号: #{version}"
258
+ puts
259
+
260
+ # 调用 pindo dev tag 命令,使用 --tag 参数
261
+ begin
262
+ tag_cmd = Pindo::Command::Dev::Tag.new(CLAide::ARGV.new(["--tag=#{version}"]))
263
+ tag_cmd.run
264
+ puts "✅ Git Tag 创建成功"
265
+ rescue => e
266
+ puts "⚠️ 创建 Git Tag 失败: #{e.message}"
267
+ end
268
+ end
269
+
270
+ def check_uncommitted_files(package_dir)
271
+ # 获取未跟踪和已修改的文件
272
+ untracked_files = git!(%W(-C #{package_dir} ls-files --others --exclude-standard)).strip
273
+ modified_files = git!(%W(-C #{package_dir} diff --name-only)).strip
274
+ staged_files = git!(%W(-C #{package_dir} diff --cached --name-only)).strip
275
+
276
+ all_uncommitted = []
277
+ all_uncommitted += untracked_files.split("\n").reject(&:empty?) if !untracked_files.empty?
278
+ all_uncommitted += modified_files.split("\n").reject(&:empty?) if !modified_files.empty?
279
+ all_uncommitted += staged_files.split("\n").reject(&:empty?) if !staged_files.empty?
280
+
281
+ # 过滤掉 build 相关目录
282
+ all_uncommitted = all_uncommitted.reject do |file|
283
+ file.start_with?('build/') || file.start_with?('Build/') ||
284
+ file.start_with?('/build/') || file.start_with?('/Build/')
285
+ end
286
+
287
+ if all_uncommitted.any?
288
+ puts
289
+ puts "═" * 60
290
+ puts "⚠️ 警告:检测到未提交的文件".red
291
+ puts "═" * 60
292
+ puts
293
+ puts "以下文件尚未提交到 Git:".red
294
+ puts
295
+ all_uncommitted.each do |file|
296
+ puts " • #{file}".red
297
+ end
298
+ puts
299
+ puts "═" * 60
300
+ puts "建议先提交这些文件,或将它们添加到 .gitignore 中".red
301
+ puts "═" * 60
302
+ puts
303
+ end
304
+ end
305
+
306
+ def ensure_build_directory_ignored(package_dir)
307
+ gitignore_path = File.join(package_dir, '.gitignore')
308
+
309
+ # 要忽略的目录
310
+ build_patterns = [
311
+ 'build/',
312
+ '/build/',
313
+ 'Build/',
314
+ '/Build/'
315
+ ]
316
+
317
+ # 读取现有的 .gitignore 内容
318
+ existing_patterns = []
319
+ if File.exist?(gitignore_path)
320
+ existing_patterns = File.readlines(gitignore_path).map(&:strip).reject(&:empty?)
321
+ end
322
+
323
+ # 检查是否已经包含 build 目录的忽略规则
324
+ has_build_ignore = existing_patterns.any? do |pattern|
325
+ pattern = pattern.strip
326
+ # 检查是否匹配 build 目录的各种形式
327
+ build_patterns.any? { |bp| pattern == bp || pattern == bp.chomp('/') }
328
+ end
329
+
330
+ # 如果没有包含 build 目录,则添加
331
+ unless has_build_ignore
332
+ puts "📝 更新 .gitignore 文件,添加 build/ 目录..."
333
+
334
+ # 追加 build 目录到 .gitignore
335
+ File.open(gitignore_path, 'a') do |file|
336
+ # 如果文件不为空且最后没有换行,添加换行
337
+ file.puts if existing_patterns.any? && !File.read(gitignore_path).end_with?("\n")
338
+ file.puts "# Unity build output"
339
+ file.puts "build/"
340
+ file.puts "Build/"
341
+ end
342
+
343
+ puts "✅ 已将 build 目录添加到 .gitignore"
344
+
345
+ # 提交 .gitignore 的更改
346
+ begin
347
+ git!(%W(-C #{package_dir} add .gitignore))
348
+ git!(%W(-C #{package_dir} commit -m "chore: 添加 build 目录到 .gitignore"))
349
+ puts "✅ 已提交 .gitignore 更改"
350
+ rescue => e
351
+ puts "⚠️ 提交 .gitignore 失败: #{e.message}"
352
+ end
353
+ else
354
+ puts "✅ .gitignore 已包含 build 目录规则"
355
+ end
356
+ end
357
+
358
+ end
359
+ end
360
+ end
361
+ end
@@ -3,6 +3,9 @@
3
3
  require 'pindo/command/unity/ipa'
4
4
  require 'pindo/command/unity/apk'
5
5
  require 'pindo/command/unity/autobuild'
6
+ require 'pindo/command/unity/initpack'
7
+ require 'pindo/command/unity/pack'
8
+ require 'pindo/command/unity/upload'
6
9
 
7
10
  module Pindo
8
11
  class Command
@@ -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
@@ -6,7 +6,7 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.5.3"
9
+ VERSION = "5.5.5"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
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.3
4
+ version: 5.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-27 00:00:00.000000000 Z
10
+ date: 2025-09-29 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