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,384 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'pathname'
6
+ require 'yaml'
7
+ require 'unipod/config'
8
+
9
+ module UniPod
10
+ class Command
11
+ class Push < Command
12
+ self.summary = '将Unity包推送到本地索引仓库'
13
+ self.description = <<-DESC
14
+ 将当前目录下的Unity包信息推送到本地索引仓库中。
15
+ 在Unity包的git仓库目录下执行此命令,将包信息添加到索引仓库。
16
+ 默认使用配置的索引仓库,也可以通过--repo选项指定特定仓库。
17
+ DESC
18
+
19
+ #-----------------------------------------------
20
+ # 命令配置与初始化
21
+ #-----------------------------------------------
22
+
23
+ def self.options
24
+ [
25
+ ['--repo=NAME', '指定推送的目标索引仓库名称'],
26
+ ['--allow-warnings', '允许包含警告的包推送'],
27
+ ['--force', '强制覆盖已存在的包信息']
28
+ ].concat(super)
29
+ end
30
+
31
+ def initialize(argv)
32
+ # 获取默认Git URL
33
+ default_git_url = 'https://gitee.com/goodunitylib/JoyUnityLibIndex.git'
34
+
35
+ # 从默认Git URL中提取仓库名称
36
+ default_repo_name = Config.extract_repo_name_from_url(default_git_url)
37
+
38
+ @repo_name = argv.option('repo') || default_repo_name
39
+ @allow_warnings = argv.flag?('allow-warnings', false)
40
+ @force = argv.flag?('force', false)
41
+ @package_path = Dir.pwd
42
+ super
43
+ end
44
+
45
+ def validate!
46
+ super
47
+ package_json_path = File.join(@package_path, 'package.json')
48
+ help! "当前目录不是有效的Unity包目录: #{@package_path}" unless File.exist?(package_json_path)
49
+
50
+ begin
51
+ @package_info = JSON.parse(File.read(package_json_path))
52
+ rescue => e
53
+ help! "无法解析package.json文件: #{e.message}"
54
+ end
55
+
56
+ help! "package.json中缺少name字段" unless @package_info['name']
57
+ help! "package.json中缺少version字段" unless @package_info['version']
58
+ end
59
+
60
+ #-----------------------------------------------
61
+ # 主执行流程
62
+ #-----------------------------------------------
63
+
64
+ def run
65
+ UI.puts Rainbow("正在推送Unity包: #{@package_info['name']}@#{@package_info['version']}").green
66
+
67
+ # 验证包内容
68
+ validate_package
69
+
70
+ # 从package.json获取版本号
71
+ version = @package_info['version']
72
+ UI.puts Rainbow("准备为当前版本创建Git标签: v#{version}").cyan
73
+
74
+ # 处理未提交的文件
75
+ handle_uncommitted_files
76
+
77
+ # 创建并推送Git标签
78
+ if create_git_tag(version)
79
+ UI.puts Rainbow("成功创建Git标签: v#{version}").green
80
+ else
81
+ UI.warning Rainbow("创建Git标签失败,继续推送到索引仓库").red
82
+ end
83
+
84
+ # 推送到索引仓库
85
+ push_to_index_repo
86
+ end
87
+
88
+ #-----------------------------------------------
89
+ # Git操作相关功能
90
+ #-----------------------------------------------
91
+
92
+ # 处理未提交的文件
93
+ def handle_uncommitted_files
94
+ uncommitted_files = get_uncommitted_files
95
+ return if uncommitted_files.empty?
96
+
97
+ UI.error Rainbow("\n仓库中有未提交的文件:").red
98
+ uncommitted_files.each do |file|
99
+ UI.puts Rainbow(" #{file}").red
100
+ end
101
+
102
+ choices = {
103
+ '1.自动提交这些文件后继续': :commit,
104
+ '2.忽略并继续推送': :ignore,
105
+ '3.取消操作': :cancel
106
+ }
107
+
108
+ choice = UI.select(Rainbow("如何处理这些未提交的文件?").yellow, choices)
109
+
110
+ case choice
111
+ when :commit
112
+ auto_commit_files
113
+ when :cancel
114
+ UI.puts Rainbow("操作已取消").yellow
115
+ exit 0
116
+ when :ignore
117
+ UI.puts Rainbow("忽略这些文件,继续推送").yellow
118
+ end
119
+ end
120
+
121
+ # 自动提交所有未提交的文件
122
+ def auto_commit_files
123
+ UI.puts Rainbow("正在自动提交所有文件...").yellow
124
+
125
+ # 执行git提交
126
+ commit_message = "自动提交:准备发布 #{@package_info['name']}@#{@package_info['version']}"
127
+
128
+ # 添加所有文件
129
+ add_result = system("git add -A")
130
+ unless add_result
131
+ UI.error Rainbow("添加文件到暂存区失败,请手动检查").red
132
+ exit 1
133
+ end
134
+
135
+ # 提交所有文件
136
+ commit_result = system("git commit -m \"#{commit_message}\"")
137
+ unless commit_result
138
+ UI.error Rainbow("提交文件失败,请手动检查").red
139
+ exit 1
140
+ end
141
+
142
+ UI.success Rainbow("成功提交所有文件").green
143
+
144
+ # 推送到远程仓库
145
+ UI.puts Rainbow("正在推送更改到远程仓库...").yellow
146
+ push_result = system("git push")
147
+
148
+ if push_result
149
+ UI.success Rainbow("成功推送更改到远程仓库").green
150
+ else
151
+ UI.warning Rainbow("提交成功,但推送到远程仓库失败,请稍后手动推送").red
152
+ if UI.yes?("是否继续?")
153
+ return
154
+ else
155
+ exit 1
156
+ end
157
+ end
158
+ end
159
+
160
+ # 创建并推送Git标签
161
+ def create_git_tag(version)
162
+ tag_name = "v#{version}"
163
+
164
+ # 检查标签是否已存在
165
+ existing_tags = `git tag -l "#{tag_name}" 2>/dev/null`.strip
166
+ if !existing_tags.empty?
167
+ UI.warning "标签 #{tag_name} 已存在,跳过创建"
168
+ return true
169
+ end
170
+
171
+ # 创建标签
172
+ UI.puts "正在创建Git标签: #{tag_name}..." if @verbose
173
+ create_result = system("git tag -a #{tag_name} -m \"Release version #{version}\"")
174
+
175
+ # 推送标签到远程
176
+ if create_result
177
+ UI.puts "正在推送Git标签到远程..." if @verbose
178
+ push_result = system("git push origin #{tag_name}")
179
+
180
+ if push_result
181
+ return true
182
+ else
183
+ UI.warning "标签已创建但推送到远程失败,请稍后手动推送: git push origin #{tag_name}"
184
+ return true
185
+ end
186
+ else
187
+ UI.error "创建Git标签失败"
188
+ return false
189
+ end
190
+ end
191
+
192
+ # 获取所有未提交的文件列表
193
+ def get_uncommitted_files
194
+ # 获取所有未提交的文件列表
195
+ uncommitted = `git status --porcelain 2>/dev/null`.strip
196
+ return [] if uncommitted.empty?
197
+
198
+ # 解析git status输出,提取文件名
199
+ uncommitted.split("\n").map do |line|
200
+ line.strip[3..-1] # 去除状态标记,只保留文件路径
201
+ end
202
+ end
203
+
204
+ # 检查是否有未提交的更改
205
+ def has_uncommitted_changes
206
+ !get_uncommitted_files.empty?
207
+ end
208
+
209
+ # 获取Git仓库远程URL
210
+ def get_git_remote_url
211
+ url = `git config --get remote.origin.url 2>/dev/null`.strip
212
+ url.empty? ? nil : url
213
+ end
214
+
215
+ # 获取当前Git提交的哈希
216
+ def get_git_revision
217
+ `git rev-parse HEAD 2>/dev/null`.strip
218
+ end
219
+
220
+ # 获取当前HEAD指向的标签
221
+ def get_git_tag
222
+ tags = `git tag --points-at HEAD 2>/dev/null`.strip.split("\n")
223
+ tags.empty? ? nil : tags.first
224
+ end
225
+
226
+ # 获取当前分支名
227
+ def get_git_branch
228
+ `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
229
+ end
230
+
231
+ #-----------------------------------------------
232
+ # 包验证与推送功能
233
+ #-----------------------------------------------
234
+
235
+ private
236
+
237
+ # 验证包内容
238
+ def validate_package
239
+ # 检查必要字段
240
+ required_fields = ['name', 'version', 'displayName', 'description']
241
+ missing_fields = required_fields.select { |field| @package_info[field].nil? || @package_info[field].empty? }
242
+
243
+ unless missing_fields.empty?
244
+ if @allow_warnings
245
+ UI.warning "包信息缺少以下字段: #{missing_fields.join(', ')}"
246
+ else
247
+ UI.error "包信息缺少以下字段: #{missing_fields.join(', ')}"
248
+ UI.error "使用--allow-warnings选项忽略此警告"
249
+ exit 1
250
+ end
251
+ end
252
+
253
+ # 检查包名格式
254
+ unless @package_info['name'] =~ /^[a-z0-9][a-z0-9\.-]*\.[a-z0-9][a-z0-9\.-]*$/
255
+ if @allow_warnings
256
+ UI.warning "包名格式不符合Unity推荐的格式 (例如: com.company.package)"
257
+ else
258
+ UI.error "包名格式不符合Unity推荐的格式 (例如: com.company.package)"
259
+ UI.error "使用--allow-warnings选项忽略此警告"
260
+ exit 1
261
+ end
262
+ end
263
+
264
+ UI.puts "包验证通过" if @verbose
265
+ end
266
+
267
+ # 推送到索引仓库
268
+ def push_to_index_repo
269
+ # 获取缓存目录
270
+ cache_dir = Config.detect_default_cache_dir
271
+ index_repo_dir = File.join(cache_dir, 'scoped', @repo_name)
272
+
273
+ # 确保目录存在
274
+ FileUtils.mkdir_p(index_repo_dir)
275
+
276
+ # 检查是否是git仓库
277
+ is_git_repo = File.directory?(File.join(index_repo_dir, '.git'))
278
+ if is_git_repo
279
+ Dir.chdir(index_repo_dir) do
280
+ system("git pull")
281
+ end
282
+ end
283
+
284
+ # 为包创建Specs目录结构
285
+ package_name = @package_info['name']
286
+ package_version = @package_info['version']
287
+
288
+ specs_dir = File.join(index_repo_dir, 'Specs', package_name, package_version)
289
+ FileUtils.mkdir_p(specs_dir)
290
+
291
+ # 准备spec.json文件
292
+ spec_json = create_spec_json
293
+
294
+ # 写入spec.json文件
295
+ spec_path = File.join(specs_dir, 'spec.json')
296
+ if File.exist?(spec_path) && !@force
297
+ UI.error "包版本已存在,使用--force选项覆盖"
298
+ return
299
+ end
300
+
301
+ File.write(spec_path, JSON.pretty_generate(spec_json))
302
+ UI.puts "写入包规格信息到: #{spec_path}" if @verbose
303
+
304
+ # 如果是git仓库,提交更改
305
+ if is_git_repo
306
+ Dir.chdir(index_repo_dir) do
307
+ system("git add .")
308
+ commit_message = "添加包: #{package_name}@#{package_version}"
309
+ system("git commit -m \"#{commit_message}\"")
310
+
311
+ # 尝试推送到远程仓库
312
+ push_result = system("git push")
313
+ if push_result
314
+ UI.puts Rainbow("已提交并推送到远程索引仓库").green
315
+ else
316
+ UI.warning "已提交到本地索引仓库,但推送到远程失败,请稍后手动推送"
317
+ end
318
+ end
319
+ else
320
+ UI.puts Rainbow("已添加到本地索引仓库").green
321
+ end
322
+
323
+ # 更新包缓存
324
+ refresh_cache
325
+
326
+ UI.puts Rainbow("包 #{package_name}@#{package_version} 已成功推送到索引仓库: #{@repo_name}").green
327
+ end
328
+
329
+ # 创建包规格JSON
330
+ def create_spec_json
331
+ # 复制基本信息
332
+ spec = {
333
+ 'name' => @package_info['name'],
334
+ 'version' => @package_info['version'],
335
+ 'displayName' => @package_info['displayName'] || @package_info['name'].split('.').last.capitalize,
336
+ 'description' => @package_info['description'] || '',
337
+ 'unity' => @package_info['unity'] || '2019.4'
338
+ }
339
+
340
+ # 复制其他可选字段
341
+ ['keywords', 'category', 'dependencies', 'author', 'repository'].each do |field|
342
+ spec[field] = @package_info[field] if @package_info[field]
343
+ end
344
+
345
+ # 添加git仓库信息,基于当前目录
346
+ git_url = get_git_remote_url
347
+ if git_url
348
+ spec['git'] = {
349
+ 'url' => git_url,
350
+ 'revision' => get_git_revision
351
+ }
352
+
353
+ # 如果当前提交有标签,使用标签作为版本
354
+ git_tag = get_git_tag
355
+ if git_tag
356
+ spec['git']['tag'] = git_tag
357
+ else
358
+ # 否则使用分支名
359
+ git_branch = get_git_branch
360
+ spec['git']['branch'] = git_branch if git_branch
361
+ end
362
+ end
363
+
364
+ # 添加时间戳
365
+ spec['publishedAt'] = Time.now.iso8601
366
+
367
+ spec
368
+ end
369
+
370
+ # 刷新缓存
371
+ def refresh_cache
372
+ # 尝试通知服务器刷新缓存
373
+ begin
374
+ require 'net/http'
375
+ url = URI.parse("http://#{Config.server_host}:#{Config.server_port}/refresh")
376
+ response = Net::HTTP.get_response(url)
377
+ UI.puts "通知服务器刷新缓存: #{response.code}" if @verbose
378
+ rescue => e
379
+ UI.warning "无法通知服务器刷新缓存: #{e.message}" if @verbose
380
+ end
381
+ end
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'unipod/server/server'
4
+
5
+ module UniPod
6
+ class Command
7
+ class Server < Command
8
+ self.summary = '启动本地UniPod服务器'
9
+ self.description = <<-DESC
10
+ 启动本地UniPod服务器,用于托管Unity包。
11
+ 该服务器可用作开发环境的包仓库或本地测试。
12
+ DESC
13
+
14
+ def self.options
15
+ [
16
+ ['--port=PORT', '指定服务器端口 (默认: 7748)'],
17
+ ['--host=HOST', '指定服务器主机 (默认: 0.0.0.0)'],
18
+ ['--no-browser', '不自动打开浏览器']
19
+ ].concat(super)
20
+ end
21
+
22
+ def initialize(argv)
23
+ @port = argv.option('port', '7748')
24
+ @host = argv.option('host', '0.0.0.0')
25
+ @browser = !argv.flag?('no-browser', false)
26
+ super
27
+ end
28
+
29
+ def validate!
30
+ super
31
+ help! "端口必须是有效数字" unless @port =~ /^\d+$/
32
+ end
33
+
34
+ def run
35
+ UI.puts Rainbow("启动UniPod服务器: #{@host}:#{@port}").green
36
+
37
+ server_options = {
38
+ port: @port.to_i,
39
+ host: @host,
40
+ verbose: @verbose
41
+ }
42
+
43
+ verdaccio_server = UniPod::Server::VerdaccioServer.new(server_options)
44
+ # 显示Unity配置提示
45
+ display_unity_config_hint
46
+ # 如果启用了浏览器选项,打开浏览器
47
+ if @browser
48
+ UI.puts "正在打开浏览器..." if @verbose
49
+ open_browser("http://localhost:#{@port}")
50
+ end
51
+
52
+ # 启动服务器(这会阻塞直到服务器停止)
53
+ begin
54
+ verdaccio_server.start
55
+ rescue Interrupt
56
+ UI.puts Rainbow("\n正在停止服务器...").yellow
57
+ verdaccio_server.stop
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # 显示Unity配置提示,使用本机IP
64
+ def display_unity_config_hint
65
+ # 获取本机IP地址
66
+ # require 'socket'
67
+ # local_ip = begin
68
+ # Socket.ip_address_list.detect{|intf| intf.ipv4_private?}&.ip_address || 'localhost'
69
+ # rescue
70
+ # 'localhost'
71
+ # end
72
+
73
+ local_ip = 'localhost'
74
+
75
+ UI.puts "\n============ Unity配置提示 ============"
76
+ UI.puts "请在Unity项目的Packages/manifest.json文件中添加以下配置:"
77
+ UI.puts %Q{
78
+ "scopedRegistries": [
79
+ {
80
+ "name": "JoyUnityLibIndex",
81
+ "url": "http://#{local_ip}:#{@port}",
82
+ "index_url": "https://gitee.com/goodunitylib/JoyUnityLibIndex.git",
83
+ "scopes": [
84
+ "com.joyunitystu"
85
+ ]
86
+ }
87
+ ]
88
+ }
89
+
90
+ end
91
+
92
+ def detect_cache_dir
93
+ # 使用 Config 类提供的缓存目录,确保所有系统统一
94
+ require 'unipod/config'
95
+ UniPod::Config.cache_dir
96
+ end
97
+
98
+ def open_browser(url)
99
+ # 根据操作系统打开浏览器
100
+ cmd = case RUBY_PLATFORM
101
+ when /darwin/
102
+ "open '#{url}'"
103
+ when /mswin|mingw|cygwin/
104
+ "start '#{url}'"
105
+ when /linux|bsd/
106
+ "xdg-open '#{url}'"
107
+ else
108
+ return # 不支持的操作系统
109
+ end
110
+
111
+ system(cmd)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'claide'
4
+ require 'rainbow'
5
+
6
+ module UniPod
7
+ class Command < CLAide::Command
8
+ require 'unipod/command/install'
9
+ require 'unipod/command/push'
10
+ require 'unipod/command/server'
11
+
12
+ self.abstract_command = true
13
+ self.command = 'unipod'
14
+ self.version = VERSION
15
+ self.description = 'UniPod是用于管理Unity包的命令行工具。'
16
+ self.plugin_prefixes = %w[unipod]
17
+
18
+ def self.options
19
+ [
20
+ ['--verbose', '显示详细输出'],
21
+ ['--no-ansi', '禁用彩色输出'],
22
+ ].concat(super)
23
+ end
24
+
25
+ def initialize(argv)
26
+ @verbose = argv.flag?('verbose')
27
+ Rainbow.enabled = false if argv.flag?('no-ansi')
28
+ super
29
+ end
30
+
31
+ def validate!
32
+ super
33
+ banner! if @verbose
34
+ end
35
+
36
+ def banner!
37
+ puts "\n#{Rainbow(self.class.description).green}\n"
38
+ end
39
+ end
40
+ end