unipod 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +79 -0
- data/exe/unipod +13 -0
- data/lib/unipod/command/install.rb +384 -0
- data/lib/unipod/command/push.rb +384 -0
- data/lib/unipod/command/server.rb +115 -0
- data/lib/unipod/command.rb +40 -0
- data/lib/unipod/config.rb +393 -0
- data/lib/unipod/server/cache_manager.rb +1106 -0
- data/lib/unipod/server/download_handler.rb +359 -0
- data/lib/unipod/server/index.html +460 -0
- data/lib/unipod/server/package_builder_queue.rb +255 -0
- data/lib/unipod/server/server.rb +393 -0
- data/lib/unipod/server/server_handler.rb +526 -0
- data/lib/unipod/ui.rb +173 -0
- data/lib/unipod/version.rb +5 -0
- data/lib/unipod.rb +33 -0
- metadata +226 -0
@@ -0,0 +1,1106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'open3'
|
5
|
+
require 'json'
|
6
|
+
require 'pathname'
|
7
|
+
require 'yaml'
|
8
|
+
require 'digest'
|
9
|
+
require 'open-uri'
|
10
|
+
require 'unipod/config'
|
11
|
+
|
12
|
+
module UniPod
|
13
|
+
module Server
|
14
|
+
# 管理本地缓存和索引仓库
|
15
|
+
class CacheManager
|
16
|
+
DEFAULT_CACHE_DIR = File.join(Dir.home, 'Library', 'Caches', 'UniPod')
|
17
|
+
DEFAULT_INDEX_REPO_DIR = File.join(DEFAULT_CACHE_DIR, 'scoped')
|
18
|
+
DEFAULT_PACKAGES_DIR = File.join(DEFAULT_CACHE_DIR, 'packages')
|
19
|
+
DEFAULT_TARBALLS_DIR = File.join(DEFAULT_CACHE_DIR, 'tarballs')
|
20
|
+
|
21
|
+
# 增加Unity和NPM的包类型常量
|
22
|
+
PACKAGE_TYPE_UNITY = 'unity'.freeze
|
23
|
+
PACKAGE_TYPE_NPM = 'npm'.freeze
|
24
|
+
|
25
|
+
def initialize(options = {})
|
26
|
+
@cache_dir = options[:cache_dir] || detect_cache_dir
|
27
|
+
@index_repo_dir = options[:index_repo_dir] || File.join(@cache_dir, 'scoped')
|
28
|
+
@packages_dir = File.join(@cache_dir, 'packages')
|
29
|
+
@tarballs_dir = File.join(@cache_dir, 'tarballs')
|
30
|
+
@verbose = options[:verbose] || false
|
31
|
+
@index_repos = []
|
32
|
+
@cache = {} # 内存缓存
|
33
|
+
@last_update_time = {} # 最后更新时间
|
34
|
+
@index_updated = false # 索引是否已更新
|
35
|
+
@package_type_cache = {} # 包类型缓存
|
36
|
+
|
37
|
+
# 确保缓存目录存在
|
38
|
+
FileUtils.mkdir_p(@cache_dir)
|
39
|
+
FileUtils.mkdir_p(@index_repo_dir)
|
40
|
+
FileUtils.mkdir_p(@packages_dir)
|
41
|
+
FileUtils.mkdir_p(@tarballs_dir)
|
42
|
+
|
43
|
+
# 从配置中读取索引库
|
44
|
+
load_index_repos_from_config
|
45
|
+
|
46
|
+
# 初始化索引库
|
47
|
+
update_index_repos
|
48
|
+
end
|
49
|
+
|
50
|
+
# 刷新缓存的公开方法,确保在private方法列表之前定义
|
51
|
+
def refresh_cache
|
52
|
+
UI.puts "刷新包缓存..." if @verbose
|
53
|
+
@index_updated = false
|
54
|
+
@cache = {} # 清空缓存
|
55
|
+
get_all_packages # 重新加载所有包
|
56
|
+
UI.puts "包缓存已刷新,共发现#{@cache.size}个包" if @verbose
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
# 从Unity的manifest.json文件中读取索引库配置
|
61
|
+
def load_index_repos_from_config
|
62
|
+
# 从Config模块获取配置的索引库
|
63
|
+
config_repos = Config.index_repos || []
|
64
|
+
|
65
|
+
# 将配置的索引库添加到索引库列表
|
66
|
+
config_repos.each do |repo|
|
67
|
+
if repo[:package_index_url]
|
68
|
+
@index_repos << repo
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# 如果没有配置索引库,使用默认索引库
|
73
|
+
if @index_repos.empty?
|
74
|
+
# 默认Git URL
|
75
|
+
default_git_url = 'https://gitee.com/goodunitylib/JoyUnityLibIndex.git'
|
76
|
+
|
77
|
+
# 从URL中提取仓库名称
|
78
|
+
repo_name = extract_repo_name_from_url(default_git_url)
|
79
|
+
|
80
|
+
@index_repos = [
|
81
|
+
{
|
82
|
+
name: repo_name,
|
83
|
+
url: default_git_url,
|
84
|
+
package_index_url: default_git_url
|
85
|
+
}
|
86
|
+
]
|
87
|
+
end
|
88
|
+
|
89
|
+
UI.puts "加载了#{@index_repos.size}个索引库配置" if @verbose
|
90
|
+
end
|
91
|
+
|
92
|
+
# 从Git URL中提取仓库名称
|
93
|
+
def extract_repo_name_from_url(url)
|
94
|
+
# 处理URL格式
|
95
|
+
# 示例: https://gitee.com/goodunitylib/JoyUnityLibIndex.git -> JoyUnityLibIndex
|
96
|
+
# 示例: git@github.com:user/repo.git -> repo
|
97
|
+
# 示例: https://github.com/user/repo -> repo
|
98
|
+
|
99
|
+
if url.nil? || url.empty?
|
100
|
+
return 'default_repo'
|
101
|
+
end
|
102
|
+
|
103
|
+
# 移除尾部的.git(如果有)
|
104
|
+
url = url.sub(/\.git\z/, '')
|
105
|
+
|
106
|
+
# 处理不同格式的URL
|
107
|
+
if url.include?('/')
|
108
|
+
# 处理https://格式或git://格式
|
109
|
+
repo_name = url.split('/').last
|
110
|
+
elsif url.include?(':')
|
111
|
+
# 处理git@格式
|
112
|
+
repo_name = url.split(':').last.split('/').last
|
113
|
+
else
|
114
|
+
# 无法解析时使用默认名称
|
115
|
+
repo_name = 'default_repo'
|
116
|
+
end
|
117
|
+
|
118
|
+
# 确保名称不包含特殊字符
|
119
|
+
repo_name.gsub(/[^a-zA-Z0-9_-]/, '_')
|
120
|
+
end
|
121
|
+
|
122
|
+
# 获取包信息
|
123
|
+
def get_package_info(package_name)
|
124
|
+
# 从缓存中获取
|
125
|
+
if @cache.key?(package_name)
|
126
|
+
return @cache[package_name]
|
127
|
+
end
|
128
|
+
|
129
|
+
ensure_index_updated
|
130
|
+
|
131
|
+
UI.puts "获取包信息: #{package_name}" if @verbose
|
132
|
+
|
133
|
+
# 从索引库中查找包信息
|
134
|
+
@index_repos.each do |repo|
|
135
|
+
repo_dir = get_repo_dir(repo[:name])
|
136
|
+
specs_dir = File.join(repo_dir, 'Specs')
|
137
|
+
|
138
|
+
# 首先检查Specs目录(CocoaPods结构)
|
139
|
+
package_dir = File.join(specs_dir, package_name)
|
140
|
+
if Dir.exist?(package_dir)
|
141
|
+
# 查找最新版本
|
142
|
+
version_dirs = Dir.glob(File.join(package_dir, '*')).select { |d| File.directory?(d) }
|
143
|
+
if version_dirs.any?
|
144
|
+
# 获取最新版本
|
145
|
+
latest_version_dir = version_dirs.sort_by { |d| Gem::Version.new(File.basename(d)) rescue '0.0.0' }.last
|
146
|
+
spec_path = File.join(latest_version_dir, 'spec.json')
|
147
|
+
|
148
|
+
if File.exist?(spec_path)
|
149
|
+
begin
|
150
|
+
spec = JSON.parse(File.read(spec_path))
|
151
|
+
package_info = convert_spec_to_package(spec)
|
152
|
+
processed_info = ensure_package_structure(package_info)
|
153
|
+
@cache[package_name] = processed_info
|
154
|
+
return processed_info
|
155
|
+
rescue => e
|
156
|
+
UI.puts "解析spec.json错误: #{e.message}" if @verbose
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# 向后兼容:检查package.json
|
163
|
+
package_path = File.join(repo_dir, package_name, 'package.json')
|
164
|
+
if File.exist?(package_path)
|
165
|
+
begin
|
166
|
+
package_info = JSON.parse(File.read(package_path))
|
167
|
+
processed_info = ensure_package_structure(package_info)
|
168
|
+
@cache[package_name] = processed_info
|
169
|
+
return processed_info
|
170
|
+
rescue => e
|
171
|
+
UI.puts "解析package.json错误: #{e.message}" if @verbose
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
|
179
|
+
# 获取所有包的信息
|
180
|
+
def get_all_packages
|
181
|
+
UI.puts 'CacheManager: 获取所有包' if @verbose
|
182
|
+
|
183
|
+
# 检查索引是否已更新且缓存不为空
|
184
|
+
if is_index_updated? && !@cache.empty?
|
185
|
+
UI.puts "CacheManager: 返回缓存中的 #{@cache.size} 个包" if @verbose
|
186
|
+
# 确保返回格式化的结果
|
187
|
+
return format_packages_response(@cache.values)
|
188
|
+
end
|
189
|
+
|
190
|
+
packages = {}
|
191
|
+
|
192
|
+
# 获取存储库目录中的package.json文件
|
193
|
+
package_files = Dir.glob(File.join(@index_repo_dir, '**/package.json'))
|
194
|
+
spec_files = Dir.glob(File.join(@index_repo_dir, '**/spec.json'))
|
195
|
+
|
196
|
+
UI.puts "CacheManager: 找到 #{package_files.size} 个package.json文件" if @verbose
|
197
|
+
UI.puts "CacheManager: 找到 #{spec_files.size} 个spec.json文件" if @verbose
|
198
|
+
|
199
|
+
# 处理所有找到的package.json文件
|
200
|
+
package_files.each do |package_file|
|
201
|
+
begin
|
202
|
+
package_data = JSON.parse(File.read(package_file))
|
203
|
+
package_name = package_data['name']
|
204
|
+
|
205
|
+
if package_name.nil? || package_name.empty?
|
206
|
+
UI.puts "CacheManager: 忽略没有名称的包: #{package_file}" if @verbose
|
207
|
+
next
|
208
|
+
end
|
209
|
+
|
210
|
+
UI.puts "CacheManager: 处理包 #{package_name}" if @verbose
|
211
|
+
packages[package_name] = package_data
|
212
|
+
|
213
|
+
rescue JSON::ParserError => e
|
214
|
+
UI.puts "CacheManager: 解析错误 #{package_file}: #{e.message}" if @verbose
|
215
|
+
rescue => e
|
216
|
+
UI.puts "CacheManager: 处理 #{package_file} 时出错: #{e.message}" if @verbose
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# 处理所有找到的spec.json文件
|
221
|
+
spec_files.each do |spec_file|
|
222
|
+
begin
|
223
|
+
spec_data = JSON.parse(File.read(spec_file))
|
224
|
+
package_name = spec_data['name']
|
225
|
+
|
226
|
+
if package_name.nil? || package_name.empty?
|
227
|
+
UI.puts "CacheManager: 忽略没有名称的规格: #{spec_file}" if @verbose
|
228
|
+
next
|
229
|
+
end
|
230
|
+
|
231
|
+
if packages.key?(package_name)
|
232
|
+
# 合并spec.json中的信息到现有包
|
233
|
+
packages[package_name] = packages[package_name].merge(spec_data)
|
234
|
+
UI.puts "CacheManager: 合并规格到包 #{package_name}" if @verbose
|
235
|
+
else
|
236
|
+
# 添加新包
|
237
|
+
packages[package_name] = spec_data
|
238
|
+
UI.puts "CacheManager: 添加规格作为新包 #{package_name}" if @verbose
|
239
|
+
end
|
240
|
+
|
241
|
+
rescue JSON::ParserError => e
|
242
|
+
UI.puts "CacheManager: 解析错误 #{spec_file}: #{e.message}" if @verbose
|
243
|
+
rescue => e
|
244
|
+
UI.puts "CacheManager: 处理 #{spec_file} 时出错: #{e.message}" if @verbose
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# 如果没有找到包,添加示例包以确保功能正常运行
|
249
|
+
if packages.empty?
|
250
|
+
UI.puts "CacheManager: 警告 - 未找到有效的包,创建示例包" if @verbose
|
251
|
+
# 创建并添加示例包
|
252
|
+
example_package = create_example_package
|
253
|
+
packages[example_package['name']] = example_package
|
254
|
+
UI.puts "CacheManager: 添加了示例包: #{example_package['name']}" if @verbose
|
255
|
+
end
|
256
|
+
|
257
|
+
# 更新缓存
|
258
|
+
@cache = packages
|
259
|
+
@index_updated = true
|
260
|
+
|
261
|
+
# 使用format_packages_response格式化返回结果
|
262
|
+
formatted_result = format_packages_response(@cache.values)
|
263
|
+
UI.puts "CacheManager: 返回 #{formatted_result['objects'].size} 个包" if @verbose
|
264
|
+
|
265
|
+
return formatted_result
|
266
|
+
end
|
267
|
+
|
268
|
+
# 获取包的版本信息
|
269
|
+
def get_package_versions(package_name)
|
270
|
+
UI.puts "获取包版本信息: #{package_name}" if @verbose
|
271
|
+
|
272
|
+
package_info = get_package_info(package_name)
|
273
|
+
return nil unless package_info
|
274
|
+
|
275
|
+
versions = {}
|
276
|
+
package_info['versions'].each do |version, info|
|
277
|
+
versions[version] = ensure_package_structure(info)
|
278
|
+
end
|
279
|
+
|
280
|
+
{
|
281
|
+
'name' => package_name,
|
282
|
+
'versions' => versions,
|
283
|
+
'time' => {
|
284
|
+
'modified' => Time.now.iso8601,
|
285
|
+
'created' => Time.now.iso8601
|
286
|
+
},
|
287
|
+
'dist-tags' => package_info['dist-tags'] || { 'latest' => package_info['version'] }
|
288
|
+
}
|
289
|
+
end
|
290
|
+
|
291
|
+
# 获取包的压缩文件
|
292
|
+
def get_package_tarball(package_name, tarball_name)
|
293
|
+
UI.puts "获取包压缩文件: #{package_name} - #{tarball_name}" if @verbose
|
294
|
+
|
295
|
+
# 查找缓存的压缩文件
|
296
|
+
tarball_path = File.join(@tarballs_dir, package_name, tarball_name)
|
297
|
+
if File.exist?(tarball_path)
|
298
|
+
UI.puts "使用缓存的压缩文件: #{tarball_path}" if @verbose
|
299
|
+
return tarball_path
|
300
|
+
end
|
301
|
+
|
302
|
+
UI.puts "缓存中没有找到压缩文件,尝试从Git仓库构建" if @verbose
|
303
|
+
|
304
|
+
# 获取包信息
|
305
|
+
package_info = get_package_info(package_name)
|
306
|
+
unless package_info
|
307
|
+
UI.puts "未找到包信息: #{package_name}" if @verbose
|
308
|
+
return nil
|
309
|
+
end
|
310
|
+
|
311
|
+
# 检查是否有Git仓库信息
|
312
|
+
if package_info['git'] && package_info['git']['url']
|
313
|
+
git_url = package_info['git']['url']
|
314
|
+
git_tag = package_info['git']['tag'] || package_info['version']
|
315
|
+
git_branch = package_info['git']['branch'] || 'main'
|
316
|
+
|
317
|
+
UI.puts "从Git仓库构建压缩包: #{git_url} (tag: #{git_tag}, branch: #{git_branch})" if @verbose
|
318
|
+
|
319
|
+
# 调用克隆并构建包的方法
|
320
|
+
if clone_and_build_package(package_name, git_url, git_tag, git_branch)
|
321
|
+
# 检查是否成功生成压缩文件
|
322
|
+
if File.exist?(tarball_path)
|
323
|
+
UI.puts "成功构建压缩包: #{tarball_path}" if @verbose
|
324
|
+
return tarball_path
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
# 如果还是没有找到,尝试从索引库中查找现有的tgz文件
|
330
|
+
@index_repos.each do |repo|
|
331
|
+
repo_dir = get_repo_dir(repo[:name])
|
332
|
+
pkg_dir = File.join(repo_dir, package_name)
|
333
|
+
|
334
|
+
# 检查是否有对应的压缩文件
|
335
|
+
tarball_paths = Dir.glob(File.join(pkg_dir, "*.tgz"))
|
336
|
+
if !tarball_paths.empty?
|
337
|
+
# 保存到缓存
|
338
|
+
FileUtils.mkdir_p(File.dirname(tarball_path))
|
339
|
+
FileUtils.cp(tarball_paths.first, tarball_path)
|
340
|
+
return tarball_path
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
nil
|
345
|
+
end
|
346
|
+
|
347
|
+
# 搜索包
|
348
|
+
def search_packages(query)
|
349
|
+
# 确保索引是最新的
|
350
|
+
ensure_index_updated
|
351
|
+
|
352
|
+
UI.puts "搜索包: #{query}" if @verbose
|
353
|
+
|
354
|
+
# 获取所有包信息
|
355
|
+
all_packages = get_all_packages
|
356
|
+
|
357
|
+
# 如果没有查询,返回所有包
|
358
|
+
if query.nil? || query.empty?
|
359
|
+
return all_packages
|
360
|
+
end
|
361
|
+
|
362
|
+
# 过滤包
|
363
|
+
filtered_packages = all_packages['objects'].select do |obj|
|
364
|
+
package = obj['package']
|
365
|
+
name_matches_query?(package['name'], query) ||
|
366
|
+
(package['description'] && package['description'].downcase.include?(query.downcase)) ||
|
367
|
+
(package['keywords'] && package['keywords'].any? { |k| k.downcase.include?(query.downcase) })
|
368
|
+
end
|
369
|
+
|
370
|
+
# 为空结果时尝试更灵活的匹配
|
371
|
+
if filtered_packages.empty?
|
372
|
+
# 尝试作用域匹配
|
373
|
+
if query.include?('.')
|
374
|
+
scope = query.split('.').first
|
375
|
+
filtered_packages = all_packages['objects'].select do |obj|
|
376
|
+
package = obj['package']
|
377
|
+
package['name'].start_with?(scope)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# 尝试前缀匹配
|
382
|
+
if filtered_packages.empty?
|
383
|
+
filtered_packages = all_packages['objects'].select do |obj|
|
384
|
+
package = obj['package']
|
385
|
+
package['name'].include?(query.downcase)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
{
|
391
|
+
'objects' => filtered_packages,
|
392
|
+
'total' => filtered_packages.size,
|
393
|
+
'time' => all_packages['time']
|
394
|
+
}
|
395
|
+
end
|
396
|
+
|
397
|
+
private
|
398
|
+
|
399
|
+
# 检查包名是否匹配查询条件
|
400
|
+
def name_matches_query?(name, query)
|
401
|
+
# 标准化名称和查询
|
402
|
+
name_lower = name.downcase
|
403
|
+
query_lower = query.downcase
|
404
|
+
|
405
|
+
# 直接匹配
|
406
|
+
return true if name_lower == query_lower
|
407
|
+
|
408
|
+
# 处理作用域名称匹配
|
409
|
+
if query_lower.include?('.')
|
410
|
+
# Unity包格式: com.company.package
|
411
|
+
parts = query_lower.split('.')
|
412
|
+
return true if name_lower == parts.join('.')
|
413
|
+
|
414
|
+
# 匹配前缀
|
415
|
+
return true if name_lower.start_with?(query_lower)
|
416
|
+
end
|
417
|
+
|
418
|
+
# 部分匹配
|
419
|
+
name_lower.include?(query_lower)
|
420
|
+
end
|
421
|
+
|
422
|
+
def detect_cache_dir
|
423
|
+
# 使用 Config 类提供的缓存目录,确保所有系统统一
|
424
|
+
UniPod::Config.cache_dir
|
425
|
+
end
|
426
|
+
|
427
|
+
def init_cache_dirs
|
428
|
+
FileUtils.mkdir_p(@index_repo_dir)
|
429
|
+
FileUtils.mkdir_p(@packages_dir)
|
430
|
+
FileUtils.mkdir_p(@tarballs_dir)
|
431
|
+
end
|
432
|
+
|
433
|
+
# 获取当前缓存目录
|
434
|
+
def cache_dir
|
435
|
+
detect_cache_dir
|
436
|
+
end
|
437
|
+
|
438
|
+
# 获取索引仓库目录
|
439
|
+
def scoped_dir
|
440
|
+
File.join(cache_dir, 'scoped')
|
441
|
+
end
|
442
|
+
|
443
|
+
# 获取包仓库目录
|
444
|
+
def packages_dir
|
445
|
+
File.join(cache_dir, 'packages')
|
446
|
+
end
|
447
|
+
|
448
|
+
# 获取压缩文件目录
|
449
|
+
def tarballs_dir
|
450
|
+
File.join(cache_dir, 'tarballs')
|
451
|
+
end
|
452
|
+
|
453
|
+
def update_index_repos
|
454
|
+
# 如果最后更新时间在1小时内,跳过
|
455
|
+
if @index_updated
|
456
|
+
return true
|
457
|
+
end
|
458
|
+
|
459
|
+
UI.puts "更新索引库" if @verbose
|
460
|
+
|
461
|
+
success = true
|
462
|
+
@index_repos.each do |repo|
|
463
|
+
success = success && update_index_repo(repo)
|
464
|
+
end
|
465
|
+
|
466
|
+
if @index_repos.empty?
|
467
|
+
# 如果没有配置索引库,创建一个默认目录
|
468
|
+
UI.puts "没有配置索引库,创建默认目录结构" if @verbose
|
469
|
+
|
470
|
+
# 获取默认仓库名称
|
471
|
+
default_git_url = 'https://gitee.com/goodunitylib/JoyUnityLibIndex.git'
|
472
|
+
default_repo_name = extract_repo_name_from_url(default_git_url)
|
473
|
+
|
474
|
+
FileUtils.mkdir_p(File.join(@index_repo_dir, default_repo_name))
|
475
|
+
|
476
|
+
# 获取包信息
|
477
|
+
dirs = Dir.glob(File.join(@index_repo_dir, '**/package.json'))
|
478
|
+
dirs.each do |d|
|
479
|
+
UI.puts "发现包: #{d}" if @verbose
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
@index_updated = success
|
484
|
+
get_all_packages if success # 预加载所有包信息
|
485
|
+
success
|
486
|
+
end
|
487
|
+
|
488
|
+
def update_index_repo(repo)
|
489
|
+
repo_name = repo[:name]
|
490
|
+
repo_url = repo[:package_index_url] || repo[:url]
|
491
|
+
repo_dir = get_repo_dir(repo_name)
|
492
|
+
|
493
|
+
# 检查最后更新时间
|
494
|
+
last_update = @last_update_time[repo_name]
|
495
|
+
if last_update && (Time.now - last_update) < 3600 # 1小时
|
496
|
+
UI.puts "索引库 #{repo_name} 已在1小时内更新,跳过" if @verbose
|
497
|
+
return true
|
498
|
+
end
|
499
|
+
|
500
|
+
UI.puts "更新索引库: #{repo_name} - #{repo_url}" if @verbose
|
501
|
+
|
502
|
+
begin
|
503
|
+
# 如果目录不存在,克隆仓库
|
504
|
+
if !Dir.exist?(repo_dir)
|
505
|
+
UI.puts "克隆索引库: #{repo_url} 到 #{repo_dir}" if @verbose
|
506
|
+
|
507
|
+
# 使用git克隆
|
508
|
+
clone_cmd = "git clone #{repo_url} #{repo_dir}"
|
509
|
+
system(clone_cmd)
|
510
|
+
|
511
|
+
unless $?.success?
|
512
|
+
UI.error "克隆索引库失败: #{repo_url}"
|
513
|
+
return false
|
514
|
+
end
|
515
|
+
else
|
516
|
+
# 更新仓库
|
517
|
+
UI.puts "更新索引库: #{repo_dir}" if @verbose
|
518
|
+
|
519
|
+
# 切换到仓库目录
|
520
|
+
Dir.chdir(repo_dir) do
|
521
|
+
# 拉取更新
|
522
|
+
pull_cmd = "git pull"
|
523
|
+
system(pull_cmd)
|
524
|
+
|
525
|
+
unless $?.success?
|
526
|
+
UI.error "更新索引库失败: #{repo_dir}"
|
527
|
+
return false
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
# 更新最后更新时间
|
533
|
+
@last_update_time[repo_name] = Time.now
|
534
|
+
|
535
|
+
true
|
536
|
+
rescue => e
|
537
|
+
UI.error "更新索引库时出错: #{e.message}"
|
538
|
+
false
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
def find_package_file(package_name)
|
543
|
+
# 首先查找spec.json文件
|
544
|
+
spec_files = Dir.glob(File.join(@index_repo_dir, "*/Specs/#{package_name}/**/spec.json"))
|
545
|
+
return spec_files.first if spec_files.any?
|
546
|
+
|
547
|
+
# 如果没有找到spec.json,查找package.json
|
548
|
+
Dir.glob(File.join(@index_repo_dir, "*/#{package_name}/**/package.json")).first
|
549
|
+
end
|
550
|
+
|
551
|
+
def convert_spec_to_package(spec_info)
|
552
|
+
# 将spec.json格式转换为标准的package.json格式
|
553
|
+
package_info = spec_info.dup
|
554
|
+
|
555
|
+
# 确保有displayName字段(Unity需要)
|
556
|
+
package_info['displayName'] ||= humanize_package_name(package_info['name'])
|
557
|
+
|
558
|
+
# 确保有unity字段
|
559
|
+
package_info['unity'] ||= '2019.4'
|
560
|
+
|
561
|
+
# 添加Unity特定字段
|
562
|
+
package_info['unity'] ||= '2019.4'
|
563
|
+
package_info['type'] ||= 'library'
|
564
|
+
|
565
|
+
# 添加versions字段
|
566
|
+
package_info['versions'] = {
|
567
|
+
spec_info['version'] => package_info.dup
|
568
|
+
}
|
569
|
+
|
570
|
+
# 添加repository字段
|
571
|
+
if spec_info['git'] && spec_info['git']['url']
|
572
|
+
package_info['repository'] = {
|
573
|
+
'type' => 'git',
|
574
|
+
'url' => spec_info['git']['url']
|
575
|
+
}
|
576
|
+
end
|
577
|
+
|
578
|
+
# 添加dist标签
|
579
|
+
tarball_name = "#{package_info['name'].gsub('/', '-')}-#{package_info['version']}.tgz"
|
580
|
+
package_info['dist'] = {
|
581
|
+
'tarball' => "http://localhost:7748/#{package_info['name']}/-/#{tarball_name}"
|
582
|
+
}
|
583
|
+
|
584
|
+
package_info
|
585
|
+
end
|
586
|
+
|
587
|
+
# 确保包信息具有Unity包管理器需要的结构
|
588
|
+
def ensure_package_structure(package_info)
|
589
|
+
return {} unless package_info
|
590
|
+
|
591
|
+
# 检测包类型
|
592
|
+
package_type = detect_package_type(package_info)
|
593
|
+
|
594
|
+
# 复制包信息,确保不修改原始对象
|
595
|
+
info = package_info.clone
|
596
|
+
|
597
|
+
# 添加必要的字段
|
598
|
+
info['name'] ||= 'unknown'
|
599
|
+
info['version'] ||= '1.0.0'
|
600
|
+
info['description'] ||= ''
|
601
|
+
info['author'] ||= { 'name' => 'Unknown' }
|
602
|
+
info['keywords'] ||= []
|
603
|
+
info['maintainers'] ||= [{ 'name' => info['author']['name'] || 'Unknown' }]
|
604
|
+
|
605
|
+
# 处理Unity特定字段
|
606
|
+
if package_type == PACKAGE_TYPE_UNITY
|
607
|
+
# 对于Unity包,确保有正确的格式
|
608
|
+
info['displayName'] ||= humanize_package_name(info['name'])
|
609
|
+
info['unity'] ||= '*'
|
610
|
+
|
611
|
+
# 确保dependencies格式正确
|
612
|
+
if info['dependencies']
|
613
|
+
info['dependencies'].each do |k, v|
|
614
|
+
# 确保版本格式是Unity兼容的
|
615
|
+
info['dependencies'][k] = ensure_unity_version_format(v)
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
# 处理版本信息
|
621
|
+
if !info['versions'] || info['versions'].empty?
|
622
|
+
version = info['version']
|
623
|
+
info['versions'] = {
|
624
|
+
version => {
|
625
|
+
'name' => info['name'],
|
626
|
+
'version' => version,
|
627
|
+
'description' => info['description'],
|
628
|
+
'displayName' => info['displayName'],
|
629
|
+
'author' => info['author'],
|
630
|
+
'dependencies' => info['dependencies'] || {},
|
631
|
+
'keywords' => info['keywords'],
|
632
|
+
'dist' => {
|
633
|
+
'shasum' => '',
|
634
|
+
'tarball' => "./#{info['name']}/#{info['name']}-#{version}.tgz"
|
635
|
+
}
|
636
|
+
}
|
637
|
+
}
|
638
|
+
end
|
639
|
+
|
640
|
+
# 确保dependencies字段在每个版本中都存在
|
641
|
+
info['versions'].each do |version, version_info|
|
642
|
+
version_info['dependencies'] ||= {}
|
643
|
+
|
644
|
+
# 处理Unity特定字段
|
645
|
+
if package_type == PACKAGE_TYPE_UNITY
|
646
|
+
version_info['displayName'] ||= info['displayName'] || humanize_package_name(info['name'])
|
647
|
+
version_info['unity'] ||= info['unity'] || '*'
|
648
|
+
|
649
|
+
# 确保有特定的Unity字段
|
650
|
+
version_info['unity'] ||= info['unity'] || '*'
|
651
|
+
version_info['unityRelease'] ||= info['unityRelease'] || ''
|
652
|
+
|
653
|
+
# 确保dependencies格式正确
|
654
|
+
if version_info['dependencies']
|
655
|
+
version_info['dependencies'].each do |k, v|
|
656
|
+
# 确保版本格式是Unity兼容的
|
657
|
+
version_info['dependencies'][k] = ensure_unity_version_format(v)
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
# 确保dist-tags字段存在
|
664
|
+
info['dist-tags'] ||= { 'latest' => info['version'] || info['versions'].keys.sort.last }
|
665
|
+
|
666
|
+
info
|
667
|
+
end
|
668
|
+
|
669
|
+
# 将包名转换为更友好的显示名称
|
670
|
+
def humanize_package_name(name)
|
671
|
+
return name unless name
|
672
|
+
|
673
|
+
# 处理Unity包名格式 (com.company.package)
|
674
|
+
if name.include?('.')
|
675
|
+
parts = name.split('.')
|
676
|
+
if parts.size > 1
|
677
|
+
# 使用最后一部分作为显示名称
|
678
|
+
display_name = parts.last
|
679
|
+
# 将camelCase转换为有空格的形式
|
680
|
+
display_name = display_name.gsub(/([A-Z])/, ' \1').strip
|
681
|
+
# 首字母大写
|
682
|
+
return display_name.capitalize
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
# 处理NPM包名格式 (@scope/package)
|
687
|
+
if name.include?('/')
|
688
|
+
parts = name.split('/')
|
689
|
+
if parts.size > 1
|
690
|
+
# 移除前缀@
|
691
|
+
scope = parts.first.sub(/^@/, '')
|
692
|
+
package = parts.last
|
693
|
+
# 将camelCase转换为有空格的形式
|
694
|
+
package = package.gsub(/([A-Z])/, ' \1').strip
|
695
|
+
# 首字母大写
|
696
|
+
return "#{scope.capitalize} #{package.capitalize}"
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
# 将camelCase或snake_case转换为有空格的形式
|
701
|
+
name = name.gsub(/([A-Z])/, ' \1').gsub('_', ' ').strip
|
702
|
+
name.capitalize
|
703
|
+
end
|
704
|
+
|
705
|
+
# 确保版本号符合Unity要求
|
706
|
+
def ensure_unity_version_format(version)
|
707
|
+
return '*' if version.nil? || version.empty?
|
708
|
+
|
709
|
+
# 处理特殊值
|
710
|
+
return version if ['*', 'latest'].include?(version)
|
711
|
+
|
712
|
+
# 如果是纯数字或常规版本号,添加^前缀
|
713
|
+
if version =~ /^\d/ && !version.start_with?('^', '~', '>', '<', '=')
|
714
|
+
return "^#{version}"
|
715
|
+
end
|
716
|
+
|
717
|
+
version
|
718
|
+
end
|
719
|
+
|
720
|
+
def clone_and_build_package(package_name, git_url, git_tag = nil, git_branch = nil)
|
721
|
+
UI.puts "克隆并构建包: #{package_name} (#{git_url})" if @verbose
|
722
|
+
|
723
|
+
# 创建包目录
|
724
|
+
package_dir = File.join(@packages_dir, package_name)
|
725
|
+
FileUtils.mkdir_p(package_dir) unless Dir.exist?(package_dir)
|
726
|
+
|
727
|
+
begin
|
728
|
+
# 检查目录是否已经是Git仓库
|
729
|
+
if File.directory?(File.join(package_dir, '.git'))
|
730
|
+
# 已存在Git仓库,更新它
|
731
|
+
UI.puts "更新现有的Git仓库: #{package_dir}" if @verbose
|
732
|
+
Dir.chdir(package_dir) do
|
733
|
+
# 获取远程URL
|
734
|
+
remote_url = `git config --get remote.origin.url`.strip
|
735
|
+
|
736
|
+
# 如果远程URL与预期不同,重新设置
|
737
|
+
if remote_url != git_url
|
738
|
+
UI.puts "重新设置远程URL: #{git_url}" if @verbose
|
739
|
+
run_git_command("git remote set-url origin #{git_url}")
|
740
|
+
end
|
741
|
+
|
742
|
+
# 获取最新更改
|
743
|
+
run_git_command("git fetch --all --quiet")
|
744
|
+
|
745
|
+
# 尝试切换到指定标签或分支
|
746
|
+
if git_tag
|
747
|
+
UI.puts "切换到标签: #{git_tag}" if @verbose
|
748
|
+
result = run_git_command("git checkout tags/#{git_tag} -f --quiet")
|
749
|
+
|
750
|
+
# 如果标签不存在,尝试直接使用作为提交ID
|
751
|
+
unless result
|
752
|
+
UI.puts "标签不存在,尝试作为提交ID: #{git_tag}" if @verbose
|
753
|
+
result = run_git_command("git checkout #{git_tag} -f --quiet")
|
754
|
+
end
|
755
|
+
|
756
|
+
# 如果仍然失败,尝试使用分支
|
757
|
+
unless result
|
758
|
+
UI.puts "使用分支: #{git_branch}" if @verbose
|
759
|
+
run_git_command("git checkout #{git_branch} -f --quiet && git pull --quiet")
|
760
|
+
end
|
761
|
+
elsif git_branch
|
762
|
+
UI.puts "切换到分支: #{git_branch}" if @verbose
|
763
|
+
run_git_command("git checkout #{git_branch} -f --quiet && git pull --quiet")
|
764
|
+
else
|
765
|
+
# 如果没有指定标签或分支,使用主分支
|
766
|
+
UI.puts "使用默认分支" if @verbose
|
767
|
+
run_git_command("git checkout main -f --quiet || git checkout master -f --quiet")
|
768
|
+
run_git_command("git pull --quiet")
|
769
|
+
end
|
770
|
+
end
|
771
|
+
else
|
772
|
+
# 不存在Git仓库,克隆它
|
773
|
+
UI.puts "克隆新的Git仓库: #{git_url} 到 #{package_dir}" if @verbose
|
774
|
+
|
775
|
+
# 先清空目录(如果非空)
|
776
|
+
FileUtils.rm_rf(package_dir)
|
777
|
+
FileUtils.mkdir_p(package_dir)
|
778
|
+
|
779
|
+
# 克隆仓库 - 使用--quiet选项减少输出
|
780
|
+
clone_cmd = "git clone --quiet #{git_url} #{package_dir}"
|
781
|
+
unless run_git_command(clone_cmd)
|
782
|
+
UI.error "克隆Git仓库失败: #{git_url}"
|
783
|
+
return false
|
784
|
+
end
|
785
|
+
|
786
|
+
# 切换到指定标签或分支
|
787
|
+
Dir.chdir(package_dir) do
|
788
|
+
if git_tag
|
789
|
+
UI.puts "切换到标签: #{git_tag}" if @verbose
|
790
|
+
result = run_git_command("git checkout tags/#{git_tag} --quiet")
|
791
|
+
|
792
|
+
# 如果标签不存在,尝试直接使用作为提交ID
|
793
|
+
unless result
|
794
|
+
UI.puts "标签不存在,尝试作为提交ID: #{git_tag}" if @verbose
|
795
|
+
result = run_git_command("git checkout #{git_tag} --quiet")
|
796
|
+
end
|
797
|
+
|
798
|
+
# 如果仍然失败,尝试使用分支
|
799
|
+
unless result
|
800
|
+
UI.puts "使用分支: #{git_branch}" if @verbose
|
801
|
+
run_git_command("git checkout #{git_branch} --quiet")
|
802
|
+
end
|
803
|
+
elsif git_branch
|
804
|
+
UI.puts "切换到分支: #{git_branch}" if @verbose
|
805
|
+
run_git_command("git checkout #{git_branch} --quiet")
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
# 构建压缩包
|
811
|
+
build_package_tarball(package_name, package_dir)
|
812
|
+
|
813
|
+
true
|
814
|
+
rescue => e
|
815
|
+
UI.error "克隆并构建包时出错: #{e.message}"
|
816
|
+
UI.error e.backtrace.join("\n") if @verbose
|
817
|
+
false
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
# 执行Git命令并减少输出
|
822
|
+
def run_git_command(cmd)
|
823
|
+
# 1. 将命令输出重定向到/dev/null (Unix) 或 NUL (Windows)
|
824
|
+
# 2. 使用system返回命令执行结果(成功/失败)
|
825
|
+
null_device = RUBY_PLATFORM =~ /mswin|mingw|cygwin/ ? 'NUL' : '/dev/null'
|
826
|
+
|
827
|
+
# 默认不显示详细输出,除非启用了verbose且明确标记为需要输出
|
828
|
+
if @verbose && ENV['UNIPOD_GIT_VERBOSE'] == 'true'
|
829
|
+
system(cmd)
|
830
|
+
else
|
831
|
+
# 重定向标准输出和错误输出
|
832
|
+
system("#{cmd} > #{null_device} 2>&1")
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
def build_package_tarball(package_name, package_dir)
|
837
|
+
UI.puts "构建包压缩文件: #{package_name}" if @verbose
|
838
|
+
|
839
|
+
# 确保包目录存在
|
840
|
+
unless Dir.exist?(package_dir)
|
841
|
+
UI.error "包目录不存在: #{package_dir}"
|
842
|
+
return false
|
843
|
+
end
|
844
|
+
|
845
|
+
# 确保package.json文件存在
|
846
|
+
package_json_path = File.join(package_dir, 'package.json')
|
847
|
+
unless File.exist?(package_json_path)
|
848
|
+
UI.error "package.json文件不存在: #{package_json_path}"
|
849
|
+
return false
|
850
|
+
end
|
851
|
+
|
852
|
+
# 读取package.json获取版本信息
|
853
|
+
begin
|
854
|
+
package_json = JSON.parse(File.read(package_json_path))
|
855
|
+
version = package_json['version']
|
856
|
+
|
857
|
+
unless version
|
858
|
+
UI.error "package.json中未找到版本信息"
|
859
|
+
return false
|
860
|
+
end
|
861
|
+
|
862
|
+
# 创建压缩文件目录
|
863
|
+
tarball_dir = File.join(@tarballs_dir, package_name)
|
864
|
+
FileUtils.mkdir_p(tarball_dir)
|
865
|
+
|
866
|
+
# 压缩文件路径
|
867
|
+
tarball_filename = "#{package_name.gsub('/', '-')}-#{version}.tgz"
|
868
|
+
tarball_path = File.join(tarball_dir, tarball_filename)
|
869
|
+
|
870
|
+
# 创建临时目录
|
871
|
+
temp_dir = File.join(@cache_dir, 'temp', "#{package_name}-#{version}")
|
872
|
+
FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
|
873
|
+
FileUtils.mkdir_p(temp_dir)
|
874
|
+
|
875
|
+
# 复制文件到临时目录(排除.git目录)
|
876
|
+
UI.puts "复制文件到临时目录: #{temp_dir}" if @verbose
|
877
|
+
Dir.glob(File.join(package_dir, '*')).each do |path|
|
878
|
+
basename = File.basename(path)
|
879
|
+
next if basename == '.git' # 排除.git目录
|
880
|
+
|
881
|
+
if File.directory?(path)
|
882
|
+
FileUtils.cp_r(path, temp_dir)
|
883
|
+
else
|
884
|
+
FileUtils.cp(path, temp_dir)
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
# 创建压缩文件
|
889
|
+
UI.puts "创建压缩文件: #{tarball_path}" if @verbose
|
890
|
+
|
891
|
+
Dir.chdir(temp_dir) do
|
892
|
+
system("tar -czf #{tarball_path} .")
|
893
|
+
end
|
894
|
+
|
895
|
+
# 清理临时目录
|
896
|
+
FileUtils.rm_rf(temp_dir)
|
897
|
+
|
898
|
+
# 检查压缩文件是否创建成功
|
899
|
+
if File.exist?(tarball_path)
|
900
|
+
UI.puts "压缩文件创建成功: #{tarball_path}" if @verbose
|
901
|
+
return true
|
902
|
+
else
|
903
|
+
UI.error "压缩文件创建失败"
|
904
|
+
return false
|
905
|
+
end
|
906
|
+
rescue => e
|
907
|
+
UI.error "构建压缩文件时出错: #{e.message}"
|
908
|
+
UI.error e.backtrace.join("\n") if @verbose
|
909
|
+
false
|
910
|
+
end
|
911
|
+
end
|
912
|
+
|
913
|
+
def verbose?
|
914
|
+
# 可以从配置或环境变量获取
|
915
|
+
ENV['UNIPOD_VERBOSE'] == 'true'
|
916
|
+
end
|
917
|
+
|
918
|
+
# 计算搜索得分
|
919
|
+
def calculate_search_score(name, query)
|
920
|
+
return 1.0 if query.empty?
|
921
|
+
|
922
|
+
name_downcase = name.downcase
|
923
|
+
query_downcase = query.downcase
|
924
|
+
|
925
|
+
if name_downcase == query_downcase
|
926
|
+
# 完全匹配
|
927
|
+
return 1.0
|
928
|
+
elsif name_downcase.start_with?(query_downcase)
|
929
|
+
# 前缀匹配
|
930
|
+
return 0.9
|
931
|
+
elsif name_downcase.include?(query_downcase)
|
932
|
+
# 包含匹配
|
933
|
+
return 0.7
|
934
|
+
else
|
935
|
+
# 使用Levenshtein距离计算相似度
|
936
|
+
return calculate_similarity(name_downcase, query_downcase)
|
937
|
+
end
|
938
|
+
end
|
939
|
+
|
940
|
+
# 计算字符串相似度
|
941
|
+
def calculate_similarity(str1, str2)
|
942
|
+
len1 = str1.length
|
943
|
+
len2 = str2.length
|
944
|
+
|
945
|
+
# 如果一个字符串为空,距离等于另一个字符串的长度
|
946
|
+
return 0.0 if len1 == 0 || len2 == 0
|
947
|
+
|
948
|
+
# 使用更简单的方法计算相似度
|
949
|
+
common_chars = 0
|
950
|
+
str2.each_char do |char|
|
951
|
+
if str1.include?(char)
|
952
|
+
common_chars += 1
|
953
|
+
end
|
954
|
+
end
|
955
|
+
|
956
|
+
# 计算相似度得分 (0-1之间)
|
957
|
+
similarity = common_chars.to_f / [len1, len2].max
|
958
|
+
|
959
|
+
# 归一化得分
|
960
|
+
[similarity, 0.5].max
|
961
|
+
end
|
962
|
+
|
963
|
+
# 确保索引已更新
|
964
|
+
def ensure_index_updated(force_refresh = false)
|
965
|
+
if force_refresh
|
966
|
+
UI.puts "强制刷新缓存..." if @verbose
|
967
|
+
@index_updated = false
|
968
|
+
@cache = {} # 清空缓存
|
969
|
+
end
|
970
|
+
update_index_repos unless @index_updated
|
971
|
+
end
|
972
|
+
|
973
|
+
# 获取索引库目录
|
974
|
+
def get_repo_dir(repo_name)
|
975
|
+
repo_name = repo_name.gsub(/[^a-zA-Z0-9_-]/, '_')
|
976
|
+
File.join(@index_repo_dir, repo_name)
|
977
|
+
end
|
978
|
+
|
979
|
+
# 检测包类型
|
980
|
+
def detect_package_type(package_info)
|
981
|
+
# 如果已经检测过,直接返回
|
982
|
+
if @package_type_cache.key?(package_info['name'])
|
983
|
+
return @package_type_cache[package_info['name']]
|
984
|
+
end
|
985
|
+
|
986
|
+
type = PACKAGE_TYPE_NPM
|
987
|
+
|
988
|
+
# 检查Unity特定字段
|
989
|
+
if package_info['name']&.include?('.') || package_info['unity'] || package_info['displayName']
|
990
|
+
type = PACKAGE_TYPE_UNITY
|
991
|
+
end
|
992
|
+
|
993
|
+
# 缓存结果
|
994
|
+
@package_type_cache[package_info['name']] = type
|
995
|
+
type
|
996
|
+
end
|
997
|
+
|
998
|
+
# 转换包信息为返回格式
|
999
|
+
def format_packages_response(packages)
|
1000
|
+
packages_array = packages.is_a?(Hash) ? packages.values : packages
|
1001
|
+
|
1002
|
+
objects = packages_array.map do |package|
|
1003
|
+
{
|
1004
|
+
'package' => {
|
1005
|
+
'name' => package['name'],
|
1006
|
+
'scope' => package_scope(package['name']),
|
1007
|
+
'version' => package['version'] || package['dist-tags']&.[]('latest') || '1.0.0',
|
1008
|
+
'description' => package['description'] || '',
|
1009
|
+
'keywords' => package['keywords'] || [],
|
1010
|
+
'date' => Time.now.iso8601,
|
1011
|
+
'links' => {},
|
1012
|
+
'author' => package['author'] || { 'name' => 'Unknown' },
|
1013
|
+
'publisher' => package['author'] || { 'name' => 'Unknown' },
|
1014
|
+
'maintainers' => package['maintainers'] || [{ 'name' => 'Unknown' }]
|
1015
|
+
},
|
1016
|
+
'score' => {
|
1017
|
+
'final' => 1.0,
|
1018
|
+
'detail' => {
|
1019
|
+
'quality' => 1.0,
|
1020
|
+
'popularity' => 1.0,
|
1021
|
+
'maintenance' => 1.0
|
1022
|
+
}
|
1023
|
+
},
|
1024
|
+
'searchScore' => 1.0
|
1025
|
+
}
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
{
|
1029
|
+
'objects' => objects,
|
1030
|
+
'total' => objects.size,
|
1031
|
+
'time' => Time.now.iso8601
|
1032
|
+
}
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
# 获取包的作用域
|
1036
|
+
def package_scope(name)
|
1037
|
+
return nil unless name
|
1038
|
+
|
1039
|
+
# 检查Unity包格式
|
1040
|
+
if name.include?('.')
|
1041
|
+
parts = name.split('.')
|
1042
|
+
if parts.size >= 2
|
1043
|
+
return parts[0, parts.size - 1].join('.')
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
# 检查NPM包格式
|
1048
|
+
if name.start_with?('@')
|
1049
|
+
return name.split('/').first
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
nil
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
# 确保索引已更新
|
1056
|
+
def is_index_updated?
|
1057
|
+
@index_updated
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
# 创建示例包
|
1061
|
+
def create_example_package
|
1062
|
+
package_name = 'com.dreamstudio.example'
|
1063
|
+
version = '1.0.0'
|
1064
|
+
|
1065
|
+
# 确保示例包的目录结构存在
|
1066
|
+
tarball_dir = File.join(@tarballs_dir, package_name)
|
1067
|
+
FileUtils.mkdir_p(tarball_dir)
|
1068
|
+
|
1069
|
+
# 创建示例包结构
|
1070
|
+
{
|
1071
|
+
'name' => package_name,
|
1072
|
+
'displayName' => 'Dream Studio Example Package',
|
1073
|
+
'version' => version,
|
1074
|
+
'unity' => '2019.4',
|
1075
|
+
'description' => '这是一个示例包,用于测试Unity包管理器功能',
|
1076
|
+
'keywords' => ['example', 'test', 'unity'],
|
1077
|
+
'category' => 'Libraries',
|
1078
|
+
'author' => {
|
1079
|
+
'name' => 'DreamStudio',
|
1080
|
+
'email' => 'example@dreamstudio.com',
|
1081
|
+
'url' => 'https://www.dreamstudio.com'
|
1082
|
+
},
|
1083
|
+
'maintainers' => [
|
1084
|
+
{
|
1085
|
+
'name' => 'DreamStudio',
|
1086
|
+
'email' => 'example@dreamstudio.com'
|
1087
|
+
}
|
1088
|
+
],
|
1089
|
+
'dependencies' => {},
|
1090
|
+
'repository' => {
|
1091
|
+
'type' => 'git',
|
1092
|
+
'url' => 'https://github.com/dreamstudio/example-package.git'
|
1093
|
+
},
|
1094
|
+
'license' => 'MIT',
|
1095
|
+
'dist' => {
|
1096
|
+
'tarball' => "http://127.0.0.1:7748/#{package_name}/-/#{package_name}-#{version}.tgz",
|
1097
|
+
'type' => 'tgz'
|
1098
|
+
}
|
1099
|
+
}
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
# 提供缓存目录的访问方法
|
1103
|
+
attr_reader :cache_dir
|
1104
|
+
end
|
1105
|
+
end
|
1106
|
+
end
|