- 增加 UTF-8 输出编码设置 - 增加 Unity Assets/ 路径中文检查 - 增加未跟踪 Markdown 专项提示 - 修正 AssestBundle -> AssetBundle 拼写 - 细化 commit-module-mappings 中 entity/event/scene/network/skill/pool/ui/resource 映射 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
352 lines
12 KiB
PowerShell
352 lines
12 KiB
PowerShell
param(
|
|
[string]$UnityProjectRoot = '',
|
|
[string]$DocsRoot = ''
|
|
)
|
|
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
|
|
$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 Show-ChinesePathWarnings {
|
|
param([object[]]$Changes)
|
|
|
|
$chineseRegex = New-Object System.Text.RegularExpressions.Regex '\p{IsCJKUnifiedIdeographs}', ([System.Text.RegularExpressions.RegexOptions]::CultureInvariant)
|
|
$offenders = @($Changes | Where-Object {
|
|
$_.Path -like 'Assets/*' -and $chineseRegex.IsMatch($_.Path)
|
|
})
|
|
Write-Output ''
|
|
Write-Output '[Chinese path check]'
|
|
if ($offenders.Count -eq 0) {
|
|
Write-Output ' No Chinese characters found in Unity project paths.'
|
|
}
|
|
else {
|
|
foreach ($change in $offenders) {
|
|
Write-Warning ("Chinese characters in Unity path: {0}" -f $change.Path)
|
|
}
|
|
}
|
|
}
|
|
|
|
function Show-UntrackedMarkdownHint {
|
|
param([object[]]$Changes)
|
|
|
|
$untrackedMarkdown = @($Changes | Where-Object {
|
|
$_.Status -match '^\?\?$' -and $_.Path -like '*.md'
|
|
})
|
|
if ($untrackedMarkdown.Count -gt 0) {
|
|
Write-Output ''
|
|
Write-Output '[Untracked Markdown hint]'
|
|
Write-Output ' The following Markdown files are untracked. If they are long-term knowledge, remember to update modules/模块索引.md.'
|
|
foreach ($change in $untrackedMarkdown) {
|
|
Write-Output (" - {0}" -f $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 UnityAI 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
|
|
Show-ChinesePathWarnings ($docsChanges + $unityChanges)
|
|
Show-UntrackedMarkdownHint $docsChanges
|
|
|
|
$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
|