fix(py-порты): ошибки печатать в тот же поток, что и PS

Двадцать один py-порт печатал ошибки в stderr, тогда как их PS-мастера пишут
через Write-Host в stdout. Счётчики совпадали один в один (14↔14, 19↔19,
13↔13) — сообщения были те же, разъехался только поток. Это нарушало
соответствие из docs/python-porting-guide.md, где Write-Host сопоставлен
обычному print.

Это не косметика. Харнесс не чередует потоки, а группирует: сначала весь
stderr, потом весь stdout. Из-за этого в py-порте вердикт «Error dumping
configuration (code: 1)» печатался ПЕРЕД строками, которые его объясняют, а
причина из лога платформы оказывалась в самом низу — причинный порядок вывода
переворачивался. Порт, работающий на macOS, читался хуже того, что работает на
Windows.

Тесты этого не ловили по построению: текст ошибки сверяют только кейсы со
строковым expectError, а он смотрит в stderr — потому такие кейсы есть лишь у
семейства, где потоки сходятся, а в db-* их ноль.

Добавлен гард check-error-streams.mjs: нет записи в stderr в PS-порте — не
должно быть и в py, и симметрично. Он сразу нашёл пять навыков сверх тех, что
я насчитал вручную, и отсеял два ложных срабатывания (в meta-remove слово
Write-Error стоит в комментарии «почему НЕ Write-Error»).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-23 19:03:34 +03:00
co-authored by Claude Opus 5
parent fdeae5c87f
commit 4f61ef77ca
25 changed files with 279 additions and 253 deletions
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# web-publish v1.8 — Publish 1C infobase via Apache (+_version_dir/_version_key: общий эталон db-семейства)
# web-publish v1.9 — Publish 1C infobase via Apache (+_version_dir/_version_key: общий эталон db-семейства)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""
@@ -147,7 +147,7 @@ def main():
ver = os.path.basename(os.path.dirname(v8_path))
print(f'Auto-selected platform {ver}: {v8_path}')
else:
print('Error: платформа 1С не найдена. Укажите -V8Path', file=sys.stderr)
print('Error: платформа 1С не найдена. Укажите -V8Path')
sys.exit(1)
elif os.path.isfile(v8_path):
v8_path = os.path.dirname(v8_path)
@@ -155,12 +155,12 @@ def main():
# Validate wsap24.dll
wsap_dll = os.path.join(v8_path, 'wsap24.dll')
if not os.path.exists(wsap_dll):
print(f'Error: wsap24.dll не найден в {v8_path}', file=sys.stderr)
print(f'Error: wsap24.dll не найден в {v8_path}')
sys.exit(1)
# --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print('Error: укажите -InfoBasePath или -InfoBaseServer + -InfoBaseRef', file=sys.stderr)
print('Error: укажите -InfoBasePath или -InfoBaseServer + -InfoBaseRef')
sys.exit(1)
# --- Resolve ApachePath ---
@@ -213,7 +213,7 @@ def main():
zip_url = f'https://www.apachelounge.com{zip_url}'
print(f'Найдено: {zip_url}')
else:
print('Не удалось определить ссылку автоматически.', file=sys.stderr)
print('Не удалось определить ссылку автоматически.')
print(f'Скачайте вручную: {download_page}')
sys.exit(1)
@@ -221,7 +221,7 @@ def main():
except SystemExit:
raise
except Exception as e:
print(f'Error: не удалось скачать Apache: {e}', file=sys.stderr)
print(f'Error: не удалось скачать Apache: {e}')
print('Скачайте вручную: https://www.apachelounge.com/download/')
sys.exit(1)
@@ -244,7 +244,7 @@ def main():
if found_inner:
inner_dir = found_inner
else:
print('Error: каталог Apache24 не найден в архиве', file=sys.stderr)
print('Error: каталог Apache24 не найден в архиве')
sys.exit(1)
os.makedirs(apache_path, exist_ok=True)
@@ -297,7 +297,7 @@ def main():
app_name = app_name.lower()
if not app_name:
print('Error: не удалось определить имя публикации. Укажите -AppName', file=sys.stderr)
print('Error: не удалось определить имя публикации. Укажите -AppName')
sys.exit(1)
print(f'Публикация: {app_name}')
@@ -340,7 +340,7 @@ def main():
# --- Update httpd.conf ---
conf_file = os.path.join(apache_path, 'conf', 'httpd.conf')
if not os.path.exists(conf_file):
print(f'Error: httpd.conf не найден: {conf_file}', file=sys.stderr)
print(f'Error: httpd.conf не найден: {conf_file}')
sys.exit(1)
with open(conf_file, 'r', encoding='utf-8-sig') as f:
@@ -415,7 +415,7 @@ def main():
holder_name = f'{holder_proc.name()} (PID: {holder_pid})'
except (psutil.NoSuchProcess, psutil.AccessDenied):
holder_name = f'PID {holder_pid}'
print(f'Error: порт {port} занят процессом {holder_name}', file=sys.stderr)
print(f'Error: порт {port} занят процессом {holder_name}')
print('Укажите другой порт: -Port 9090')
sys.exit(1)
@@ -451,7 +451,7 @@ def main():
if httpd_check:
print(f'Apache запущен (PID: {httpd_check[0].pid})')
else:
print('Apache не удалось запустить', file=sys.stderr)
print('Apache не удалось запустить')
# Run config test for diagnostics
try:
result = subprocess.run(