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
+5 -5
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cf-info v1.6 — Compact summary of 1C configuration root
# cf-info v1.7 — Compact summary of 1C configuration root
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -61,11 +61,11 @@ if os.path.isdir(config_path):
if os.path.isfile(candidate):
config_path = candidate
else:
print(f"[ERROR] No Configuration.xml found in directory: {config_path}", file=sys.stderr)
print(f"[ERROR] No Configuration.xml found in directory: {config_path}")
sys.exit(1)
if not os.path.isfile(config_path):
print(f"[ERROR] File not found: {config_path}", file=sys.stderr)
print(f"[ERROR] File not found: {config_path}")
sys.exit(1)
# --- Load XML ---
@@ -82,12 +82,12 @@ NS = {
md_root = xml_root # root is MetaDataObject itself
if etree.QName(md_root.tag).localname != "MetaDataObject":
print("[ERROR] Not a valid 1C metadata XML file (no MetaDataObject root)", file=sys.stderr)
print("[ERROR] Not a valid 1C metadata XML file (no MetaDataObject root)")
sys.exit(1)
cfg_node = md_root.find("md:Configuration", NS)
if cfg_node is None:
print("[ERROR] No <Configuration> element found", file=sys.stderr)
print("[ERROR] No <Configuration> element found")
sys.exit(1)
version = md_root.get("version", "")
+10 -16
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-create v1.13 — Create 1C information base
# db-create v1.14 — Create 1C information base
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -120,7 +120,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -128,7 +127,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -196,14 +194,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -245,14 +241,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -292,7 +288,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -313,7 +309,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -421,15 +417,15 @@ def main():
# --- Validate connection ---
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Validate template ---
if args.UseTemplate and not os.path.exists(args.UseTemplate):
print(f"Error: template file not found: {args.UseTemplate}", file=sys.stderr)
print(f"Error: template file not found: {args.UseTemplate}")
sys.exit(1)
# --- ibcmd branch (file infobase only) ---
@@ -456,10 +452,9 @@ def main():
print(
f"Error: exit code 0 but 1Cv8.1CD is missing or empty at {args.InfoBasePath} "
"— information base was not created",
file=sys.stderr,
)
else:
print(f"Error creating information base (code: {exit_code})", file=sys.stderr)
print(f"Error creating information base (code: {exit_code})")
print_platform_output(result)
sys.exit(exit_code)
@@ -516,10 +511,9 @@ def main():
print(
f"Error: exit code 0 but 1Cv8.1CD is missing or empty at {args.InfoBasePath} "
"— information base was not created",
file=sys.stderr,
)
else:
print(f"Error creating information base (code: {exit_code})", file=sys.stderr)
print(f"Error creating information base (code: {exit_code})")
if os.path.isfile(out_file):
try:
+13 -17
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-dump-cf v1.15 — Dump 1C configuration to CF file
# db-dump-cf v1.16 — Dump 1C configuration to CF file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -120,7 +120,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -128,7 +127,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -196,14 +194,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -245,14 +241,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -283,7 +279,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -300,7 +296,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -372,7 +368,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -442,10 +438,10 @@ def main():
# --- Validate connection ---
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Ensure output directory exists ---
@@ -456,7 +452,7 @@ def main():
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
if args.AllExtensions:
print("Error: ibcmd config save does not support -AllExtensions (use -Extension)", file=sys.stderr)
print("Error: ibcmd config save does not support -AllExtensions (use -Extension)")
sys.exit(1)
arguments = ["infobase", "config", "save", f"--db-path={args.InfoBasePath}"]
if args.Extension:
@@ -479,9 +475,9 @@ def main():
if exit_code == 0:
print(f"Configuration dumped successfully to: {args.OutputFile}")
elif out_missing:
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — configuration was not dumped", file=sys.stderr)
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — configuration was not dumped")
else:
print(f"Error dumping configuration (code: {exit_code})", file=sys.stderr)
print(f"Error dumping configuration (code: {exit_code})")
sys.exit(exit_code)
# --- Temp dir ---
@@ -529,9 +525,9 @@ def main():
if exit_code == 0:
print(f"Configuration dumped successfully to: {args.OutputFile}")
elif out_missing:
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — configuration was not dumped", file=sys.stderr)
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — configuration was not dumped")
else:
print(f"Error dumping configuration (code: {exit_code})", file=sys.stderr)
print(f"Error dumping configuration (code: {exit_code})")
if os.path.isfile(out_file):
try:
+12 -16
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-dump-dt v1.14 — Dump 1C information base to DT file
# db-dump-dt v1.15 — Dump 1C information base to DT file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -120,7 +120,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -128,7 +127,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -196,14 +194,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -245,14 +241,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -283,7 +279,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -300,7 +296,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -372,7 +368,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -440,10 +436,10 @@ def main():
# --- Validate connection ---
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Ensure output directory exists ---
@@ -472,9 +468,9 @@ def main():
if exit_code == 0:
print(f"Information base dumped successfully to: {args.OutputFile}")
elif out_missing:
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — information base was not dumped", file=sys.stderr)
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — information base was not dumped")
else:
print(f"Error dumping information base (code: {exit_code})", file=sys.stderr)
print(f"Error dumping information base (code: {exit_code})")
sys.exit(exit_code)
# --- Temp dir ---
@@ -516,9 +512,9 @@ def main():
if exit_code == 0:
print(f"Information base dumped successfully to: {args.OutputFile}")
elif out_missing:
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — information base was not dumped", file=sys.stderr)
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — information base was not dumped")
else:
print(f"Error dumping information base (code: {exit_code})", file=sys.stderr)
print(f"Error dumping information base (code: {exit_code})")
if os.path.isfile(out_file):
try:
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-dump-xml v1.20 — Dump 1C configuration to XML files
# db-dump-xml v1.21 — Dump 1C configuration to XML files
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -205,7 +205,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -213,7 +212,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -281,14 +279,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -330,14 +326,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -368,7 +364,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -385,7 +381,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -457,7 +453,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -545,10 +541,10 @@ def main():
# --- Validate connection ---
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Validate Partial mode ---
@@ -556,7 +552,7 @@ def main():
# то, что уже напечатал другой навык (например /db-repo update со списком полученных объектов).
if args.ObjectsFile:
if not os.path.exists(args.ObjectsFile):
print("Error: -ObjectsFile not found: %s" % args.ObjectsFile, file=sys.stderr)
print("Error: -ObjectsFile not found: %s" % args.ObjectsFile)
sys.exit(1)
with open(args.ObjectsFile, encoding="utf-8-sig") as f:
from_file = [s.strip() for s in f.read().splitlines()
@@ -569,7 +565,7 @@ def main():
if args.Mode == "UpdateInfo":
# Не «шире/уже», а другая операция: обновление ConfigDumpInfo без выгрузки файлов.
print("Error: -Mode UpdateInfo does not take an object list — it only refreshes "
"ConfigDumpInfo.xml", file=sys.stderr)
"ConfigDumpInfo.xml")
sys.exit(1)
if args.Mode in ("Full", "Changes"):
print("[note] перечислены объекты — выгружаются только они; -Mode %s не применён"
@@ -578,7 +574,7 @@ def main():
if not args.Mode:
args.Mode = "Changes"
if args.Mode == "Partial" and not args.Objects:
print("Error: -Objects or -ObjectsFile required for Partial mode", file=sys.stderr)
print("Error: -Objects or -ObjectsFile required for Partial mode")
sys.exit(1)
# --- Create output dir if needed ---
@@ -589,12 +585,12 @@ def main():
# --- ibcmd branch (file infobase only; hierarchical Full/Changes) ---
if engine == "ibcmd":
if args.Format == "Plain":
print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)")
sys.exit(1)
if args.AllExtensions:
arguments = ["infobase", "config", "export", "all-extensions", args.ConfigDir, f"--db-path={args.InfoBasePath}"]
elif args.Mode == "UpdateInfo":
print("Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8", file=sys.stderr)
print("Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8")
sys.exit(1)
elif args.Mode == "Partial":
obj_list = [o.strip() for o in args.Objects.split(",") if o.strip()]
@@ -624,9 +620,9 @@ def main():
if exit_code == 0:
print(f"Configuration exported successfully to: {args.ConfigDir}")
elif out_missing:
print(f"Error: exit code 0 but no files under {args.ConfigDir} — configuration was not exported", file=sys.stderr)
print(f"Error: exit code 0 but no files under {args.ConfigDir} — configuration was not exported")
else:
print(f"Error exporting configuration (code: {exit_code})", file=sys.stderr)
print(f"Error exporting configuration (code: {exit_code})")
sys.exit(exit_code)
# --- Temp dir ---
@@ -703,9 +699,9 @@ def main():
print("Dump completed successfully")
print(f"Configuration dumped to: {args.ConfigDir}")
elif out_missing:
print(f"Error: exit code 0 but no files under {args.ConfigDir} — configuration was not dumped", file=sys.stderr)
print(f"Error: exit code 0 but no files under {args.ConfigDir} — configuration was not dumped")
else:
print(f"Error dumping configuration (code: {exit_code})", file=sys.stderr)
print(f"Error dumping configuration (code: {exit_code})")
if os.path.isfile(out_file):
try:
+12 -16
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-load-cf v1.16 — Load 1C configuration from CF file
# db-load-cf v1.17 — Load 1C configuration from CF file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -120,7 +120,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -128,7 +127,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -196,14 +194,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -245,14 +241,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -283,7 +279,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -300,7 +296,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -372,7 +368,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -460,21 +456,21 @@ def main():
# --- Validate connection ---
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Validate input file ---
if not os.path.isfile(args.InputFile):
print(f"Error: input file not found: {args.InputFile}", file=sys.stderr)
print(f"Error: input file not found: {args.InputFile}")
sys.exit(1)
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
if args.AllExtensions:
print("Error: ibcmd config load does not support -AllExtensions (use -Extension)", file=sys.stderr)
print("Error: ibcmd config load does not support -AllExtensions (use -Extension)")
sys.exit(1)
arguments = ["infobase", "config", "load", f"--db-path={args.InfoBasePath}"]
if args.Extension:
@@ -493,7 +489,7 @@ def main():
if result.returncode == 0:
print(f"Configuration loaded successfully from: {args.InputFile}")
else:
print(f"Error loading configuration (code: {result.returncode}){describe_exit(result.returncode)}", file=sys.stderr)
print(f"Error loading configuration (code: {result.returncode}){describe_exit(result.returncode)}")
sys.exit(result.returncode)
# --- Temp dir ---
@@ -537,7 +533,7 @@ def main():
if exit_code == 0:
print(f"Configuration loaded successfully from: {args.InputFile}")
else:
print(f"Error loading configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
print(f"Error loading configuration (code: {exit_code}){describe_exit(exit_code)}")
if os.path.isfile(out_file):
try:
+11 -15
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-load-dt v1.15 — Load 1C information base from DT file
# db-load-dt v1.16 — Load 1C information base from DT file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -120,7 +120,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -128,7 +127,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -196,14 +194,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -245,14 +241,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -283,7 +279,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -300,7 +296,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -372,7 +368,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -460,15 +456,15 @@ def main():
# --- Validate connection ---
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Validate input file ---
if not os.path.isfile(args.InputFile):
print(f"Error: input file not found: {args.InputFile}", file=sys.stderr)
print(f"Error: input file not found: {args.InputFile}")
sys.exit(1)
# --- ibcmd branch (file infobase only) ---
@@ -490,7 +486,7 @@ def main():
if result.returncode == 0:
print(f"Information base restored successfully from: {args.InputFile}")
else:
print(f"Error restoring information base (code: {result.returncode}){describe_exit(result.returncode)}", file=sys.stderr)
print(f"Error restoring information base (code: {result.returncode}){describe_exit(result.returncode)}")
sys.exit(result.returncode)
# --- Temp dir ---
@@ -532,7 +528,7 @@ def main():
if exit_code == 0:
print(f"Information base restored successfully from: {args.InputFile}")
else:
print(f"Error restoring information base (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
print(f"Error restoring information base (code: {exit_code}){describe_exit(exit_code)}")
if os.path.isfile(out_file):
try:
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-load-git v1.25 — Load Git changes into 1C database
# db-load-git v1.26 — Load Git changes into 1C database
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -226,7 +226,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -234,7 +233,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -302,14 +300,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -351,14 +347,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -389,7 +385,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -406,7 +402,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -510,7 +506,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -635,10 +631,10 @@ def main():
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Resolve additional arguments for the selected engine ---
@@ -655,19 +651,19 @@ def main():
# --- Validate config dir ---
if not os.path.exists(args.ConfigDir):
print(f"Error: config directory not found: {args.ConfigDir}", file=sys.stderr)
print(f"Error: config directory not found: {args.ConfigDir}")
sys.exit(1)
# --- Validate Commit mode ---
if args.Source == "Commit" and not args.CommitRange:
print("Error: -CommitRange required for Source=Commit", file=sys.stderr)
print("Error: -CommitRange required for Source=Commit")
sys.exit(1)
# --- Check git ---
try:
subprocess.run(["git", "--version"], capture_output=True, text=True, check=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print("Error: git not found in PATH", file=sys.stderr)
print("Error: git not found in PATH")
sys.exit(1)
# --- Get changed files from Git ---
@@ -746,10 +742,10 @@ def main():
config_files.append(rel_path)
if support_skipped:
print("[ВНИМАНИЕ] Состояние поддержки изменено в коммите, но частично не загружается (исключено):", file=sys.stderr)
print("[ВНИМАНИЕ] Состояние поддержки изменено в коммите, но частично не загружается (исключено):")
for sf in support_skipped:
print(f" - {sf}", file=sys.stderr)
print(" Смена состояния поддержки применяется только полной загрузкой (db-load-xml -Mode Full).", file=sys.stderr)
print(f" - {sf}")
print(" Смена состояния поддержки применяется только полной загрузкой (db-load-xml -Mode Full).")
if len(config_files) == 0:
print("No configuration files found in changes")
@@ -773,10 +769,10 @@ def main():
if engine == "ibcmd":
# --- ibcmd branch (file infobase only; import specific files) ---
if args.Format == "Plain":
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)")
sys.exit(1)
if args.AllExtensions:
print("Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)", file=sys.stderr)
print("Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)")
sys.exit(1)
arguments = ["infobase", "config", "import", "files"] + config_files
arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
@@ -793,7 +789,7 @@ def main():
print(f"Running: ibcmd {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName)}")
result = run_ibcmd([v8path] + arguments, bool(args.UserName))
if result.returncode != 0:
print(f"Error loading changes (code: {result.returncode}){describe_exit(result.returncode)}", file=sys.stderr)
print(f"Error loading changes (code: {result.returncode}){describe_exit(result.returncode)}")
sys.exit(result.returncode)
print(f"Changes loaded successfully ({len(config_files)} files)")
exit_code = 0
@@ -811,7 +807,7 @@ def main():
if exit_code == 0:
print("Database configuration updated successfully")
else:
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}")
print_platform_output(ar)
sys.exit(exit_code)
@@ -873,7 +869,7 @@ def main():
if exit_code == 0:
print("Load completed successfully")
else:
print(f"Error loading configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
print(f"Error loading configuration (code: {exit_code}){describe_exit(exit_code)}")
log_content = ""
if os.path.isfile(out_file):
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-load-xml v1.27 — Load 1C configuration from XML files
# db-load-xml v1.28 — Load 1C configuration from XML files
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -226,7 +226,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -234,7 +233,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -302,14 +300,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -351,14 +347,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -389,7 +385,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -406,7 +402,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -510,7 +506,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -624,15 +620,15 @@ def main():
# --- Validate connection ---
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Validate config dir ---
if not os.path.exists(args.ConfigDir):
print(f"Error: config directory not found: {args.ConfigDir}", file=sys.stderr)
print(f"Error: config directory not found: {args.ConfigDir}")
sys.exit(1)
# --- Validate Partial mode ---
@@ -645,13 +641,13 @@ def main():
if not args.Mode:
args.Mode = "Full"
if args.Mode == "Partial" and not args.Files and not args.ListFile:
print("Error: -Files or -ListFile required for Partial mode", file=sys.stderr)
print("Error: -Files or -ListFile required for Partial mode")
sys.exit(1)
# --- ibcmd branch (file infobase only; hierarchical full-directory import) ---
if engine == "ibcmd":
if args.Format == "Plain":
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)")
sys.exit(1)
if args.AllExtensions:
arguments = ["infobase", "config", "import", "all-extensions", args.ConfigDir, f"--db-path={args.InfoBasePath}"]
@@ -659,7 +655,7 @@ def main():
# partial: import specific files (relative to ConfigDir)
if args.ListFile:
if not os.path.isfile(args.ListFile):
print(f"Error: list file not found: {args.ListFile}", file=sys.stderr)
print(f"Error: list file not found: {args.ListFile}")
sys.exit(1)
with open(args.ListFile, encoding="utf-8-sig") as f:
file_list = [ln.strip() for ln in f if ln.strip()]
@@ -668,7 +664,7 @@ def main():
else:
file_list = []
if not file_list:
print("Error: -Files or -ListFile required for partial import", file=sys.stderr)
print("Error: -Files or -ListFile required for partial import")
sys.exit(1)
arguments = ["infobase", "config", "import", "files"] + file_list
arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
@@ -690,7 +686,7 @@ def main():
print(f"Running: ibcmd {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName)}")
result = run_ibcmd([v8path] + arguments, bool(args.UserName))
if result.returncode != 0:
print(f"Error loading configuration from files (code: {result.returncode}){describe_exit(result.returncode)}", file=sys.stderr)
print(f"Error loading configuration from files (code: {result.returncode}){describe_exit(result.returncode)}")
sys.exit(result.returncode)
print(f"Configuration loaded successfully from: {args.ConfigDir}")
exit_code = 0
@@ -708,7 +704,7 @@ def main():
if exit_code == 0:
print("Database configuration updated successfully")
else:
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}")
print_platform_output(ar)
sys.exit(exit_code)
@@ -745,7 +741,7 @@ def main():
# Build list file
if args.ListFile:
if not os.path.isfile(args.ListFile):
print(f"Error: list file not found: {args.ListFile}", file=sys.stderr)
print(f"Error: list file not found: {args.ListFile}")
sys.exit(1)
with open(args.ListFile, encoding="utf-8-sig") as f:
raw_list = [ln.strip() for ln in f if ln.strip()]
@@ -757,12 +753,12 @@ def main():
support_files = [x for x in raw_list if support_re.search(x)]
file_list = [x for x in raw_list if not support_re.search(x)]
if support_files:
print("[ВНИМАНИЕ] Служебные файлы состояния поддержки исключены из частичной загрузки (частично не грузятся):", file=sys.stderr)
print("[ВНИМАНИЕ] Служебные файлы состояния поддержки исключены из частичной загрузки (частично не грузятся):")
for sf in support_files:
print(f" - {sf}", file=sys.stderr)
print(" Смена состояния поддержки применяется только полной загрузкой: -Mode Full.", file=sys.stderr)
print(f" - {sf}")
print(" Смена состояния поддержки применяется только полной загрузкой: -Mode Full.")
if not file_list:
print("Error: после исключения служебных файлов поддержки загружать нечего. Для смены поддержки используйте -Mode Full.", file=sys.stderr)
print("Error: после исключения служебных файлов поддержки загружать нечего. Для смены поддержки используйте -Mode Full.")
sys.exit(1)
generated_list_file = os.path.join(temp_dir, "load_list.txt")
with open(generated_list_file, "w", encoding="utf-8-sig") as f:
@@ -819,7 +815,7 @@ def main():
if exit_code == 0:
print("Load completed successfully")
else:
print(f"Error loading configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
print(f"Error loading configuration (code: {exit_code}){describe_exit(exit_code)}")
if log_content:
print("--- Log ---")
+5 -7
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-repo v1.13 — 1C configuration repository operations
# db-repo v1.14 — 1C configuration repository operations
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# NB: движок только 1cv8 — ibcmd работу с хранилищем не поддерживает (нет такого режима).
"""Работа с хранилищем конфигурации 1С.
@@ -132,7 +132,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -140,7 +139,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -224,14 +222,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -254,7 +252,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -271,7 +269,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
+6 -10
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-run v1.9 — Launch 1C:Enterprise
# db-run v1.10 — Launch 1C:Enterprise
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -117,7 +117,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -125,7 +124,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -193,14 +191,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -225,7 +221,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -260,14 +256,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -327,7 +323,7 @@ def main():
# --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- Build arguments ---
@@ -377,7 +373,7 @@ def main():
time.sleep(0.2)
rc = proc.poll()
if rc is not None:
print(f"Error: 1C:Enterprise exited immediately (code: {rc})", file=sys.stderr)
print(f"Error: 1C:Enterprise exited immediately (code: {rc})")
sys.exit(rc if rc and rc > 0 else 1)
print(f"PID: {proc.pid}")
print("1C:Enterprise launched")
+11 -15
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-update v1.18 — Update 1C database configuration
# db-update v1.19 — Update 1C database configuration
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -205,7 +205,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -213,7 +212,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -281,14 +279,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -330,14 +326,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -368,7 +364,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -385,7 +381,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -489,7 +485,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -586,16 +582,16 @@ def main():
# --- Validate connection ---
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef")
sys.exit(1)
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
if args.AllExtensions:
print("Error: ibcmd config apply does not support -AllExtensions (use -Extension)", file=sys.stderr)
print("Error: ibcmd config apply does not support -AllExtensions (use -Extension)")
sys.exit(1)
arguments = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"]
if args.Dynamic == "+":
@@ -617,7 +613,7 @@ def main():
if result.returncode == 0:
print("Database configuration updated successfully")
else:
print(f"Error updating database configuration (code: {result.returncode}){describe_exit(result.returncode)}", file=sys.stderr)
print(f"Error updating database configuration (code: {result.returncode}){describe_exit(result.returncode)}")
sys.exit(result.returncode)
# --- Temp dir ---
@@ -674,7 +670,7 @@ def main():
if exit_code == 0:
print("Database configuration updated successfully")
else:
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}")
log_content = ""
if os.path.isfile(out_file):
+13 -17
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# epf-build v1.15 — Build external data processor or report (EPF/ERF) from XML sources
# epf-build v1.16 — Build external data processor or report (EPF/ERF) from XML sources
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -120,7 +120,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -128,7 +127,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -196,14 +194,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -245,14 +241,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -283,7 +279,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -300,7 +296,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -372,7 +368,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -440,7 +436,7 @@ def main():
}
extra_args = resolve_extra_args(engine, v8_extra, ibcmd_extra, arg_hints)
if engine == "ibcmd" and args.InfoBaseServer and args.InfoBaseRef:
print("Error: ibcmd supports file infobases only (use -InfoBasePath or omit for stub)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath or omit for stub)")
sys.exit(1)
# --- Auto-create stub database if no connection specified ---
@@ -461,14 +457,14 @@ def main():
stub_cmd += ["-AdditionalIbcmdArguments"] + list(ibcmd_extra)
result = subprocess.run(stub_cmd, capture_output=False)
if result.returncode != 0:
print("Error: failed to create stub database", file=sys.stderr)
print("Error: failed to create stub database")
sys.exit(1)
args.InfoBasePath = auto_base_path
auto_created_base = auto_base_path
# --- Validate source file ---
if not os.path.isfile(args.SourceFile):
print(f"Error: source file not found: {args.SourceFile}", file=sys.stderr)
print(f"Error: source file not found: {args.SourceFile}")
sys.exit(1)
# --- Ensure output directory exists ---
@@ -502,9 +498,9 @@ def main():
if exit_code == 0:
print(f"External data processor/report built successfully: {args.OutputFile}")
elif out_missing:
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — build produced no output", file=sys.stderr)
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — build produced no output")
else:
print(f"Error building external data processor/report (code: {exit_code})", file=sys.stderr)
print(f"Error building external data processor/report (code: {exit_code})")
sys.exit(exit_code)
# --- Build arguments ---
@@ -541,9 +537,9 @@ def main():
if exit_code == 0:
print(f"Build completed successfully: {args.OutputFile}")
elif out_missing:
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — build produced no output", file=sys.stderr)
print(f"Error: exit code 0 but no non-empty file at {args.OutputFile} — build produced no output")
else:
print(f"Error building (code: {exit_code})", file=sys.stderr)
print(f"Error building (code: {exit_code})")
if os.path.isfile(out_file):
try:
+14 -18
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# epf-dump v1.14 — Dump external data processor or report (EPF/ERF) to XML sources
# epf-dump v1.15 — Dump external data processor or report (EPF/ERF) to XML sources
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -120,7 +120,6 @@ def assert_extra_args(extra, engine, hints):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
@@ -128,7 +127,6 @@ def assert_extra_args(extra, engine, hints):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
@@ -196,14 +194,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
@@ -245,14 +241,14 @@ def resolve_v8path(v8path):
v8path = max(candidates, key=_version_key)
print(f"Auto-selected platform {_version_dir(v8path)}: {v8path}")
else:
print("Error: 1C executable not found. Specify -V8Path", file=sys.stderr)
print("Error: 1C executable not found. Specify -V8Path")
sys.exit(1)
if os.path.isdir(v8path):
# PY-only: на *nix исполняемый называется "1cv8" (без .exe); ibcmd — только явным путём.
exe = "1cv8.exe" if os.name == "nt" else "1cv8"
v8path = os.path.join(v8path, exe)
if not os.path.isfile(v8path):
print(f"Error: 1C executable not found at {v8path}", file=sys.stderr)
print(f"Error: 1C executable not found at {v8path}")
sys.exit(1)
return v8path
@@ -283,7 +279,7 @@ def assert_infobase_exists(path):
if not path:
return
if not os.path.isfile(os.path.join(path, "1Cv8.1CD")):
print(f"Error: information base not found at {path} (no 1Cv8.1CD)", file=sys.stderr)
print(f"Error: information base not found at {path} (no 1Cv8.1CD)")
sys.exit(1)
@@ -300,7 +296,7 @@ def clean_path(value, param=""):
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
print(f"Error: {param or 'path'} contains a quote character: {value}")
sys.exit(1)
return v
@@ -372,7 +368,7 @@ def run_ibcmd(cmd, has_username=False, warn_no_user=True):
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stdout.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
@@ -448,20 +444,20 @@ def main():
# --- Validate database connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef", file=sys.stderr)
print("Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef")
print("Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly.")
sys.exit(1)
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
print("Error: ibcmd supports file infobases only (use -InfoBasePath)")
sys.exit(1)
if args.Format == "Plain":
print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)")
sys.exit(1)
# --- Validate input file ---
if not os.path.isfile(args.InputFile):
print(f"Error: input file not found: {args.InputFile}", file=sys.stderr)
print(f"Error: input file not found: {args.InputFile}")
sys.exit(1)
# --- Ensure output directory exists ---
@@ -493,9 +489,9 @@ def main():
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)
print(f"Error: exit code 0 but no files under {args.OutputDir} — dump produced no output")
else:
print(f"Error dumping external data processor/report (code: {exit_code})", file=sys.stderr)
print(f"Error dumping external data processor/report (code: {exit_code})")
sys.exit(exit_code)
# --- Build arguments ---
@@ -533,9 +529,9 @@ def main():
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)
print(f"Error: exit code 0 but no files under {args.OutputDir} — dump produced no output")
else:
print(f"Error dumping (code: {exit_code})", file=sys.stderr)
print(f"Error dumping (code: {exit_code})")
if os.path.isfile(out_file):
try:
@@ -1,4 +1,4 @@
# meta-validate v1.23 — Validate 1C metadata object structure (Python port)
# meta-validate v1.24 — Validate 1C metadata object structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -80,7 +80,7 @@ if len(path_list) > 1:
except Exception as e:
# Падение одного объекта не должно рвать батч — в варианте с отдельным процессом
# это обеспечивалось изоляцией процессов.
print(f"[ERROR] {single_path}: {type(e).__name__}: {e}", file=sys.stderr)
print(f"[ERROR] {single_path}: {type(e).__name__}: {e}")
rc = 1
finally:
sys.argv = _saved_argv
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# role-info v1.5 — Analyze 1C role rights
# role-info v1.6 — Analyze 1C role rights
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -55,7 +55,7 @@ if not os.path.isabs(rights_path):
rights_path = os.path.join(os.getcwd(), rights_path)
if not os.path.isfile(rights_path):
print(f"[ERROR] File not found: {rights_path}", file=sys.stderr)
print(f"[ERROR] File not found: {rights_path}")
sys.exit(1)
# --- Try to find metadata file for role name/synonym ---
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# subsystem-info v1.5 — Compact summary of 1C subsystem structure
# subsystem-info v1.6 — Compact summary of 1C subsystem structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -105,7 +105,7 @@ def load_subsystem_xml(xml_path):
doc_root = tree.getroot()
sub = doc_root.find("md:Subsystem", NS)
if sub is None:
print(f"[ERROR] Not a valid subsystem XML: {xml_path}", file=sys.stderr)
print(f"[ERROR] Not a valid subsystem XML: {xml_path}")
sys.exit(1)
return {"Doc": doc_root, "Sub": sub}
@@ -408,7 +408,7 @@ if args.Mode == "tree":
root_dir = subsystem_path
else:
if not os.path.isfile(subsystem_path):
print(f"[ERROR] File not found: {subsystem_path}", file=sys.stderr)
print(f"[ERROR] File not found: {subsystem_path}")
sys.exit(1)
root_xml = subsystem_path
@@ -488,7 +488,7 @@ if args.Mode == "tree":
if args.Name:
xml_files = [f for f in xml_files if os.path.splitext(f)[0] == args.Name]
if not xml_files:
print(f"[ERROR] Subsystem '{args.Name}' not found in {root_dir}", file=sys.stderr)
print(f"[ERROR] Subsystem '{args.Name}' not found in {root_dir}")
sys.exit(1)
for i, fname in enumerate(xml_files):
build_tree_entry(os.path.join(root_dir, fname), "", i == len(xml_files) - 1, True)
@@ -500,7 +500,7 @@ elif args.Mode == "ci":
# Mode: ci -- CommandInterface.xml
# ============================================================
if os.path.isdir(subsystem_path):
print("[ERROR] ci mode requires a subsystem .xml file, not a directory", file=sys.stderr)
print("[ERROR] ci mode requires a subsystem .xml file, not a directory")
sys.exit(1)
# File not found -- check Dir/Name/Name.xml -> Dir/Name.xml
if not os.path.isfile(subsystem_path):
@@ -511,7 +511,7 @@ elif args.Mode == "ci":
if os.path.isfile(c):
subsystem_path = c
if not os.path.isfile(subsystem_path):
print(f"[ERROR] File not found: {subsystem_path}", file=sys.stderr)
print(f"[ERROR] File not found: {subsystem_path}")
sys.exit(1)
parsed = load_subsystem_xml(subsystem_path)
@@ -535,7 +535,7 @@ else:
elif os.path.isfile(sibling):
subsystem_path = sibling
else:
print(f"[ERROR] No {dir_name}.xml found in directory. Use -Mode tree for directory listing.", file=sys.stderr)
print(f"[ERROR] No {dir_name}.xml found in directory. Use -Mode tree for directory listing.")
sys.exit(1)
# File not found -- check Dir/Name/Name.xml -> Dir/Name.xml
@@ -547,7 +547,7 @@ else:
if os.path.isfile(c):
subsystem_path = c
if not os.path.isfile(subsystem_path):
print(f"[ERROR] File not found: {subsystem_path}", file=sys.stderr)
print(f"[ERROR] File not found: {subsystem_path}")
sys.exit(1)
parsed = load_subsystem_xml(subsystem_path)
@@ -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(
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# web-stop v1.2 — Stop Apache HTTP Server
# web-stop v1.3 — Stop Apache HTTP Server
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""
@@ -130,7 +130,7 @@ def main():
time.sleep(1)
final = get_our_httpd(httpd_exe_norm)
if final:
print('Error: не удалось остановить Apache', file=sys.stderr)
print('Error: не удалось остановить Apache')
sys.exit(1)
print('Apache остановлен')
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# web-unpublish v1.2 — Remove 1C web publication
# web-unpublish v1.3 — Remove 1C web publication
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""
@@ -75,13 +75,13 @@ def main():
# --- Validate params ---
if not args.All and not args.AppName:
print('Error: укажите -AppName или -All', file=sys.stderr)
print('Error: укажите -AppName или -All')
sys.exit(1)
# --- Read 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:
@@ -161,7 +161,7 @@ def main():
if check:
print('Apache перезапущен')
else:
print('Error: Apache не удалось перезапустить', file=sys.stderr)
print('Error: Apache не удалось перезапустить')
sys.exit(1)
else:
print('Публикаций не осталось — останавливаю Apache...')
@@ -1,4 +1,4 @@
# xdto-decompile v1.2 — Convert 1C XDTO package to XML Schema (XSD) (Python port)
# xdto-decompile v1.3 — Convert 1C XDTO package to XML Schema (XSD) (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -89,7 +89,7 @@ elif os.path.isdir(package_path):
md_path = m
if not bin_path:
print(f"Не найден Ext/Package.bin для пути: {package_path}", file=sys.stderr)
print(f"Не найден Ext/Package.bin для пути: {package_path}")
sys.exit(1)
doc = _parse_xml(bin_path)
@@ -101,7 +101,7 @@ def local(el):
if local(pkg) != "package":
print(f"Ожидался корневой <package>, получен <{local(pkg)}>", file=sys.stderr)
print(f"Ожидался корневой <package>, получен <{local(pkg)}>")
sys.exit(1)
target_ns = pkg.get("targetNamespace")
+5
View File
@@ -105,6 +105,11 @@ Switch-параметры (`-NoValidate`) → `action='store_true'`.
| `Write-Host "text"` | `print("text")` |
| `Write-Error "text"` | `print("text", file=sys.stderr)` |
> Поток обязателен к соблюдению, а не рекомендателен: харнесс не чередует stdout и stderr,
> а группирует их — вердикт, ушедший не в тот поток, печатается ПЕРЕД строками, которые его
> объясняют, и причинный порядок вывода переворачивается. Держит инвариант
> `tests/skills/check-error-streams.mjs`.
## lxml vs stdlib
- **Compile/init скрипты** (строковая сборка): только stdlib
+1
View File
@@ -111,6 +111,7 @@ node tests/skills/check-inline-drift.mjs --list # реестр: семья →
| `check-form-purposes.mjs` | таблица назначений форм в `form-add`: состав видов, свойство «основная форма» и оба порта сходятся с `docs/1c-form-spec.md` |
| `check-positional-binding.mjs` | read-only навыки (`*-info` / `*-validate` / `cfe-diff`): позиционным остаётся только путь ко входу, лишний позиционный аргумент не перезаписывает указанный файл |
| `check-agent-portability.mjs` | исходники навыков не привязаны к конкретному AI-агенту: единственная разрешённая форма — плейсхолдер `${CLAUDE_SKILL_DIR}/`, который разворачивает `scripts/switch.py` |
| `check-error-streams.mjs` | сообщения об ошибках идут в один и тот же поток в обоих портах навыка (соответствие из `docs/python-porting-guide.md`) |
| `check-nonascii-fs.mjs` | `fsutil`: удаление и копирование держат не-ASCII пути (кириллический `%TEMP%`, кириллические имена объектов 1С), обе копии модуля не разошлись |
`check-inline-drift.mjs` держит реестр семей внутри себя: у каждой семьи перечислены варианты, у
+1
View File
@@ -17,6 +17,7 @@ const GUARDS = [
['check-form-purposes.mjs', 'назначения форм в form-add: согласованы со спецификацией и между портами'],
['check-positional-binding.mjs', 'read-only навыки: позиционным остаётся только путь ко входу'],
['check-agent-portability.mjs', 'исходники навыков: без привязки к конкретному AI-агенту'],
['check-error-streams.mjs', 'сообщения об ошибках: один и тот же поток в обоих портах'],
['check-nonascii-fs.mjs', 'fsutil: удаление и копирование держат не-ASCII пути, копии не разошлись'],
];
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env node
// Инвариант: сообщения об ошибках уходят в ОДИН И ТОТ ЖЕ поток в обоих портах навыка.
//
// Соответствие задано в docs/python-porting-guide.md: `Write-Host` → `print`,
// `Write-Error` → `print(..., file=sys.stderr)`. Тринадцать навыков группы db-*/epf-*/web-*
// его нарушали: PS писал ошибки через Write-Host (stdout), а py-порт — в stderr. Счётчики
// совпадали один в один (14↔14, 19↔19, 13↔13) — то есть сообщения были те же, разъехался
// только поток.
//
// Почему это не косметика: харнесс не чередует потоки, а группирует — сначала весь stderr,
// потом весь stdout. Из-за этого в py-порте вердикт «Error dumping configuration (code: 1)»
// печатался ПЕРЕД строками, которые его объясняют, а причина из лога платформы оказывалась
// в самом низу. Причинный порядок вывода переворачивался, и порт для macOS читался хуже
// того, что работает на Windows.
//
// Почему не ловилось тестами: текст ошибки сверяют только кейсы со строковым `expectError`,
// а он смотрит в stderr — поэтому такие кейсы есть лишь у семейства, где потоки сходятся.
// В db-* их ноль, и дыра пряталась за собственным следствием.
//
// Запуск: node tests/skills/check-error-streams.mjs
import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const SKILLS = join(ROOT, '.claude', 'skills');
const errors = [];
let checked = 0;
for (const skill of readdirSync(SKILLS)) {
const dir = join(SKILLS, skill, 'scripts');
if (!existsSync(dir)) continue;
for (const file of readdirSync(dir)) {
if (!file.endsWith('.ps1')) continue;
const base = file.slice(0, -4);
const pyPath = join(dir, base + '.py');
if (!existsSync(pyPath)) continue;
// Форм записи в stderr по две с каждой стороны, и считать надо обе: PS пишет через
// Write-Error и через [Console]::Error.WriteLine, py — через file=sys.stderr и
// sys.stderr.write. Комментарии выбрасываем: в meta-remove.ps1 слово Write-Error стоит
// в пояснении «почему НЕ Write-Error», и по одной форме гард давал ложную тревогу.
const strip = (text, marker) => text.split('\n')
.filter(l => !l.trimStart().startsWith(marker)).join('\n');
const ps = strip(readFileSync(join(dir, file), 'utf8').replace(/^/, ''), '#');
const py = strip(readFileSync(pyPath, 'utf8'), '#');
const psErr = (ps.match(/Write-Error|Console\]::Error\.Write/g) || []).length;
const pyErr = (py.match(/file=sys\.stderr|sys\.stderr\.write/g) || []).length;
checked++;
if (psErr === 0 && pyErr > 0) {
errors.push(`${skill}/${base}: PS не использует Write-Error, а py пишет в stderr `
+ `(${pyErr} мест). Ошибки должны идти в тот же поток, что и в PS — убрать file=sys.stderr.`);
}
if (psErr > 0 && pyErr === 0) {
errors.push(`${skill}/${base}: PS использует Write-Error (${psErr} мест), а py пишет всё `
+ `в stdout. Ошибки должны идти в тот же поток — добавить file=sys.stderr.`);
}
}
}
console.log(`Проверено пар портов: ${checked}`);
if (errors.length === 0) {
console.log('OK — потоки сообщений об ошибках совпадают в обоих портах.');
process.exit(0);
}
console.log(`\n${errors.length} РАСХОЖДЕНИЙ:`);
for (const e of errors) console.log(` [ERROR] ${e}`);
console.log('\nСоответствие потоков: docs/python-porting-guide.md, таблица маппинга PS → Python.');
process.exit(1);