From 06485d216bc8bb351980b8931d25c9b466d7ea58 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 19 Jul 2026 20:15:34 +0300 Subject: [PATCH] =?UTF-8?q?fix(epf-dump):=20=D0=BF=D0=BE=D1=81=D1=82=D1=83?= =?UTF-8?q?=D1=81=D0=BB=D0=BE=D0=B2=D0=B8=D0=B5=20=D0=B2=D1=8B=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=B0=D1=82=D0=B0=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=B0=20+=20=D0=BC=D0=B0=D1=81=D0=BA=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B0=20=D1=83=D1=87=D1=91=D1=82=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Пропущенный при разборе #49-52 навык того же класса: epf-dump разбирает EPF/ERF через платформу в каталог XML. Успех определялся только по коду возврата (ложный успех при пустом выходе), а строка Running светила /P<пароль> (1cv8) и --password= (ibcmd). Добавлены postcondition непустого OutputDir и маскировка, обе ветки, оба порта. Покрывает и erf-dump (общий скрипт). Co-Authored-By: Claude Opus 4.8 --- .claude/skills/epf-dump/scripts/epf-dump.ps1 | 28 ++++++++++++-- .claude/skills/epf-dump/scripts/epf-dump.py | 40 +++++++++++++++++--- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/.claude/skills/epf-dump/scripts/epf-dump.ps1 b/.claude/skills/epf-dump/scripts/epf-dump.ps1 index eb79647a..8ff025a5 100644 --- a/.claude/skills/epf-dump/scripts/epf-dump.ps1 +++ b/.claude/skills/epf-dump/scripts/epf-dump.ps1 @@ -1,4 +1,4 @@ -# epf-dump v1.6 — Dump external data processor or report (EPF/ERF) to XML sources +# epf-dump v1.7 — Dump external data processor or report (EPF/ERF) to XML sources # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # NB: *nix-раскладку платформы (/opt/1cv8//1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется. <# @@ -155,6 +155,19 @@ function Invoke-IbcmdProcess { } +function Test-DirNonEmpty { + # Postcondition: the platform must have written files into the output directory. + # Exit code 0 with an empty dir (broken/headless env) is a false success — reject it. + param([string]$Path) + return (Test-Path $Path -PathType Container) -and ([bool](Get-ChildItem -LiteralPath $Path -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) +} + +function Format-SafeArgs { + # Mask credential tokens (/N, /P for 1cv8; --user=, --password= for ibcmd) for display. + param([string[]]$A) + ($A | ForEach-Object { $_ -replace '^(/[NP]).+', '$1***' -replace '^(--(user|password)=).+', '$1***' }) -join ' ' +} + $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } if ($engine -eq "ibcmd") { if (-not $InfoBasePath) { @@ -189,12 +202,16 @@ try { if ($UserName) { $arguments += "--user=$UserName" } if ($Password) { $arguments += "--password=$Password" } $arguments += "--data=$tempDir" - Write-Host "Running: ibcmd $($arguments -join ' ')" + Write-Host "Running: ibcmd $(Format-SafeArgs $arguments)" $__ib = Invoke-IbcmdProcess $V8Path $arguments $output = $__ib.Output $exitCode = $__ib.ExitCode + $outMissing = ($exitCode -eq 0) -and -not (Test-DirNonEmpty $OutputDir) + if ($outMissing) { $exitCode = 1 } if ($exitCode -eq 0) { Write-Host "External data processor/report dumped successfully to: $OutputDir" -ForegroundColor Green + } elseif ($outMissing) { + Write-Host "Error: exit code 0 but no files under $OutputDir — dump produced no output" -ForegroundColor Red } else { Write-Host "Error dumping external data processor/report (code: $exitCode)" -ForegroundColor Red } @@ -224,13 +241,18 @@ try { $arguments += "/DisableStartupDialogs" # --- Execute --- - Write-Host "Running: 1cv8.exe $($arguments -join ' ')" + Write-Host "Running: 1cv8.exe $(Format-SafeArgs $arguments)" $process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru $exitCode = $process.ExitCode # --- Result --- + # Postcondition: exit 0 with an empty output directory is a false success. + $outMissing = ($exitCode -eq 0) -and -not (Test-DirNonEmpty $OutputDir) + if ($outMissing) { $exitCode = 1 } if ($exitCode -eq 0) { Write-Host "Dump completed successfully to: $OutputDir" -ForegroundColor Green + } elseif ($outMissing) { + Write-Host "Error: exit code 0 but no files under $OutputDir — dump produced no output" -ForegroundColor Red } else { Write-Host "Error dumping (code: $exitCode)" -ForegroundColor Red } diff --git a/.claude/skills/epf-dump/scripts/epf-dump.py b/.claude/skills/epf-dump/scripts/epf-dump.py index 9c4e1c31..10b2e41f 100644 --- a/.claude/skills/epf-dump/scripts/epf-dump.py +++ b/.claude/skills/epf-dump/scripts/epf-dump.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# epf-dump v1.6 — Dump external data processor or report (EPF/ERF) to XML sources +# epf-dump v1.7 — Dump external data processor or report (EPF/ERF) to XML sources # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -99,6 +99,22 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True): return subprocess.run(cmd, input="", capture_output=True, encoding="utf-8", errors="replace") +def dir_nonempty(path): + """Postcondition: the platform must have written files into the output directory. + Exit code 0 with an empty dir (broken/headless env) is a false success — reject it.""" + return os.path.isdir(path) and any(os.scandir(path)) + + +def _mask(args): + """Mask credential tokens (/N, /P for 1cv8; --user=, --password= for ibcmd) for display.""" + out = [] + for a in args: + a = re.sub(r"^(/[NP]).+", r"\1***", a) + a = re.sub(r"^(--(?:user|password)=).+", r"\1***", a) + out.append(a) + return out + + def main(): sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") @@ -163,17 +179,23 @@ def main(): if args.Password: arguments.append(f"--password={args.Password}") arguments.append(f"--data={ib_data}") - print(f"Running: ibcmd {' '.join(arguments)}") + print(f"Running: ibcmd {' '.join(_mask(arguments))}") result = run_ibcmd([v8path] + arguments, warn_no_user=False) - if result.returncode == 0: + exit_code = result.returncode + out_missing = exit_code == 0 and not dir_nonempty(args.OutputDir) + if out_missing: + exit_code = 1 + if exit_code == 0: print(f"External data processor/report dumped successfully to: {args.OutputDir}") + elif out_missing: + print(f"Error: exit code 0 but no files under {args.OutputDir} — dump produced no output", file=sys.stderr) else: - print(f"Error dumping external data processor/report (code: {result.returncode})", file=sys.stderr) + print(f"Error dumping external data processor/report (code: {exit_code})", file=sys.stderr) if result.stdout: print(result.stdout) if result.stderr: print(result.stderr, file=sys.stderr) - sys.exit(result.returncode) + sys.exit(exit_code) # --- Build arguments --- arguments = ["DESIGNER"] @@ -197,7 +219,7 @@ def main(): arguments.append("/DisableStartupDialogs") # --- Execute --- - print(f"Running: 1cv8.exe {' '.join(arguments)}") + print(f"Running: 1cv8.exe {' '.join(_mask(arguments))}") result = subprocess.run( [v8path] + arguments, capture_output=True, @@ -206,8 +228,14 @@ def main(): exit_code = result.returncode # --- Result --- + # Postcondition: exit 0 with an empty output directory is a false success. + out_missing = exit_code == 0 and not dir_nonempty(args.OutputDir) + if out_missing: + exit_code = 1 if exit_code == 0: print(f"Dump completed successfully to: {args.OutputDir}") + elif out_missing: + print(f"Error: exit code 0 but no files under {args.OutputDir} — dump produced no output", file=sys.stderr) else: print(f"Error dumping (code: {exit_code})", file=sys.stderr)