chore(validation): enforce package graph boundaries

Reject internal package version drift, dependency cycles, and runtime-to-integration reverse dependencies before launching the external UPM consumer validation.
This commit is contained in:
2026-08-26 01:18:02 +08:00
parent 724e0bc8d8
commit 3e612b975a
2 changed files with 69 additions and 8 deletions
@@ -3,7 +3,8 @@ param(
[string]$UnityEditorPath,
[string]$ProjectRoot,
[switch]$KeepProject,
[switch]$SkipTests
[switch]$SkipTests,
[switch]$GraphOnly
)
$ErrorActionPreference = 'Stop'
@@ -101,8 +102,12 @@ foreach ($record in $packageRecords) {
$packageByName[$record.Name] = $record
}
$versionErrors = @()
$graphErrors = @()
$localDependencies = @{}
foreach ($record in $packageRecords) {
$dependencies = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::Ordinal)
$localDependencies[$record.Name] = $dependencies
if (-not $record.Manifest.dependencies) {
continue
}
@@ -112,14 +117,60 @@ foreach ($record in $packageRecords) {
continue
}
[void]$dependencies.Add($dependency.Name)
$actualVersion = $packageByName[$dependency.Name].Version
if ([string]$dependency.Value -ne $actualVersion) {
$versionErrors += "$($record.Name) requires $($dependency.Name) $($dependency.Value), local version is $actualVersion"
$graphErrors += "$($record.Name) requires $($dependency.Name) $($dependency.Value), local version is $actualVersion"
}
$dependsOnIntegration = $dependency.Name -like '*-integration-*'
$isIntegrationOrStarter =
$record.Name -like '*-integration-*' -or
$record.Name -like '*-starter-*'
if ($dependsOnIntegration -and -not $isIntegrationOrStarter) {
$graphErrors += "$($record.Name) must not depend on integration package $($dependency.Name)"
}
}
}
if ($versionErrors.Count -gt 0) {
throw "Internal package versions are inconsistent:`n$($versionErrors -join "`n")"
if ($graphErrors.Count -gt 0) {
throw "Internal package graph is invalid:`n$($graphErrors -join "`n")"
}
$resolvedPackages = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::Ordinal)
do {
$madeProgress = $false
foreach ($packageName in $localDependencies.Keys | Sort-Object) {
if ($resolvedPackages.Contains($packageName)) {
continue
}
$unresolvedDependencies = @($localDependencies[$packageName] |
Where-Object { -not $resolvedPackages.Contains($_) })
if ($unresolvedDependencies.Count -eq 0) {
[void]$resolvedPackages.Add($packageName)
$madeProgress = $true
}
}
} while ($madeProgress)
if ($resolvedPackages.Count -ne $packageRecords.Count) {
$cycleDetails = $localDependencies.Keys |
Where-Object { -not $resolvedPackages.Contains($_) } |
Sort-Object |
ForEach-Object {
$packageName = $_
$blockedBy = @($localDependencies[$packageName] |
Where-Object { -not $resolvedPackages.Contains($_) } |
Sort-Object)
"$packageName -> $($blockedBy -join ', ')"
}
throw "Circular internal package dependencies detected:`n$($cycleDetails -join "`n")"
}
Write-Host "Package graph PASS packages=$($packageRecords.Count)"
if ($GraphOnly) {
return
}
if (Test-Path -LiteralPath $consumerRoot) {
@@ -133,6 +184,7 @@ $dependencies = [ordered]@{
'com.unity.test-framework' = '1.1.33'
'com.unity.textmeshpro' = '3.0.7'
'com.unity.ugui' = '1.0.0'
'com.unity.modules.uielements' = '1.0.0'
}
foreach ($record in $packageRecords | Sort-Object Name) {
$dependencies[$record.Name] = Convert-ToFileDependency $record.Directory