pindo 5.7.1 → 5.8.0

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.
@@ -0,0 +1,176 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+ require 'pindo/base/executable'
4
+ require_relative '../../base/funlog'
5
+
6
+ module Pindo
7
+
8
+ module AndroidResHelper
9
+ extend Pindo::Executable
10
+ executable :sips
11
+
12
+ # Android icon 各密度尺寸定义
13
+ ANDROID_ICON_DENSITIES = {
14
+ 'mdpi' => 48,
15
+ 'hdpi' => 72,
16
+ 'xhdpi' => 96,
17
+ 'xxhdpi' => 144,
18
+ 'xxxhdpi' => 192
19
+ }.freeze
20
+
21
+ # 创建单个密度的Android icon(同时生成方形和圆形命名)
22
+ # @param icon_name [String] 原始icon路径
23
+ # @param new_icon_dir [String] 输出目录
24
+ # @param density [String] 密度名称(mdpi, hdpi等)
25
+ # @param size [Integer] 图标尺寸
26
+ # @return [Hash] 生成的图标路径,失败返回nil
27
+ def self.create_icon_for_density(icon_name:nil, new_icon_dir:nil, density:nil, size:nil)
28
+ unless File.exist?(icon_name)
29
+ Funlog.instance.fancyinfo_error("文件不存在: #{icon_name}")
30
+ return nil
31
+ end
32
+
33
+ begin
34
+ density_dir = File.join(new_icon_dir, "mipmap-#{density}")
35
+ FileUtils.mkdir_p(density_dir)
36
+
37
+ result = {}
38
+
39
+ # 生成两个文件:ic_launcher.png 和 ic_launcher_round.png
40
+ # 注意:两个都是方形,不切圆角,只是文件名不同
41
+ ['ic_launcher.png', 'ic_launcher_round.png'].each do |filename|
42
+ output_path = File.join(density_dir, filename)
43
+
44
+ command = [
45
+ 'sips',
46
+ '--matchTo', '/System/Library/ColorSync/Profiles/sRGB Profile.icc',
47
+ '-z', size.to_s, size.to_s,
48
+ icon_name,
49
+ '--out', output_path
50
+ ]
51
+
52
+ Executable.capture_command('sips', command, capture: :out)
53
+
54
+ unless File.exist?(output_path)
55
+ Funlog.instance.fancyinfo_error("生成icon失败: #{output_path}")
56
+ return nil
57
+ end
58
+
59
+ result[filename] = output_path
60
+ end
61
+
62
+ return result
63
+ rescue => e
64
+ Funlog.instance.fancyinfo_error("生成#{density}密度icon时出错: #{e.message}")
65
+ return nil
66
+ end
67
+ end
68
+
69
+ # 创建所有密度的Android icons
70
+ # @param icon_name [String] 原始icon路径(建议512x512或1024x1024)
71
+ # @param new_icon_dir [String] 输出目录
72
+ # @return [Hash] 所有生成的图标路径,按密度分组,失败返回nil
73
+ def self.create_icons(icon_name:nil, new_icon_dir:nil)
74
+ unless File.exist?(icon_name)
75
+ Funlog.instance.fancyinfo_error("文件不存在: #{icon_name}")
76
+ return nil
77
+ end
78
+
79
+ begin
80
+ FileUtils.mkdir_p(new_icon_dir)
81
+ rescue => e
82
+ Funlog.instance.fancyinfo_error("创建输出目录失败: #{e.message}")
83
+ return nil
84
+ end
85
+
86
+ all_icons = {}
87
+ success_count = 0
88
+
89
+ ANDROID_ICON_DENSITIES.each do |density, size|
90
+ icons = create_icon_for_density(
91
+ icon_name: icon_name,
92
+ new_icon_dir: new_icon_dir,
93
+ density: density,
94
+ size: size
95
+ )
96
+
97
+ if icons
98
+ all_icons[density] = icons
99
+ success_count += 1
100
+ else
101
+ Funlog.instance.fancyinfo_error("#{density}密度icon生成失败,跳过")
102
+ end
103
+ end
104
+
105
+ # 如果所有密度都失败了,返回 nil
106
+ return success_count > 0 ? all_icons : nil
107
+ end
108
+
109
+ # 安装Android icon到项目中
110
+ # @param proj_dir [String] Android项目目录
111
+ # @param new_icon_dir [String] icon文件目录(包含各密度文件夹)
112
+ # @return [Boolean] 是否成功安装
113
+ def self.install_icon(proj_dir:nil, new_icon_dir:nil)
114
+ unless proj_dir && File.directory?(proj_dir)
115
+ Funlog.instance.fancyinfo_error("项目目录不存在: #{proj_dir}")
116
+ return false
117
+ end
118
+
119
+ unless new_icon_dir && File.directory?(new_icon_dir)
120
+ Funlog.instance.fancyinfo_error("Icon目录不存在: #{new_icon_dir}")
121
+ return false
122
+ end
123
+
124
+ begin
125
+ # 查找 res 目录(支持 app/src/main/res 和 unityLibrary/src/main/res)
126
+ possible_res_dirs = [
127
+ File.join(proj_dir, "app/src/main/res"),
128
+ File.join(proj_dir, "unityLibrary/src/main/res")
129
+ ]
130
+
131
+ res_dir = possible_res_dirs.find { |dir| File.directory?(dir) }
132
+
133
+ unless res_dir
134
+ Funlog.instance.fancyinfo_error("未找到Android res目录,检查的路径:\n#{possible_res_dirs.join("\n")}")
135
+ return false
136
+ end
137
+
138
+ success_count = 0
139
+ total_files = 0
140
+
141
+ ANDROID_ICON_DENSITIES.keys.each do |density|
142
+ source_dir = File.join(new_icon_dir, "mipmap-#{density}")
143
+ target_dir = File.join(res_dir, "mipmap-#{density}")
144
+
145
+ next unless File.directory?(source_dir)
146
+
147
+ # 创建目标目录
148
+ FileUtils.mkdir_p(target_dir) unless File.directory?(target_dir)
149
+
150
+ # 复制两个图标文件
151
+ ['ic_launcher.png', 'ic_launcher_round.png'].each do |filename|
152
+ source_file = File.join(source_dir, filename)
153
+ target_file = File.join(target_dir, filename)
154
+
155
+ if File.exist?(source_file)
156
+ FileUtils.cp(source_file, target_file)
157
+ success_count += 1
158
+ total_files += 1
159
+ puts " ✓ 已安装 mipmap-#{density}/#{filename}"
160
+ end
161
+ end
162
+ end
163
+
164
+ puts "\n总计安装 #{success_count}/#{total_files} 个图标文件"
165
+
166
+ return success_count > 0
167
+ rescue => e
168
+ Funlog.instance.fancyinfo_error("安装icon时出错: #{e.message}")
169
+ puts e.backtrace
170
+ return false
171
+ end
172
+ end
173
+
174
+ end
175
+
176
+ end
@@ -80,9 +80,15 @@ module Pindo
80
80
  # 构建 APK
81
81
  # 确保使用正确的 Java 版本 (Java 11+)
82
82
  java_cmd = find_java_command
83
- # puts "\e[36m使用 Java 命令: #{java_cmd}\e[0m"
83
+
84
+ # 验证 Java 版本
85
+ unless verify_java_version(java_cmd)
86
+ raise RuntimeError, "Java 版本不符合要求,bundletool 需要 Java 11+,当前使用的 Java: #{java_cmd}"
87
+ end
88
+
89
+ # 使用数组形式构建命令,正确处理包含空格的路径
84
90
  bundletool_cmd = [
85
- "#{java_cmd} -jar #{bundle_tool} build-apks",
91
+ java_cmd, "-jar", bundle_tool, "build-apks",
86
92
  "--bundle=#{paths[:bundle]}",
87
93
  "--output=#{paths[:output_apks]}",
88
94
  "--ks=#{ks}",
@@ -90,9 +96,18 @@ module Pindo
90
96
  "--ks-key-alias=#{key_alias}",
91
97
  "--key-pass=pass:#{key_pass}",
92
98
  "--mode=universal"
93
- ].join(" ")
99
+ ]
100
+
101
+ # 验证关键文件是否存在
102
+ unless File.exist?(paths[:bundle])
103
+ raise RuntimeError, "AAB 文件不存在: #{paths[:bundle]}"
104
+ end
105
+
106
+ unless File.exist?(ks)
107
+ raise RuntimeError, "Keystore 文件不存在"
108
+ end
94
109
 
95
- unless system(bundletool_cmd)
110
+ unless system(*bundletool_cmd)
96
111
  # 检查是否是 Java 版本问题
97
112
  if java_cmd == 'java'
98
113
  raise RuntimeError, "APKS 构建失败。可能是 Java 版本不兼容,bundletool 需要 Java 11+,请检查 JAVA_HOME 环境变量或安装正确的 Java 版本"
@@ -7,8 +7,6 @@ module Pindo
7
7
  module GradleHelper
8
8
  include BaseAndroidHelper
9
9
 
10
- # 常量定义
11
- RECOMMENDED_GRADLE_VERSION = '7.6.4'
12
10
 
13
11
  # Gradle缓存目录
14
12
  GRADLE_CACHE_DIR = File.expand_path("~/.pindo/pindo_common_config/android_tools/gradle-cache")
@@ -52,51 +50,8 @@ module Pindo
52
50
  end
53
51
 
54
52
  def update_gradle_version(project_path)
55
- # 智能更新gradle wrapper版本
56
- wrapper_path = File.join(project_path, "gradle/wrapper/gradle-wrapper.properties")
57
-
58
- unless File.exist?(wrapper_path)
59
- puts "\e[33m⚠ 未找到gradle-wrapper.properties文件,跳过版本更新\e[0m"
60
- return false
61
- end
62
-
63
- content = File.read(wrapper_path)
64
- original_content = content.dup
65
-
66
- # 解析当前版本
67
- current_version = parse_gradle_version(wrapper_path)
68
- if current_version.nil?
69
- puts "\e[33m⚠ 无法解析当前Gradle版本\e[0m"
70
- return false
71
- end
72
-
73
- puts "当前Gradle版本: #{current_version}"
74
-
75
- # 根据当前版本智能选择目标版本
76
- target_version = determine_target_gradle_version(current_version)
77
-
78
- if target_version == current_version
79
- puts "\e[32m✓ Gradle版本已是最新,无需更新\e[0m"
80
- return true
81
- end
82
-
83
- puts "更新Gradle版本: #{current_version} -> #{target_version}"
84
-
85
- # 更新版本
86
- content.gsub!("gradle-#{current_version}-bin.zip", "gradle-#{target_version}-bin.zip")
87
- content.gsub!("gradleVersion=#{current_version}", "gradleVersion=#{target_version}")
88
-
89
- if content != original_content
90
- File.write(wrapper_path, content)
91
- puts "\e[32m✓ Gradle版本更新完成\e[0m"
92
-
93
- # 重新配置gradle wrapper
94
- setup_gradle_wrapper_from_properties(project_path)
95
- return true
96
- else
97
- puts "\e[33m⚠ Gradle版本更新失败,未找到匹配的版本字符串\e[0m"
98
- return false
99
- end
53
+ # 直接调用setup_gradle_wrapper_from_properties,功能相同
54
+ setup_gradle_wrapper_from_properties(project_path)
100
55
  end
101
56
 
102
57
  # 基于gradle-wrapper.properties自动配置gradle工具
@@ -128,28 +83,6 @@ module Pindo
128
83
  end
129
84
  end
130
85
 
131
- # 检查gradle wrapper配置是否完整
132
- def verify_gradle_wrapper_setup(project_path)
133
- wrapper_dir = File.join(project_path, "gradle/wrapper")
134
- wrapper_jar_path = File.join(wrapper_dir, "gradle-wrapper.jar")
135
- gradlew_path = File.join(project_path, "gradlew")
136
- properties_path = File.join(wrapper_dir, "gradle-wrapper.properties")
137
-
138
- all_exist = File.exist?(wrapper_jar_path) &&
139
- File.exist?(gradlew_path) &&
140
- File.exist?(properties_path)
141
-
142
- if all_exist
143
- puts "\e[32m✓ Gradle Wrapper配置完整\e[0m"
144
- return true
145
- else
146
- puts "\e[33m⚠ Gradle Wrapper配置不完整:\e[0m"
147
- puts " gradle-wrapper.jar: #{File.exist?(wrapper_jar_path) ? '✓' : '✗'}"
148
- puts " gradlew: #{File.exist?(gradlew_path) ? '✓' : '✗'}"
149
- puts " gradle-wrapper.properties: #{File.exist?(properties_path) ? '✓' : '✗'}"
150
- return false
151
- end
152
- end
153
86
 
154
87
  private
155
88
 
@@ -170,21 +103,6 @@ module Pindo
170
103
  nil
171
104
  end
172
105
 
173
- # 根据当前版本确定目标版本
174
- def determine_target_gradle_version(current_version)
175
- # 主版本号匹配
176
- major_version = current_version.split('.').first.to_i
177
- case major_version
178
- when 0..6
179
- return RECOMMENDED_GRADLE_VERSION # 旧版本升级到推荐版本
180
- when 7
181
- return current_version # 已经是7.x,保持当前版本
182
- when 8
183
- return current_version # 已经是8.x,保持当前版本
184
- else
185
- return RECOMMENDED_GRADLE_VERSION # 默认升级到推荐版本
186
- end
187
- end
188
106
 
189
107
  # 简化的Gradle Wrapper生成策略
190
108
  def setup_gradle_wrapper_improved(project_path, gradle_version)
@@ -296,7 +214,6 @@ module Pindo
296
214
 
297
215
  # 简化的curl命令,避免特殊字符问题
298
216
  cmd = "curl -L -o '#{output_path}' '#{url}'"
299
- # puts "执行命令: #{cmd}"
300
217
 
301
218
  if system(cmd)
302
219
  puts "✓ curl下载成功: #{File.size(output_path)} 字节"
@@ -340,31 +257,12 @@ module Pindo
340
257
 
341
258
  # 设置Gradle环境变量
342
259
  def setup_gradle_environment(gradle_home)
343
- puts "设置Gradle环境变量..."
344
-
345
260
  # 直接检查gradle可执行文件
346
261
  gradle_bin_dir = File.join(gradle_home, "bin")
347
262
  gradle_executable = File.join(gradle_bin_dir, "gradle")
348
263
 
349
- puts "检查gradle可执行文件: #{gradle_executable}"
350
-
351
264
  unless File.exist?(gradle_executable)
352
265
  puts "✗ Gradle可执行文件不存在: #{gradle_executable}"
353
- # 列出gradle_home目录内容用于调试
354
- if File.directory?(gradle_home)
355
- puts "gradle_home目录内容:"
356
- Dir.entries(gradle_home).each do |entry|
357
- next if entry.start_with?('.')
358
- entry_path = File.join(gradle_home, entry)
359
- if File.directory?(entry_path)
360
- puts " 目录: #{entry}"
361
- else
362
- puts " 文件: #{entry}"
363
- end
364
- end
365
- else
366
- puts "gradle_home目录不存在: #{gradle_home}"
367
- end
368
266
  return false
369
267
  end
370
268
 
@@ -373,8 +271,6 @@ module Pindo
373
271
  ENV['PATH'] = "#{gradle_bin_dir}:#{ENV['PATH']}"
374
272
 
375
273
  puts "✓ Gradle环境变量设置完成"
376
- puts " GRADLE_HOME: #{ENV['GRADLE_HOME']}"
377
- puts " PATH已更新: #{gradle_bin_dir}"
378
274
 
379
275
  true
380
276
  end
@@ -384,10 +280,8 @@ module Pindo
384
280
  Dir.chdir(project_path) do
385
281
  # 使用gradle wrapper命令
386
282
  cmd = "gradle wrapper"
387
- # puts "执行命令: #{cmd}"
388
283
 
389
284
  if system(cmd)
390
- # puts "✓ 官方Gradle命令生成wrapper成功"
391
285
  return true
392
286
  else
393
287
  puts "✗ 官方Gradle命令生成wrapper失败"
@@ -26,9 +26,9 @@ module Pindo
26
26
  xcdoe_unitylib_project = Xcodeproj::Project::open(unity_project_path)
27
27
  xcdoe_unitylib_project.targets.each do |target|
28
28
  if target.name == 'Unity-iPhone'
29
- target.shell_script_build_phases.each do |phase|
29
+ target.shell_script_build_phases&.each do |phase|
30
30
  if phase.name.eql?("Crashlytics Run Script")
31
- target.remove_build_phase(phase)
31
+ target.build_phases.delete(phase)
32
32
  end
33
33
  end
34
34
  end
@@ -615,7 +615,7 @@ module Pindo
615
615
 
616
616
  projectId = app_info_obj["id"]
617
617
  packageId = app_version_info_obj["id"]
618
- indexNo = app_version_info_obj["incId"]
618
+ indexNo = app_version_info_obj["indexNo"]
619
619
 
620
620
  if projectId.nil? || packageId.nil?
621
621
  Funlog.instance.fancyinfo_error("缺少必要的项目ID或包ID")
@@ -164,6 +164,40 @@ module Pindo
164
164
  # 解析和验证
165
165
  # ============================================
166
166
 
167
+ # 从 package.json 的 author 字段提取作者名称
168
+ # 支持两种格式:
169
+ # 1. 字符串: "Wade"
170
+ # 2. 对象: {"name": "Wade", "email": "wade@example.com"}
171
+ def self.extract_author_name(author_field)
172
+ return 'Unity Package' if author_field.nil?
173
+
174
+ if author_field.is_a?(Hash)
175
+ # 如果是对象,提取 name 字段
176
+ author_name = author_field['name'] || author_field[:name] || 'Unity Package'
177
+ elsif author_field.is_a?(String)
178
+ # 如果是字符串,直接使用
179
+ author_name = author_field
180
+ else
181
+ author_name = 'Unity Package'
182
+ end
183
+
184
+ # 确保是简单的文本,去除换行和多余空格
185
+ author_name.to_s.strip.gsub(/[\r\n]+/, ' ')
186
+ end
187
+
188
+ # 验证 authors 字段格式(必须是简单文本,不能是 JSON)
189
+ def self.validate_authors_format(authors_text)
190
+ return false if authors_text.nil? || authors_text.empty?
191
+
192
+ # 检查是否包含 JSON 特征({, }, :, "name", "email" 等)
193
+ if authors_text.include?('{') || authors_text.include?('}') ||
194
+ authors_text.include?('"name"') || authors_text.include?('"email"')
195
+ return false
196
+ end
197
+
198
+ true
199
+ end
200
+
167
201
  # 解析 package.json
168
202
  def self.parse_package_json(package_dir)
169
203
  file_path = File.join(package_dir, "package.json")
@@ -238,6 +272,25 @@ module Pindo
238
272
  unless missing.empty?
239
273
  raise Informative, ".nuspec 缺少必需字段: #{missing.join(', ')}"
240
274
  end
275
+
276
+ # 验证 authors 字段格式
277
+ if nuspec_info['authors']
278
+ unless validate_authors_format(nuspec_info['authors'])
279
+ raise Informative, <<~ERROR
280
+ .nuspec 文件中的 <authors> 字段格式错误!
281
+
282
+ 当前格式: #{nuspec_info['authors']}
283
+
284
+ 正确格式应该是简单的文本,例如:
285
+ <authors>Wade</authors>
286
+
287
+ 错误格式示例:
288
+ <authors>{"name": "wade","email": "joymaker@unipod.dev"}</authors>
289
+
290
+ 请修正 .nuspec 文件中的 authors 字段。
291
+ ERROR
292
+ end
293
+ end
241
294
  end
242
295
 
243
296
  # ============================================
@@ -259,6 +312,23 @@ module Pindo
259
312
  description = package_info['description'] || package_info['displayName'] || 'Unity Package'
260
313
  release_notes = package_info['releaseNotes'] || package_info['changelogUrl'] || 'No release notes'
261
314
 
315
+ # 提取 author 名称(支持字符串和对象格式)
316
+ author_name = extract_author_name(package_info['author'])
317
+
318
+ # 验证 author 格式
319
+ unless validate_authors_format(author_name)
320
+ raise Informative, <<~ERROR
321
+ package.json 中的 author 字段格式无法转换为有效的 <authors> 格式!
322
+
323
+ 当前 author 值: #{package_info['author'].inspect}
324
+ 提取的作者名称: #{author_name}
325
+
326
+ 请确保 author 字段为以下格式之一:
327
+ 1. 字符串: "author": "Wade"
328
+ 2. 对象: "author": {"name": "Wade", "email": "wade@example.com"}
329
+ ERROR
330
+ end
331
+
262
332
  # 构建基础的 metadata 内容
263
333
  metadata_content = []
264
334
  metadata_content << " <id>#{nuspec_id}</id>"
@@ -267,7 +337,7 @@ module Pindo
267
337
  if package_info['displayName'] && !package_info['displayName'].empty?
268
338
  metadata_content << " <title>#{package_info['displayName']}</title>"
269
339
  end
270
- metadata_content << " <authors>#{package_info['author'] || 'Unity Package'}</authors>"
340
+ metadata_content << " <authors>#{author_name}</authors>"
271
341
  metadata_content << " <requireLicenseAcceptance>false</requireLicenseAcceptance>"
272
342
  metadata_content << " <license type=\"expression\">MIT</license>"
273
343
  metadata_content << " <projectUrl>#{project_url}</projectUrl>"