diff --git a/.claude/skills/db-create/SKILL.md b/.claude/skills/db-create/SKILL.md index f7678fb8..21cfda61 100644 --- a/.claude/skills/db-create/SKILL.md +++ b/.claude/skills/db-create/SKILL.md @@ -25,7 +25,7 @@ allowed-tools: ## Параметры подключения Прочитай `.v8-project.json` из корня проекта для `v8path` (путь к платформе). -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). После создания базы предложи зарегистрировать через `/db-list add`. ## Команда @@ -38,7 +38,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" <п | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Путь к файловой базе | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-create/scripts/db-create.ps1 b/.claude/skills/db-create/scripts/db-create.ps1 index 6d846d92..9f360833 100644 --- a/.claude/skills/db-create/scripts/db-create.ps1 +++ b/.claude/skills/db-create/scripts/db-create.ps1 @@ -1,4 +1,4 @@ -# db-create v1.1 — Create 1C information base +# db-create v1.3 — Create 1C information base # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -109,8 +109,16 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + # --- Validate connection --- -if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } +} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -126,6 +134,30 @@ $tempDir = Join-Path $env:TEMP "db_create_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only) --- + $arguments = @("infobase", "create", "--db-path=$InfoBasePath", "--create-database") + if ($UseTemplate) { + if ([System.IO.Path]::GetExtension($UseTemplate) -ieq ".dt") { + $arguments += "--restore=$UseTemplate" + } else { + $arguments += "--load=$UseTemplate", "--apply" + } + } + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Information base created successfully: $InfoBasePath" -ForegroundColor Green + } else { + Write-Host "Error creating information base (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("CREATEINFOBASE") diff --git a/.claude/skills/db-create/scripts/db-create.py b/.claude/skills/db-create/scripts/db-create.py index 8424eb28..a5c9566c 100644 --- a/.claude/skills/db-create/scripts/db-create.py +++ b/.claude/skills/db-create/scripts/db-create.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-create v1.1 — Create 1C information base +# db-create v1.3 — Create 1C information base # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -82,9 +83,14 @@ def main(): args = parser.parse_args() v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" # --- Validate connection --- - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + if engine == "ibcmd": + if not args.InfoBasePath: + print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr) + 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) sys.exit(1) @@ -93,6 +99,29 @@ def main(): print(f"Error: template file not found: {args.UseTemplate}", file=sys.stderr) sys.exit(1) + # --- ibcmd branch (file infobase only) --- + if engine == "ibcmd": + arguments = ["infobase", "create", f"--db-path={args.InfoBasePath}", "--create-database"] + if args.UseTemplate: + if os.path.splitext(args.UseTemplate)[1].lower() == ".dt": + arguments.append(f"--restore={args.UseTemplate}") + else: + arguments.extend([f"--load={args.UseTemplate}", "--apply"]) + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print(f"Information base created successfully: {args.InfoBasePath}") + else: + print(f"Error creating information base (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Temp dir --- temp_dir = os.path.join(tempfile.gettempdir(), f"db_create_{random.randint(0, 999999)}") os.makedirs(temp_dir, exist_ok=True) diff --git a/.claude/skills/db-dump-cf/SKILL.md b/.claude/skills/db-dump-cf/SKILL.md index 84e3677a..faf43e76 100644 --- a/.claude/skills/db-dump-cf/SKILL.md +++ b/.claude/skills/db-dump-cf/SKILL.md @@ -28,7 +28,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. @@ -42,7 +42,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" <п | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-dump-cf/scripts/db-dump-cf.ps1 b/.claude/skills/db-dump-cf/scripts/db-dump-cf.ps1 index 23ac9f41..78d90c9e 100644 --- a/.claude/skills/db-dump-cf/scripts/db-dump-cf.ps1 +++ b/.claude/skills/db-dump-cf/scripts/db-dump-cf.ps1 @@ -1,4 +1,4 @@ -# db-dump-cf v1.1 — Dump 1C configuration to CF file +# db-dump-cf v1.3 — Dump 1C configuration to CF file # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -118,8 +118,16 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + # --- Validate connection --- -if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } +} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -135,6 +143,29 @@ $tempDir = Join-Path $env:TEMP "db_dump_cf_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only) --- + if ($AllExtensions) { + Write-Host "Error: ibcmd config save does not support -AllExtensions (use -Extension)" -ForegroundColor Red + exit 1 + } + $arguments = @("infobase", "config", "save", "--db-path=$InfoBasePath") + if ($Extension) { $arguments += "--extension=$Extension" } + $arguments += "$OutputFile" + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Configuration dumped successfully to: $OutputFile" -ForegroundColor Green + } else { + Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/db-dump-cf/scripts/db-dump-cf.py b/.claude/skills/db-dump-cf/scripts/db-dump-cf.py index 5668e614..3ec4cff9 100644 --- a/.claude/skills/db-dump-cf/scripts/db-dump-cf.py +++ b/.claude/skills/db-dump-cf/scripts/db-dump-cf.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-dump-cf v1.1 — Dump 1C configuration to CF file +# db-dump-cf v1.3 — Dump 1C configuration to CF file # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -84,9 +85,14 @@ def main(): args = parser.parse_args() v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" # --- Validate connection --- - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + if engine == "ibcmd": + if not args.InfoBasePath: + print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr) + 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) sys.exit(1) @@ -95,6 +101,30 @@ def main(): if out_dir and not os.path.isdir(out_dir): os.makedirs(out_dir, exist_ok=True) + # --- 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) + sys.exit(1) + arguments = ["infobase", "config", "save", f"--db-path={args.InfoBasePath}"] + if args.Extension: + arguments.append(f"--extension={args.Extension}") + arguments.append(args.OutputFile) + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print(f"Configuration dumped successfully to: {args.OutputFile}") + else: + print(f"Error dumping configuration (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Temp dir --- temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_cf_{random.randint(0, 999999)}") os.makedirs(temp_dir, exist_ok=True) diff --git a/.claude/skills/db-dump-dt/SKILL.md b/.claude/skills/db-dump-dt/SKILL.md index 1aab41ad..3bb393ad 100644 --- a/.claude/skills/db-dump-dt/SKILL.md +++ b/.claude/skills/db-dump-dt/SKILL.md @@ -31,7 +31,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. @@ -45,7 +45,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" <п | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-dump-dt/scripts/db-dump-dt.ps1 b/.claude/skills/db-dump-dt/scripts/db-dump-dt.ps1 index 63ff1193..3655b512 100644 --- a/.claude/skills/db-dump-dt/scripts/db-dump-dt.ps1 +++ b/.claude/skills/db-dump-dt/scripts/db-dump-dt.ps1 @@ -1,4 +1,4 @@ -# db-dump-dt v1.1 — Dump 1C information base to DT file +# db-dump-dt v1.3 — Dump 1C information base to DT file # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -102,8 +102,16 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + # --- Validate connection --- -if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } +} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -119,6 +127,27 @@ $tempDir = Join-Path $env:TEMP "db_dump_dt_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only) --- + $arguments = @("infobase", "dump", "--db-path=$InfoBasePath") + if ($UserName) { $arguments += "--user=$UserName" } + if ($Password) { $arguments += "--password=$Password" } + $arguments += "$OutputFile" + + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Information base dumped successfully to: $OutputFile" -ForegroundColor Green + } else { + Write-Host "Error dumping information base (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/db-dump-dt/scripts/db-dump-dt.py b/.claude/skills/db-dump-dt/scripts/db-dump-dt.py index 684aa9fc..29aafef7 100644 --- a/.claude/skills/db-dump-dt/scripts/db-dump-dt.py +++ b/.claude/skills/db-dump-dt/scripts/db-dump-dt.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-dump-dt v1.1 — Dump 1C information base to DT file +# db-dump-dt v1.3 — Dump 1C information base to DT file # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -82,9 +83,14 @@ def main(): args = parser.parse_args() v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" # --- Validate connection --- - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + if engine == "ibcmd": + if not args.InfoBasePath: + print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr) + 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) sys.exit(1) @@ -93,6 +99,29 @@ def main(): if out_dir and not os.path.isdir(out_dir): os.makedirs(out_dir, exist_ok=True) + # --- ibcmd branch (file infobase only) --- + if engine == "ibcmd": + arguments = ["infobase", "dump", f"--db-path={args.InfoBasePath}"] + if args.UserName: + arguments.append(f"--user={args.UserName}") + if args.Password: + arguments.append(f"--password={args.Password}") + arguments.append(args.OutputFile) + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print(f"Information base dumped successfully to: {args.OutputFile}") + else: + print(f"Error dumping information base (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Temp dir --- temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_dt_{random.randint(0, 999999)}") os.makedirs(temp_dir, exist_ok=True) diff --git a/.claude/skills/db-dump-xml/SKILL.md b/.claude/skills/db-dump-xml/SKILL.md index 12d44d2a..69e3ba04 100644 --- a/.claude/skills/db-dump-xml/SKILL.md +++ b/.claude/skills/db-dump-xml/SKILL.md @@ -29,7 +29,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если в записи базы указан `configSrc` — используй как каталог выгрузки по умолчанию. @@ -44,7 +44,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" < | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 b/.claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 index ffc472d5..8527bb2b 100644 --- a/.claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 +++ b/.claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 @@ -1,4 +1,4 @@ -# db-dump-xml v1.1 — Dump 1C configuration to XML files +# db-dump-xml v1.5 — Dump 1C configuration to XML files # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -141,8 +141,16 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + # --- Validate connection --- -if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } +} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -164,6 +172,41 @@ $tempDir = Join-Path $env:TEMP "db_dump_xml_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only; hierarchical Full/Changes) --- + if ($Format -eq "Plain") { + Write-Host "Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red + exit 1 + } + if ($AllExtensions) { + $arguments = @("infobase", "config", "export", "all-extensions", "$ConfigDir", "--db-path=$InfoBasePath") + } elseif ($Mode -eq "UpdateInfo") { + Write-Host "Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8" -ForegroundColor Red + exit 1 + } elseif ($Mode -eq "Partial") { + $objList = @($Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) + $arguments = @("infobase", "config", "export", "objects") + $objList + $arguments += "--out=$ConfigDir", "--db-path=$InfoBasePath" + if ($Extension) { $arguments += "--extension=$Extension" } + } else { + $arguments = @("infobase", "config", "export", "--db-path=$InfoBasePath") + if ($Extension) { $arguments += "--extension=$Extension" } + $arguments += "$ConfigDir" + } + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Configuration exported successfully to: $ConfigDir" -ForegroundColor Green + } else { + Write-Host "Error exporting configuration (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/db-dump-xml/scripts/db-dump-xml.py b/.claude/skills/db-dump-xml/scripts/db-dump-xml.py index c8de84a5..ce8a228d 100644 --- a/.claude/skills/db-dump-xml/scripts/db-dump-xml.py +++ b/.claude/skills/db-dump-xml/scripts/db-dump-xml.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-dump-xml v1.1 — Dump 1C configuration to XML files +# db-dump-xml v1.5 — Dump 1C configuration to XML files # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -98,9 +99,14 @@ def main(): # --- Resolve V8Path --- v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" # --- Validate connection --- - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + if engine == "ibcmd": + if not args.InfoBasePath: + print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr) + 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) sys.exit(1) @@ -114,6 +120,42 @@ def main(): os.makedirs(args.ConfigDir, exist_ok=True) print(f"Created output directory: {args.ConfigDir}") + # --- 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) + 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) + sys.exit(1) + elif args.Mode == "Partial": + obj_list = [o.strip() for o in args.Objects.split(",") if o.strip()] + arguments = ["infobase", "config", "export", "objects"] + obj_list + arguments += [f"--out={args.ConfigDir}", f"--db-path={args.InfoBasePath}"] + if args.Extension: + arguments.append(f"--extension={args.Extension}") + else: + arguments = ["infobase", "config", "export", f"--db-path={args.InfoBasePath}"] + if args.Extension: + arguments.append(f"--extension={args.Extension}") + arguments.append(args.ConfigDir) + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print(f"Configuration exported successfully to: {args.ConfigDir}") + else: + print(f"Error exporting configuration (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Temp dir --- temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_xml_{random.randint(0, 999999)}") os.makedirs(temp_dir, exist_ok=True) diff --git a/.claude/skills/db-load-cf/SKILL.md b/.claude/skills/db-load-cf/SKILL.md index ef9c7d48..e989979c 100644 --- a/.claude/skills/db-load-cf/SKILL.md +++ b/.claude/skills/db-load-cf/SKILL.md @@ -29,7 +29,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. @@ -43,7 +43,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <п | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-load-cf/scripts/db-load-cf.ps1 b/.claude/skills/db-load-cf/scripts/db-load-cf.ps1 index 5766214a..1fa70603 100644 --- a/.claude/skills/db-load-cf/scripts/db-load-cf.ps1 +++ b/.claude/skills/db-load-cf/scripts/db-load-cf.ps1 @@ -1,4 +1,4 @@ -# db-load-cf v1.1 — Load 1C configuration from CF file +# db-load-cf v1.3 — Load 1C configuration from CF file # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -118,8 +118,16 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + # --- Validate connection --- -if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } +} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -135,6 +143,29 @@ $tempDir = Join-Path $env:TEMP "db_load_cf_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only) --- + if ($AllExtensions) { + Write-Host "Error: ibcmd config load does not support -AllExtensions (use -Extension)" -ForegroundColor Red + exit 1 + } + $arguments = @("infobase", "config", "load", "--db-path=$InfoBasePath") + if ($Extension) { $arguments += "--extension=$Extension" } + $arguments += "$InputFile" + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Configuration loaded successfully from: $InputFile" -ForegroundColor Green + } else { + Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/db-load-cf/scripts/db-load-cf.py b/.claude/skills/db-load-cf/scripts/db-load-cf.py index e00a7c8d..c08cdd20 100644 --- a/.claude/skills/db-load-cf/scripts/db-load-cf.py +++ b/.claude/skills/db-load-cf/scripts/db-load-cf.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-load-cf v1.1 — Load 1C configuration from CF file +# db-load-cf v1.3 — Load 1C configuration from CF file # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -84,9 +85,14 @@ def main(): args = parser.parse_args() v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" # --- Validate connection --- - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + if engine == "ibcmd": + if not args.InfoBasePath: + print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr) + 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) sys.exit(1) @@ -95,6 +101,30 @@ def main(): print(f"Error: input file not found: {args.InputFile}", file=sys.stderr) 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) + sys.exit(1) + arguments = ["infobase", "config", "load", f"--db-path={args.InfoBasePath}"] + if args.Extension: + arguments.append(f"--extension={args.Extension}") + arguments.append(args.InputFile) + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print(f"Configuration loaded successfully from: {args.InputFile}") + else: + print(f"Error loading configuration (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Temp dir --- temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_cf_{random.randint(0, 999999)}") os.makedirs(temp_dir, exist_ok=True) diff --git a/.claude/skills/db-load-dt/SKILL.md b/.claude/skills/db-load-dt/SKILL.md index fcecd4fa..9cd07565 100644 --- a/.claude/skills/db-load-dt/SKILL.md +++ b/.claude/skills/db-load-dt/SKILL.md @@ -45,7 +45,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. @@ -59,7 +59,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" <п | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-load-dt/scripts/db-load-dt.ps1 b/.claude/skills/db-load-dt/scripts/db-load-dt.ps1 index aa5108fb..b2a8b01f 100644 --- a/.claude/skills/db-load-dt/scripts/db-load-dt.ps1 +++ b/.claude/skills/db-load-dt/scripts/db-load-dt.ps1 @@ -1,4 +1,4 @@ -# db-load-dt v1.1 — Load 1C information base from DT file +# db-load-dt v1.3 — Load 1C information base from DT file # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -115,8 +115,16 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + # --- Validate connection --- -if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } +} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -132,6 +140,28 @@ $tempDir = Join-Path $env:TEMP "db_load_dt_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only) --- + $arguments = @("infobase", "restore", "--db-path=$InfoBasePath") + if (-not (Test-Path (Join-Path $InfoBasePath "1Cv8.1CD"))) { $arguments += "--create-database" } + if ($UserName) { $arguments += "--user=$UserName" } + if ($Password) { $arguments += "--password=$Password" } + $arguments += "$InputFile" + + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Information base restored successfully from: $InputFile" -ForegroundColor Green + } else { + Write-Host "Error restoring information base (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/db-load-dt/scripts/db-load-dt.py b/.claude/skills/db-load-dt/scripts/db-load-dt.py index 2c5ff391..bb08de35 100644 --- a/.claude/skills/db-load-dt/scripts/db-load-dt.py +++ b/.claude/skills/db-load-dt/scripts/db-load-dt.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-load-dt v1.1 — Load 1C information base from DT file +# db-load-dt v1.3 — Load 1C information base from DT file # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -84,9 +85,14 @@ def main(): args = parser.parse_args() v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" # --- Validate connection --- - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + if engine == "ibcmd": + if not args.InfoBasePath: + print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr) + 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) sys.exit(1) @@ -95,6 +101,31 @@ def main(): print(f"Error: input file not found: {args.InputFile}", file=sys.stderr) sys.exit(1) + # --- ibcmd branch (file infobase only) --- + if engine == "ibcmd": + arguments = ["infobase", "restore", f"--db-path={args.InfoBasePath}"] + if not os.path.isfile(os.path.join(args.InfoBasePath, "1Cv8.1CD")): + arguments.append("--create-database") + if args.UserName: + arguments.append(f"--user={args.UserName}") + if args.Password: + arguments.append(f"--password={args.Password}") + arguments.append(args.InputFile) + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print(f"Information base restored successfully from: {args.InputFile}") + else: + print(f"Error restoring information base (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Temp dir --- temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_dt_{random.randint(0, 999999)}") os.makedirs(temp_dir, exist_ok=True) diff --git a/.claude/skills/db-load-git/SKILL.md b/.claude/skills/db-load-git/SKILL.md index d260aba0..2915f308 100644 --- a/.claude/skills/db-load-git/SKILL.md +++ b/.claude/skills/db-load-git/SKILL.md @@ -30,7 +30,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если в записи базы указан `configSrc` — используй как каталог конфигурации. @@ -45,7 +45,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" < | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-load-git/scripts/db-load-git.ps1 b/.claude/skills/db-load-git/scripts/db-load-git.ps1 index 96767f23..7fca7fe7 100644 --- a/.claude/skills/db-load-git/scripts/db-load-git.ps1 +++ b/.claude/skills/db-load-git/scripts/db-load-git.ps1 @@ -1,4 +1,4 @@ -# db-load-git v1.5 — Load Git changes into 1C database +# db-load-git v1.7 — Load Git changes into 1C database # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -163,9 +163,16 @@ if (-not $DryRun) { } } -# --- Validate connection (skip if DryRun) --- +# --- Detect engine + validate connection (skip if DryRun) --- +$engine = "1cv8" if (-not $DryRun) { - if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { + $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } + } elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -319,6 +326,47 @@ $tempDir = Join-Path $env:TEMP "db_load_git_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only; import specific files) --- + if ($Format -eq "Plain") { + Write-Host "Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red + exit 1 + } + if ($AllExtensions) { + Write-Host "Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)" -ForegroundColor Red + exit 1 + } + $arguments = @("infobase", "config", "import", "files") + $configFiles + $arguments += "--base-dir=$ConfigDir", "--db-path=$InfoBasePath" + if ($Extension) { $arguments += "--extension=$Extension" } + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + Write-Host "Error loading changes (code: $exitCode)" -ForegroundColor Red + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + Write-Host "Changes loaded successfully ($($configFiles.Count) files)" -ForegroundColor Green + if ($output) { Write-Host ($output | Out-String) } + if ($UpdateDB) { + $applyArgs = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force") + $applyArgs += "--data=$tempDir" + Write-Host "Running: ibcmd $($applyArgs -join ' ')" + $applyOut = & $V8Path @applyArgs 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Database configuration updated successfully" -ForegroundColor Green + } else { + Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red + } + if ($applyOut) { Write-Host ($applyOut | Out-String) } + } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Write list file (UTF-8 with BOM) --- $listFile = Join-Path $tempDir "load_list.txt" $utf8Bom = New-Object System.Text.UTF8Encoding($true) diff --git a/.claude/skills/db-load-git/scripts/db-load-git.py b/.claude/skills/db-load-git/scripts/db-load-git.py index c51444e9..afa82c9f 100644 --- a/.claude/skills/db-load-git/scripts/db-load-git.py +++ b/.claude/skills/db-load-git/scripts/db-load-git.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-load-git v1.5 — Load Git changes into 1C database +# db-load-git v1.7 — Load Git changes into 1C database # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -125,9 +126,15 @@ def main(): if not args.DryRun: v8path = resolve_v8path(args.V8Path) - # --- Validate connection (skip if DryRun) --- + # --- Detect engine + validate connection (skip if DryRun) --- + engine = "1cv8" if not args.DryRun: - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + 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) + 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) sys.exit(1) @@ -248,6 +255,50 @@ def main(): os.makedirs(temp_dir, exist_ok=True) try: + 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) + sys.exit(1) + if args.AllExtensions: + print("Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)", file=sys.stderr) + sys.exit(1) + arguments = ["infobase", "config", "import", "files"] + config_files + arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"] + if args.Extension: + arguments.append(f"--extension={args.Extension}") + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode != 0: + print(f"Error loading changes (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + print(f"Changes loaded successfully ({len(config_files)} files)") + if result.stdout: + print(result.stdout) + exit_code = 0 + if args.UpdateDB: + apply_args = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"] + apply_args.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(apply_args)}") + ar = subprocess.run([v8path] + apply_args, capture_output=True, encoding="utf-8", errors="replace") + exit_code = ar.returncode + if exit_code == 0: + print("Database configuration updated successfully") + else: + print(f"Error updating database configuration (code: {exit_code})", file=sys.stderr) + if ar.stdout: + print(ar.stdout) + if ar.stderr: + print(ar.stderr, file=sys.stderr) + sys.exit(exit_code) + # --- Write list file (UTF-8 with BOM) --- list_file = os.path.join(temp_dir, "load_list.txt") with open(list_file, "w", encoding="utf-8-sig") as f: diff --git a/.claude/skills/db-load-xml/SKILL.md b/.claude/skills/db-load-xml/SKILL.md index 0d4e721b..3cfb283c 100644 --- a/.claude/skills/db-load-xml/SKILL.md +++ b/.claude/skills/db-load-xml/SKILL.md @@ -30,7 +30,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если в записи базы указан `configSrc` — используй как каталог загрузки по умолчанию. @@ -45,7 +45,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" < | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-load-xml/scripts/db-load-xml.ps1 b/.claude/skills/db-load-xml/scripts/db-load-xml.ps1 index c79945f2..3fa152fc 100644 --- a/.claude/skills/db-load-xml/scripts/db-load-xml.ps1 +++ b/.claude/skills/db-load-xml/scripts/db-load-xml.ps1 @@ -1,4 +1,4 @@ -# db-load-xml v1.5 — Load 1C configuration from XML files +# db-load-xml v1.9 — Load 1C configuration from XML files # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -150,8 +150,16 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + # --- Validate connection --- -if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } +} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -173,6 +181,67 @@ $tempDir = Join-Path $env:TEMP "db_load_xml_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only; hierarchical full-directory import) --- + if ($Format -eq "Plain") { + Write-Host "Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red + exit 1 + } + if ($AllExtensions) { + $arguments = @("infobase", "config", "import", "all-extensions", "$ConfigDir", "--db-path=$InfoBasePath") + } elseif ($Mode -eq "Partial" -or $Files -or $ListFile) { + # partial: import specific files (relative to ConfigDir) + $fileList = @() + if ($ListFile) { + if (-not (Test-Path $ListFile)) { + Write-Host "Error: list file not found: $ListFile" -ForegroundColor Red + exit 1 + } + $fileList = @(Get-Content -Path $ListFile -Encoding UTF8 | ForEach-Object { $_.Trim() } | Where-Object { $_ }) + } elseif ($Files) { + $fileList = @($Files -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) + } + if ($fileList.Count -eq 0) { + Write-Host "Error: -Files or -ListFile required for partial import" -ForegroundColor Red + exit 1 + } + $arguments = @("infobase", "config", "import", "files") + $fileList + $arguments += "--base-dir=$ConfigDir", "--db-path=$InfoBasePath" + if ($Extension) { $arguments += "--extension=$Extension" } + } else { + $arguments = @("infobase", "config", "import", "--db-path=$InfoBasePath") + if ($Extension) { $arguments += "--extension=$Extension" } + $arguments += "$ConfigDir" + } + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + Write-Host "Error loading configuration from files (code: $exitCode)" -ForegroundColor Red + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + Write-Host "Configuration loaded successfully from: $ConfigDir" -ForegroundColor Green + if ($output) { Write-Host ($output | Out-String) } + + if ($UpdateDB) { + $applyArgs = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force") + $applyArgs += "--data=$tempDir" + Write-Host "Running: ibcmd $($applyArgs -join ' ')" + $applyOut = & $V8Path @applyArgs 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Database configuration updated successfully" -ForegroundColor Green + } else { + Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red + } + if ($applyOut) { Write-Host ($applyOut | Out-String) } + } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/db-load-xml/scripts/db-load-xml.py b/.claude/skills/db-load-xml/scripts/db-load-xml.py index dca6a724..7a40334f 100644 --- a/.claude/skills/db-load-xml/scripts/db-load-xml.py +++ b/.claude/skills/db-load-xml/scripts/db-load-xml.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-load-xml v1.5 — Load 1C configuration from XML files +# db-load-xml v1.9 — Load 1C configuration from XML files # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -106,8 +107,14 @@ def main(): # --- Resolve V8Path --- v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" + # --- Validate connection --- - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + if engine == "ibcmd": + if not args.InfoBasePath: + print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr) + 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) sys.exit(1) @@ -121,6 +128,69 @@ def main(): print("Error: -Files or -ListFile required for Partial mode", file=sys.stderr) 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) + sys.exit(1) + if args.AllExtensions: + arguments = ["infobase", "config", "import", "all-extensions", args.ConfigDir, f"--db-path={args.InfoBasePath}"] + elif args.Mode == "Partial" or args.Files or args.ListFile: + # 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) + sys.exit(1) + with open(args.ListFile, encoding="utf-8-sig") as f: + file_list = [ln.strip() for ln in f if ln.strip()] + elif args.Files: + file_list = [p.strip() for p in args.Files.split(",") if p.strip()] + else: + file_list = [] + if not file_list: + print("Error: -Files or -ListFile required for partial import", file=sys.stderr) + sys.exit(1) + arguments = ["infobase", "config", "import", "files"] + file_list + arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"] + if args.Extension: + arguments.append(f"--extension={args.Extension}") + else: + arguments = ["infobase", "config", "import", f"--db-path={args.InfoBasePath}"] + if args.Extension: + arguments.append(f"--extension={args.Extension}") + arguments.append(args.ConfigDir) + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode != 0: + print(f"Error loading configuration from files (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + print(f"Configuration loaded successfully from: {args.ConfigDir}") + if result.stdout: + print(result.stdout) + exit_code = 0 + if args.UpdateDB: + apply_args = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"] + apply_args.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(apply_args)}") + ar = subprocess.run([v8path] + apply_args, capture_output=True, encoding="utf-8", errors="replace") + exit_code = ar.returncode + if exit_code == 0: + print("Database configuration updated successfully") + else: + print(f"Error updating database configuration (code: {exit_code})", file=sys.stderr) + if ar.stdout: + print(ar.stdout) + if ar.stderr: + print(ar.stderr, file=sys.stderr) + sys.exit(exit_code) + # --- Temp dir --- temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_xml_{random.randint(0, 999999)}") os.makedirs(temp_dir, exist_ok=True) diff --git a/.claude/skills/db-run/SKILL.md b/.claude/skills/db-run/SKILL.md index c62ce4be..2c0ac7f3 100644 --- a/.claude/skills/db-run/SKILL.md +++ b/.claude/skills/db-run/SKILL.md @@ -29,7 +29,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. diff --git a/.claude/skills/db-update/SKILL.md b/.claude/skills/db-update/SKILL.md index 96c88624..fc788e1b 100644 --- a/.claude/skills/db-update/SKILL.md +++ b/.claude/skills/db-update/SKILL.md @@ -28,7 +28,7 @@ allowed-tools: 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если ветка не совпала — используй `default` -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если файла нет — предложи `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. @@ -42,7 +42,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <п | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/db-update/scripts/db-update.ps1 b/.claude/skills/db-update/scripts/db-update.ps1 index 3279a33b..89aecd5b 100644 --- a/.claude/skills/db-update/scripts/db-update.ps1 +++ b/.claude/skills/db-update/scripts/db-update.ps1 @@ -1,4 +1,4 @@ -# db-update v1.1 — Update 1C database configuration +# db-update v1.3 — Update 1C database configuration # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -131,8 +131,16 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } + # --- Validate connection --- -if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } +} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red exit 1 } @@ -142,6 +150,30 @@ $tempDir = Join-Path $env:TEMP "db_update_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch (file infobase only) --- + if ($AllExtensions) { + Write-Host "Error: ibcmd config apply does not support -AllExtensions (use -Extension)" -ForegroundColor Red + exit 1 + } + $arguments = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force") + if ($Dynamic -eq "+") { $arguments += "--dynamic=auto" } + elseif ($Dynamic -eq "-") { $arguments += "--dynamic=disable" } + if ($Extension) { $arguments += "--extension=$Extension" } + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "Database configuration updated successfully" -ForegroundColor Green + } else { + Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/db-update/scripts/db-update.py b/.claude/skills/db-update/scripts/db-update.py index 3e66dad6..2d98c862 100644 --- a/.claude/skills/db-update/scripts/db-update.py +++ b/.claude/skills/db-update/scripts/db-update.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# db-update v1.1 — Update 1C database configuration +# db-update v1.3 — Update 1C database configuration # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -87,11 +88,44 @@ def main(): v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" + # --- Validate connection --- - if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): + if engine == "ibcmd": + if not args.InfoBasePath: + print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr) + 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) 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) + sys.exit(1) + arguments = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"] + if args.Dynamic == "+": + arguments.append("--dynamic=auto") + elif args.Dynamic == "-": + arguments.append("--dynamic=disable") + if args.Extension: + arguments.append(f"--extension={args.Extension}") + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print("Database configuration updated successfully") + else: + print(f"Error updating database configuration (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Temp dir --- temp_dir = os.path.join(tempfile.gettempdir(), f"db_update_{random.randint(0, 999999)}") os.makedirs(temp_dir, exist_ok=True) diff --git a/.claude/skills/epf-build/SKILL.md b/.claude/skills/epf-build/SKILL.md index a7f13331..de59cc49 100644 --- a/.claude/skills/epf-build/SKILL.md +++ b/.claude/skills/epf-build/SKILL.md @@ -34,7 +34,7 @@ allowed-tools: 5. Если ветка не совпала — используй `default` 6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для EPF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки. -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. ## Команда @@ -47,7 +47,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <п | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/epf-build/scripts/epf-build.ps1 b/.claude/skills/epf-build/scripts/epf-build.ps1 index 7a021132..31ac7e25 100644 --- a/.claude/skills/epf-build/scripts/epf-build.ps1 +++ b/.claude/skills/epf-build/scripts/epf-build.ps1 @@ -1,4 +1,4 @@ -# epf-build v1.1 — Build external data processor or report (EPF/ERF) from XML sources +# epf-build v1.3 — Build external data processor or report (EPF/ERF) from XML sources # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -112,6 +112,13 @@ if (-not (Test-Path $V8Path)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } +if ($engine -eq "ibcmd" -and $InfoBaseServer -and $InfoBaseRef) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath or omit for stub)" -ForegroundColor Red + exit 1 +} + # --- Auto-create stub database if no connection specified --- $autoCreatedBase = $null if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { @@ -146,6 +153,24 @@ $tempDir = Join-Path $env:TEMP "epf_build_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch: build EPF/ERF via config import --out --- + $srcDir = Split-Path $SourceFile -Parent + $arguments = @("infobase", "config", "import", "$srcDir", "--out=$OutputFile", "--db-path=$InfoBasePath") + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "External data processor/report built successfully: $OutputFile" -ForegroundColor Green + } else { + Write-Host "Error building external data processor/report (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/epf-build/scripts/epf-build.py b/.claude/skills/epf-build/scripts/epf-build.py index 0447d32d..04abf2cb 100644 --- a/.claude/skills/epf-build/scripts/epf-build.py +++ b/.claude/skills/epf-build/scripts/epf-build.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# epf-build v1.1 — Build external data processor or report (EPF/ERF) from XML sources +# epf-build v1.3 — Build external data processor or report (EPF/ERF) from XML sources # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -84,6 +85,10 @@ def main(): # --- Resolve V8Path --- v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" + 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) + sys.exit(1) # --- Auto-create stub database if no connection specified --- auto_created_base = None @@ -117,6 +122,25 @@ def main(): os.makedirs(temp_dir, exist_ok=True) try: + if engine == "ibcmd": + # --- ibcmd branch: build EPF/ERF via config import --out --- + src_dir = os.path.dirname(os.path.abspath(args.SourceFile)) + arguments = ["infobase", "config", "import", src_dir, f"--out={args.OutputFile}", f"--db-path={args.InfoBasePath}"] + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print(f"External data processor/report built successfully: {args.OutputFile}") + else: + print(f"Error building external data processor/report (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Build arguments --- arguments = ["DESIGNER"] diff --git a/.claude/skills/epf-build/scripts/stub-db-create.ps1 b/.claude/skills/epf-build/scripts/stub-db-create.ps1 index dde1a7bf..7396c60c 100644 --- a/.claude/skills/epf-build/scripts/stub-db-create.ps1 +++ b/.claude/skills/epf-build/scripts/stub-db-create.ps1 @@ -1,4 +1,4 @@ -# stub-db-create v1.0 — Create temp 1C infobase with metadata stubs for EPF/ERF build +# stub-db-create v1.2 — Create temp 1C infobase with metadata stubs for EPF/ERF build # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -1252,6 +1252,29 @@ $propsXml $childObjLine } } +# --- 5a. Stub via ibcmd (one call: create [--import --apply]) --- +$stubEngine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } +if ($stubEngine -eq "ibcmd") { + Write-Host "Creating infobase (ibcmd): $TempBasePath" + $ibData = Join-Path $env:TEMP "stub_data_$(Get-Random)" + New-Item -ItemType Directory -Path $ibData -Force | Out-Null + $ibArgs = @("infobase", "create", "--db-path=$TempBasePath", "--create-database") + if ($hasRefTypes) { $ibArgs += "--import=$(Join-Path $TempBasePath 'cfg')", "--apply", "--force" } + $ibArgs += "--data=$ibData" + $ibOut = & $V8Path @ibArgs 2>&1 + $ibRc = $LASTEXITCODE + Remove-Item -Path $ibData -Recurse -Force -ErrorAction SilentlyContinue + if ($ibRc -ne 0) { + if ($ibOut) { Write-Host ($ibOut | Out-String) } + Write-Error "Failed to create stub infobase (code: $ibRc)" + exit 1 + } + if ($hasRefTypes) { Remove-Item -Path (Join-Path $TempBasePath "cfg") -Recurse -Force -ErrorAction SilentlyContinue } + Write-Host "[OK] Stub database created: $TempBasePath" + Write-Host $TempBasePath + exit 0 +} + # --- 5. Create infobase --- Write-Host "Creating infobase: $TempBasePath" $createArgs = "CREATEINFOBASE File=`"$TempBasePath`" /DisableStartupDialogs" diff --git a/.claude/skills/epf-build/scripts/stub-db-create.py b/.claude/skills/epf-build/scripts/stub-db-create.py index 2f9882e0..21868f1f 100644 --- a/.claude/skills/epf-build/scripts/stub-db-create.py +++ b/.claude/skills/epf-build/scripts/stub-db-create.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# stub-db-create v1.0 — Create temp 1C infobase with metadata stubs for EPF/ERF build +# stub-db-create v1.2 — Create temp 1C infobase with metadata stubs for EPF/ERF build # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -1034,6 +1034,32 @@ def main(): if register_columns: print('WARNING: Register column categories (Dimension/Resource/Attribute) are guessed. Form field bindings may not survive round-trip through a real database.') + # Stub via ibcmd (one call: create [--import --apply]) + stub_engine = "ibcmd" if os.path.basename(args.V8Path).lower().startswith("ibcmd") else "1cv8" + if stub_engine == "ibcmd": + import shutil + print(f'Creating infobase (ibcmd): {temp_base}') + ib_data = tempfile.mkdtemp(prefix="stub_data_") + ib_args = [args.V8Path, 'infobase', 'create', f'--db-path={temp_base}', '--create-database'] + if has_ref_types: + ib_args += [f'--import={os.path.join(temp_base, "cfg")}', '--apply', '--force'] + ib_args.append(f'--data={ib_data}') + result = subprocess.run(ib_args, capture_output=True, encoding='utf-8', errors='replace') + shutil.rmtree(ib_data, ignore_errors=True) + if result.returncode != 0: + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + print(f'Failed to create stub infobase (code: {result.returncode})', file=sys.stderr) + sys.exit(1) + if has_ref_types: + import shutil + shutil.rmtree(os.path.join(temp_base, 'cfg'), ignore_errors=True) + print(f'[OK] Stub database created: {temp_base}') + print(temp_base) + sys.exit(0) + # Create infobase print(f'Creating infobase: {temp_base}') result = subprocess.run( diff --git a/.claude/skills/epf-dump/SKILL.md b/.claude/skills/epf-dump/SKILL.md index a76106e7..b6057563 100644 --- a/.claude/skills/epf-dump/SKILL.md +++ b/.claude/skills/epf-dump/SKILL.md @@ -33,7 +33,7 @@ allowed-tools: 5. Если ветка не совпала — используй `default` 6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`. -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. ## Команда @@ -46,7 +46,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" <па | Параметр | Обязательный | Описание | |----------|:------------:|----------| -| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | +| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` | | `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseRef <имя>` | * | Имя базы на сервере | diff --git a/.claude/skills/epf-dump/scripts/epf-dump.ps1 b/.claude/skills/epf-dump/scripts/epf-dump.ps1 index 631a3619..fb01a70b 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.1 — Dump external data processor or report (EPF/ERF) to XML sources +# epf-dump v1.3 — Dump external data processor or report (EPF/ERF) to XML sources # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills <# .SYNOPSIS @@ -126,6 +126,19 @@ if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { exit 1 } +# --- Detect engine (ibcmd vs 1cv8) by exe name --- +$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } +if ($engine -eq "ibcmd") { + if (-not $InfoBasePath) { + Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red + exit 1 + } + if ($Format -eq "Plain") { + Write-Host "Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red + exit 1 + } +} + # --- Validate input file --- if (-not (Test-Path $InputFile)) { Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red @@ -142,6 +155,23 @@ $tempDir = Join-Path $env:TEMP "epf_dump_$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null try { + if ($engine -eq "ibcmd") { + # --- ibcmd branch: dump EPF/ERF via config export --file --- + $arguments = @("infobase", "config", "export", "--file=$InputFile", "$OutputDir", "--db-path=$InfoBasePath") + $arguments += "--data=$tempDir" + Write-Host "Running: ibcmd $($arguments -join ' ')" + $output = & $V8Path @arguments 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Host "External data processor/report dumped successfully to: $OutputDir" -ForegroundColor Green + } else { + Write-Host "Error dumping external data processor/report (code: $exitCode)" -ForegroundColor Red + } + if ($output) { Write-Host ($output | Out-String) } + exit $exitCode + } + + # --- 1cv8 branch --- # --- Build arguments --- $arguments = @("DESIGNER") diff --git a/.claude/skills/epf-dump/scripts/epf-dump.py b/.claude/skills/epf-dump/scripts/epf-dump.py index efc0e1a4..31c0140a 100644 --- a/.claude/skills/epf-dump/scripts/epf-dump.py +++ b/.claude/skills/epf-dump/scripts/epf-dump.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# epf-dump v1.1 — Dump external data processor or report (EPF/ERF) to XML sources +# epf-dump v1.3 — Dump external data processor or report (EPF/ERF) to XML sources # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse +import atexit import glob import json import os @@ -90,12 +91,20 @@ def main(): # --- Resolve V8Path --- v8path = resolve_v8path(args.V8Path) + engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8" # --- 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("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) + sys.exit(1) + if args.Format == "Plain": + print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr) + sys.exit(1) # --- Validate input file --- if not os.path.isfile(args.InputFile): @@ -111,6 +120,24 @@ def main(): os.makedirs(temp_dir, exist_ok=True) try: + if engine == "ibcmd": + # --- ibcmd branch: dump EPF/ERF via config export --file --- + arguments = ["infobase", "config", "export", f"--file={args.InputFile}", args.OutputDir, f"--db-path={args.InfoBasePath}"] + ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") + atexit.register(shutil.rmtree, ib_data, ignore_errors=True) + arguments.append(f"--data={ib_data}") + print(f"Running: ibcmd {' '.join(arguments)}") + result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace") + if result.returncode == 0: + print(f"External data processor/report dumped successfully to: {args.OutputDir}") + else: + print(f"Error dumping external data processor/report (code: {result.returncode})", file=sys.stderr) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) + # --- Build arguments --- arguments = ["DESIGNER"] diff --git a/.claude/skills/erf-build/SKILL.md b/.claude/skills/erf-build/SKILL.md index c87e85ba..860505b1 100644 --- a/.claude/skills/erf-build/SKILL.md +++ b/.claude/skills/erf-build/SKILL.md @@ -34,7 +34,7 @@ allowed-tools: 5. Если ветка не совпала — используй `default` 6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для ERF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки. -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. ## Команда diff --git a/.claude/skills/erf-dump/SKILL.md b/.claude/skills/erf-dump/SKILL.md index 3a05af7c..d79a66b7 100644 --- a/.claude/skills/erf-dump/SKILL.md +++ b/.claude/skills/erf-dump/SKILL.md @@ -33,7 +33,7 @@ allowed-tools: 5. Если ветка не совпала — используй `default` 6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`. -Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` +Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files). Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. ## Команда diff --git a/tests/skills/README.md b/tests/skills/README.md index f01106cf..dc58b1f4 100644 --- a/tests/skills/README.md +++ b/tests/skills/README.md @@ -31,6 +31,41 @@ node tests/skills/verify-snapshots.mjs --help # полный Перепрогоняет навык из DSL кейса и грузит результат в 1С — отлавливает случаи, когда снапшоты обновили, но платформа уже не принимает выход. +## Интеграционные тесты + +Помимо snapshot-кейсов есть многошаговые сценарии в `integration/<имя>.test.mjs` — цепочка навыков (init → compile → build → validate…), проверяющая что навыки работают вместе. Запуск: + +```bash +node tests/skills/runner.mjs integration # все интеграционные +node tests/skills/runner.mjs integration/platform-partial # один сценарий +``` + +Тест-модуль экспортирует `name`, `setup`, `steps` и опционально: + +| Экспорт | Описание | +|---|---| +| `requiresPlatform` | `true` — нужен 1С (резолвится из `.v8-project.json`). Без платформы тест `○ skipped` | +| `engines` | Массив движков для **матрицы**: по умолчанию `['1cv8']`. `['1cv8','ibcmd']` — те же шаги прогоняются на обоих движках | + +### Движковая матрица (1cv8 / ibcmd) + +Навыки `db-*`/`epf-*` выбирают движок по имени exe в `-V8Path` (опт-ин: `ibcmd.exe` → ibcmd, иначе DESIGNER). Тест с `engines: ['1cv8','ibcmd']` прогоняется по разу на каждый движок: на ibcmd-проходе плейсхолдер `{v8path}` подставляется в `ibcmd.exe`, на 1cv8 — в каталог `bin` (авто-резолв `1cv8.exe`). Результаты помечаются суффиксом id: `… [1cv8]` / `… [ibcmd]`. + +ibcmd-проход автоматически `○ skipped`, если рядом с `1cv8.exe` нет `ibcmd.exe`. Шаги тестов при этом **не меняются** — добавляется одна строка `export const engines`. Так контракт «операция держится на обоих движках» кодируется без дублирования сценария. + +### Типы шагов + +Шаг — это запуск навыка (`script` + `args` + опц. `input`/`validate`) либо один из вспомогательных: + +| Поле шага | Действие | +|---|---| +| `script` + `args` | Запустить навык. `args` поддерживают плейсхолдеры `{workDir}`, `{inputFile}`, `{v8path}` и др. | +| `input` | JSON, передаётся навыку через temp-файл (`{inputFile}`) | +| `writeFile` + `content` | Записать файл (путь — плейсхолдеры) | +| `editFile` + `replace` + `with` | Подстановочная замена в файле (напр. вставить маркер). Падает, если паттерн не найден | +| `assertContains` + `expect` | Упасть, если файл не содержит подстроку (проверка round-trip) | +| `validate` | Доп. валидация навыком после шага (только с `--with-validation`) | + ## Что делать при падении 1. Смотри **case id** в выводе — это путь к файлу кейса (можно перезапустить: `node runner.mjs `) @@ -221,10 +256,11 @@ node tests/skills/runner.mjs cases/meta-compile/enum --update-snapshots # од ``` tests/skills/ - runner.mjs # тест-раннер (snapshot-сравнение) + runner.mjs # тест-раннер (snapshot-сравнение + интеграционные) verify-snapshots.mjs # платформенная верификация снапшотов README.md # этот файл .cache/ # кэш фикстур (в .gitignore) + integration/ # многошаговые сценарии (*.test.mjs), в т.ч. движковая матрица 1cv8/ibcmd cases/ <навык>/ _skill.json # конфиг навыка diff --git a/tests/skills/integration/platform-config.test.mjs b/tests/skills/integration/platform-config.test.mjs index 599d3525..71e717d2 100644 --- a/tests/skills/integration/platform-config.test.mjs +++ b/tests/skills/integration/platform-config.test.mjs @@ -5,6 +5,9 @@ export const name = 'Загрузка конфигурации в платформу 1С'; export const setup = 'none'; export const requiresPlatform = true; +// Engine matrix: same load path must hold on DESIGNER (1cv8) and ibcmd. +// The ibcmd pass is skipped automatically when ibcmd.exe is not present. +export const engines = ['1cv8', 'ibcmd']; export const steps = [ // ── 1. Build minimal config ── diff --git a/tests/skills/integration/platform-epf.test.mjs b/tests/skills/integration/platform-epf.test.mjs index 626f9afc..c2ffc4b9 100644 --- a/tests/skills/integration/platform-epf.test.mjs +++ b/tests/skills/integration/platform-epf.test.mjs @@ -5,6 +5,9 @@ export const name = 'Сборка и разборка внешней обработки (roundtrip)'; export const setup = 'none'; export const requiresPlatform = true; +// Engine matrix: same roundtrip must hold on DESIGNER (1cv8) and ibcmd. +// The ibcmd pass is skipped automatically when ibcmd.exe is not present. +export const engines = ['1cv8', 'ibcmd']; export const steps = [ // ── 1. Create EPF ── diff --git a/tests/skills/integration/platform-partial.test.mjs b/tests/skills/integration/platform-partial.test.mjs new file mode 100644 index 00000000..d8a850db --- /dev/null +++ b/tests/skills/integration/platform-partial.test.mjs @@ -0,0 +1,86 @@ +// platform-partial.test.mjs — partial dump/load round-trip with marker survival +// Requires: 1C platform (1cv8.exe) via .v8-project.json +// Exercises partial config import (Mode Partial -Files) and partial export +// (Mode Partial -Objects) on BOTH engines: DESIGNER (1cv8) and ibcmd. Proves a +// partially-loaded change actually propagates by round-tripping a marker +// (ibtestMARK). Mirrors the proven debug/ibtest/lifecycle.sh partial flow. + +export const name = 'Частичная выгрузка/загрузка объекта (round-trip маркера)'; +export const setup = 'none'; +export const requiresPlatform = true; +// Engine matrix: partial round-trip must hold on DESIGNER (1cv8) and ibcmd. +export const engines = ['1cv8', 'ibcmd']; + +export const steps = [ + // ── 1. Build minimal config ── + { + name: 'cf-init: пустая конфигурация', + script: 'cf-init/scripts/cf-init', + args: { '-Name': 'ИбcmdТест', '-OutputDir': '{workDir}/config' }, + }, + { + name: 'meta-compile: Справочник Товары', + script: 'meta-compile/scripts/meta-compile', + input: { type: 'Catalog', name: 'Товары', codeLength: 9, descriptionLength: 100 }, + args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}/config' }, + }, + { + name: 'cf-edit: регистрация справочника', + script: 'cf-edit/scripts/cf-edit', + input: [{ operation: 'add-childObject', value: 'Catalog.Товары' }], + args: { '-ConfigPath': '{workDir}/config', '-DefinitionFile': '{inputFile}' }, + }, + + // ── 2. Create file IB and load baseline (full, unmarked) via ibcmd ── + { + name: 'db-create: файловая ИБ', + script: 'db-create/scripts/db-create', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb' }, + }, + { + name: 'db-load-xml: загрузка конфигурации (Full)', + script: 'db-load-xml/scripts/db-load-xml', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', '-ConfigDir': '{workDir}/config' }, + }, + { + name: 'db-update: обновление БД', + script: 'db-update/scripts/db-update', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb' }, + }, + + // ── 3. Mark the source object, then partial-LOAD just that object ── + { + name: 'editFile: маркер в Comment справочника', + editFile: '{workDir}/config/Catalogs/Товары.xml', + replace: '', + with: 'ibtestMARK', + }, + { + name: 'db-load-xml: частичная загрузка Товары (Partial)', + script: 'db-load-xml/scripts/db-load-xml', + args: { + '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', + '-ConfigDir': '{workDir}/config', '-Mode': 'Partial', '-Files': 'Catalogs/Товары.xml', + }, + }, + { + name: 'db-update: обновление БД (после partial load)', + script: 'db-update/scripts/db-update', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb' }, + }, + + // ── 4. Partial-DUMP the object back and verify the marker survived ── + { + name: 'db-dump-xml: частичная выгрузка Товары (Partial)', + script: 'db-dump-xml/scripts/db-dump-xml', + args: { + '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', + '-ConfigDir': '{workDir}/pv', '-Mode': 'Partial', '-Objects': 'Справочник.Товары', + }, + }, + { + name: 'assert: маркер ibtestMARK пережил round-trip', + assertContains: '{workDir}/pv/Catalogs/Товары.xml', + expect: 'ibtestMARK', + }, +]; diff --git a/tests/skills/runner.mjs b/tests/skills/runner.mjs index bfaddc80..e47560b0 100644 --- a/tests/skills/runner.mjs +++ b/tests/skills/runner.mjs @@ -972,10 +972,12 @@ function loadV8Context() { const v8bin = proj.v8path; const v8exe = v8bin ? (existsSync(join(v8bin, '1cv8.exe')) ? join(v8bin, '1cv8.exe') : null) : null; if (!v8exe) return null; + const ibcmdExe = v8bin && existsSync(join(v8bin, 'ibcmd.exe')) ? join(v8bin, 'ibcmd.exe') : null; const defaultDb = proj.databases?.find(d => d.id === proj.default) || proj.databases?.[0]; return { v8path: v8bin, v8exe, + ibcmdExe, dbPath: defaultDb?.path || '', dbUser: defaultDb?.user || '', dbPassword: defaultDb?.password || '', @@ -994,20 +996,40 @@ async function discoverIntegration(filter) { const id = `integration/${testName}`; if (filter && !id.startsWith(filter) && !id.includes(filter)) continue; const mod = await import(`file://${join(INTEGRATION, file).replace(/\\/g, '/')}`); - results.push({ id, name: mod.name || testName, steps: mod.steps || [], file, cache: mod.cache, setup: mod.setup || 'empty-config', requiresPlatform: !!mod.requiresPlatform }); + const engines = Array.isArray(mod.engines) && mod.engines.length ? mod.engines : ['1cv8']; + results.push({ id, name: mod.name || testName, steps: mod.steps || [], file, cache: mod.cache, setup: mod.setup || 'empty-config', requiresPlatform: !!mod.requiresPlatform, engines }); } return results; } +// Run a test once per declared engine (engine matrix). The ibcmd pass swaps +// {v8path} → ibcmd.exe so the same steps exercise the ibcmd opt-in branch. async function runIntegrationTest(test, opts) { + const engines = test.engines && test.engines.length ? test.engines : ['1cv8']; + // No platform at all → single skipped result (don't multiply across engines) + if (test.requiresPlatform && !opts.v8ctx) { + return [{ id: test.id, name: test.name, passed: true, skipped: true, skipReason: 'no platform', steps: [], elapsed: '0.0s', errors: [] }]; + } + const out = []; + const labelEngine = engines.length > 1; + for (const engine of engines) { + out.push(await runIntegrationOnce(test, opts, engine, labelEngine)); + } + return out; +} + +async function runIntegrationOnce(test, opts, engine, labelEngine) { const t0 = performance.now(); const stepResults = []; let workspace = null; + const idSuffix = labelEngine ? ` [${engine}]` : ''; + const id = test.id + idSuffix; + const name = test.name + idSuffix; - // Skip platform-dependent tests if platform unavailable - if (test.requiresPlatform && !opts.v8ctx) { + // ibcmd pass requires ibcmd.exe alongside 1cv8.exe + if (engine === 'ibcmd' && !opts.v8ctx?.ibcmdExe) { const elapsed = ((performance.now() - t0) / 1000).toFixed(1); - return { id: test.id, name: test.name, passed: true, skipped: true, steps: [], elapsed: `${elapsed}s`, errors: [] }; + return { id, name, passed: true, skipped: true, skipReason: 'no ibcmd.exe', steps: [], elapsed: `${elapsed}s`, errors: [] }; } try { @@ -1015,17 +1037,19 @@ async function runIntegrationTest(test, opts) { const fixturePath = test.setup === 'none' ? null : ensureSetup(test.setup, opts.runtime, CASES); if (fixturePath === SKIP) { const elapsed = ((performance.now() - t0) / 1000).toFixed(1); - return { id: test.id, name: test.name, passed: true, skipped: true, steps: [], elapsed: `${elapsed}s`, errors: [] }; + return { id, name, passed: true, skipped: true, skipReason: 'fixture unavailable', steps: [], elapsed: `${elapsed}s`, errors: [] }; } workspace = createWorkspace(fixturePath, false); const workDir = workspace.path; - // Platform placeholders + // Platform placeholders. {v8path} resolves to ibcmd.exe on the ibcmd pass + // (engine detected by exe name) and to the bin dir otherwise (auto-resolves 1cv8.exe). const v8 = opts.v8ctx || {}; + const v8pathForEngine = engine === 'ibcmd' ? (v8.ibcmdExe || '') : (v8.v8path || ''); const replacePlaceholders = (s) => s .replace('{workDir}', workDir) .replace('{inputFile}', '') - .replace('{v8path}', v8.v8path || '') + .replace('{v8path}', v8pathForEngine) .replace('{v8exe}', v8.v8exe || '') .replace('{dbPath}', v8.dbPath || '') .replace('{dbUser}', v8.dbUser || '') @@ -1052,6 +1076,41 @@ async function runIntegrationTest(test, opts) { continue; } + // editFile step: substring replace in an existing file (e.g. inject a marker) + if (step.editFile) { + try { + const target = replacePlaceholders(step.editFile); + const abs = target.includes(':') || target.startsWith('/') ? target : join(workDir, target); + let txt = readFileSync(abs, 'utf8'); + if (!txt.includes(step.replace)) throw new Error(`pattern not found: ${step.replace}`); + txt = txt.replace(step.replace, replacePlaceholders(step.with ?? '')); + writeFileSync(abs, txt, 'utf8'); + const stepElapsed = ((performance.now() - stepT0) / 1000).toFixed(1); + stepResults.push({ name: step.name, passed: true, elapsed: `${stepElapsed}s` }); + } catch (e) { + stepResults.push({ name: step.name, passed: false, error: `editFile failed: ${e.message}` }); + break; + } + continue; + } + + // assertContains step: fail unless target file contains the expected substring + if (step.assertContains) { + try { + const target = replacePlaceholders(step.assertContains); + const abs = target.includes(':') || target.startsWith('/') ? target : join(workDir, target); + const txt = existsSync(abs) ? readFileSync(abs, 'utf8') : ''; + const needle = replacePlaceholders(step.expect ?? ''); + if (!txt.includes(needle)) throw new Error(`"${needle}" not found in ${target}`); + const stepElapsed = ((performance.now() - stepT0) / 1000).toFixed(1); + stepResults.push({ name: step.name, passed: true, elapsed: `${stepElapsed}s` }); + } catch (e) { + stepResults.push({ name: step.name, passed: false, error: `assert failed: ${e.message}` }); + break; + } + continue; + } + // Write input if provided let inputFile = null; if (step.input) { @@ -1112,10 +1171,10 @@ async function runIntegrationTest(test, opts) { const allPassed = stepResults.every(s => s.passed); const elapsed = ((performance.now() - t0) / 1000).toFixed(1); - return { id: test.id, name: test.name, passed: allPassed, steps: stepResults, elapsed: `${elapsed}s`, errors: allPassed ? [] : stepResults.filter(s => !s.passed).map(s => s.error) }; + return { id, name, passed: allPassed, steps: stepResults, elapsed: `${elapsed}s`, errors: allPassed ? [] : stepResults.filter(s => !s.passed).map(s => s.error) }; } catch (e) { const elapsed = ((performance.now() - t0) / 1000).toFixed(1); - return { id: test.id, name: test.name, passed: false, steps: stepResults, elapsed: `${elapsed}s`, errors: [`Runner error: ${e.message}`] }; + return { id, name, passed: false, steps: stepResults, elapsed: `${elapsed}s`, errors: [`Runner error: ${e.message}`] }; } finally { if (workspace) cleanupWorkspace(workspace); } @@ -1125,7 +1184,7 @@ function printIntegrationReport(results, opts) { console.log(''); for (const r of results) { const icon = r.skipped ? '\u25CB' : r.passed ? '\u2713' : '\u2717'; - const suffix = r.skipped ? ' [skipped — no platform]' : ''; + const suffix = r.skipped ? ` [skipped — ${r.skipReason || 'no platform'}]` : ''; console.log(` ${icon} ${r.name} (${r.elapsed}) ${r.id}${suffix}`); for (const step of r.steps) { const sIcon = step.passed ? '\u2713' : '\u2717'; @@ -1166,7 +1225,7 @@ async function main() { console.log(`\nRunning ${integrationTests.length} integration test(s)... [runtime: ${opts.runtime}${valStr}]`); const integrationResults = []; for (const test of integrationTests) { - integrationResults.push(await runIntegrationTest(test, opts)); + integrationResults.push(...await runIntegrationTest(test, opts)); } integrationOk = printIntegrationReport(integrationResults, opts); }