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.
@@ -0,0 +1,393 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+ require 'securerandom'
6
+ require 'json'
7
+
8
+ module UniPod
9
+ class Config
10
+ DEFAULT_CONFIG_PATH = File.join(Dir.home, '.unipod', 'config.yml')
11
+
12
+ # 定义在类外部,可以被类方法使用
13
+ def self.detect_default_cache_dir
14
+ # 统一使用 macOS 风格的缓存目录,避免使用 ~/.unipod/cache
15
+ if RUBY_PLATFORM =~ /darwin/
16
+ # Mac OS - 标准缓存目录
17
+ File.expand_path('~/Library/Caches/UniPod')
18
+ elsif RUBY_PLATFORM =~ /mswin|mingw|cygwin/
19
+ # Windows
20
+ win_cache_dir = ENV['LOCALAPPDATA'] ? File.join(ENV['LOCALAPPDATA'], 'UniPod', 'Cache') : File.expand_path('~/AppData/Local/UniPod/Cache')
21
+ File.expand_path(win_cache_dir)
22
+ else
23
+ # Linux/其他平台 - 遵循 XDG 规范
24
+ xdg_cache_dir = ENV['XDG_CACHE_HOME'] || File.expand_path('~/.cache')
25
+ File.expand_path(File.join(xdg_cache_dir, 'unipod'))
26
+ end
27
+ end
28
+
29
+ # 从Git URL中提取仓库名称,此方法需要与CacheManager中的同名方法保持一致
30
+ def self.extract_repo_name_from_url(url)
31
+ if url.nil? || url.empty?
32
+ return 'default_repo'
33
+ end
34
+
35
+ # 移除尾部的.git(如果有)
36
+ url = url.sub(/\.git\z/, '')
37
+
38
+ # 处理不同格式的URL
39
+ if url.include?('/')
40
+ # 处理https://格式或git://格式
41
+ repo_name = url.split('/').last
42
+ elsif url.include?(':')
43
+ # 处理git@格式
44
+ repo_name = url.split(':').last.split('/').last
45
+ else
46
+ # 无法解析时使用默认名称
47
+ repo_name = 'default_repo'
48
+ end
49
+
50
+ # 确保名称不包含特殊字符
51
+ repo_name.gsub(/[^a-zA-Z0-9_-]/, '_')
52
+ end
53
+
54
+ DEFAULT_CONFIG = {
55
+ server: {
56
+ host: 'localhost',
57
+ port: 7748,
58
+ timeout: 120, # 增加默认超时时间
59
+ max_clients: 10, # 降低最大客户端数量
60
+ log_level: 'info',
61
+ session_id: SecureRandom.hex(8) # 为每次启动生成唯一会话ID
62
+ },
63
+ cache: {
64
+ dir: detect_default_cache_dir,
65
+ max_size: 1024, # MB
66
+ expiry: 7 # days
67
+ },
68
+ repositories: {
69
+ index_repos: [
70
+ {
71
+ name: extract_repo_name_from_url('https://gitee.com/goodunitylib/JoyUnityLibIndex.git'),
72
+ url: 'https://gitee.com/goodunitylib/JoyUnityLibIndex.git',
73
+ enabled: true
74
+ }
75
+ ],
76
+ custom_repos: [],
77
+ scope_mappings: {}
78
+ },
79
+ unity: {
80
+ package_format: 'upm', # 'upm' 或 'tgz'
81
+ preload_packages: true, # 预加载包信息
82
+ compatibility: '2019.4' # 最低兼容版本
83
+ }
84
+ }
85
+
86
+ class << self
87
+ def load_config
88
+ # 初始化默认配置
89
+ @config = DEFAULT_CONFIG.dup
90
+
91
+ # 如果配置文件存在,尝试读取并合并
92
+ if File.exist?(config_path)
93
+ begin
94
+ user_config = YAML.load_file(config_path)
95
+ # 使用深度合并来确保用户配置覆盖默认配置,但保留未指定的默认值
96
+ deep_merge!(@config, user_config)
97
+ rescue => e
98
+ # 如果配置文件读取失败,使用默认配置,不终止程序
99
+ puts "注意: 配置文件读取失败,使用默认配置: #{e.message}" if ENV['UNIPOD_VERBOSE'] == 'true'
100
+ end
101
+ end
102
+
103
+ # 检查环境变量中的配置覆盖
104
+ apply_env_overrides
105
+
106
+ @config
107
+ end
108
+
109
+ # 从环境变量中读取配置
110
+ def apply_env_overrides
111
+ # 服务器配置
112
+ @config[:server][:host] = ENV['UNIPOD_SERVER_HOST'] if ENV['UNIPOD_SERVER_HOST']
113
+ @config[:server][:port] = ENV['UNIPOD_SERVER_PORT'].to_i if ENV['UNIPOD_SERVER_PORT']
114
+
115
+ # 缓存配置
116
+ @config[:cache][:dir] = ENV['UNIPOD_CACHE_DIR'] if ENV['UNIPOD_CACHE_DIR']
117
+
118
+ # 其他环境变量...
119
+ end
120
+
121
+ def save_config
122
+ # 确保配置目录存在
123
+ FileUtils.mkdir_p(File.dirname(config_path))
124
+
125
+ # 保存配置到文件
126
+ File.open(config_path, 'w') do |f|
127
+ f.write(YAML.dump(@config))
128
+ end
129
+
130
+ puts "配置已保存到: #{config_path}" if ENV['UNIPOD_VERBOSE'] == 'true'
131
+ end
132
+
133
+ def config_path
134
+ @config_path ||= ENV['UNIPOD_CONFIG'] || DEFAULT_CONFIG_PATH
135
+ end
136
+
137
+ def config
138
+ @config ||= load_config
139
+ end
140
+
141
+ def get(key, default = nil)
142
+ keys = key.to_s.split('.')
143
+ value = config
144
+
145
+ keys.each do |k|
146
+ if value.is_a?(Hash) && value.key?(k.to_sym)
147
+ value = value[k.to_sym]
148
+ elsif value.is_a?(Hash) && value.key?(k)
149
+ value = value[k]
150
+ else
151
+ return default
152
+ end
153
+ end
154
+
155
+ value
156
+ end
157
+
158
+ def set(key, value)
159
+ keys = key.to_s.split('.')
160
+ last_key = keys.pop
161
+
162
+ # 导航到最后一个键的父节点
163
+ node = config
164
+ keys.each do |k|
165
+ sym_key = k.to_sym
166
+ str_key = k.to_s
167
+
168
+ key_to_use = node.key?(sym_key) ? sym_key : (node.key?(str_key) ? str_key : sym_key)
169
+
170
+ node[key_to_use] ||= {}
171
+ node = node[key_to_use]
172
+ end
173
+
174
+ # 设置值
175
+ last_sym_key = last_key.to_sym
176
+ last_str_key = last_key.to_s
177
+
178
+ key_to_use = node.key?(last_sym_key) ? last_sym_key : (node.key?(last_str_key) ? last_str_key : last_sym_key)
179
+
180
+ node[key_to_use] = value
181
+
182
+ # 保存配置 - 仅当用户显式调用save_config时才写入文件
183
+
184
+ value
185
+ end
186
+
187
+ def server_host
188
+ get('server.host', 'localhost')
189
+ end
190
+
191
+ def server_port
192
+ get('server.port', 7748)
193
+ end
194
+
195
+ def server_timeout
196
+ get('server.timeout', 120)
197
+ end
198
+
199
+ def server_max_clients
200
+ get('server.max_clients', 10)
201
+ end
202
+
203
+ def server_log_level
204
+ get('server.log_level', 'info')
205
+ end
206
+
207
+ def server_session_id
208
+ get('server.session_id', SecureRandom.hex(8))
209
+ end
210
+
211
+ def cache_dir
212
+ # 确保使用默认缓存目录,而不是 ~/.unipod/cache
213
+ get('cache.dir', detect_default_cache_dir)
214
+ end
215
+
216
+ def cache_max_size
217
+ get('cache.max_size', 1024) # MB
218
+ end
219
+
220
+ def cache_expiry
221
+ get('cache.expiry', 7) # days
222
+ end
223
+
224
+ def index_repos
225
+ config = load_config
226
+ repos = config['index_repos'] || []
227
+
228
+ # 查找Unity项目的manifest.json文件,加载其中的package_index_url
229
+ manifest_repos = load_unity_manifest_repos
230
+
231
+ # 合并从manifest中读取的仓库信息
232
+ manifest_repos.each do |manifest_repo|
233
+ # 检查是否已存在同名仓库
234
+ existing_repo = repos.find { |r| r[:name] == manifest_repo[:name] }
235
+ if existing_repo
236
+ # 更新现有仓库信息
237
+ existing_repo[:package_index_url] = manifest_repo[:package_index_url] if manifest_repo[:package_index_url]
238
+ else
239
+ # 添加新仓库
240
+ repos << manifest_repo
241
+ end
242
+ end
243
+
244
+ repos
245
+ end
246
+
247
+ def custom_repos
248
+ get('repositories.custom_repos', [])
249
+ end
250
+
251
+ def unity_package_format
252
+ get('unity.package_format', 'upm')
253
+ end
254
+
255
+ def unity_preload_packages
256
+ get('unity.preload_packages', true)
257
+ end
258
+
259
+ def unity_compatibility
260
+ get('unity.compatibility', '2019.4')
261
+ end
262
+
263
+ def add_index_repo(name, url, enabled = true)
264
+ repos = get('repositories.index_repos', [])
265
+
266
+ # 检查是否已存在同名仓库
267
+ existing_repo = repos.find { |r| r[:name] == name }
268
+ if existing_repo
269
+ # 更新现有仓库信息
270
+ existing_repo[:url] = url
271
+ existing_repo[:enabled] = enabled
272
+ else
273
+ # 添加新仓库
274
+ repos << {
275
+ name: name,
276
+ url: url,
277
+ enabled: enabled
278
+ }
279
+ end
280
+
281
+ set('repositories.index_repos', repos)
282
+ end
283
+
284
+ def add_scope_mapping(scope, repo_name)
285
+ mappings = get('repositories.scope_mappings', {})
286
+ mappings[scope] = repo_name
287
+ set('repositories.scope_mappings', mappings)
288
+ end
289
+
290
+ def get_repo_for_scope(scope)
291
+ mappings = get('repositories.scope_mappings', {})
292
+ mappings[scope]
293
+ end
294
+
295
+ def remove_index_repo(name)
296
+ repos = index_repos.dup
297
+ repos.reject! { |r| r[:name] == name }
298
+ set('repositories.index_repos', repos)
299
+ end
300
+
301
+ def add_custom_repo(name, url, type = 'git', enabled = true)
302
+ repos = custom_repos.dup
303
+
304
+ # 检查是否已存在
305
+ existing = repos.find { |r| r[:name] == name }
306
+ if existing
307
+ existing[:url] = url
308
+ existing[:type] = type
309
+ existing[:enabled] = enabled
310
+ else
311
+ repos << { name: name, url: url, type: type, enabled: enabled }
312
+ end
313
+
314
+ set('repositories.custom_repos', repos)
315
+ end
316
+
317
+ # 从Unity的manifest.json文件加载仓库信息
318
+ def load_unity_manifest_repos
319
+ repos = []
320
+
321
+ # 查找当前目录及其父目录中的manifest.json文件
322
+ manifest_path = find_unity_manifest
323
+ return repos unless manifest_path
324
+
325
+ begin
326
+ manifest = JSON.parse(File.read(manifest_path))
327
+
328
+ # 检查是否有scopedRegistries配置
329
+ if manifest['scopedRegistries'] && manifest['scopedRegistries'].is_a?(Array)
330
+ manifest['scopedRegistries'].each do |registry|
331
+ next unless registry['name'] && registry['url']
332
+
333
+ repo = {
334
+ name: registry['name'].downcase.gsub(/[^a-z0-9_-]/, '_'),
335
+ url: registry['url'],
336
+ package_index_url: registry['package_index_url'] || registry['index_url'] # 支持两种字段名称
337
+ }
338
+
339
+ # 如果有package_index_url或index_url,才添加仓库
340
+ if repo[:package_index_url]
341
+ repos << repo
342
+ end
343
+ end
344
+ end
345
+ rescue => e
346
+ # 忽略解析错误
347
+ puts "无法解析Unity的manifest.json文件: #{e.message}" if ENV['UNIPOD_VERBOSE'] == 'true'
348
+ end
349
+
350
+ repos
351
+ end
352
+
353
+ # 在当前目录及其父目录中查找Unity的manifest.json文件
354
+ def find_unity_manifest
355
+ current_dir = Dir.pwd
356
+ max_depth = 10 # 限制向上查找的层数
357
+
358
+ depth = 0
359
+ while depth < max_depth
360
+ # 检查当前目录是否有manifest.json文件
361
+ manifest_path = File.join(current_dir, 'Packages', 'manifest.json')
362
+ return manifest_path if File.exist?(manifest_path)
363
+
364
+ # 检查是否已到达根目录
365
+ parent_dir = File.dirname(current_dir)
366
+ break if parent_dir == current_dir # 已到达根目录
367
+
368
+ current_dir = parent_dir
369
+ depth += 1
370
+ end
371
+
372
+ nil # 未找到manifest.json文件
373
+ end
374
+
375
+ private
376
+
377
+ # 深度合并哈希
378
+ def deep_merge!(target, source)
379
+ source.each do |key, value|
380
+ target_value = target[key]
381
+
382
+ if target_value.is_a?(Hash) && value.is_a?(Hash)
383
+ deep_merge!(target_value, value)
384
+ else
385
+ target[key] = value
386
+ end
387
+ end
388
+
389
+ target
390
+ end
391
+ end
392
+ end
393
+ end