|
|
@@ -1,226 +0,0 @@
|
|
|
-# Regenerate gallery\index.json from every *.zip under gallery\packages\.
|
|
|
-#
|
|
|
-# Layout convention: gallery\packages\<host>\<id>\<id>-<version>.zip
|
|
|
-# Each zip must contain a manifest.json at its root. The manifest's "host"
|
|
|
-# field decides which host tab the package belongs to (missing => platform,
|
|
|
-# backward compatible with plain extension packages).
|
|
|
-#
|
|
|
-# Rules (do not relax):
|
|
|
-# - All plugin packages live in this single index.json.
|
|
|
-# - 0.1.0 skeleton packages are excluded (removed from the product).
|
|
|
-# - downloadUrl is stored relative (packages\...); GalleryClient rewrites it
|
|
|
-# against whatever source root is actually configured.
|
|
|
-#
|
|
|
-# Invoked by each project's post-build target: parallel builds launch several
|
|
|
-# copies at once, so the whole regeneration runs under a named mutex.
|
|
|
-$ErrorActionPreference = 'Stop'
|
|
|
-
|
|
|
-function Invoke-WithRetry
|
|
|
-{
|
|
|
- param([scriptblock]$Action, [int]$Attempts = 5)
|
|
|
- for ($i = 1; ; $i++)
|
|
|
- {
|
|
|
- try { return & $Action }
|
|
|
- catch
|
|
|
- {
|
|
|
- if ($i -ge $Attempts) { throw }
|
|
|
- Start-Sleep -Milliseconds (250 * $i)
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function Invoke-Regenerate
|
|
|
-{
|
|
|
- $root = Split-Path -Parent $PSScriptRoot
|
|
|
- $galleryDir = Join-Path $root 'gallery'
|
|
|
- $packagesDir = Join-Path $galleryDir 'packages'
|
|
|
- $indexPath = Join-Path $galleryDir 'index.json'
|
|
|
-
|
|
|
- if (-not (Test-Path $packagesDir)) {
|
|
|
- throw "packages directory not found: $packagesDir"
|
|
|
- }
|
|
|
-
|
|
|
- Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
|
-
|
|
|
- $sha = [System.Security.Cryptography.SHA256]::Create()
|
|
|
- $entries = New-Object System.Collections.Generic.List[object]
|
|
|
- $seen = @{}
|
|
|
-
|
|
|
- $zipFiles = Invoke-WithRetry { Get-ChildItem -Path $packagesDir -Recurse -Filter '*.zip' | Sort-Object FullName }
|
|
|
-
|
|
|
- foreach ($zipFile in $zipFiles) {
|
|
|
- # The producing build may still be flushing the zip; retry the open.
|
|
|
- $zip = Invoke-WithRetry { [System.IO.Compression.ZipFile]::OpenRead($zipFile.FullName) }
|
|
|
- try {
|
|
|
- $manifestEntry = $zip.Entries |
|
|
|
- Where-Object { $_.FullName -replace '\\', '/' -eq 'manifest.json' } |
|
|
|
- Select-Object -First 1
|
|
|
- if (-not $manifestEntry) {
|
|
|
- Write-Warning "skip (no manifest.json at root): $($zipFile.Name)"
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- $reader = New-Object System.IO.StreamReader($manifestEntry.Open(), [System.Text.Encoding]::UTF8)
|
|
|
- try { $manifest = $reader.ReadToEnd() } finally { $reader.Dispose() }
|
|
|
-
|
|
|
- $j = $manifest | ConvertFrom-Json
|
|
|
- if (-not $j.id -or -not $j.version) {
|
|
|
- Write-Warning "skip (manifest missing id/version): $($zipFile.Name)"
|
|
|
- continue
|
|
|
- }
|
|
|
- if ($j.version -eq '0.1.0') {
|
|
|
- Write-Host "exclude 0.1.0 package: $($j.id)"
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- $dedupKey = "$($j.id)@$($j.version)"
|
|
|
- if ($seen.ContainsKey($dedupKey)) {
|
|
|
- Write-Warning "skip duplicate: $dedupKey ($($zipFile.Name))"
|
|
|
- continue
|
|
|
- }
|
|
|
- $seen[$dedupKey] = $true
|
|
|
-
|
|
|
- # Hash + size come from the packaged zip itself.
|
|
|
- $fs = Invoke-WithRetry { [System.IO.File]::OpenRead($zipFile.FullName) }
|
|
|
- try {
|
|
|
- $hashBytes = $sha.ComputeHash($fs)
|
|
|
- $size = $fs.Length
|
|
|
- } finally { $fs.Dispose() }
|
|
|
- $hashHex = ([System.BitConverter]::ToString($hashBytes) -replace '-', '').ToLowerInvariant()
|
|
|
-
|
|
|
- $relativeUrl = $zipFile.FullName.Substring($galleryDir.Length + 1) -replace '\\', '/'
|
|
|
-
|
|
|
- $entry = [ordered]@{
|
|
|
- id = [string]$j.id
|
|
|
- name = [string]$j.name
|
|
|
- version = [string]$j.version
|
|
|
- publisher = if ($j.publisher) { [string]$j.publisher } else { $null }
|
|
|
- description = if ($j.description) { [string]$j.description } else { $null }
|
|
|
- downloadUrl = $relativeUrl
|
|
|
- sha256 = $hashHex
|
|
|
- size = $size
|
|
|
- }
|
|
|
- if ($j.host) { $entry['host'] = [string]$j.host }
|
|
|
- if ($j.category) { $entry['category'] = [string]$j.category }
|
|
|
- if ($j.engine) { $entry['engine'] = [string]$j.engine }
|
|
|
- if ($j.kind) { $entry['kind'] = [string]$j.kind }
|
|
|
- # 构建形态(platform 包区分 Debug/Release 用;缺省不带该字段 → 消费方按 Release 处理)
|
|
|
- if ($j.flavor) { $entry['flavor'] = [string]$j.flavor }
|
|
|
- if ($j.guid) { $entry['guid'] = [string]$j.guid }
|
|
|
- # 本版特性:来自包内 manifest.json 的 notes 字段(扩展管理页"本版特性"区展示)
|
|
|
- if ($j.notes) { $entry['notes'] = [string]$j.notes }
|
|
|
- $tfLine = ($manifest -split "`r?`n") | Where-Object { $_ -match 'targetFramework' } | Select-Object -First 1
|
|
|
- if ($tfLine) { $entry['targetFramework'] = ($tfLine -replace '.*:\s*"([^"]+)".*', '$1') }
|
|
|
- if ($j.references) { $entry['references'] = @($j.references) }
|
|
|
- if ($j.dependencies -and @($j.dependencies).Count -gt 0) {
|
|
|
- $entry['dependencies'] = @($j.dependencies)
|
|
|
- }
|
|
|
- # 运行环境(包内 manifest.json 的 targetFramework:在线仓库尚未下载包时的唯一来源);
|
|
|
- # 取值直接按 JSON 文本提取,避免依赖 PS 对象成员访问(在本机环境下试过会静默取到空值)
|
|
|
- # 宿主要求:{ id, minVersion } → "TeamPLT>=2.1.0"
|
|
|
- if ($j.requiresHost) {
|
|
|
- $rhText = if ($j.requiresHost.minVersion) { "$($j.requiresHost.id)>=$($j.requiresHost.minVersion)" } else { [string]$j.requiresHost.id }
|
|
|
- $entry['requiresHost'] = $rhText
|
|
|
- }
|
|
|
- if ($j.arch -and @($j.arch).Count -gt 0) { $entry['arch'] = @($j.arch) }
|
|
|
- if ($j.nativeDependencies -and @($j.nativeDependencies).Count -gt 0) {
|
|
|
- $entry['nativeDependencies'] = @($j.nativeDependencies)
|
|
|
- }
|
|
|
-
|
|
|
- # versions[]:index.json 契约里"多版本"就装在这个数组里(SDK 的 GalleryPackage.AllVersions 按它读取),
|
|
|
- # 平台扩展管理页的"版本"下拉也只认这一个来源 —— 缺了它,界面上永远只显示顶层 version 那一版,
|
|
|
- # 旧版本即使 zip 就在仓库里也选不到、装不了、回滚不了。
|
|
|
- $entry['versions'] = @([ordered]@{
|
|
|
- version = [string]$j.version
|
|
|
- downloadUrl = $relativeUrl
|
|
|
- sha256 = $hashHex
|
|
|
- size = $size
|
|
|
- releasedAt = (Get-Date).ToString('yyyy-MM-dd')
|
|
|
- status = 'stable'
|
|
|
- notes = if ($j.notes) { [string]$j.notes } else { $null }
|
|
|
- })
|
|
|
-
|
|
|
- $entries.Add([PSCustomObject]$entry)
|
|
|
- Write-Host ("indexed {0} {1} [{2}]" -f $j.id, $j.version, $(if ($j.host) { $j.host } else { 'platform' }))
|
|
|
- } finally {
|
|
|
- $zip.Dispose()
|
|
|
- }
|
|
|
- }
|
|
|
- $sha.Dispose()
|
|
|
-
|
|
|
- # 合并同 id 的多条为一个条目:packages[] 里每个 id 只应出现一次,多版本装进各自的 versions[]。
|
|
|
- # 不做这步的话,同 id 的每个版本各占一条,平台按 id 取一条展示 —— 表现为"版本下拉只剩最新一版"。
|
|
|
- function Get-VersionKey([string]$v) {
|
|
|
- $parsed = $null
|
|
|
- if ([version]::TryParse($v, [ref]$parsed)) { return $parsed }
|
|
|
- return [version]'0.0'
|
|
|
- }
|
|
|
-
|
|
|
- $byId = [ordered]@{}
|
|
|
- foreach ($e in $entries) {
|
|
|
- $key = [string]$e.id
|
|
|
- if ($byId.Contains($key)) {
|
|
|
- $cur = $byId[$key]
|
|
|
- $mergedVersions = @($cur.versions) + @($e.versions)
|
|
|
-
|
|
|
- # 顶层字段必须对齐到"更高的版本",而不是沿用先遇到的那条。
|
|
|
- # dependencies / references / kind / description 等各版本可能不同 ——
|
|
|
- # 例如 1.0.0 依赖后来废弃的契约包,1.0.1 改依赖新契约包。
|
|
|
- # 若顶层留在旧版(文件序通常旧版在前),平台按 id 读到的就是旧依赖,
|
|
|
- # 表现为"明明装了新版,却提示旧依赖未安装"。故版本更高时整体替换,再挂回累计版本列表。
|
|
|
- if ((Get-VersionKey $e.version) -gt (Get-VersionKey $cur.version)) {
|
|
|
- $cur = $e.PSObject.Copy()
|
|
|
- }
|
|
|
- $cur.versions = $mergedVersions
|
|
|
- $byId[$key] = $cur
|
|
|
- } else {
|
|
|
- $byId[$key] = $e
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $merged = New-Object System.Collections.Generic.List[object]
|
|
|
- foreach ($key in $byId.Keys) {
|
|
|
- $p = $byId[$key]
|
|
|
- $versions = @($p.versions | Sort-Object -Property @{ Expression = {
|
|
|
- $v = $null
|
|
|
- if ([version]::TryParse([string]$_.version, [ref]$v)) { $v } else { [version]'0.0' }
|
|
|
- } } -Descending)
|
|
|
- if ($versions.Count -eq 0) { $merged.Add($p); continue }
|
|
|
- # 顶层字段对齐到最高版本,保证只读顶层字段的旧消费方也拿到最新版。
|
|
|
- $top = $versions[0]
|
|
|
- $p.versions = $versions
|
|
|
- $p.version = [string]$top.version
|
|
|
- $p.downloadUrl = $top.downloadUrl
|
|
|
- $p.sha256 = $top.sha256
|
|
|
- $p.size = $top.size
|
|
|
- if (-not $p.notes -and $top.notes) { $p.notes = $top.notes }
|
|
|
- $merged.Add($p)
|
|
|
- }
|
|
|
-
|
|
|
- # Stable order: host (platform first), then id.
|
|
|
- $sorted = @($merged | Sort-Object @{ Expression = { if ($_.host) { $_.host } else { 'platform' } } }, @{ Expression = { $_.id } })
|
|
|
-
|
|
|
- $index = [ordered]@{
|
|
|
- schemaVersion = 1
|
|
|
- generatedAt = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
|
|
|
- packages = $sorted
|
|
|
- }
|
|
|
-
|
|
|
- $json = $index | ConvertTo-Json -Depth 10
|
|
|
- Invoke-WithRetry { [System.IO.File]::WriteAllText($indexPath, $json, (New-Object System.Text.UTF8Encoding($false))) }
|
|
|
- Write-Host "REGEN DONE: $($sorted.Count) package(s) -> $indexPath"
|
|
|
-}
|
|
|
-
|
|
|
-# Serialize parallel post-build invocations within this session.
|
|
|
-$mutex = New-Object System.Threading.Mutex($false, 'Local\TeamAAS-regen-gallery-index')
|
|
|
-$hasHandle = $false
|
|
|
-try {
|
|
|
- if (-not $mutex.WaitOne([TimeSpan]::FromMinutes(5))) {
|
|
|
- throw 'timed out waiting for gallery index lock'
|
|
|
- }
|
|
|
- $hasHandle = $true
|
|
|
- Invoke-Regenerate
|
|
|
-} finally {
|
|
|
- if ($hasHandle) { $mutex.ReleaseMutex() }
|
|
|
- $mutex.Dispose()
|
|
|
-}
|