【整合】 框架使用规则
This commit is contained in:
311
hooks/workflow-check.ps1
Normal file
311
hooks/workflow-check.ps1
Normal file
@@ -0,0 +1,311 @@
|
||||
param(
|
||||
[string]$UnityProjectRoot = '',
|
||||
[string]$DocsRoot = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Get-FullPath {
|
||||
param([string]$Path, [string]$BasePath)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Path)) {
|
||||
return $null
|
||||
}
|
||||
if (-not [System.IO.Path]::IsPathRooted($Path)) {
|
||||
$Path = Join-Path $BasePath $Path
|
||||
}
|
||||
return [System.IO.Path]::GetFullPath($Path)
|
||||
}
|
||||
|
||||
function Test-UnityProject {
|
||||
param([string]$Path)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Path)) {
|
||||
return $false
|
||||
}
|
||||
return (Test-Path -LiteralPath (Join-Path $Path 'Assets')) -and
|
||||
(Test-Path -LiteralPath (Join-Path $Path 'ProjectSettings/ProjectVersion.txt'))
|
||||
}
|
||||
|
||||
function Resolve-UnityProject {
|
||||
param([string]$ExplicitPath, [string]$DocumentationRoot)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($ExplicitPath)) {
|
||||
$resolved = Get-FullPath $ExplicitPath (Get-Location).Path
|
||||
if (-not (Test-UnityProject $resolved)) {
|
||||
throw "Unity project was not found: $resolved"
|
||||
}
|
||||
return $resolved
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($env:UNITY_PROJECT_ROOT)) {
|
||||
$resolved = Get-FullPath $env:UNITY_PROJECT_ROOT (Get-Location).Path
|
||||
if (-not (Test-UnityProject $resolved)) {
|
||||
throw "UNITY_PROJECT_ROOT is not a Unity project: $resolved"
|
||||
}
|
||||
return $resolved
|
||||
}
|
||||
|
||||
$current = [System.IO.Path]::GetFullPath((Get-Location).Path)
|
||||
if (Test-UnityProject $current) {
|
||||
return $current
|
||||
}
|
||||
|
||||
$parent = Split-Path -Parent $DocumentationRoot
|
||||
$candidates = @()
|
||||
if (Test-Path -LiteralPath $parent) {
|
||||
$directories = Get-ChildItem -LiteralPath $parent -Directory -ErrorAction SilentlyContinue
|
||||
foreach ($directory in $directories) {
|
||||
if (Test-UnityProject $directory.FullName) {
|
||||
$candidates += $directory.FullName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($candidates.Count -eq 1) {
|
||||
return [System.IO.Path]::GetFullPath($candidates[0])
|
||||
}
|
||||
if ($candidates.Count -gt 1) {
|
||||
Write-Warning 'Multiple sibling Unity projects were found. Use -UnityProjectRoot to select one.'
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-GitRoot {
|
||||
param([string]$Path)
|
||||
|
||||
$result = & git -C $Path rev-parse --show-toplevel 2>$null
|
||||
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($result)) {
|
||||
return $null
|
||||
}
|
||||
return [System.IO.Path]::GetFullPath(($result | Select-Object -First 1))
|
||||
}
|
||||
|
||||
function Get-GitChanges {
|
||||
param([string]$RepositoryRoot, [string]$RepositoryName)
|
||||
|
||||
$result = @()
|
||||
if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
|
||||
return $result
|
||||
}
|
||||
|
||||
$lines = & git -C $RepositoryRoot -c core.quotepath=false status --porcelain=v1 --untracked-files=all
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Could not read git status: $RepositoryRoot"
|
||||
}
|
||||
|
||||
foreach ($line in $lines) {
|
||||
if ([string]::IsNullOrWhiteSpace($line) -or $line.Length -lt 4) {
|
||||
continue
|
||||
}
|
||||
$path = $line.Substring(3).Trim()
|
||||
if ($path.Contains(' -> ')) {
|
||||
$path = ($path -split ' -> ')[-1]
|
||||
}
|
||||
$result += [pscustomobject]@{
|
||||
Repository = $RepositoryName
|
||||
Root = $RepositoryRoot
|
||||
Status = $line.Substring(0, 2)
|
||||
Path = $path.Replace('\', '/')
|
||||
}
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Show-Changes {
|
||||
param([string]$Title, [object[]]$Changes)
|
||||
|
||||
Write-Output ""
|
||||
Write-Output "[$Title]"
|
||||
if ($Changes.Count -eq 0) {
|
||||
Write-Output ' Clean'
|
||||
return
|
||||
}
|
||||
foreach ($change in $Changes) {
|
||||
Write-Output (" {0} {1}" -f $change.Status, $change.Path)
|
||||
}
|
||||
}
|
||||
|
||||
function Get-MarkdownLinkFailures {
|
||||
param([string]$DocumentationRoot, [object[]]$Changes)
|
||||
|
||||
$failures = @()
|
||||
$markdownChanges = $Changes | Where-Object { $_.Path -like '*.md' -and $_.Status -notmatch 'D' }
|
||||
foreach ($change in $markdownChanges) {
|
||||
$fullPath = Join-Path $DocumentationRoot $change.Path
|
||||
if (-not (Test-Path -LiteralPath $fullPath)) {
|
||||
continue
|
||||
}
|
||||
$content = [System.IO.File]::ReadAllText($fullPath, [System.Text.Encoding]::UTF8)
|
||||
$matches = [regex]::Matches($content, '!?(?:\[[^\]]*\])\((?<path>[^\)]+)\)')
|
||||
foreach ($match in $matches) {
|
||||
$link = $match.Groups['path'].Value.Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($link) -or $link.StartsWith('#') -or
|
||||
$link -match '^[a-zA-Z][a-zA-Z0-9+.-]*://' -or $link -match '^mailto:') {
|
||||
continue
|
||||
}
|
||||
if ($link.StartsWith('<') -and $link.EndsWith('>')) {
|
||||
$link = $link.Substring(1, $link.Length - 2)
|
||||
}
|
||||
$pathOnly = ($link -split '#')[0]
|
||||
if ([string]::IsNullOrWhiteSpace($pathOnly)) {
|
||||
continue
|
||||
}
|
||||
$decoded = [System.Uri]::UnescapeDataString($pathOnly)
|
||||
$target = Get-FullPath $decoded (Split-Path -Parent $fullPath)
|
||||
if (-not (Test-Path -LiteralPath $target)) {
|
||||
$failures += [pscustomobject]@{
|
||||
Source = $change.Path
|
||||
Link = $link
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $failures
|
||||
}
|
||||
|
||||
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
if ([string]::IsNullOrWhiteSpace($DocsRoot)) {
|
||||
$DocsRoot = Split-Path -Parent $scriptRoot
|
||||
}
|
||||
$DocsRoot = Get-FullPath $DocsRoot (Get-Location).Path
|
||||
$docsGitRoot = Get-GitRoot $DocsRoot
|
||||
if ([string]::IsNullOrWhiteSpace($docsGitRoot)) {
|
||||
throw "Documentation git repository was not found: $DocsRoot"
|
||||
}
|
||||
$DocsRoot = $docsGitRoot
|
||||
|
||||
$resolvedUnityRoot = Resolve-UnityProject $UnityProjectRoot $DocsRoot
|
||||
$unityGitRoot = $null
|
||||
if (-not [string]::IsNullOrWhiteSpace($resolvedUnityRoot)) {
|
||||
$unityGitRoot = Get-GitRoot $resolvedUnityRoot
|
||||
if ([string]::IsNullOrWhiteSpace($unityGitRoot)) {
|
||||
throw "Unity project is not a git repository: $resolvedUnityRoot"
|
||||
}
|
||||
}
|
||||
|
||||
$docsChanges = @(Get-GitChanges $DocsRoot 'UnityAI')
|
||||
$unityChanges = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($unityGitRoot)) {
|
||||
$unityChanges = @(Get-GitChanges $unityGitRoot (Split-Path -Leaf $resolvedUnityRoot))
|
||||
}
|
||||
|
||||
Write-Output '[workflow-check] Read-only StrayFog workflow check'
|
||||
Write-Output ("Documentation repository: {0}" -f $DocsRoot)
|
||||
if ($resolvedUnityRoot) {
|
||||
Write-Output ("Unity project: {0}" -f $resolvedUnityRoot)
|
||||
$versionFile = Join-Path $resolvedUnityRoot 'ProjectSettings/ProjectVersion.txt'
|
||||
$versionLine = Get-Content -LiteralPath $versionFile -Encoding UTF8 |
|
||||
Where-Object { $_ -match '^m_EditorVersion:' } | Select-Object -First 1
|
||||
if ($versionLine) {
|
||||
Write-Output ("Unity version: {0}" -f (($versionLine -split ':', 2)[1].Trim()))
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Warning 'Unity project was not found. Running documentation-only checks.'
|
||||
}
|
||||
|
||||
Show-Changes 'UnityAI changes' $docsChanges
|
||||
Show-Changes 'Unity project changes' $unityChanges
|
||||
|
||||
$unityPaths = @($unityChanges | ForEach-Object { $_.Path })
|
||||
$copyRelationships = @(
|
||||
[pscustomobject]@{
|
||||
Name = 'CopyToHF'
|
||||
SourcePattern = '^Assets/StrayFog/Editor/CopyToX/CopyToHF/'
|
||||
TargetPattern = '^Assets/Game/GameHFScripte/CopyToHF/'
|
||||
GeneratedName = 'HF'
|
||||
},
|
||||
[pscustomobject]@{
|
||||
Name = 'CopyToGeneral'
|
||||
SourcePattern = '^Assets/StrayFog/Editor/CopyToX/CopyToGeneral/'
|
||||
TargetPattern = '^Assets/Game/GameGeneralScripte/CopyToGeneral/'
|
||||
GeneratedName = 'General'
|
||||
}
|
||||
)
|
||||
|
||||
Write-Output ''
|
||||
Write-Output '[Source and generation boundary]'
|
||||
$relationshipDetected = $false
|
||||
foreach ($relationship in $copyRelationships) {
|
||||
$hasSource = @($unityPaths | Where-Object {
|
||||
$_ -match $relationship.SourcePattern
|
||||
}).Count -gt 0
|
||||
$hasTarget = @($unityPaths | Where-Object {
|
||||
$_ -match $relationship.TargetPattern
|
||||
}).Count -gt 0
|
||||
|
||||
if (-not $hasSource -and -not $hasTarget) {
|
||||
continue
|
||||
}
|
||||
$relationshipDetected = $true
|
||||
if ($hasSource -and -not $hasTarget) {
|
||||
Write-Warning ("{0} source changed but generated target did not change. Run CopyToX and inspect the generated diff." -f
|
||||
$relationship.Name)
|
||||
}
|
||||
elseif ($hasTarget -and -not $hasSource) {
|
||||
Write-Warning ("Generated {0} files changed without {1} source changes. Confirm that generated files were not edited directly." -f
|
||||
$relationship.GeneratedName, $relationship.Name)
|
||||
}
|
||||
else {
|
||||
Write-Output (" Source and generated target changes were both detected for {0}." -f
|
||||
$relationship.Name)
|
||||
}
|
||||
}
|
||||
if (-not $relationshipDetected) {
|
||||
Write-Output ' No CopyToX source/target relationship was detected.'
|
||||
}
|
||||
|
||||
$mappingPath = Join-Path $scriptRoot 'workflow-path-mappings.json'
|
||||
$mapping = Get-Content -LiteralPath $mappingPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
$documents = @{}
|
||||
$verifications = @{}
|
||||
foreach ($path in $unityPaths) {
|
||||
foreach ($entry in $mapping.mappings) {
|
||||
if ($path -match $entry.pattern) {
|
||||
$documents[$entry.document] = $true
|
||||
$verifications[$entry.verification] = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output ''
|
||||
Write-Output '[Suggested verification]'
|
||||
if ($verifications.Count -eq 0) {
|
||||
Write-Output ' No Unity verification suggestions for the current diff.'
|
||||
}
|
||||
else {
|
||||
foreach ($item in $verifications.Keys | Sort-Object) {
|
||||
Write-Output (" - {0}" -f $item)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output ''
|
||||
Write-Output '[Documentation impact]'
|
||||
if ($documents.Count -eq 0) {
|
||||
Write-Output ' No module document mapping matched the Unity diff.'
|
||||
}
|
||||
else {
|
||||
foreach ($document in $documents.Keys | Sort-Object) {
|
||||
$changed = @($docsChanges | Where-Object { $_.Path -eq $document }).Count -gt 0
|
||||
$marker = if ($changed) { 'updated' } else { 'review if long-term behavior changed' }
|
||||
Write-Output (" - {0} ({1})" -f $document, $marker)
|
||||
}
|
||||
}
|
||||
|
||||
$linkFailures = @(Get-MarkdownLinkFailures $DocsRoot $docsChanges)
|
||||
Write-Output ''
|
||||
Write-Output '[Markdown links]'
|
||||
if ($linkFailures.Count -eq 0) {
|
||||
Write-Output ' Changed Markdown links are valid.'
|
||||
}
|
||||
else {
|
||||
foreach ($failure in $linkFailures) {
|
||||
Write-Error ("Broken link in {0}: {1}" -f $failure.Source, $failure.Link)
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Output ''
|
||||
Write-Output '[workflow-check] Completed without writing files.'
|
||||
exit 0
|
||||
Reference in New Issue
Block a user