regen-index.ps1 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # Regenerate index.json for THIS gallery repository (PLT\Gallery).
  2. #
  3. # 为什么自带一份:
  4. # 主仓库 build\regen-gallery-index.ps1 写死了 "<脚本父目录的父目录>\gallery",
  5. # 只能服务 TeamAAS-2.1\gallery,无法指向这里。
  6. #
  7. # 格式:对齐远端 Gallery 仓库的 GalleryPackage schema(含多版本 versions[]),
  8. # 以保持内容为准:字段全部来自包内 manifest.json,不再手写,避免 index 与包不一致。
  9. # 版本历史会继承旧 index 中同 id 的其它版本条目(releasedAt / status / notes 不丢)。
  10. #
  11. # 用法(改了 packages\ 下任何 zip 之后执行):
  12. # powershell -NoProfile -ExecutionPolicy Bypass -File .\regen-index.ps1
  13. #
  14. # 布局约定:packages\<host>\<id>\<id>-<version>.zip(zip 根目录必须含 manifest.json)
  15. $ErrorActionPreference = 'Stop'
  16. $galleryDir = $PSScriptRoot
  17. $packagesDir = Join-Path $galleryDir 'packages'
  18. $indexPath = Join-Path $galleryDir 'index.json'
  19. if (-not (Test-Path $packagesDir)) {
  20. throw "packages directory not found: $packagesDir"
  21. }
  22. Add-Type -AssemblyName System.IO.Compression.FileSystem
  23. # 旧 index:仅用于继承版本历史
  24. $history = @{}
  25. if (Test-Path $indexPath) {
  26. try {
  27. $old = (Get-Content $indexPath -Raw -Encoding UTF8) | ConvertFrom-Json
  28. foreach ($p in $old.packages) {
  29. foreach ($v in $p.versions) {
  30. $history["$($p.id)@$($v.version)"] = @{
  31. releasedAt = $v.releasedAt
  32. status = if ($v.status) { $v.status } else { 'stable' }
  33. notes = $v.notes
  34. }
  35. }
  36. }
  37. } catch {
  38. Write-Warning "旧 index.json 解析失败,版本历史不继承:$($_.Exception.Message)"
  39. }
  40. }
  41. $today = (Get-Date).ToString('yyyy-MM-dd')
  42. $sha = [System.Security.Cryptography.SHA256]::Create()
  43. $entries = New-Object System.Collections.Generic.List[object]
  44. $seen = @{}
  45. foreach ($zipFile in (Get-ChildItem -Path $packagesDir -Recurse -Filter '*.zip' | Sort-Object FullName)) {
  46. $zip = [System.IO.Compression.ZipFile]::OpenRead($zipFile.FullName)
  47. try {
  48. $manifestEntry = $zip.Entries |
  49. Where-Object { $_.FullName -replace '\\', '/' -eq 'manifest.json' } |
  50. Select-Object -First 1
  51. if (-not $manifestEntry) {
  52. Write-Warning "skip (no manifest.json at root): $($zipFile.Name)"
  53. continue
  54. }
  55. $reader = New-Object System.IO.StreamReader($manifestEntry.Open(), [System.Text.Encoding]::UTF8)
  56. try { $manifest = $reader.ReadToEnd() } finally { $reader.Dispose() }
  57. $j = $manifest | ConvertFrom-Json
  58. if (-not $j.id -or -not $j.version) {
  59. Write-Warning "skip (manifest missing id/version): $($zipFile.Name)"
  60. continue
  61. }
  62. $key = "$($j.id)@$($j.version)"
  63. if ($seen.ContainsKey($key)) {
  64. Write-Warning "skip duplicate: $key ($($zipFile.Name))"
  65. continue
  66. }
  67. $seen[$key] = $true
  68. $fs = [System.IO.File]::OpenRead($zipFile.FullName)
  69. try {
  70. $hashBytes = $sha.ComputeHash($fs)
  71. $size = $fs.Length
  72. } finally { $fs.Dispose() }
  73. $hashHex = ([System.BitConverter]::ToString($hashBytes) -replace '-', '').ToLowerInvariant()
  74. # downloadUrl 存相对路径:GalleryClient 会按实际源根拼接,源换域名也不用改包。
  75. $relativeUrl = $zipFile.FullName.Substring($galleryDir.Length + 1) -replace '\\', '/'
  76. $hostName = if ($j.host) { [string]$j.host } else { 'platform' }
  77. $kind = if ($j.kind) { [string]$j.kind } else { 'extension' }
  78. $requires = $null
  79. if ($j.requiresHost) {
  80. $requires = if ($j.requiresHost.minVersion) {
  81. "$($j.requiresHost.id)>=$($j.requiresHost.minVersion)"
  82. } else { [string]$j.requiresHost.id }
  83. }
  84. $prev = $history[$key]
  85. $versionEntry = [ordered]@{
  86. version = [string]$j.version
  87. downloadUrl = $relativeUrl
  88. sha256 = $hashHex
  89. size = $size
  90. releasedAt = if ($prev) { $prev.releasedAt } else { $today }
  91. status = if ($prev) { $prev.status } else { 'stable' }
  92. # 本版特性优先取被发布物料自己的 manifest.notes(每个版本各自的说明),
  93. # 其次继承旧 index 里的同版本记录,最后才是兜底文案。
  94. notes = if ($j.notes) { [string]$j.notes } elseif ($prev -and $prev.notes) { $prev.notes } else { '首次收录' }
  95. }
  96. $entry = [ordered]@{
  97. id = [string]$j.id
  98. name = [string]$j.name
  99. version = [string]$j.version
  100. publisher = if ($j.publisher) { [string]$j.publisher } else { $null }
  101. kind = $kind
  102. host = $hostName
  103. description = if ($j.description) { [string]$j.description } else { $null }
  104. notes = if ($j.notes) { [string]$j.notes } else { $null }
  105. downloadUrl = $relativeUrl
  106. sha256 = $hashHex
  107. size = $size
  108. versions = @($versionEntry)
  109. }
  110. # 以下字段"有才写":与平台仓 build\regen-gallery-index.ps1 保持同一套口径,
  111. # 也避免给不声明这些项的包留一堆 null(消费方按"缺省"处理更干净)。
  112. if ($j.category) { $entry['category'] = [string]$j.category }
  113. if ($j.engine) { $entry['engine'] = [string]$j.engine }
  114. # 构建形态(platform 包区分 Debug/Release 用;缺省不带该字段 → 消费方按 Release 处理)
  115. if ($j.flavor) { $entry['flavor'] = [string]$j.flavor }
  116. if ($j.guid) { $entry['guid'] = [string]$j.guid }
  117. if ($j.targetFramework) { $entry['targetFramework'] = [string]$j.targetFramework }
  118. if ($j.references -and @($j.references).Count -gt 0) { $entry['references'] = @($j.references) }
  119. if ($j.dependencies -and @($j.dependencies).Count -gt 0) { $entry['dependencies'] = @($j.dependencies) }
  120. if ($requires) { $entry['requiresHost'] = $requires }
  121. if ($j.arch -and @($j.arch).Count -gt 0) { $entry['arch'] = @($j.arch) }
  122. # 随包原生库:安装时这些文件必须落地(厂商原生库版本要严格跟随插件),
  123. # 缺了会沿用"平台已有同名则跳过"的去重策略,装上新版插件却还是旧原生库。
  124. if ($j.nativeDependencies -and @($j.nativeDependencies).Count -gt 0) { $entry['nativeDependencies'] = @($j.nativeDependencies) } if ($j.flavor) { $entry['flavor'] = [string]$j.flavor }
  125. $entries.Add([PSCustomObject]$entry)
  126. Write-Host ("indexed {0} {1} [{2}] {3} bytes {4}" -f $j.id, $j.version, $hostName, $size, $hashHex)
  127. } finally {
  128. $zip.Dispose()
  129. }
  130. }
  131. $sha.Dispose()
  132. # ---------------------------------------------------------------------------
  133. # 合并同 id 的多条为单一条目:
  134. # index.json 的契约是 packages[] 每个 id 只出现一次,多版本装在 versions[] 里
  135. # (SDK 侧 GalleryPackage.AllVersions 就按这个读)。
  136. # 若同 id 各版本各占一条,平台按 id 取一条展示 —— 表现为"版本下拉只有最新一版",
  137. # 旧版本永远选不到。这里按 id 归并,并把顶层字段对齐到最高版本。
  138. # ---------------------------------------------------------------------------
  139. function Get-VersionKey([string]$v) {
  140. $parsed = $null
  141. if ([version]::TryParse($v, [ref]$parsed)) { return $parsed }
  142. return [version]'0.0'
  143. }
  144. $byId = [ordered]@{}
  145. foreach ($e in $entries) {
  146. $id = [string]$e.id
  147. if ($byId.Contains($id)) {
  148. $cur = $byId[$id]
  149. $mergedVersions = @($cur.versions) + @($e.versions)
  150. # 顶层字段必须对齐到"更高的版本",而不是沿用先遇到的那条。
  151. # dependencies / references / kind / description 等各版本可能不同 ——
  152. # 例如 1.0.0 依赖后来废弃的契约包,1.0.1 改依赖新契约包。
  153. # 若顶层留在旧版(文件序通常旧版在前),平台按 id 读到的就是旧依赖,
  154. # 表现为"明明装了新版,却提示旧依赖未安装"。故版本更高时整体替换,再挂回累计版本列表。
  155. if ((Get-VersionKey $e.version) -gt (Get-VersionKey $cur.version)) {
  156. $cur = $e.PSObject.Copy()
  157. }
  158. $cur.versions = $mergedVersions
  159. if (-not $cur.notes -and $e.notes) { $cur.notes = $e.notes }
  160. $byId[$id] = $cur
  161. } else {
  162. $byId[$id] = $e
  163. }
  164. }
  165. $merged = New-Object System.Collections.Generic.List[object]
  166. foreach ($id in $byId.Keys) {
  167. $p = $byId[$id]
  168. $versions = @($p.versions | Sort-Object -Property @{ Expression = {
  169. $v = $null
  170. if ([version]::TryParse([string]$_.version, [ref]$v)) { $v } else { [version]'0.0' }
  171. } } -Descending)
  172. if ($versions.Count -eq 0) { continue }
  173. $top = $versions[0]
  174. $p.versions = $versions
  175. $p.version = [string]$top.version
  176. $p.downloadUrl = $top.downloadUrl
  177. $p.sha256 = $top.sha256
  178. $p.size = $top.size
  179. $p.notes = if ($p.notes) { $p.notes } else { $top.notes }
  180. $merged.Add([PSCustomObject]$p)
  181. }
  182. # Stable order: host (platform first), then id.
  183. $sorted = @($merged | Sort-Object @{ Expression = { $_.host } }, @{ Expression = { $_.id } })
  184. $index = [ordered]@{
  185. schemaVersion = 1
  186. generatedAt = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
  187. packages = $sorted
  188. }
  189. $json = $index | ConvertTo-Json -Depth 12
  190. # ConvertTo-Json 会把 ">" 转义成 \u003e(合法 JSON 但可读性差,且与线上文件不一致),还原。
  191. $json = $json -replace '\\u003e', '>' -replace '\\u003c', '<' -replace '\\u0026', '&'
  192. [System.IO.File]::WriteAllText($indexPath, $json, (New-Object System.Text.UTF8Encoding($false)))
  193. Write-Host "REGEN DONE: $($sorted.Count) package(s) -> $indexPath"