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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c7188707354d3aaa6e73863b24e0e063900b6cfeb1464e85444f90a0c2a5ee6a
4
+ data.tar.gz: 2ffdac624a5407164017bfd5979a710ddbc1c25d320e1608ffaed8015ff5784f
5
+ SHA512:
6
+ metadata.gz: 48d4a07e99595b6d9190628e8439a360e10acd7b6af009a063b32091c2ea41de1cd62bd8415a392227824fb66b1afc40901c94056e148d944a616a01de1bab41
7
+ data.tar.gz: f4c2f9b4e8e9567763f18b77cc280ef24533824b0187851086dce91b0d8d2f0b03fad98c5cc1aff4594e39cb1c0762cbec4cf8592c1a776c440b732a5ba1b1ba
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 UniPod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # UniPod
2
+
3
+ UniPod是一个用于管理Unity包的命令行工具,类似于CocoaPods对iOS包的管理。
4
+
5
+ ## 安装
6
+
7
+ 使用RubyGems安装:
8
+
9
+ ```bash
10
+ gem install unipod
11
+ ```
12
+
13
+ ## 使用方法
14
+
15
+ ### 初始化项目
16
+
17
+ 在Unity项目目录中创建一个`UnipodFile`:
18
+
19
+ ```ruby
20
+ # UnipodFile 示例
21
+ dependency 'Analytics', '~> 1.0'
22
+ dependency 'UIKit', '~> 2.3.1'
23
+ ```
24
+
25
+ ### 安装依赖
26
+
27
+ ```bash
28
+ # 安装UnipodFile中的所有依赖
29
+ unipod install
30
+
31
+ # 安装特定包
32
+ unipod install Analytics
33
+ ```
34
+
35
+ ### 推送包到仓库
36
+
37
+ ```bash
38
+ # 推送包到默认仓库
39
+ unipod push path/to/package.unitypackage
40
+
41
+ # 推送到指定仓库
42
+ unipod push path/to/package.unitypackage --repo=private-repo
43
+ ```
44
+
45
+ ### 启动本地服务器
46
+
47
+ ```bash
48
+ # 启动默认配置的服务器
49
+ unipod server
50
+
51
+ # 自定义端口和主机
52
+ unipod server --port=9000 --host=0.0.0.0
53
+ ```
54
+
55
+ ## 开发
56
+
57
+ ```bash
58
+ # 克隆仓库
59
+ git clone https://github.com/yourusername/unipod.git
60
+ cd unipod
61
+
62
+ # 安装依赖
63
+ bundle install
64
+
65
+ # 安装开发版本到本地
66
+ rake install
67
+ ```
68
+
69
+ ## 贡献
70
+
71
+ 1. Fork 项目
72
+ 2. 创建特性分支 (`git checkout -b feature/amazing-feature`)
73
+ 3. 提交变更 (`git commit -m 'Add some amazing feature'`)
74
+ 4. 推送到分支 (`git push origin feature/amazing-feature`)
75
+ 5. 创建Pull Request
76
+
77
+ ## 许可证
78
+
79
+ 本项目使用MIT许可证 - 详见 [LICENSE](LICENSE) 文件
data/exe/unipod ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'unipod'
6
+
7
+ begin
8
+ UniPod::Command.run(ARGV)
9
+ rescue => e
10
+ UniPod::UI.error "发生错误: #{e.message}"
11
+ UniPod::UI.error e.backtrace.join("\n") if ENV['UNIPOD_DEBUG']
12
+ exit 1
13
+ end
@@ -0,0 +1,384 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'unipod/config'
7
+ require 'unipod/server/cache_manager'
8
+ require 'unipod/server/package_builder_queue'
9
+
10
+ module UniPod
11
+ class Command
12
+ class Install < Command
13
+ self.summary = '安装项目所需的Unity包'
14
+ self.description = <<-DESC
15
+ 安装Unity项目所需的包依赖,读取manifest.json中定义的包列表。
16
+ 1. 读取Unity项目当前需要的所有Package(版本号形式引入的Package)
17
+ 2. 从索引库中获取这些Package的依赖信息
18
+ 3. 下载所有需要的Package的Git仓库,并生成.tgz文件
19
+ 4. 将Package解压到Unity项目的Library/PackageCache目录
20
+
21
+ 如果提供了特定的包名,则只安装该包。
22
+ DESC
23
+
24
+ self.arguments = [
25
+ CLAide::Argument.new('PACKAGE', false)
26
+ ]
27
+
28
+ def self.options
29
+ [
30
+ ['--repo-update', '在安装前更新本地仓库'],
31
+ ['--project-path=PATH', 'Unity项目的路径,默认为当前目录'],
32
+ ['--verbose', '显示详细安装过程']
33
+ ].concat(super)
34
+ end
35
+
36
+ def initialize(argv)
37
+ @package_name = argv.shift_argument
38
+ @repo_update = argv.flag?('repo-update', false)
39
+ @verbose = argv.flag?('verbose', false)
40
+ @project_path = argv.option('project-path', Dir.pwd)
41
+ @cache_manager = nil
42
+ @builder_queue = nil
43
+ @unity_packages = {}
44
+ @processed_packages = {}
45
+ super
46
+ end
47
+
48
+ def validate!
49
+ super
50
+ manifest_path = File.join(@project_path, 'Packages', 'manifest.json')
51
+ unless File.exist?(manifest_path)
52
+ help! "未找到Unity项目的manifest.json文件: #{manifest_path}"
53
+ end
54
+ end
55
+
56
+ def run
57
+ UI.puts Rainbow("正在安装Unity包...").green
58
+
59
+ # 读取Unity项目的manifest.json文件,包括scopedRegistries配置
60
+ read_unity_manifest
61
+
62
+ # 初始化缓存管理器
63
+ @cache_manager = UniPod::Server::CacheManager.new(verbose: @verbose)
64
+
65
+ # 初始化包构建队列
66
+ @builder_queue = UniPod::Server::PackageBuilderQueue.instance
67
+ @builder_queue.start(@cache_manager, verbose: @verbose)
68
+
69
+ # 如果需要,更新仓库
70
+ if @repo_update
71
+ UI.puts "更新本地仓库缓存..."
72
+ @cache_manager.refresh_cache
73
+ end
74
+
75
+ begin
76
+ # 根据参数决定安装特定包还是所有依赖
77
+ if @package_name
78
+ UI.puts "安装特定包: #{@package_name}"
79
+ install_package(@package_name)
80
+ else
81
+ UI.puts "扫描并安装所有所需的包..."
82
+ install_all_dependencies
83
+ end
84
+
85
+ UI.puts Rainbow("所有包安装完成!").green
86
+ ensure
87
+ # 停止包构建队列
88
+ @builder_queue.stop if @builder_queue
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ # 读取Unity项目的manifest.json文件,获取项目所需的包和索引库配置
95
+ def read_unity_manifest
96
+ manifest_path = File.join(@project_path, 'Packages', 'manifest.json')
97
+ if File.exist?(manifest_path)
98
+ begin
99
+ manifest_data = JSON.parse(File.read(manifest_path))
100
+
101
+ # 读取dependencies部分
102
+ if manifest_data['dependencies'].is_a?(Hash)
103
+ dependencies = manifest_data['dependencies']
104
+
105
+ # 筛选出以版本号形式引入的包(不是本地路径或git URL)
106
+ dependencies.each do |package_name, version|
107
+ # 跳过本地路径或git URL形式的包
108
+ unless version.start_with?('file:', 'git:', 'https://', 'git@')
109
+ # 标准化版本号(删除^和~等前缀)
110
+ clean_version = version.gsub(/[\^~>=<]/, '')
111
+ @unity_packages[package_name] = clean_version
112
+ UI.puts "发现包: #{package_name}@#{clean_version}" if @verbose
113
+ end
114
+ end
115
+
116
+ UI.puts "Unity项目中发现 #{@unity_packages.size} 个需要通过UniPod安装的包"
117
+ else
118
+ UI.puts "manifest.json中没有找到dependencies部分或格式不正确"
119
+ end
120
+
121
+ # 读取scopedRegistries部分,获取索引库配置
122
+ if manifest_data['scopedRegistries'].is_a?(Array)
123
+ scoped_registries = manifest_data['scopedRegistries']
124
+
125
+ UI.puts "发现 #{scoped_registries.size} 个作用域注册表配置" if @verbose
126
+
127
+ # 处理每个scopedRegistry
128
+ scoped_registries.each do |registry|
129
+ if registry['index_url']
130
+ registry_name = registry['name'] || extract_repo_name_from_url(registry['index_url'])
131
+ index_url = registry['index_url']
132
+ scopes = registry['scopes'] || []
133
+
134
+ UI.puts "发现索引库: #{registry_name} (#{index_url}), 作用域: #{scopes.join(', ')}" if @verbose
135
+
136
+ # 将索引库信息传递给Config
137
+ UniPod::Config.add_index_repo(registry_name, index_url)
138
+
139
+ # 记录作用域与索引库的映射关系
140
+ scopes.each do |scope|
141
+ UniPod::Config.add_scope_mapping(scope, registry_name)
142
+ end
143
+ end
144
+ end
145
+ else
146
+ UI.puts "manifest.json中没有找到scopedRegistries部分或格式不正确"
147
+ end
148
+ rescue JSON::ParserError => e
149
+ UI.error "解析manifest.json文件失败: #{e.message}"
150
+ end
151
+ else
152
+ UI.error "未找到Unity项目的manifest.json文件: #{manifest_path}"
153
+ end
154
+ end
155
+
156
+ # 从URL中提取仓库名称
157
+ def extract_repo_name_from_url(url)
158
+ return nil if url.nil? || url.empty?
159
+
160
+ # 处理不同格式的Git URL
161
+ repo_name = nil
162
+
163
+ # 处理SSH格式: git@github.com:user/repo.git
164
+ if url.start_with?('git@')
165
+ match = url.match(/git@[^:]+:([^\/]+)\/([^\.]+)/)
166
+ repo_name = match[2] if match && match[2]
167
+ # 处理HTTPS格式: https://github.com/user/repo.git
168
+ elsif url.start_with?('http://', 'https://', 'git://')
169
+ uri = URI.parse(url)
170
+ path = uri.path
171
+ # 移除.git后缀和前导/
172
+ path = path.sub(/\.git$/, '').sub(/^\//, '')
173
+ # 获取最后一部分作为仓库名
174
+ repo_name = path.split('/').last
175
+ end
176
+
177
+ # 如果提取失败,使用URL的哈希值作为后备
178
+ unless repo_name
179
+ require 'digest'
180
+ repo_name = "repo-#{Digest::MD5.hexdigest(url)[0..7]}"
181
+ end
182
+
183
+ # 确保仓库名称只包含合法字符
184
+ repo_name.gsub(/[^a-zA-Z0-9_-]/, '_')
185
+ end
186
+
187
+ # 安装所有Unity项目需要的包
188
+ def install_all_dependencies
189
+ # 先筛选出存在于索引库中的包
190
+ valid_packages = {}
191
+
192
+ UI.puts "正在筛选manifest中的包..."
193
+ @unity_packages.each do |package_name, version|
194
+ # 检查包是否存在于索引库中
195
+ package_info = @cache_manager.get_package_info(package_name)
196
+ if package_info
197
+ valid_packages[package_name] = version
198
+ UI.puts "√ 包 #{package_name}@#{version} 在索引库中存在" if @verbose
199
+ else
200
+ UI.puts "× 跳过包 #{package_name}@#{version} (未在索引库中找到)" if @verbose
201
+ end
202
+ end
203
+
204
+ UI.puts "共找到 #{valid_packages.size}/#{@unity_packages.size} 个包在索引库中存在"
205
+
206
+ # 将所有有效包添加到处理队列
207
+ packages_to_process = valid_packages.dup
208
+
209
+ # 处理所有包
210
+ until packages_to_process.empty?
211
+ package_name, version = packages_to_process.shift
212
+ next if @processed_packages.key?("#{package_name}@#{version}")
213
+
214
+ # 安装包及其依赖
215
+ new_dependencies = install_package_with_dependencies(package_name, version)
216
+
217
+ # 将新的依赖添加到处理队列
218
+ new_dependencies.each do |dep_name, dep_version|
219
+ # 再次检查依赖包是否在索引库中存在
220
+ if @cache_manager.get_package_info(dep_name) && !@processed_packages.key?("#{dep_name}@#{dep_version}")
221
+ packages_to_process[dep_name] = dep_version
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ # 安装特定的包
228
+ def install_package(package_name)
229
+ # 检查是否在Unity包列表中
230
+ unless @unity_packages.key?(package_name)
231
+ UI.puts "警告: 包 #{package_name} 不在Unity项目的manifest.json中"
232
+ if UI.confirm("是否仍然要安装此包?")
233
+ # 默认使用最新版本
234
+ @unity_packages[package_name] = 'latest'
235
+ else
236
+ return
237
+ end
238
+ end
239
+
240
+ # 检查包是否存在于索引库中
241
+ package_info = @cache_manager.get_package_info(package_name)
242
+ unless package_info
243
+ UI.error "包 #{package_name} 在索引库中不存在,无法安装"
244
+ return
245
+ end
246
+
247
+ # 安装包及其依赖
248
+ install_package_with_dependencies(package_name, @unity_packages[package_name])
249
+ end
250
+
251
+ # 安装包及其依赖,返回依赖列表
252
+ def install_package_with_dependencies(package_name, version)
253
+ package_key = "#{package_name}@#{version}"
254
+
255
+ # 如果已经处理过此包,直接返回
256
+ return {} if @processed_packages.key?(package_key)
257
+
258
+ UI.puts Rainbow("处理包: #{package_key}").yellow
259
+
260
+ # 获取包信息
261
+ package_info = @cache_manager.get_package_info(package_name)
262
+ unless package_info
263
+ # UI.puts "警告: 在索引库中未找到包 #{package_name},这可能是Unity内置包或公共包"
264
+ @processed_packages[package_key] = true
265
+ return {}
266
+ end
267
+
268
+ # 确定要安装的版本
269
+ if version == 'latest' && package_info['version']
270
+ version = package_info['version']
271
+ UI.puts "使用最新版本: #{version}" if @verbose
272
+ end
273
+
274
+ # 获取包的特定版本信息
275
+ version_info = nil
276
+ if package_info['versions'] && package_info['versions'][version]
277
+ version_info = package_info['versions'][version]
278
+ else
279
+ # UI.puts "警告: 未找到包 #{package_name} 的版本 #{version},使用最新版本信息"
280
+ version_info = package_info
281
+ end
282
+
283
+ # 收集依赖信息
284
+ dependencies = {}
285
+ if version_info && version_info['dependencies'].is_a?(Hash)
286
+ version_info['dependencies'].each do |dep_name, dep_version|
287
+ # 标准化版本号
288
+ clean_version = dep_version.gsub(/[\^~>=<]/, '')
289
+ dependencies[dep_name] = clean_version
290
+ end
291
+
292
+ UI.puts "包 #{package_key} 有 #{dependencies.size} 个依赖项" if @verbose
293
+ end
294
+
295
+ # 下载并安装包
296
+ tarball_path = download_and_extract_package(package_name, version, version_info)
297
+
298
+ # 如果安装成功,标记为已处理
299
+ if tarball_path
300
+ @processed_packages[package_key] = true
301
+ return dependencies
302
+ else
303
+ UI.puts "警告: 包 #{package_key} 安装失败"
304
+ @processed_packages[package_key] = false
305
+ return {}
306
+ end
307
+ end
308
+
309
+ # 下载并解压包到Unity项目的Library/PackageCache目录
310
+ def download_and_extract_package(package_name, version, version_info)
311
+ tarball_name = "#{package_name}-#{version}.tgz"
312
+
313
+ # 检查是否已存在tarball文件
314
+ tarball_path = @cache_manager.get_package_tarball(package_name, tarball_name)
315
+
316
+ if tarball_path && File.exist?(tarball_path)
317
+ UI.puts "使用缓存的tarball: #{tarball_path}" if @verbose
318
+ else
319
+ # 需要从Git仓库构建tarball
320
+ UI.puts "未找到包 #{package_name}@#{version} 的tarball,尝试构建..."
321
+
322
+ if version_info && version_info['git'] && version_info['git']['url']
323
+ git_url = version_info['git']['url']
324
+ git_tag = version_info['git']['tag'] || "v#{version}"
325
+ git_branch = version_info['git']['branch'] || 'main'
326
+
327
+ UI.puts "从Git仓库构建包: #{git_url} (tag: #{git_tag})"
328
+
329
+ # 使用同步方式构建包,因为我们需要立即安装
330
+ if @cache_manager.clone_and_build_package(package_name, git_url, git_tag, git_branch)
331
+ tarball_path = @cache_manager.get_package_tarball(package_name, tarball_name)
332
+ if !tarball_path || !File.exist?(tarball_path)
333
+ UI.error "构建包失败: #{package_name}@#{version}"
334
+ return nil
335
+ end
336
+ else
337
+ UI.error "克隆Git仓库失败: #{git_url}"
338
+ return nil
339
+ end
340
+ else
341
+ UI.error "包 #{package_name}@#{version} 没有Git仓库信息,无法构建"
342
+ return nil
343
+ end
344
+ end
345
+
346
+ # 解压包到Unity项目的Library/PackageCache目录
347
+ extract_package_to_unity(package_name, version, tarball_path)
348
+
349
+ return tarball_path
350
+ end
351
+
352
+ # 解压包到Unity项目的Library/PackageCache目录
353
+ def extract_package_to_unity(package_name, version, tarball_path)
354
+ # 确定目标目录
355
+ package_cache_dir = File.join(@project_path, 'Library', 'PackageCache')
356
+ package_dir = File.join(package_cache_dir, "#{package_name}@#{version}")
357
+
358
+ # 创建目标目录
359
+ FileUtils.mkdir_p(package_cache_dir)
360
+
361
+ # 如果目标目录已存在,先删除
362
+ if Dir.exist?(package_dir)
363
+ UI.puts "目标目录已存在,删除: #{package_dir}" if @verbose
364
+ FileUtils.rm_rf(package_dir)
365
+ end
366
+
367
+ # 创建目标目录
368
+ FileUtils.mkdir_p(package_dir)
369
+
370
+ # 解压tarball到目标目录
371
+ UI.puts "解压包到: #{package_dir}"
372
+ system("tar -xzf #{tarball_path} -C #{package_dir}")
373
+
374
+ if $?.success?
375
+ UI.puts Rainbow("✓ 已安装: #{package_name}@#{version}").green
376
+ return true
377
+ else
378
+ UI.error "解压包失败: #{package_name}@#{version}"
379
+ return false
380
+ end
381
+ end
382
+ end
383
+ end
384
+ end