Class: Pindo::Unity::NugetHelper

Inherits:
Object
  • Object
show all
Defined in:
lib/pindo/module/unity/nuget_helper.rb

Class Method Summary collapse

Class Method Details

.check_conflicts(package_info, nuspec_info) ⇒ Object

检查冲突(忽略 ID 大小写差异)



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/pindo/module/unity/nuget_helper.rb', line 260

def self.check_conflicts(package_info, nuspec_info)
  conflicts = []

  # ID 对比(都转为小写比较)
  if to_lowercase_id(package_info['name']) != to_lowercase_id(nuspec_info['id'])
    conflicts << "ID不一致: package.json(#{package_info['name']}) vs .nuspec(#{nuspec_info['id']})"
  end

  # 版本对比
  if package_info['version'] != nuspec_info['version']
    conflicts << "版本不一致: package.json(#{package_info['version']}) vs .nuspec(#{nuspec_info['version']})"
  end

  # displayName/title 对比(title 是可选字段)
  if nuspec_info['title'] && package_info['displayName'] != nuspec_info['title']
    conflicts << "标题不一致: package.json displayName(#{package_info['displayName']}) vs .nuspec title(#{nuspec_info['title']})"
  end

  # releaseNotes 对比
  pkg_notes = package_info['releaseNotes'] || package_info['changelogUrl'] || ""
  nuspec_notes = nuspec_info['releaseNotes'] || ""
  if !pkg_notes.empty? && !nuspec_notes.empty? && pkg_notes != nuspec_notes
    conflicts << "发布说明不一致"
  end

  conflicts
end

.check_nuget_toolObject

检测 NuGet 工具(Unity Package 只使用 nuget 命令)

Raises:



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/pindo/module/unity/nuget_helper.rb', line 469

def self.check_nuget_tool
  if system("nuget help > /dev/null 2>&1")
    return true
  end

  raise Informative, "    \u672A\u627E\u5230 nuget \u547D\u4EE4\uFF0C\u8BF7\u5B89\u88C5 NuGet CLI\uFF1A\n\n    macOS:\n      brew install nuget\n\n    Linux:\n      sudo apt install nuget  # Ubuntu/Debian\n      sudo yum install nuget  # CentOS/RHEL\n\n    Windows:\n      choco install nuget.commandline\n      \u6216\u4E0B\u8F7D nuget.exe: https://www.nuget.org/downloads\n  ERROR\nend\n"

.detect_files(package_dir) ⇒ Object

检测文件存在情况



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/pindo/module/unity/nuget_helper.rb', line 90

def self.detect_files(package_dir)
  has_package_json = File.exist?(File.join(package_dir, "package.json"))
  nuspec_file = find_nuspec_file(package_dir)
  has_nuspec = !nuspec_file.nil?

  {
    has_package_json: has_package_json,
    has_nuspec: has_nuspec,
    nuspec_file: nuspec_file
  }
end

.extract_author_name(author_field) ⇒ Object

从 package.json 的 author 字段提取作者名称支持两种格式:

  1. 字符串: “Wade”

  2. 对象: “Wade”, “email”: “[email protected]



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/pindo/module/unity/nuget_helper.rb', line 202

def self.extract_author_name(author_field)
  return 'Unity Package' if author_field.nil?

  if author_field.is_a?(Hash)
    # 如果是对象,提取 name 字段
    author_name = author_field['name'] || author_field[:name] || 'Unity Package'
  elsif author_field.is_a?(String)
    # 如果是字符串,直接使用
    author_name = author_field
  else
    author_name = 'Unity Package'
  end

  # 确保是简单的文本,去除换行和多余空格
  author_name.to_s.strip.gsub(/[\r\n]+/, ' ')
end

.find_nupkg_file(package_dir) ⇒ Object

查找 .nupkg 文件



532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/pindo/module/unity/nuget_helper.rb', line 532

def self.find_nupkg_file(package_dir)
  # 优先在 build 目录查找
  build_dir = File.join(package_dir, "build")
  if File.directory?(build_dir)
    nupkg_files = Dir.glob(File.join(build_dir, "*.nupkg"))
    unless nupkg_files.empty?
      return nupkg_files.max_by { |f| File.mtime(f) }
    end
  end

  # 如果 build 目录没有,在当前目录查找
  nupkg_files = Dir.glob(File.join(package_dir, "*.nupkg"))

  if nupkg_files.empty?
    return nil
  end

  nupkg_files.max_by { |f| File.mtime(f) }
end

.find_nuspec_file(package_dir) ⇒ Object

查找 .nuspec 文件



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/pindo/module/unity/nuget_helper.rb', line 75

def self.find_nuspec_file(package_dir)
  # 优先根据 package.json 的 displayName 查找
  if File.exist?(File.join(package_dir, "package.json"))
    package_info = parse_package_json(package_dir)
    display_name = package_info['displayName']
    expected_nuspec = File.join(package_dir, "#{display_name}.nuspec")
    return expected_nuspec if File.exist?(expected_nuspec)
  end

  # 查找任意 .nuspec 文件
  nuspec_files = Dir.glob(File.join(package_dir, "*.nuspec"))
  nuspec_files.first
end

.generate_nuspec_from_package_json(package_info, nuspec_path, nuspec_id) ⇒ Object

生成 .nuspec(从 package.json)



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/pindo/module/unity/nuget_helper.rb', line 332

def self.generate_nuspec_from_package_json(package_info, nuspec_path, nuspec_id)
  # 提取 projectUrl
  project_url = ''
  if package_info['repository'].is_a?(Hash)
    project_url = package_info['repository']['url'] || ''
  elsif package_info['repository'].is_a?(String)
    project_url = package_info['repository']
  end
  project_url = package_info['homepage'] if project_url.empty? && package_info['homepage']

  # 确保必需字段有值
  description = package_info['description'] || package_info['displayName'] || 'Unity Package'
  release_notes = package_info['releaseNotes'] || package_info['changelogUrl'] || 'No release notes'

  # 提取 author 名称(支持字符串和对象格式)
  author_name = extract_author_name(package_info['author'])

  # 验证 author 格式
  unless validate_authors_format(author_name)
    raise Informative, "      package.json \u4E2D\u7684 author \u5B57\u6BB5\u683C\u5F0F\u65E0\u6CD5\u8F6C\u6362\u4E3A\u6709\u6548\u7684 <authors> \u683C\u5F0F\uFF01\n\n      \u5F53\u524D author \u503C: \#{package_info['author'].inspect}\n      \u63D0\u53D6\u7684\u4F5C\u8005\u540D\u79F0: \#{author_name}\n\n      \u8BF7\u786E\u4FDD author \u5B57\u6BB5\u4E3A\u4EE5\u4E0B\u683C\u5F0F\u4E4B\u4E00:\n        1. \u5B57\u7B26\u4E32: \"author\": \"Wade\"\n        2. \u5BF9\u8C61: \"author\": {\"name\": \"Wade\", \"email\": \"[email protected]\"}\n    ERROR\n  end\n\n  # \u6784\u5EFA\u57FA\u7840\u7684 metadata \u5185\u5BB9\n  metadata_content = []\n  metadata_content << \"      <id>\#{nuspec_id}</id>\"\n  metadata_content << \"      <version>\#{package_info['version']}</version>\"\n  # title \u662F\u53EF\u9009\u5B57\u6BB5\uFF0C\u53EA\u6709 displayName \u5B58\u5728\u65F6\u624D\u6DFB\u52A0\n  if package_info['displayName'] && !package_info['displayName'].empty?\n    metadata_content << \"      <title>\#{package_info['displayName']}</title>\"\n  end\n  metadata_content << \"      <authors>\#{author_name}</authors>\"\n  metadata_content << \"      <requireLicenseAcceptance>false</requireLicenseAcceptance>\"\n  metadata_content << \"      <license type=\\\"expression\\\">MIT</license>\"\n  metadata_content << \"      <projectUrl>\#{project_url}</projectUrl>\"\n  metadata_content << \"      <description>\#{description}</description>\"\n  metadata_content << \"      <releaseNotes>\#{release_notes}</releaseNotes>\"\n\n  # README.md \u662F\u5FC5\u9700\u7684\uFF0C\u603B\u662F\u6DFB\u52A0 readme \u6807\u7B7E\n  metadata_content << \"      <readme>README.md</readme>\"\n\n  # \u68C0\u67E5 README.md \u662F\u5426\u5B58\u5728\uFF0C\u7528\u4E8E files \u90E8\u5206\u7684\u5904\u7406\n  readme_path = File.join(File.dirname(nuspec_path), \"README.md\")\n\n  # \u6784\u5EFA files \u90E8\u5206\uFF0C\u5982\u679C\u6709 README.md \u9700\u8981\u7279\u6B8A\u5904\u7406\n  files_content = []\n  if File.exist?(readme_path)\n    files_content << '    <file src=\"README.md\" target=\"\" />'\n    files_content << '    <file src=\"**\" exclude=\"build/**;.git/**;*.nuspec;*.nupkg;README.md\" target=\"content\" />'\n  else\n    files_content << '    <file src=\"**\" exclude=\"build/**;.git/**;*.nuspec;*.nupkg\" target=\"content\" />'\n  end\n\n  nuspec_content = <<~XML\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <package xmlns=\"http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd\">\n      <metadata>\n    \#{metadata_content.join(\"\\n\")}\n      </metadata>\n      <files>\n    \#{files_content.join(\"\\n\")}\n      </files>\n    </package>\n  XML\n\n  File.write(nuspec_path, nuspec_content)\nend\n"

.generate_package_json_from_nuspec(nuspec_info, package_json_path, package_json_name) ⇒ Object

生成 package.json(从 .nuspec)



409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/pindo/module/unity/nuget_helper.rb', line 409

def self.generate_package_json_from_nuspec(nuspec_info, package_json_path, package_json_name)
  package_content = {
    "name" => package_json_name,
    "version" => nuspec_info['version'],
    "displayName" => nuspec_info['title'] || package_json_name,  # 如果没有 title,使用 name 作为 displayName
    "description" => nuspec_info['description'],
    "unity" => "2020.3",
    "releaseNotes" => nuspec_info['releaseNotes']
  }

  # 添加 repository 字段(如果有 projectUrl)
  if nuspec_info['projectUrl'] && !nuspec_info['projectUrl'].empty?
    package_content["repository"] = {
      "type" => "git",
      "url" => nuspec_info['projectUrl']
    }
  end

  File.write(package_json_path, JSON.pretty_generate(package_content))
end

.generate_release_notes_from_git(package_dir) ⇒ Object

生成 Release Notes(从 Git 提交历史)



556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
# File 'lib/pindo/module/unity/nuget_helper.rb', line 556

def self.generate_release_notes_from_git(package_dir)
  # 获取 .nuspec 文件并解析版本(用于默认消息)
  nuspec_file = find_nuspec_file(package_dir)
  return nil if nuspec_file.nil?

  nuspec_info = parse_nuspec(nuspec_file)
  nuspec_version = nuspec_info['version']
  return nil if nuspec_version.nil? || nuspec_version.empty?

  # 默认消息(带 feat: 标签)
  default_message = "feat: 更新版本到 #{nuspec_version}"

  # 如果不是 Git 仓库,返回默认消息
  unless Pindo::GitHandler.is_git_directory?(local_repo_dir: package_dir)
    return default_message
  end

  git_root = Pindo::GitHandler.git_root_directory(local_repo_dir: package_dir)
  unless git_root
    return default_message
  end

  begin
    # 查找是否存在与 nuspec 版本匹配的 tag(支持 v1.1.3, V1.1.3, 1.1.3 等格式)
    matching_tag = nil
    temp_dir = Dir.pwd
    Dir.chdir(git_root)

    # 获取所有 tags
    all_tags = Pindo::GitHandler.git!(%W(-C #{git_root} tag -l)).split("\n")

    # 查找匹配当前版本的 tag(不区分大小写,支持 v 前缀)
    matching_tag = all_tags.find do |tag|
      tag_version = tag.gsub(/^(v|V|release[\s_-]*)/i, '')
      tag_version.downcase == nuspec_version.downcase
    end

    Dir.chdir(temp_dir)

    # 确定获取哪个范围的 commits
    git_range = if matching_tag
      # 找到匹配的 tag,获取前一个 tag 到这个 tag 的 commits
      puts "📝 找到版本 tag: #{matching_tag},生成该版本的 release notes..."

      temp_dir = Dir.pwd
      Dir.chdir(git_root)

      # 获取所有 tags 并按版本号排序
      all_tags = Pindo::GitHandler.git!(%W(-C #{git_root} tag -l)).split("\n")
      sorted_tags = all_tags.sort_by do |tag|
        version_str = tag.gsub(/^(v|V|release[\s_-]*)/i, '')
        version_str.split('.').map(&:to_i)
      end

      # 找到当前 tag 的索引
      tag_index = sorted_tags.index(matching_tag)
      prev_tag = nil
      if tag_index && tag_index > 0
        prev_tag = sorted_tags[tag_index - 1]
      end

      Dir.chdir(temp_dir)

      if prev_tag
        "#{prev_tag}..#{matching_tag}"
      else
        # 如果是第一个 tag,获取所有历史
        matching_tag
      end
    else
      # 没有找到匹配的 tag,获取最新 tag 到 HEAD 的 commits
      puts "📝 未找到版本 #{nuspec_version} 的 tag,生成最新 tag 到 HEAD 的 release notes..."

      # 获取最新 tag
      latest_tag = nil
      ["v", "V", "release", ""].each do |prefix|
        latest_tag = Pindo::GitHandler.get_latest_version_tag(project_dir: git_root, tag_prefix: prefix)
        break if latest_tag
      end

      if latest_tag
        "#{latest_tag}..HEAD"
      else
        "HEAD"
      end
    end

    # 获取 commit messages(包含完整的 body)
    temp_dir = Dir.pwd
    Dir.chdir(git_root)

    # 使用特殊分隔符来区分不同的 commits
    separator = "---COMMIT-SEPARATOR---"
    commits_raw = Pindo::GitHandler.git!(%W(-C #{git_root} log #{git_range} --pretty=format:%B#{separator}))

    # 按分隔符拆分成单个 commits
    commits = commits_raw.split(separator).map(&:strip).reject(&:empty?)

    Dir.chdir(temp_dir)

    # 过滤只保留符合规范的 commits(feat:, fix:, docs:, perf:, refactor:, style:, test:, chore:, revert:)
    valid_prefixes = ['feat:', 'fix:', 'docs:', 'doc:', 'perf:', 'refactor:', 'style:', 'test:', 'chore:', 'revert:']
    filtered_commits = commits.select do |commit|
      valid_prefixes.any? { |prefix| commit.downcase.start_with?(prefix) }
    end

    # 如果没有符合规范的 commits,返回默认消息
    if filtered_commits.empty?
      return default_message
    end

    # 返回每条 commit,用换行分隔(不添加空行)
    filtered_commits.join("\n")

  rescue => e
    puts "⚠️  生成 release notes 失败: #{e.message}"
    # 返回默认消息
    default_message
  end
end

.handle_case_a(package_dir) ⇒ Object

情况A:只有 package.json,创建 .nuspec



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/pindo/module/unity/nuget_helper.rb', line 107

def self.handle_case_a(package_dir)
  package_info = parse_package_json(package_dir)
  validate_package_json(package_info)

  # 确保 package.json 的 name 是全小写
  package_info['name'] = to_lowercase_id(package_info['name'])
  update_package_json_name(package_dir, package_info['name'])

  # 检查 README.md 是否存在
  readme_path = File.join(package_dir, "README.md")
  unless File.exist?(readme_path)
    Funlog.instance.fancyinfo_warning("未找到 README.md 文件,打包前请创建")
  end

  nuspec_path = File.join(package_dir, "#{package_info['displayName']}.nuspec")

  # 生成 .nuspec,ID 使用驼峰格式
  nuspec_id = to_camelcase_id(package_info['name'])
  generate_nuspec_from_package_json(package_info, nuspec_path, nuspec_id)

  puts "  ✓ 已创建 #{package_info['displayName']}.nuspec"
  puts "    package.json name: #{package_info['name']} (全小写)"
  puts "    .nuspec id: #{nuspec_id} (驼峰)"
  puts ""
end

.handle_case_b(package_dir, nuspec_file) ⇒ Object

情况B:只有 .nuspec,创建 package.json



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/pindo/module/unity/nuget_helper.rb', line 134

def self.handle_case_b(package_dir, nuspec_file)
  nuspec_info = parse_nuspec(nuspec_file)
  validate_nuspec(nuspec_info)

  package_json_path = File.join(package_dir, "package.json")

  # package.json 的 name 使用全小写
  package_json_name = to_lowercase_id(nuspec_info['id'])
  generate_package_json_from_nuspec(nuspec_info, package_json_path, package_json_name)

  puts "  ✓ 已创建 package.json"
  puts "    .nuspec id: #{nuspec_info['id']} (驼峰)"
  puts "    package.json name: #{package_json_name} (全小写)"
  puts ""
end

.handle_case_c(package_dir, nuspec_file) ⇒ Object

情况C:同时存在,以 .nuspec 为准同步到 package.json



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/pindo/module/unity/nuget_helper.rb', line 151

def self.handle_case_c(package_dir, nuspec_file)
  package_info = parse_package_json(package_dir)
  nuspec_info = parse_nuspec(nuspec_file)

  conflicts = check_conflicts(package_info, nuspec_info)

  if conflicts.empty?
    # 即使一致,也要确保 ID 格式正确
    expected_package_name = to_lowercase_id(nuspec_info['id'])
    expected_nuspec_id = nuspec_info['id']  # 保持驼峰

    if package_info['name'] != expected_package_name
      puts "  ✓ 修正 package.json name 格式"
      puts "    #{package_info['name']} → #{expected_package_name}"
      update_package_json_name(package_dir, expected_package_name)
    else
      puts "  ✓ 两个文件信息一致"
    end
  else
    Funlog.instance.fancyinfo_warning("检测到不一致,以 .nuspec 为准同步:")
    conflicts.each { |msg| puts "    - #{msg}" }
    puts ""

    # 以 .nuspec 为准,更新 package.json
    update_package_json_from_nuspec(nuspec_info, package_dir)
    puts "  ✓ 已同步 package.json"
    puts "    .nuspec id: #{nuspec_info['id']} (驼峰)"
    puts "    package.json name: #{to_lowercase_id(nuspec_info['id'])} (全小写)"
  end
  puts ""
end

.handle_case_d(package_dir) ⇒ Object

情况D:都不存在,报错

Raises:



184
185
186
187
188
189
190
191
192
# File 'lib/pindo/module/unity/nuget_helper.rb', line 184

def self.handle_case_d(package_dir)
  raise Informative, "    \u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u6709\u6548\u7684 Unity Package\uFF1A\n    - \u7F3A\u5C11 package.json \u6587\u4EF6\n    - \u7F3A\u5C11 .nuspec \u6587\u4EF6\n\n    \u8BF7\u786E\u4FDD\u81F3\u5C11\u5B58\u5728\u5176\u4E2D\u4E00\u4E2A\u6587\u4EF6\u3002\n  ERROR\nend\n"

.nuget_init(package_dir) ⇒ Object

初始化逻辑封装



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/pindo/module/unity/nuget_helper.rb', line 15

def self.nuget_init(package_dir)
  Funlog.instance.fancyinfo_start("Unity Package 初始化")

  # 检测文件存在情况
  status = detect_files(package_dir)

  puts "  📄 package.json: #{status[:has_package_json] ? '✅' : '❌'}"
  puts "  📄 .nuspec: #{status[:has_nuspec] ? '✅' : '❌'}"
  if status[:nuspec_file]
    puts "     路径: #{File.basename(status[:nuspec_file])}"
  end
  puts ""

  # 根据4种情况处理
  case [status[:has_package_json], status[:has_nuspec]]
  when [true, false]
    Funlog.instance.fancyinfo_update("情况A: 仅有 package.json,创建 .nuspec")
    handle_case_a(package_dir)
  when [false, true]
    Funlog.instance.fancyinfo_update("情况B: 仅有 .nuspec,创建 package.json")
    handle_case_b(package_dir, status[:nuspec_file])
  when [true, true]
    Funlog.instance.fancyinfo_update("情况C: 两个文件都存在,以 .nuspec 为准同步")
    handle_case_c(package_dir, status[:nuspec_file])
  when [false, false]
    Funlog.instance.fancyinfo_error("情况D: 两个文件都不存在")
    handle_case_d(package_dir)
  end

  Funlog.instance.fancyinfo_success("初始化完成")
end

.pack_nupkg(package_dir, output_dir = nil) ⇒ Object

执行打包(Unity Package 使用 nuget pack 命令)



491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/pindo/module/unity/nuget_helper.rb', line 491

def self.pack_nupkg(package_dir, output_dir = nil)
  check_nuget_tool

  nuspec_file = find_nuspec_file(package_dir)
  if nuspec_file.nil?
    raise Informative, "未找到 .nuspec 文件,请先运行 pindo unity initpack"
  end

  output_dir ||= File.join(package_dir, "build")
  FileUtils.mkdir_p(output_dir)

  puts "📦 开始打包 Unity Package..."
  puts "   .nuspec: #{File.basename(nuspec_file)}"
  puts "   输出目录: #{output_dir}"
  puts "   使用工具: nuget pack"
  puts

  cmd = "cd \"#{package_dir}\" && nuget pack \"#{File.basename(nuspec_file)}\" -OutputDirectory \"#{output_dir}\""

  success = system(cmd)

  unless success
    raise Informative, "NuGet 打包失败,请检查 .nuspec 文件格式"
  end

  nupkg_files = Dir.glob(File.join(output_dir, "*.nupkg"))
  if nupkg_files.empty?
    raise Informative, "打包完成但未找到 .nupkg 文件"
  end

  nupkg_file = nupkg_files.max_by { |f| File.mtime(f) }
  puts "✅ 打包成功: #{File.basename(nupkg_file)}"
  puts "   路径: #{nupkg_file}"

  nupkg_file
end

.parse_nuspec(nuspec_file) ⇒ Object

解析 .nuspec

Raises:



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/pindo/module/unity/nuget_helper.rb', line 239

def self.parse_nuspec(nuspec_file)
  doc = Nokogiri::XML(File.read(nuspec_file))
  doc.remove_namespaces!
   = doc.at_xpath('//metadata')

  raise Informative, ".nuspec 文件格式错误:未找到 metadata 节点" if .nil?

  {
    'id' => .at_xpath('id')&.text,
    'version' => .at_xpath('version')&.text,
    'title' => .at_xpath('title')&.text,
    'description' => .at_xpath('description')&.text,
    'releaseNotes' => .at_xpath('releaseNotes')&.text,
    'authors' => .at_xpath('authors')&.text,
    'projectUrl' => .at_xpath('projectUrl')&.text,
    'readme' => .at_xpath('readme')&.text,
    'license' => .at_xpath('license')&.text
  }
end

.parse_package_json(package_dir) ⇒ Object

解析 package.json



233
234
235
236
# File 'lib/pindo/module/unity/nuget_helper.rb', line 233

def self.parse_package_json(package_dir)
  file_path = File.join(package_dir, "package.json")
  JSON.parse(File.read(file_path))
end

.to_camelcase_id(id) ⇒ Object

保持驼峰(用于 .nuspec)



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/pindo/module/unity/nuget_helper.rb', line 57

def self.to_camelcase_id(id)
  # 如果 id 已经是驼峰,保持原样
  # 如果是全小写,转为驼峰
  if id == id.downcase
    # 简单的转换:按点分割,每段首字母大写
    id.split('.').map do |part|
      part.split(/(?=[A-Z])/).map(&:capitalize).join
    end.join('.')
  else
    id  # 保持原有的驼峰格式
  end
end

.to_lowercase_id(id) ⇒ Object

驼峰转全小写(用于 package.json)



52
53
54
# File 'lib/pindo/module/unity/nuget_helper.rb', line 52

def self.to_lowercase_id(id)
  id.downcase
end

.update_nuspec_release_notes(nuspec_file, release_notes) ⇒ Object

更新 .nuspec 文件的 releaseNotes



678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
# File 'lib/pindo/module/unity/nuget_helper.rb', line 678

def self.update_nuspec_release_notes(nuspec_file, release_notes)
  return unless File.exist?(nuspec_file)
  return if release_notes.nil? || release_notes.empty?

  doc = Nokogiri::XML(File.read(nuspec_file))

  # 保存命名空间信息
  namespaces = doc.collect_namespaces

  # 移除命名空间以便查找
  doc.remove_namespaces!
   = doc.at_xpath('//metadata')

  return unless 

  release_notes_node = .at_xpath('releaseNotes')
  if release_notes_node
    release_notes_node.content = release_notes
  else
    # 如果不存在,创建节点
    new_node = Nokogiri::XML::Node.new('releaseNotes', doc)
    new_node.content = release_notes
    .add_child(new_node)
  end

  # 使用 save_with 选项来保持 UTF-8 编码,不转义非 ASCII 字符
  File.write(nuspec_file, doc.to_xml(encoding: 'UTF-8', save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION | Nokogiri::XML::Node::SaveOptions::AS_XML))
end

.update_package_json_from_nuspec(nuspec_info, package_dir) ⇒ Object

更新 package.json(从 .nuspec)



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/pindo/module/unity/nuget_helper.rb', line 431

def self.update_package_json_from_nuspec(nuspec_info, package_dir)
  package_json_path = File.join(package_dir, "package.json")

  # 读取现有 package.json(保留其他字段)
  package_info = parse_package_json(package_dir)

  # 更新关键字段
  package_info['name'] = to_lowercase_id(nuspec_info['id'])
  package_info['version'] = nuspec_info['version']
  # 如果 nuspec 有 title 字段才更新,否则保留原有的 displayName
  package_info['displayName'] = nuspec_info['title'] if nuspec_info['title']
  package_info['description'] = nuspec_info['description']
  package_info['releaseNotes'] = nuspec_info['releaseNotes']

  # 更新 repository 字段(如果有 projectUrl)
  if nuspec_info['projectUrl'] && !nuspec_info['projectUrl'].empty?
    package_info['repository'] = {
      "type" => "git",
      "url" => nuspec_info['projectUrl']
    }
  end

  File.write(package_json_path, JSON.pretty_generate(package_info))
end

.update_package_json_name(package_dir, new_name) ⇒ Object

更新 package.json 的 name 字段



457
458
459
460
461
462
# File 'lib/pindo/module/unity/nuget_helper.rb', line 457

def self.update_package_json_name(package_dir, new_name)
  package_json_path = File.join(package_dir, "package.json")
  package_info = parse_package_json(package_dir)
  package_info['name'] = new_name
  File.write(package_json_path, JSON.pretty_generate(package_info))
end

.validate_authors_format(authors_text) ⇒ Object

验证 authors 字段格式(必须是简单文本,不能是 JSON)



220
221
222
223
224
225
226
227
228
229
230
# File 'lib/pindo/module/unity/nuget_helper.rb', line 220

def self.validate_authors_format(authors_text)
  return false if authors_text.nil? || authors_text.empty?

  # 检查是否包含 JSON 特征({, }, :, "name", "email" 等)
  if authors_text.include?('{') || authors_text.include?('}') ||
     authors_text.include?('"name"') || authors_text.include?('"email"')
    return false
  end

  true
end

.validate_nuspec(nuspec_info) ⇒ Object

验证 .nuspec



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/pindo/module/unity/nuget_helper.rb', line 299

def self.validate_nuspec(nuspec_info)
  required_fields = ['id', 'version', 'description', 'releaseNotes', 'readme', 'projectUrl']
  missing = required_fields.select { |field| nuspec_info[field].nil? || nuspec_info[field].empty? }

  unless missing.empty?
    raise Informative, ".nuspec 缺少必需字段: #{missing.join(', ')}"
  end

  # 验证 authors 字段格式
  if nuspec_info['authors']
    unless validate_authors_format(nuspec_info['authors'])
      raise Informative, "        .nuspec \u6587\u4EF6\u4E2D\u7684 <authors> \u5B57\u6BB5\u683C\u5F0F\u9519\u8BEF\uFF01\n\n        \u5F53\u524D\u683C\u5F0F: \#{nuspec_info['authors']}\n\n        \u6B63\u786E\u683C\u5F0F\u5E94\u8BE5\u662F\u7B80\u5355\u7684\u6587\u672C\uFF0C\u4F8B\u5982:\n          <authors>Wade</authors>\n\n        \u9519\u8BEF\u683C\u5F0F\u793A\u4F8B:\n          <authors>{\"name\": \"wade\",\"email\": \"[email protected]\"}</authors>\n\n        \u8BF7\u4FEE\u6B63 .nuspec \u6587\u4EF6\u4E2D\u7684 authors \u5B57\u6BB5\u3002\n      ERROR\n    end\n  end\nend\n"

.validate_package_json(package_info) ⇒ Object

验证 package.json



289
290
291
292
293
294
295
296
# File 'lib/pindo/module/unity/nuget_helper.rb', line 289

def self.validate_package_json(package_info)
  required_fields = ['name', 'version', 'displayName']
  missing = required_fields.select { |field| package_info[field].nil? || package_info[field].empty? }

  unless missing.empty?
    raise Informative, "package.json 缺少必需字段: #{missing.join(', ')}"
  end
end