@@ -632,8 +632,31 @@ function Set-CodexMcpServer {
|
||||
}
|
||||
|
||||
$lines = @()
|
||||
$writeUtf8Bom = $false
|
||||
$newline = [Environment]::NewLine
|
||||
if (Test-Path -LiteralPath $path) {
|
||||
$rawLines = @(Get-Content -LiteralPath $path)
|
||||
$bytes = [System.IO.File]::ReadAllBytes($path)
|
||||
$offset = 0
|
||||
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
|
||||
$writeUtf8Bom = $true
|
||||
$offset = 3
|
||||
}
|
||||
|
||||
# TOML is UTF-8. Decode strictly so an unexpected legacy encoding stops
|
||||
# the update instead of being silently converted into mojibake.
|
||||
$strictUtf8 = [System.Text.UTF8Encoding]::new($false, $true)
|
||||
$text = $strictUtf8.GetString($bytes, $offset, $bytes.Length - $offset)
|
||||
$newlineMatch = [regex]::Match($text, "\r\n|\n|\r")
|
||||
if ($newlineMatch.Success) {
|
||||
$newline = $newlineMatch.Value
|
||||
}
|
||||
|
||||
$rawLines = if ([string]::IsNullOrEmpty($text)) {
|
||||
@()
|
||||
}
|
||||
else {
|
||||
@([regex]::Split($text, "\r\n|\n|\r"))
|
||||
}
|
||||
if ($rawLines.Count -eq 1 -and [string]::IsNullOrEmpty($rawLines[0])) {
|
||||
$lines = @()
|
||||
}
|
||||
@@ -668,7 +691,12 @@ function Set-CodexMcpServer {
|
||||
}
|
||||
}
|
||||
|
||||
Set-Content -LiteralPath $path -Value $lines -Encoding utf8
|
||||
$content = $lines -join $newline
|
||||
if ($lines.Count -gt 0) {
|
||||
$content += $newline
|
||||
}
|
||||
$utf8 = [System.Text.UTF8Encoding]::new($writeUtf8Bom)
|
||||
[System.IO.File]::WriteAllText($path, $content, $utf8)
|
||||
}
|
||||
|
||||
function Ensure-McpServer {
|
||||
|
||||
@@ -71,7 +71,8 @@ $scripts = @(
|
||||
'append-evidence.ps1',
|
||||
'case-guard.ps1',
|
||||
'test-routing.ps1',
|
||||
'extract-summaries.ps1'
|
||||
'extract-summaries.ps1',
|
||||
'test-bootstrap-codex-encoding.ps1'
|
||||
)
|
||||
$parseOk = 0
|
||||
$parseFail = 0
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
#Requires -Version 5.1
|
||||
param(
|
||||
[string]$ScratchDir = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$scriptDir = $PSScriptRoot
|
||||
if ([string]::IsNullOrWhiteSpace($ScratchDir)) {
|
||||
$ScratchDir = Join-Path ([System.IO.Path]::GetTempPath()) ('reverse-skill-codex-encoding-' + [guid]::NewGuid().ToString('N'))
|
||||
}
|
||||
New-Item -ItemType Directory -Path $ScratchDir -Force | Out-Null
|
||||
|
||||
. (Join-Path $scriptDir 'lib\ToolDiscovery.ps1')
|
||||
|
||||
# Load only the functions under test; do not execute bootstrap installation code.
|
||||
$bootstrapPath = Join-Path $scriptDir 'bootstrap-reverse.ps1'
|
||||
$tokens = $null
|
||||
$errors = $null
|
||||
$ast = [System.Management.Automation.Language.Parser]::ParseFile($bootstrapPath, [ref]$tokens, [ref]$errors)
|
||||
if ($errors.Count -gt 0) {
|
||||
throw "bootstrap-reverse.ps1 parse failed: $($errors[0].Message)"
|
||||
}
|
||||
foreach ($name in @('ConvertTo-TomlLiteral', 'Remove-CodexMcpServerBlocks', 'Set-CodexMcpServer')) {
|
||||
$functionAst = $ast.Find({
|
||||
param($node)
|
||||
$node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq $name
|
||||
}, $true)
|
||||
if ($null -eq $functionAst) {
|
||||
throw "Function not found: $name"
|
||||
}
|
||||
Invoke-Expression $functionAst.Extent.Text
|
||||
}
|
||||
|
||||
function Assert-True {
|
||||
param([bool]$Condition, [string]$Message)
|
||||
if (-not $Condition) {
|
||||
throw "ASSERTION FAILED: $Message"
|
||||
}
|
||||
Write-Host "[OK] $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Test-CodexConfigRoundTrip {
|
||||
param(
|
||||
[string]$Name,
|
||||
[bool]$WithBom,
|
||||
[string]$Newline
|
||||
)
|
||||
|
||||
$path = Join-Path $ScratchDir "$Name.toml"
|
||||
$original = @(
|
||||
"[projects.'e:\codex目录\blender脚本管理器']",
|
||||
'trust_level = "trusted"',
|
||||
'',
|
||||
'[mcp_servers.existing]',
|
||||
'command = "existing"'
|
||||
) -join $Newline
|
||||
$original += $Newline
|
||||
[System.IO.File]::WriteAllText($path, $original, [System.Text.UTF8Encoding]::new($WithBom))
|
||||
|
||||
$oldConfigPath = $env:CODEX_CONFIG_PATH
|
||||
try {
|
||||
$env:CODEX_CONFIG_PATH = $path
|
||||
Set-CodexMcpServer -ServerName 'encoding-test' -ServerDefinition @{
|
||||
type = 'stdio'
|
||||
command = 'cmd'
|
||||
args = @('/c', 'echo', '中文参数')
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$env:CODEX_CONFIG_PATH = $oldConfigPath
|
||||
}
|
||||
|
||||
$bytes = [System.IO.File]::ReadAllBytes($path)
|
||||
$hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF
|
||||
Assert-True ($hasBom -eq $WithBom) "$Name preserves UTF-8 BOM state"
|
||||
|
||||
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
||||
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) {
|
||||
$text = $text.Substring(1)
|
||||
}
|
||||
Assert-True ($text.Contains("[projects.'e:\codex目录\blender脚本管理器']")) "$Name preserves Chinese TOML path and closing quote"
|
||||
Assert-True ($text.Contains('[mcp_servers.encoding-test]')) "$Name adds MCP server"
|
||||
Assert-True ($text.Contains('中文参数')) "$Name preserves new Chinese values"
|
||||
if ($Newline -eq "`r`n") {
|
||||
Assert-True (-not ($text -replace "`r`n", '').Contains("`n")) "$Name preserves CRLF newlines"
|
||||
}
|
||||
}
|
||||
|
||||
Test-CodexConfigRoundTrip -Name 'utf8-no-bom-crlf' -WithBom $false -Newline "`r`n"
|
||||
Test-CodexConfigRoundTrip -Name 'utf8-bom-lf' -WithBom $true -Newline "`n"
|
||||
|
||||
$invalidPath = Join-Path $ScratchDir 'invalid-utf8.toml'
|
||||
[System.IO.File]::WriteAllBytes($invalidPath, [byte[]](0x5B, 0x80, 0x5D))
|
||||
$oldConfigPath = $env:CODEX_CONFIG_PATH
|
||||
$threw = $false
|
||||
try {
|
||||
$env:CODEX_CONFIG_PATH = $invalidPath
|
||||
Set-CodexMcpServer -ServerName 'must-not-write' -ServerDefinition @{ command = 'x' }
|
||||
}
|
||||
catch [System.Text.DecoderFallbackException] {
|
||||
$threw = $true
|
||||
}
|
||||
finally {
|
||||
$env:CODEX_CONFIG_PATH = $oldConfigPath
|
||||
}
|
||||
Assert-True $threw 'invalid UTF-8 fails closed instead of rewriting the file'
|
||||
$invalidBytes = [System.IO.File]::ReadAllBytes($invalidPath)
|
||||
Assert-True ($invalidBytes.Length -eq 3 -and $invalidBytes[1] -eq 0x80) 'invalid UTF-8 file remains unchanged'
|
||||
|
||||
Write-Host 'OVERALL: ALL PASS' -ForegroundColor Green
|
||||
|
||||
Reference in New Issue
Block a user