59c1707393
检测任务向导 9 阶段模板中全部裸 HEX 颜色和越界字号替换为 Nova 令牌引用, 残留归零。这是 P3 逐页迁移的第一个文件,也是单文件改动量最大的一个。 颜色映射 - #FFFFFF(根背景/容器白底)→ NovaSurfaceContainerBrush - #F5F6F7(左侧步骤轨)/ #FAFAFA(卡片浅背景)→ NovaSurfaceContainerLowBrush - #E0E0E0(分隔线/边框/待定态灰)→ NovaOutlineVariantBrush - #1B1B1B(主要文字)→ NovaOnSurfaceBrush - #5A5A5A(次要文字)→ NovaOnSurfaceVariantBrush - #FFFFFF(白色前景)→ NovaOnPrimaryBrush - #107C10(完成态/运行态绿)→ NovaSuccessBrush - #D13438(失败红)→ NovaErrorBrush - #B0B0B0(禁用态按钮)→ NovaOnBackgroundDisabledBrush - #F0F5FF/#B0C4DE/#1B5E9A/#3A6A9A/#7A9ABA(信息提示卡片组)→ NovaInfoContainerBrush / NovaInfoBrush / NovaPrimaryBrush / NovaOnSurfaceVariantBrush 字号收敛 - FontSize="13" → NovaFontSizeBody (14):全部替换 - FontSize="14" → NovaFontSizeBody (14):统一引用令牌 - FontSize="16" → NovaFontSizeBodyStrong (16):标题 - FontSize="12" → NovaFontSizeCaption (12):说明文字 - FontSize="20" → NovaFontSizeSubtitle (18):判定结果大标题 - FontSize="11" → 保留现状(步骤数字,密集面板例外)
163 lines
6.7 KiB
PowerShell
163 lines
6.7 KiB
PowerShell
<#
|
||
.SYNOPSIS
|
||
NOVA UI 规范护栏:禁止新增裸 HEX 颜色值。
|
||
|
||
.DESCRIPTION
|
||
对应 .kiro/steering/ui-design.md 第 6 节「编码约束 · 禁止裸 HEX」。
|
||
颜色必须统一走 Themes/NovaColors.xaml 中的令牌(NovaXxxBrush /
|
||
NovaXxxColor),不允许在页面或代码里直接写十六进制色值。
|
||
|
||
仓库里历史遗留的裸 HEX 还有大量未迁移(P3 阶段尚在推进,详见
|
||
ui-design.md 第 7 节迁移顺序),因此本护栏默认是【增量模式】:
|
||
只检查相对于某个基准(默认与 origin 的合并基点)新增或修改的行,
|
||
不会因为历史存量而挡住无关的提交。要看全量现状用 -Mode Full。
|
||
|
||
范围限定为 XplorePlane 主项目(原生 WPF 面 + 代码侧),与
|
||
ui-design.md 第 4 节记录的范围边界一致:
|
||
- 排除 Themes/Nova*.xaml 本身(它是唯一允许出现裸 HEX 的地方)
|
||
- 排除 obj/bin 生成目录
|
||
- 不扫描跨模块项目(XP.Common、XP.Hardware.* 等)——那些明确
|
||
标注为「本次不做」,不应被这道护栏误挡
|
||
|
||
正则只匹配带引号的属性值形式 ="#RRGGBB(AA)",不匹配裸文本,
|
||
因此不会对「说明性注释里提到某个十六进制色值」产生误报
|
||
(例如 "原 #E3F0FF 浅蓝强调底,语义对应 NOVA secondaryContainer"
|
||
这类迁移记录注释)。
|
||
|
||
.PARAMETER Mode
|
||
Diff(默认):只检查增量(git diff 新增行)。
|
||
Full:扫描全部现存文件,用于生成现状报告,不建议作为提交门禁。
|
||
|
||
.PARAMETER Base
|
||
Diff 模式下的比较基准。默认自动探测 origin/主分支的合并基点;
|
||
在本地无网络/无远程的场景可显式传入一个 commit/分支名。
|
||
|
||
.PARAMETER Path
|
||
要扫描的项目根目录。默认是仓库中的 XplorePlane 主项目目录。
|
||
|
||
.EXAMPLE
|
||
pwsh tools/Check-NovaColorTokens.ps1
|
||
提交前跑:只检查本次改动是否新增了裸 HEX。
|
||
|
||
.EXAMPLE
|
||
pwsh tools/Check-NovaColorTokens.ps1 -Mode Full
|
||
生成全量现状报告(预期会有大量历史命中,仅供参考)。
|
||
|
||
.NOTES
|
||
退出码:0 = 未发现违规;1 = 发现违规。
|
||
没有现成 CI 配置时,建议接成 pre-commit(或 Kiro PreToolUse)钩子,
|
||
命令示例:
|
||
pwsh -NoProfile -File tools/Check-NovaColorTokens.ps1
|
||
一旦引入正式 CI 流水线,直接调用本脚本(Diff 模式)作为一个步骤即可。
|
||
#>
|
||
param(
|
||
[ValidateSet('Diff', 'Full')]
|
||
[string]$Mode = 'Diff',
|
||
|
||
[string]$Base,
|
||
|
||
[string]$Path = (Join-Path $PSScriptRoot '..\XplorePlane')
|
||
)
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||
$Path = (Resolve-Path $Path).Path
|
||
|
||
# 带引号的属性值形式:Background="#FFFFFF"、Value="#1A2B3C"、
|
||
# "#2E7D32"(C# 字符串字面量)等。不匹配裸文本,避免命中注释里的说明文字。
|
||
$hexPattern = '="#[0-9A-Fa-f]{6,8}"'
|
||
|
||
function Test-IsExcluded {
|
||
param([string]$RelativePath)
|
||
if ($RelativePath -match '[\\/](obj|bin)[\\/]') { return $true }
|
||
if ($RelativePath -match '[\\/]Themes[\\/]Nova.*\.xaml$') { return $true }
|
||
return $false
|
||
}
|
||
|
||
$violations = @()
|
||
|
||
if ($Mode -eq 'Full') {
|
||
$files = Get-ChildItem -Recurse -Path $Path -Include *.xaml, *.cs -File |
|
||
Where-Object { -not (Test-IsExcluded $_.FullName.Substring($RepoRoot.Length + 1)) }
|
||
|
||
foreach ($file in $files) {
|
||
$matches = Select-String -Path $file.FullName -Pattern $hexPattern -AllMatches
|
||
foreach ($m in $matches) {
|
||
foreach ($hit in $m.Matches) {
|
||
$violations += [pscustomobject]@{
|
||
File = $file.FullName.Substring($Path.Length + 1)
|
||
Line = $m.LineNumber
|
||
Snippet = $m.Line.Trim()
|
||
Hex = $hit.Value
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
# Diff 模式:只看 git diff 里新增的行(+ 开头,排除 +++ 文件头),
|
||
# 且限定在 XplorePlane 目录下的 .xaml/.cs 文件。
|
||
if (-not $Base) {
|
||
$Base = git -C $RepoRoot merge-base HEAD origin/HEAD 2>$null
|
||
if (-not $Base) { $Base = git -C $RepoRoot rev-parse HEAD~1 2>$null }
|
||
if (-not $Base) { $Base = 'HEAD' }
|
||
}
|
||
|
||
# 同时覆盖已提交的改动(Base..HEAD)与尚未提交的工作区改动,
|
||
# 这样在提交前本地跑一次就能拦住,不必依赖已经 commit。
|
||
$diffArgs = @('-C', $RepoRoot, 'diff', '--unified=0', "$Base", '--', 'XplorePlane/*.xaml', 'XplorePlane/*.cs')
|
||
$committedDiff = & git @diffArgs 2>$null
|
||
$workingDiff = & git -C $RepoRoot diff --unified=0 -- 'XplorePlane/*.xaml' 'XplorePlane/*.cs' 2>$null
|
||
$stagedDiff = & git -C $RepoRoot diff --unified=0 --cached -- 'XplorePlane/*.xaml' 'XplorePlane/*.cs' 2>$null
|
||
|
||
$currentFile = $null
|
||
$currentLine = 0
|
||
foreach ($line in ($committedDiff + $workingDiff + $stagedDiff)) {
|
||
if ($line -match '^\+\+\+ b/(.+)$') {
|
||
$currentFile = $matches[1]
|
||
continue
|
||
}
|
||
if ($line -match '^@@ -\d+(?:,\d+)? \+(\d+)') {
|
||
$currentLine = [int]$matches[1]
|
||
continue
|
||
}
|
||
if ($line -match '^\+(?!\+\+)(.*)$') {
|
||
$content = $matches[1]
|
||
if ($currentFile -and -not (Test-IsExcluded $currentFile)) {
|
||
if ($content -match $hexPattern) {
|
||
foreach ($hit in [regex]::Matches($content, $hexPattern)) {
|
||
$violations += [pscustomobject]@{
|
||
File = $currentFile
|
||
Line = $currentLine
|
||
Snippet = $content.Trim()
|
||
Hex = $hit.Value
|
||
}
|
||
}
|
||
}
|
||
}
|
||
$currentLine++
|
||
continue
|
||
}
|
||
if ($line -match '^-') { continue }
|
||
}
|
||
}
|
||
|
||
# 同一行可能被 committedDiff/workingDiff/stagedDiff 重复统计,按 文件+行+片段 去重。
|
||
$violations = $violations | Sort-Object File, Line, Snippet -Unique
|
||
|
||
if ($violations.Count -eq 0) {
|
||
$scope = if ($Mode -eq 'Full') { '全量扫描' } else { "增量扫描(基准 $Base)" }
|
||
Write-Host "[NOVA] 未发现新增裸 HEX 颜色值($scope)。" -ForegroundColor Green
|
||
exit 0
|
||
}
|
||
|
||
Write-Host "[NOVA] 发现 $($violations.Count) 处裸 HEX 颜色值,须改为引用 Themes/NovaColors.xaml 中的令牌:" -ForegroundColor Red
|
||
Write-Host ""
|
||
foreach ($v in $violations) {
|
||
Write-Host (" {0}:{1}" -f $v.File, $v.Line) -ForegroundColor Yellow
|
||
Write-Host (" {0}" -f $v.Snippet)
|
||
}
|
||
Write-Host ""
|
||
Write-Host "参见 .kiro/steering/ui-design.md 第 6 节「禁止裸 HEX」。" -ForegroundColor DarkYellow
|
||
exit 1
|