feat(db-*): после загрузки расширения проверяется его применимость

Платформа отчитывается успехом и о расширении, которое не применит: отказ
всплывает лениво, при первом вызове метода, записью в журнал регистрации.
Теперь db-load-xml, db-load-git, db-load-cf и db-update после успешной операции
с расширением спрашивают платформу явно и печатают предупреждение; код возврата
операции не меняется — применение неприменимого расширения не разрушительно,
платформа просто работает по оригиналу. Поднять код возврата может -StrictLog,
он для регрессов и в инструкциях не значится.

Проверка обязана быть ОТДЕЛЬНЫМ запуском платформы: в одной командной строке
DESIGNER выполняет только последнюю пакетную команду, и дописанная проверка
отменила бы саму загрузку (замер: /LoadConfigFromFiles + /CheckCanApply… → код 0,
пустой лог, расширение в базе не изменилось). Об этом сказано в теле функции,
чтобы «оптимизация» не вернула команды в одну строку.

Выключатель: ключ -NoApplyCheck и настройка проекта extensionApplyCheck (ключ
команды сильнее). В ветке ibcmd проверка идёт соседним 1cv8; если его рядом нет —
одна строка [note], а не тишина.

Общий блок (Invoke-ApplyCheck / Invoke-ApplyCheckReport / Get-ApplyCheckEnabled и
их py-двойники) объявлен семьёй в check-inline-drift.mjs: разъехавшиеся копии
означали бы, что один навык предупреждает, а соседний по той же операции молчит.
db-load-cf получил недостающие копии Find-V8Project и ключ -StrictLog.

Раннер: у фейковой платформы появился отдельный ответ на проверку (spec.check),
выбираемый по составу аргументов, — иначе сценарий «загрузка прошла, проверка
провалилась» невыразим.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
This commit is contained in:
Nick Shirokov
2026-09-05 14:06:59 +03:00
co-authored by Claude Opus 5
parent 5943828216
commit a3ea0b7be6
28 changed files with 1137 additions and 53 deletions
+2
View File
@@ -107,6 +107,8 @@ allowed-tools:
Повторный вызов `Before`/`After`/`Instead` для уже перехваченного метода дубль не создаёт (`[ПРОПУЩЕН]`).
`-Check` смотрит исходники. Вердикт платформы — уже после загрузки в базу: `/db-cfe-admin check`.
## Команда
```powershell
+2
View File
@@ -12,6 +12,8 @@ allowed-tools:
Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты, права ролей. Аналог `/cf-validate`, но для расширений.
Проверяются исходники. Применимость — уже после загрузки в базу: `/db-cfe-admin check`.
## Параметры
| Параметр | Обяз. | Умолч. | Описание |
@@ -858,6 +858,9 @@ if ($cmd -eq 'check') {
$done = 0
$rows = @()
# Тот же вопрос платформе задаёт постусловие загрузчиков (семья Invoke-ApplyCheck в
# db-load-xml): там ответ нужен одной строкой предупреждения, здесь — строкой отчёта
# рядом с остальными проверками, поэтому запуск идёт своим Invoke-Designer.
if ($checkList -contains 'apply') {
$opArgs = @('/CheckCanApplyConfigurationExtensions')
if ($target) { $opArgs += '-Extension', "`"$target`"" }
@@ -880,6 +880,9 @@ def main():
done = 0
rows = []
# Тот же вопрос платформе задаёт постусловие загрузчиков (семья run_apply_check в
# db-load-xml): там ответ нужен одной строкой предупреждения, здесь — строкой отчёта
# рядом с остальными проверками, поэтому запуск идёт своим invoke_designer.
if "apply" in check_list:
op_args = ["/CheckCanApplyConfigurationExtensions"]
if target:
+1
View File
@@ -77,6 +77,7 @@ allowed-tools:
| `v8args` | array | Доп. аргументы запуска `1cv8.exe` для всех навыков, напр. `["/UseHwLicenses+"]` |
| `ibcmdargs` | array | Доп. аргументы `ibcmd` (форма `--ключ=значение`) |
| `newObjectPosition` | `"end"` / `"byName"` | Куда попадает новый объект в составе конфигурации: в конец своего вида (по умолчанию) или на место по имени. Переопределяется в `databases[]` |
| `extensionApplyCheck` | bool | Проверять ли применимость расширения после загрузки в базу (по умолчанию `true`). Разово отключается ключом `-NoApplyCheck` |
| `databases` | array | Массив баз данных |
| `default` | string | id базы по умолчанию |
+1
View File
@@ -51,6 +51,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <п
| `-Password <пароль>` | нет | Пароль |
| `-InputFile <путь>` | да | Путь к CF-файлу |
| `-Extension <имя>` | нет | Загрузить как расширение |
| `-NoApplyCheck` | нет | Не проверять применимость расширения после загрузки |
| `-AllExtensions` | нет | Загрузить все расширения из архива |
| `-AdditionalV8Arguments <список>` | нет | Доп. аргументы запуска `1cv8.exe` через запятую, напр. `/UseHwLicenses+` |
| `-AdditionalIbcmdArguments <список>` | нет | Доп. аргументы `ibcmd` через запятую, в форме `--ключ=значение` |
@@ -78,6 +78,16 @@ param(
[Parameter(Mandatory=$false)]
[switch]$AllExtensions,
[Parameter(Mandatory=$false)]
# Ключ для регрессов и верификации снапшотов, не для повседневного вызова: в SKILL.md
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
# но расширение при этом неприменимо.
[switch]$StrictLog,
# Пропустить проверку применимости расширения после загрузки.
[Parameter(Mandatory=$false)]
[switch]$NoApplyCheck,
[Parameter(Mandatory=$false)]
[string[]]$AdditionalV8Arguments = @(),
@@ -403,6 +413,85 @@ function Write-PlatformOutput {
}
function Find-V8Project([string]$startDir) {
$d = $startDir
for ($i = 0; $i -lt 20 -and $d; $i++) {
$pj = Join-Path $d ".v8-project.json"
if (Test-Path $pj) { return $pj }
$parent = [System.IO.Path]::GetDirectoryName($d)
if ($parent -eq $d) { break }
$d = $parent
}
return $null
}
# Постусловие применимости расширения: платформа отчитывается успехом и о расширении, которое
# не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал регистрации.
#
# Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной командной
# строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча отбрасывает —
# проверено на 8.3.24, /LoadConfigFromFiles вместе с /CheckCanApplyConfigurationExtensions
# завершились кодом 0 с пустым логом, и загрузка не состоялась.
#
# Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний исполняемый файл.
function Invoke-ApplyCheck {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$exeDir = Split-Path $Exe -Parent
$exeLeaf = Split-Path $Exe -Leaf
$v8 = if ($exeLeaf -match '^ibcmd') { Join-Path $exeDir ("1cv8" + [System.IO.Path]::GetExtension($Exe)) } else { $Exe }
if (-not (Test-Path $v8)) { return @{ Skipped = $true; Reason = "1cv8 not found at $v8"; ExitCode = 0; Lines = @() } }
$dir = Join-Path $env:TEMP "apply_check_$(Get-Random)"
New-Item -ItemType Directory -Path $dir -Force | Out-Null
try {
$a = @("DESIGNER") + $ConnArgs + @("/CheckCanApplyConfigurationExtensions")
if ($Extension) { $a += "-Extension", "`"$Extension`"" }
$outFile = Join-Path $dir "check_log.txt"
$a += "/Out", "`"$outFile`"", "/DisableStartupDialogs"
$a += $ExtraArgs
$res = Invoke-PlatformProcess $v8 $a -PreQuoted
$lines = @()
if (Test-Path $outFile) {
$raw = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($raw) { $lines = @($raw -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) }
}
return @{ Skipped = $false; Reason = ''; ExitCode = $res.ExitCode; Lines = $lines }
} finally {
if (Test-Path $dir) { Remove-Item -Path $dir -Recurse -Force -ErrorAction SilentlyContinue }
}
}
# Проверить и напечатать. $true, если платформа расширение не применит — вызывающий решает,
# поднимать ли код возврата (строгий режим).
function Invoke-ApplyCheckReport {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$ac = Invoke-ApplyCheck $Exe $ConnArgs $Extension $ExtraArgs
if ($ac.Skipped) {
Write-Host "[note] applicability check skipped: $($ac.Reason)" -ForegroundColor Yellow
return $false
}
if ($ac.ExitCode -ne 0 -or $ac.Lines.Count -gt 0) {
Write-Host "[warning] the extension is loaded, but the platform will not apply it:" -ForegroundColor Yellow
foreach ($l in $ac.Lines) { Write-Host " $l" -ForegroundColor Yellow }
return $true
}
return $false
}
# Проверять ли применимость: -NoApplyCheck сильнее настройки проекта.
function Get-ApplyCheckEnabled {
param([switch]$Disabled)
if ($Disabled) { return $false }
$pf = Find-V8Project (Get-Location).Path
if ($pf) {
try {
$proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($null -ne $proj.extensionApplyCheck) { return [bool]$proj.extensionApplyCheck }
} catch {}
}
return $true
}
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
# --- Resolve additional arguments for the selected engine ---
@@ -453,22 +542,32 @@ try {
} else {
Write-Host "Error loading configuration (code: $exitCode)$(Get-ExitAnnotation $exitCode)" -ForegroundColor Red
}
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
$acConn = @("/F", "`"$InfoBasePath`"")
if ($UserName) { $acConn += "/N`"$UserName`"" }
if ($Password) { $acConn += "/P`"$Password`"" }
if ((Invoke-ApplyCheckReport $V8Path $acConn $Extension @()) -and $StrictLog) { $exitCode = 1 }
}
Write-PlatformOutput $output
exit $exitCode
}
# --- 1cv8 branch ---
# --- Build arguments ---
$arguments = @("DESIGNER")
# Аргументы соединения собираем отдельно: тем же набором пойдёт запуск проверки применимости.
$connArgs = @()
if ($InfoBaseServer -and $InfoBaseRef) {
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
$connArgs += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
} else {
$arguments += "/F", "`"$InfoBasePath`""
$connArgs += "/F", "`"$InfoBasePath`""
}
if ($UserName) { $arguments += "/N`"$UserName`"" }
if ($Password) { $arguments += "/P`"$Password`"" }
if ($UserName) { $connArgs += "/N`"$UserName`"" }
if ($Password) { $connArgs += "/P`"$Password`"" }
$arguments = @("DESIGNER") + $connArgs
$arguments += "/LoadCfg", "`"$InputFile`""
@@ -507,6 +606,11 @@ try {
}
Write-PlatformOutput $__v8.Output
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
if ((Invoke-ApplyCheckReport $V8Path $connArgs $Extension $extraArgs) -and $StrictLog) { $exitCode = 1 }
}
exit $exitCode
} finally {
+117 -5
View File
@@ -379,6 +379,91 @@ def print_platform_output(result):
print("--- End ---")
def _sg_find_v8project(start_dir):
d = start_dir
for _ in range(20):
if not d:
break
pj = os.path.join(d, ".v8-project.json")
if os.path.isfile(pj):
return pj
parent = os.path.dirname(d)
if parent == d:
break
d = parent
return None
def run_apply_check(exe, conn_args, extension, extra_args):
"""Постусловие применимости расширения: платформа отчитывается успехом и о расширении,
которое не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал
регистрации.
Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной
командной строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча
отбрасывает — проверено на 8.3.24, /LoadConfigFromFiles вместе с
/CheckCanApplyConfigurationExtensions завершились кодом 0 с пустым логом, и загрузка не
состоялась.
Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний файл.
"""
exe_dir = os.path.dirname(exe)
leaf = os.path.basename(exe)
if leaf.lower().startswith("ibcmd"):
v8 = os.path.join(exe_dir, "1cv8" + os.path.splitext(leaf)[1])
else:
v8 = exe
if not os.path.isfile(v8):
return {"skipped": True, "reason": f"1cv8 not found at {v8}", "exit": 0, "lines": []}
temp_dir = tempfile.mkdtemp(prefix="apply_check_")
try:
a = ["DESIGNER"] + list(conn_args) + ["/CheckCanApplyConfigurationExtensions"]
if extension:
a += ["-Extension", f'"{extension}"']
out_file = os.path.join(temp_dir, "check_log.txt")
a += ["/Out", f'"{out_file}"', "/DisableStartupDialogs"]
a += list(extra_args)
r = run_v8(v8, a)
lines = []
if os.path.isfile(out_file):
with open(out_file, encoding="utf-8-sig", errors="replace") as f:
lines = [x.strip() for x in f.read().splitlines() if x.strip()]
return {"skipped": False, "reason": "", "exit": r.returncode, "lines": lines}
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def apply_check_report(exe, conn_args, extension, extra_args):
"""Проверить и напечатать. True, если платформа расширение не применит — вызывающий решает,
поднимать ли код возврата (строгий режим)."""
ac = run_apply_check(exe, conn_args, extension, extra_args)
if ac["skipped"]:
print(f"[note] applicability check skipped: {ac['reason']}")
return False
if ac["exit"] != 0 or ac["lines"]:
print("[warning] the extension is loaded, but the platform will not apply it:")
for line in ac["lines"]:
print(f" {line}")
return True
return False
def apply_check_enabled(disabled):
"""Проверять ли применимость: -NoApplyCheck сильнее настройки проекта."""
if disabled:
return False
pf = _sg_find_v8project(os.getcwd())
if pf:
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
if proj.get("extensionApplyCheck") is not None:
return bool(proj.get("extensionApplyCheck"))
except Exception:
pass
return True
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
"""Run an ibcmd command non-interactively.
@@ -444,6 +529,12 @@ def main():
parser.add_argument("-InputFile", required=True)
parser.add_argument("-Extension", default="")
parser.add_argument("-AllExtensions", action="store_true")
# Ключ для регрессов и верификации снапшотов, не для повседневного вызова: в SKILL.md
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
# но расширение при этом неприменимо.
parser.add_argument("-StrictLog", action="store_true")
# Пропустить проверку применимости расширения после загрузки.
parser.add_argument("-NoApplyCheck", action="store_true")
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
@@ -509,6 +600,18 @@ def main():
print(f"Configuration loaded successfully from: {args.InputFile}")
else:
print(f"Error loading configuration (code: {result.returncode}){describe_exit(result.returncode)}")
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
exit_code = result.returncode
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
ac_conn = ["/F", f'"{args.InfoBasePath}"']
if args.UserName:
ac_conn.append(f'/N"{args.UserName}"')
if args.Password:
ac_conn.append(f'/P"{args.Password}"')
if apply_check_report(v8path, ac_conn, args.Extension, []) and args.StrictLog:
exit_code = 1
sys.exit(result.returncode)
# --- Temp dir ---
@@ -517,17 +620,20 @@ def main():
try:
# --- Build arguments ---
arguments = ["DESIGNER"]
# Аргументы соединения собираем отдельно: тем же набором пойдёт проверка применимости.
conn_args = []
if args.InfoBaseServer and args.InfoBaseRef:
arguments.extend(["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"'])
conn_args.extend(["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"'])
else:
arguments.extend(["/F", f'"{args.InfoBasePath}"'])
conn_args.extend(["/F", f'"{args.InfoBasePath}"'])
if args.UserName:
arguments.append(f'/N"{args.UserName}"')
conn_args.append(f'/N"{args.UserName}"')
if args.Password:
arguments.append(f'/P"{args.Password}"')
conn_args.append(f'/P"{args.Password}"')
arguments = ["DESIGNER"] + conn_args
arguments.extend(["/LoadCfg", f'"{args.InputFile}"'])
@@ -565,6 +671,12 @@ def main():
except Exception:
pass
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
if apply_check_report(v8path, conn_args, args.Extension, extra_args) and args.StrictLog:
exit_code = 1
print_platform_output(result)
sys.exit(exit_code)
+1
View File
@@ -55,6 +55,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <
| `-Source <источник>` | нет | `All` (по умолч.) / `Staged` / `Unstaged` / `Commit` |
| `-CommitRange <range>` | для Commit | Диапазон коммитов (напр. `HEAD~3..HEAD`) |
| `-Extension <имя>` | нет | Загрузить в расширение |
| `-NoApplyCheck` | нет | Не проверять применимость расширения после загрузки |
| `-AllExtensions` | нет | Загрузить все расширения |
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
| `-DryRun` | нет | Только показать что будет загружено (без загрузки) |
@@ -116,6 +116,10 @@ param(
# но в логе есть отбраковка.
[switch]$StrictLog,
# Пропустить проверку применимости расширения после загрузки.
[Parameter(Mandatory=$false)]
[switch]$NoApplyCheck,
[Parameter(Mandatory=$false)]
[string]$RepositoryPath,
@@ -573,6 +577,73 @@ function Find-SilentRejections {
}
# Постусловие применимости расширения: платформа отчитывается успехом и о расширении, которое
# не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал регистрации.
#
# Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной командной
# строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча отбрасывает —
# проверено на 8.3.24, /LoadConfigFromFiles вместе с /CheckCanApplyConfigurationExtensions
# завершились кодом 0 с пустым логом, и загрузка не состоялась.
#
# Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний исполняемый файл.
function Invoke-ApplyCheck {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$exeDir = Split-Path $Exe -Parent
$exeLeaf = Split-Path $Exe -Leaf
$v8 = if ($exeLeaf -match '^ibcmd') { Join-Path $exeDir ("1cv8" + [System.IO.Path]::GetExtension($Exe)) } else { $Exe }
if (-not (Test-Path $v8)) { return @{ Skipped = $true; Reason = "1cv8 not found at $v8"; ExitCode = 0; Lines = @() } }
$dir = Join-Path $env:TEMP "apply_check_$(Get-Random)"
New-Item -ItemType Directory -Path $dir -Force | Out-Null
try {
$a = @("DESIGNER") + $ConnArgs + @("/CheckCanApplyConfigurationExtensions")
if ($Extension) { $a += "-Extension", "`"$Extension`"" }
$outFile = Join-Path $dir "check_log.txt"
$a += "/Out", "`"$outFile`"", "/DisableStartupDialogs"
$a += $ExtraArgs
$res = Invoke-PlatformProcess $v8 $a -PreQuoted
$lines = @()
if (Test-Path $outFile) {
$raw = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($raw) { $lines = @($raw -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) }
}
return @{ Skipped = $false; Reason = ''; ExitCode = $res.ExitCode; Lines = $lines }
} finally {
if (Test-Path $dir) { Remove-Item -Path $dir -Recurse -Force -ErrorAction SilentlyContinue }
}
}
# Проверить и напечатать. $true, если платформа расширение не применит — вызывающий решает,
# поднимать ли код возврата (строгий режим).
function Invoke-ApplyCheckReport {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$ac = Invoke-ApplyCheck $Exe $ConnArgs $Extension $ExtraArgs
if ($ac.Skipped) {
Write-Host "[note] applicability check skipped: $($ac.Reason)" -ForegroundColor Yellow
return $false
}
if ($ac.ExitCode -ne 0 -or $ac.Lines.Count -gt 0) {
Write-Host "[warning] the extension is loaded, but the platform will not apply it:" -ForegroundColor Yellow
foreach ($l in $ac.Lines) { Write-Host " $l" -ForegroundColor Yellow }
return $true
}
return $false
}
# Проверять ли применимость: -NoApplyCheck сильнее настройки проекта.
function Get-ApplyCheckEnabled {
param([switch]$Disabled)
if ($Disabled) { return $false }
$pf = Find-V8Project (Get-Location).Path
if ($pf) {
try {
$proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($null -ne $proj.extensionApplyCheck) { return [bool]$proj.extensionApplyCheck }
} catch {}
}
return $true
}
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
@@ -782,6 +853,13 @@ try {
} else {
Write-Host "Error updating database configuration (code: $exitCode)$(Get-ExitAnnotation $exitCode)" -ForegroundColor Red
}
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
$acConn = @("/F", "`"$InfoBasePath`"")
if ($UserName) { $acConn += "/N`"$UserName`"" }
if ($Password) { $acConn += "/P`"$Password`"" }
if ((Invoke-ApplyCheckReport $V8Path $acConn $Extension @()) -and $StrictLog) { $exitCode = 1 }
}
Write-PlatformOutput $applyOut
}
exit $exitCode
@@ -794,21 +872,24 @@ try {
[System.IO.File]::WriteAllLines($listFile, $configFiles, $utf8Bom)
# --- Build arguments ---
$arguments = @("DESIGNER")
# Аргументы соединения собираем отдельно: тем же набором пойдёт запуск проверки применимости.
$connArgs = @()
if ($InfoBaseServer -and $InfoBaseRef) {
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
$connArgs += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
} else {
$arguments += "/F", "`"$InfoBasePath`""
$connArgs += "/F", "`"$InfoBasePath`""
}
if ($UserName) { $arguments += "/N`"$UserName`"" }
if ($Password) { $arguments += "/P`"$Password`"" }
if ($UserName) { $connArgs += "/N`"$UserName`"" }
if ($Password) { $connArgs += "/P`"$Password`"" }
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
$__repo = Resolve-RepositorySettings
$arguments += Get-RepositoryArgs $__repo
$connArgs += Get-RepositoryArgs $__repo
$arguments = @("DESIGNER") + $connArgs
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
$arguments += "-listFile", "`"$listFile`""
@@ -872,6 +953,11 @@ try {
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
}
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
if ((Invoke-ApplyCheckReport $V8Path $connArgs $Extension $extraArgs) -and $StrictLog) { $exitCode = 1 }
}
exit $exitCode
} finally {
@@ -517,6 +517,76 @@ def find_silent_rejections(log_text):
return found
def run_apply_check(exe, conn_args, extension, extra_args):
"""Постусловие применимости расширения: платформа отчитывается успехом и о расширении,
которое не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал
регистрации.
Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной
командной строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча
отбрасывает — проверено на 8.3.24, /LoadConfigFromFiles вместе с
/CheckCanApplyConfigurationExtensions завершились кодом 0 с пустым логом, и загрузка не
состоялась.
Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний файл.
"""
exe_dir = os.path.dirname(exe)
leaf = os.path.basename(exe)
if leaf.lower().startswith("ibcmd"):
v8 = os.path.join(exe_dir, "1cv8" + os.path.splitext(leaf)[1])
else:
v8 = exe
if not os.path.isfile(v8):
return {"skipped": True, "reason": f"1cv8 not found at {v8}", "exit": 0, "lines": []}
temp_dir = tempfile.mkdtemp(prefix="apply_check_")
try:
a = ["DESIGNER"] + list(conn_args) + ["/CheckCanApplyConfigurationExtensions"]
if extension:
a += ["-Extension", f'"{extension}"']
out_file = os.path.join(temp_dir, "check_log.txt")
a += ["/Out", f'"{out_file}"', "/DisableStartupDialogs"]
a += list(extra_args)
r = run_v8(v8, a)
lines = []
if os.path.isfile(out_file):
with open(out_file, encoding="utf-8-sig", errors="replace") as f:
lines = [x.strip() for x in f.read().splitlines() if x.strip()]
return {"skipped": False, "reason": "", "exit": r.returncode, "lines": lines}
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def apply_check_report(exe, conn_args, extension, extra_args):
"""Проверить и напечатать. True, если платформа расширение не применит — вызывающий решает,
поднимать ли код возврата (строгий режим)."""
ac = run_apply_check(exe, conn_args, extension, extra_args)
if ac["skipped"]:
print(f"[note] applicability check skipped: {ac['reason']}")
return False
if ac["exit"] != 0 or ac["lines"]:
print("[warning] the extension is loaded, but the platform will not apply it:")
for line in ac["lines"]:
print(f" {line}")
return True
return False
def apply_check_enabled(disabled):
"""Проверять ли применимость: -NoApplyCheck сильнее настройки проекта."""
if disabled:
return False
pf = _sg_find_v8project(os.getcwd())
if pf:
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
if proj.get("extensionApplyCheck") is not None:
return bool(proj.get("extensionApplyCheck"))
except Exception:
pass
return True
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
"""Run an ibcmd command non-interactively.
@@ -626,6 +696,8 @@ def main():
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
# но в логе есть отбраковка.
parser.add_argument("-StrictLog", action="store_true")
# Пропустить проверку применимости расширения после загрузки.
parser.add_argument("-NoApplyCheck", action="store_true")
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
@@ -828,6 +900,17 @@ def main():
else:
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}")
print_platform_output(ar)
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
ac_conn = ["/F", f'"{args.InfoBasePath}"']
if args.UserName:
ac_conn.append(f'/N"{args.UserName}"')
if args.Password:
ac_conn.append(f'/P"{args.Password}"')
if apply_check_report(v8path, ac_conn, args.Extension, []) and args.StrictLog:
exit_code = 1
sys.exit(exit_code)
# --- Write list file (UTF-8 with BOM) ---
@@ -836,22 +919,25 @@ def main():
f.write("\n".join(config_files))
# --- Build arguments ---
arguments = ["DESIGNER"]
# Аргументы соединения собираем отдельно: тем же набором пойдёт проверка применимости.
conn_args = []
if args.InfoBaseServer and args.InfoBaseRef:
arguments += ["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"']
conn_args += ["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"']
else:
arguments += ["/F", f'"{args.InfoBasePath}"']
conn_args += ["/F", f'"{args.InfoBasePath}"']
if args.UserName:
arguments.append(f'/N"{args.UserName}"')
conn_args.append(f'/N"{args.UserName}"')
if args.Password:
arguments.append(f'/P"{args.Password}"')
conn_args.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
conn_args.extend(repository_args(repo))
arguments = ["DESIGNER"] + conn_args
arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"']
arguments += ["-listFile", f'"{list_file}"']
@@ -919,6 +1005,12 @@ def main():
if args.StrictLog and exit_code == 0:
exit_code = 1
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
if apply_check_report(v8path, conn_args, args.Extension, extra_args) and args.StrictLog:
exit_code = 1
sys.exit(exit_code)
finally:
+1
View File
@@ -58,6 +58,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" <
| `-ListFile <путь>` | для Partial | Путь к файлу со списком (альтернатива `-Files`) |
| `-Extension <имя>` | нет | Загрузить в расширение |
| `-AllExtensions` | нет | Загрузить все расширения |
| `-NoApplyCheck` | нет | Не проверять применимость расширения после загрузки |
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
| `-AdditionalV8Arguments <список>` | нет | Доп. аргументы запуска `1cv8.exe` через запятую, напр. `/UseHwLicenses+` |
@@ -112,6 +112,10 @@ param(
[Parameter(Mandatory=$false)]
[switch]$StrictLog,
# Пропустить проверку применимости расширения после загрузки.
[Parameter(Mandatory=$false)]
[switch]$NoApplyCheck,
[Parameter(Mandatory=$false)]
[string]$RepositoryPath,
@@ -591,6 +595,73 @@ function Find-SilentRejections {
}
# Постусловие применимости расширения: платформа отчитывается успехом и о расширении, которое
# не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал регистрации.
#
# Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной командной
# строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча отбрасывает —
# проверено на 8.3.24, /LoadConfigFromFiles вместе с /CheckCanApplyConfigurationExtensions
# завершились кодом 0 с пустым логом, и загрузка не состоялась.
#
# Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний исполняемый файл.
function Invoke-ApplyCheck {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$exeDir = Split-Path $Exe -Parent
$exeLeaf = Split-Path $Exe -Leaf
$v8 = if ($exeLeaf -match '^ibcmd') { Join-Path $exeDir ("1cv8" + [System.IO.Path]::GetExtension($Exe)) } else { $Exe }
if (-not (Test-Path $v8)) { return @{ Skipped = $true; Reason = "1cv8 not found at $v8"; ExitCode = 0; Lines = @() } }
$dir = Join-Path $env:TEMP "apply_check_$(Get-Random)"
New-Item -ItemType Directory -Path $dir -Force | Out-Null
try {
$a = @("DESIGNER") + $ConnArgs + @("/CheckCanApplyConfigurationExtensions")
if ($Extension) { $a += "-Extension", "`"$Extension`"" }
$outFile = Join-Path $dir "check_log.txt"
$a += "/Out", "`"$outFile`"", "/DisableStartupDialogs"
$a += $ExtraArgs
$res = Invoke-PlatformProcess $v8 $a -PreQuoted
$lines = @()
if (Test-Path $outFile) {
$raw = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($raw) { $lines = @($raw -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) }
}
return @{ Skipped = $false; Reason = ''; ExitCode = $res.ExitCode; Lines = $lines }
} finally {
if (Test-Path $dir) { Remove-Item -Path $dir -Recurse -Force -ErrorAction SilentlyContinue }
}
}
# Проверить и напечатать. $true, если платформа расширение не применит — вызывающий решает,
# поднимать ли код возврата (строгий режим).
function Invoke-ApplyCheckReport {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$ac = Invoke-ApplyCheck $Exe $ConnArgs $Extension $ExtraArgs
if ($ac.Skipped) {
Write-Host "[note] applicability check skipped: $($ac.Reason)" -ForegroundColor Yellow
return $false
}
if ($ac.ExitCode -ne 0 -or $ac.Lines.Count -gt 0) {
Write-Host "[warning] the extension is loaded, but the platform will not apply it:" -ForegroundColor Yellow
foreach ($l in $ac.Lines) { Write-Host " $l" -ForegroundColor Yellow }
return $true
}
return $false
}
# Проверять ли применимость: -NoApplyCheck сильнее настройки проекта.
function Get-ApplyCheckEnabled {
param([switch]$Disabled)
if ($Disabled) { return $false }
$pf = Find-V8Project (Get-Location).Path
if ($pf) {
try {
$proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($null -ne $proj.extensionApplyCheck) { return [bool]$proj.extensionApplyCheck }
} catch {}
}
return $true
}
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
# --- Resolve additional arguments for the selected engine ---
@@ -700,26 +771,36 @@ try {
}
Write-PlatformOutput $applyOut
}
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
$acConn = @("/F", "`"$InfoBasePath`"")
if ($UserName) { $acConn += "/N`"$UserName`"" }
if ($Password) { $acConn += "/P`"$Password`"" }
if ((Invoke-ApplyCheckReport $V8Path $acConn $Extension @()) -and $StrictLog) { $exitCode = 1 }
}
exit $exitCode
}
# --- 1cv8 branch ---
# --- Build arguments ---
$arguments = @("DESIGNER")
# Аргументы соединения собираем отдельно: тем же набором пойдёт запуск проверки применимости.
$connArgs = @()
if ($InfoBaseServer -and $InfoBaseRef) {
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
$connArgs += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
} else {
$arguments += "/F", "`"$InfoBasePath`""
$connArgs += "/F", "`"$InfoBasePath`""
}
if ($UserName) { $arguments += "/N`"$UserName`"" }
if ($Password) { $arguments += "/P`"$Password`"" }
if ($UserName) { $connArgs += "/N`"$UserName`"" }
if ($Password) { $connArgs += "/P`"$Password`"" }
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
$__repo = Resolve-RepositorySettings
$arguments += Get-RepositoryArgs $__repo
$connArgs += Get-RepositoryArgs $__repo
$arguments = @("DESIGNER") + $connArgs
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
@@ -825,6 +906,11 @@ try {
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
}
# Расширение могло загрузиться «успешно» и при этом остаться неприменимым — спрашиваем платформу.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
if ((Invoke-ApplyCheckReport $V8Path $connArgs $Extension $extraArgs) -and $StrictLog) { $exitCode = 1 }
}
exit $exitCode
} finally {
@@ -517,6 +517,76 @@ def find_silent_rejections(log_text):
return found
def run_apply_check(exe, conn_args, extension, extra_args):
"""Постусловие применимости расширения: платформа отчитывается успехом и о расширении,
которое не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал
регистрации.
Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной
командной строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча
отбрасывает — проверено на 8.3.24, /LoadConfigFromFiles вместе с
/CheckCanApplyConfigurationExtensions завершились кодом 0 с пустым логом, и загрузка не
состоялась.
Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний файл.
"""
exe_dir = os.path.dirname(exe)
leaf = os.path.basename(exe)
if leaf.lower().startswith("ibcmd"):
v8 = os.path.join(exe_dir, "1cv8" + os.path.splitext(leaf)[1])
else:
v8 = exe
if not os.path.isfile(v8):
return {"skipped": True, "reason": f"1cv8 not found at {v8}", "exit": 0, "lines": []}
temp_dir = tempfile.mkdtemp(prefix="apply_check_")
try:
a = ["DESIGNER"] + list(conn_args) + ["/CheckCanApplyConfigurationExtensions"]
if extension:
a += ["-Extension", f'"{extension}"']
out_file = os.path.join(temp_dir, "check_log.txt")
a += ["/Out", f'"{out_file}"', "/DisableStartupDialogs"]
a += list(extra_args)
r = run_v8(v8, a)
lines = []
if os.path.isfile(out_file):
with open(out_file, encoding="utf-8-sig", errors="replace") as f:
lines = [x.strip() for x in f.read().splitlines() if x.strip()]
return {"skipped": False, "reason": "", "exit": r.returncode, "lines": lines}
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def apply_check_report(exe, conn_args, extension, extra_args):
"""Проверить и напечатать. True, если платформа расширение не применит — вызывающий решает,
поднимать ли код возврата (строгий режим)."""
ac = run_apply_check(exe, conn_args, extension, extra_args)
if ac["skipped"]:
print(f"[note] applicability check skipped: {ac['reason']}")
return False
if ac["exit"] != 0 or ac["lines"]:
print("[warning] the extension is loaded, but the platform will not apply it:")
for line in ac["lines"]:
print(f" {line}")
return True
return False
def apply_check_enabled(disabled):
"""Проверять ли применимость: -NoApplyCheck сильнее настройки проекта."""
if disabled:
return False
pf = _sg_find_v8project(os.getcwd())
if pf:
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
if proj.get("extensionApplyCheck") is not None:
return bool(proj.get("extensionApplyCheck"))
except Exception:
pass
return True
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
"""Run an ibcmd command non-interactively.
@@ -605,6 +675,8 @@ def main():
action="store_true",
help="Treat silent rejection warnings in the log as errors (elevate exit code to 1)",
)
# Пропустить проверку применимости расширения после загрузки.
parser.add_argument("-NoApplyCheck", action="store_true")
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
@@ -725,6 +797,17 @@ def main():
else:
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}")
print_platform_output(ar)
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
ac_conn = ["/F", f'"{args.InfoBasePath}"']
if args.UserName:
ac_conn.append(f'/N"{args.UserName}"')
if args.Password:
ac_conn.append(f'/P"{args.Password}"')
if apply_check_report(v8path, ac_conn, args.Extension, []) and args.StrictLog:
exit_code = 1
sys.exit(exit_code)
# --- Temp dir ---
@@ -733,22 +816,25 @@ def main():
try:
# --- Build arguments ---
arguments = ["DESIGNER"]
# Аргументы соединения собираем отдельно: тем же набором пойдёт проверка применимости.
conn_args = []
if args.InfoBaseServer and args.InfoBaseRef:
arguments += ["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"']
conn_args += ["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"']
else:
arguments += ["/F", f'"{args.InfoBasePath}"']
conn_args += ["/F", f'"{args.InfoBasePath}"']
if args.UserName:
arguments.append(f'/N"{args.UserName}"')
conn_args.append(f'/N"{args.UserName}"')
if args.Password:
arguments.append(f'/P"{args.Password}"')
conn_args.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
conn_args.extend(repository_args(repo))
arguments = ["DESIGNER"] + conn_args
arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"']
@@ -859,6 +945,12 @@ def main():
if args.StrictLog and exit_code == 0:
exit_code = 1
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
if apply_check_report(v8path, conn_args, args.Extension, extra_args) and args.StrictLog:
exit_code = 1
sys.exit(exit_code)
finally:
+1
View File
@@ -52,6 +52,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <п
| `-Password <пароль>` | нет | Пароль |
| `-Extension <имя>` | нет | Обновить расширение |
| `-AllExtensions` | нет | Обновить все расширения |
| `-NoApplyCheck` | нет | Не проверять применимость расширения после обновления |
| `-Dynamic <on/off>` | нет | `on` — динамическое обновление, без монопольного доступа к базе; `off` — отключить |
| `-Server` | нет | Обновление на стороне сервера |
| `-WarningsAsErrors` | нет | Предупреждения считать ошибками |
+92 -6
View File
@@ -99,6 +99,10 @@ param(
# но в логе есть отбраковка.
[switch]$StrictLog,
# Пропустить проверку применимости расширения после загрузки.
[Parameter(Mandatory=$false)]
[switch]$NoApplyCheck,
[Parameter(Mandatory=$false)]
[string]$RepositoryPath,
@@ -553,6 +557,73 @@ function Find-SilentRejections {
}
# Постусловие применимости расширения: платформа отчитывается успехом и о расширении, которое
# не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал регистрации.
#
# Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной командной
# строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча отбрасывает —
# проверено на 8.3.24, /LoadConfigFromFiles вместе с /CheckCanApplyConfigurationExtensions
# завершились кодом 0 с пустым логом, и загрузка не состоялась.
#
# Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний исполняемый файл.
function Invoke-ApplyCheck {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$exeDir = Split-Path $Exe -Parent
$exeLeaf = Split-Path $Exe -Leaf
$v8 = if ($exeLeaf -match '^ibcmd') { Join-Path $exeDir ("1cv8" + [System.IO.Path]::GetExtension($Exe)) } else { $Exe }
if (-not (Test-Path $v8)) { return @{ Skipped = $true; Reason = "1cv8 not found at $v8"; ExitCode = 0; Lines = @() } }
$dir = Join-Path $env:TEMP "apply_check_$(Get-Random)"
New-Item -ItemType Directory -Path $dir -Force | Out-Null
try {
$a = @("DESIGNER") + $ConnArgs + @("/CheckCanApplyConfigurationExtensions")
if ($Extension) { $a += "-Extension", "`"$Extension`"" }
$outFile = Join-Path $dir "check_log.txt"
$a += "/Out", "`"$outFile`"", "/DisableStartupDialogs"
$a += $ExtraArgs
$res = Invoke-PlatformProcess $v8 $a -PreQuoted
$lines = @()
if (Test-Path $outFile) {
$raw = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($raw) { $lines = @($raw -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) }
}
return @{ Skipped = $false; Reason = ''; ExitCode = $res.ExitCode; Lines = $lines }
} finally {
if (Test-Path $dir) { Remove-Item -Path $dir -Recurse -Force -ErrorAction SilentlyContinue }
}
}
# Проверить и напечатать. $true, если платформа расширение не применит — вызывающий решает,
# поднимать ли код возврата (строгий режим).
function Invoke-ApplyCheckReport {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$ac = Invoke-ApplyCheck $Exe $ConnArgs $Extension $ExtraArgs
if ($ac.Skipped) {
Write-Host "[note] applicability check skipped: $($ac.Reason)" -ForegroundColor Yellow
return $false
}
if ($ac.ExitCode -ne 0 -or $ac.Lines.Count -gt 0) {
Write-Host "[warning] the extension is loaded, but the platform will not apply it:" -ForegroundColor Yellow
foreach ($l in $ac.Lines) { Write-Host " $l" -ForegroundColor Yellow }
return $true
}
return $false
}
# Проверять ли применимость: -NoApplyCheck сильнее настройки проекта.
function Get-ApplyCheckEnabled {
param([switch]$Disabled)
if ($Disabled) { return $false }
$pf = Find-V8Project (Get-Location).Path
if ($pf) {
try {
$proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($null -ne $proj.extensionApplyCheck) { return [bool]$proj.extensionApplyCheck }
} catch {}
}
return $true
}
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
# --- Resolve additional arguments for the selected engine ---
@@ -598,27 +669,37 @@ try {
} else {
Write-Host "Error updating database configuration (code: $exitCode)$(Get-ExitAnnotation $exitCode)" -ForegroundColor Red
}
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
$acConn = @("/F", "`"$InfoBasePath`"")
if ($UserName) { $acConn += "/N`"$UserName`"" }
if ($Password) { $acConn += "/P`"$Password`"" }
if ((Invoke-ApplyCheckReport $V8Path $acConn $Extension @()) -and $StrictLog) { $exitCode = 1 }
}
Write-PlatformOutput $output
exit $exitCode
}
# --- 1cv8 branch ---
# --- Build arguments ---
$arguments = @("DESIGNER")
# Аргументы соединения собираем отдельно: тем же набором пойдёт запуск проверки применимости.
$connArgs = @()
if ($InfoBaseServer -and $InfoBaseRef) {
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
$connArgs += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
} else {
$arguments += "/F", "`"$InfoBasePath`""
$connArgs += "/F", "`"$InfoBasePath`""
}
if ($UserName) { $arguments += "/N`"$UserName`"" }
if ($Password) { $arguments += "/P`"$Password`"" }
if ($UserName) { $connArgs += "/N`"$UserName`"" }
if ($Password) { $connArgs += "/P`"$Password`"" }
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
$__repo = Resolve-RepositorySettings
$arguments += Get-RepositoryArgs $__repo
$connArgs += Get-RepositoryArgs $__repo
$arguments = @("DESIGNER") + $connArgs
$arguments += "/UpdateDBCfg"
@@ -679,6 +760,11 @@ try {
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
}
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
if ((Invoke-ApplyCheckReport $V8Path $connArgs $Extension $extraArgs) -and $StrictLog) { $exitCode = 1 }
}
exit $exitCode
} finally {
+99 -6
View File
@@ -496,6 +496,76 @@ def find_silent_rejections(log_text):
return found
def run_apply_check(exe, conn_args, extension, extra_args):
"""Постусловие применимости расширения: платформа отчитывается успехом и о расширении,
которое не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал
регистрации.
Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной
командной строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча
отбрасывает — проверено на 8.3.24, /LoadConfigFromFiles вместе с
/CheckCanApplyConfigurationExtensions завершились кодом 0 с пустым логом, и загрузка не
состоялась.
Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний файл.
"""
exe_dir = os.path.dirname(exe)
leaf = os.path.basename(exe)
if leaf.lower().startswith("ibcmd"):
v8 = os.path.join(exe_dir, "1cv8" + os.path.splitext(leaf)[1])
else:
v8 = exe
if not os.path.isfile(v8):
return {"skipped": True, "reason": f"1cv8 not found at {v8}", "exit": 0, "lines": []}
temp_dir = tempfile.mkdtemp(prefix="apply_check_")
try:
a = ["DESIGNER"] + list(conn_args) + ["/CheckCanApplyConfigurationExtensions"]
if extension:
a += ["-Extension", f'"{extension}"']
out_file = os.path.join(temp_dir, "check_log.txt")
a += ["/Out", f'"{out_file}"', "/DisableStartupDialogs"]
a += list(extra_args)
r = run_v8(v8, a)
lines = []
if os.path.isfile(out_file):
with open(out_file, encoding="utf-8-sig", errors="replace") as f:
lines = [x.strip() for x in f.read().splitlines() if x.strip()]
return {"skipped": False, "reason": "", "exit": r.returncode, "lines": lines}
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def apply_check_report(exe, conn_args, extension, extra_args):
"""Проверить и напечатать. True, если платформа расширение не применит — вызывающий решает,
поднимать ли код возврата (строгий режим)."""
ac = run_apply_check(exe, conn_args, extension, extra_args)
if ac["skipped"]:
print(f"[note] applicability check skipped: {ac['reason']}")
return False
if ac["exit"] != 0 or ac["lines"]:
print("[warning] the extension is loaded, but the platform will not apply it:")
for line in ac["lines"]:
print(f" {line}")
return True
return False
def apply_check_enabled(disabled):
"""Проверять ли применимость: -NoApplyCheck сильнее настройки проекта."""
if disabled:
return False
pf = _sg_find_v8project(os.getcwd())
if pf:
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
if proj.get("extensionApplyCheck") is not None:
return bool(proj.get("extensionApplyCheck"))
except Exception:
pass
return True
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
"""Run an ibcmd command non-interactively.
@@ -572,6 +642,8 @@ def main():
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
# но в логе есть отбраковка.
parser.add_argument("-StrictLog", action="store_true")
# Пропустить проверку применимости расширения после загрузки.
parser.add_argument("-NoApplyCheck", action="store_true")
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
@@ -638,6 +710,18 @@ def main():
print("Database configuration updated successfully")
else:
print(f"Error updating database configuration (code: {result.returncode}){describe_exit(result.returncode)}")
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
exit_code = result.returncode
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
ac_conn = ["/F", f'"{args.InfoBasePath}"']
if args.UserName:
ac_conn.append(f'/N"{args.UserName}"')
if args.Password:
ac_conn.append(f'/P"{args.Password}"')
if apply_check_report(v8path, ac_conn, args.Extension, []) and args.StrictLog:
exit_code = 1
sys.exit(result.returncode)
# --- Temp dir ---
@@ -646,22 +730,25 @@ def main():
try:
# --- Build arguments ---
arguments = ["DESIGNER"]
# Аргументы соединения собираем отдельно: тем же набором пойдёт проверка применимости.
conn_args = []
if args.InfoBaseServer and args.InfoBaseRef:
arguments.extend(["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"'])
conn_args.extend(["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"'])
else:
arguments.extend(["/F", f'"{args.InfoBasePath}"'])
conn_args.extend(["/F", f'"{args.InfoBasePath}"'])
if args.UserName:
arguments.append(f'/N"{args.UserName}"')
conn_args.append(f'/N"{args.UserName}"')
if args.Password:
arguments.append(f'/P"{args.Password}"')
conn_args.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
conn_args.extend(repository_args(repo))
arguments = ["DESIGNER"] + conn_args
arguments.append("/UpdateDBCfg")
@@ -724,6 +811,12 @@ def main():
if args.StrictLog and exit_code == 0:
exit_code = 1
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
if apply_check_report(v8path, conn_args, args.Extension, extra_args) and args.StrictLog:
exit_code = 1
sys.exit(exit_code)
finally:
+1
View File
@@ -81,6 +81,7 @@
| `default` | string | нет | — | `id` базы по умолчанию | `/db-list` |
| `editingAllowedCheck` | `"deny"`/`"warn"`/`"off"` | нет | `deny` | Глобальная реакция support-guard на правку объектов на замке (см. ниже) | Руками |
| `newObjectPosition` | `"end"`/`"byName"` | нет | `end` | Куда навыки ставят новый объект в `<ChildObjects>` (см. ниже) | Руками |
| `extensionApplyCheck` | bool | нет | `true` | Проверять ли применимость расширения после загрузки в базу (навыки `db-load-*`, `db-update`); разово отключается ключом `-NoApplyCheck` | Руками |
| `skillSuggester` | `"on"`/`"off"` | нет | `on` | Подсказки навыков от хука skill-suggester (только если хук включён, см. ниже) | Руками |
| `webPath` | string | нет | `tools/apache24` | Каталог Apache HTTP Server | Руками |
| `ffmpegPath` | string | нет | `tools/ffmpeg/bin/ffmpeg.exe` | Путь к ffmpeg | Руками |
@@ -0,0 +1,33 @@
{
"name": "Проверка прошла — предупреждения нет",
"fakePlatform": {
"log": "Загрузка конфигурации из файлов.\r\n",
"check": {
"log": "",
"exit": 0
}
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-ConfigDir",
"{workDir}/src",
"-Extension",
"Проба"
],
"expect": {
"stdoutContains": "Load completed successfully",
"stdoutNotContains": "will not apply"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется постусловие",
"preRun": [
{
"writeFile": {
"path": "src/Configuration.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject/>"
}
}
]
}
@@ -0,0 +1,34 @@
{
"name": "Загрузка провалилась — применимость не проверяем (проверять нечего)",
"fakePlatform": {
"log": "Ошибка загрузки конфигурации\r\n",
"exit": 1,
"check": {
"log": "Проба: Список параметров метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
}
},
"preRun": [
{
"writeFile": {
"path": "src/Configuration.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject/>"
}
}
],
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-ConfigDir",
"{workDir}/src",
"-Extension",
"Проба"
],
"expectError": true,
"expect": {
"stdoutNotContains": "will not apply"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется условие запуска"
}
@@ -0,0 +1,31 @@
{
"name": "Загрузка основной конфигурации: применимость не проверяется",
"fakePlatform": {
"log": "Загрузка конфигурации из файлов.\r\n",
"check": {
"log": "Проба: Список параметров метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
}
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-ConfigDir",
"{workDir}/src"
],
"expect": {
"stdoutContains": "Load completed successfully",
"stdoutNotContains": "will not apply"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется условие запуска",
"preRun": [
{
"writeFile": {
"path": "src/Configuration.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject/>"
}
}
]
}
@@ -0,0 +1,40 @@
{
"name": "extensionApplyCheck: false в .v8-project.json выключает проверку",
"fakePlatform": {
"log": "Загрузка конфигурации из файлов.\r\n",
"check": {
"log": "Проба: Список параметров метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
}
},
"cwd": "workDir",
"preRun": [
{
"writeFile": {
"path": "src/Configuration.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject/>"
}
},
{
"writeFile": {
"path": ".v8-project.json",
"content": "{\n \"extensionApplyCheck\": false\n}"
}
}
],
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-ConfigDir",
"{workDir}/src",
"-Extension",
"Проба"
],
"expect": {
"stdoutContains": "Load completed successfully",
"stdoutNotContains": "will not apply"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется настройка проекта"
}
@@ -0,0 +1,34 @@
{
"name": "-NoApplyCheck: проверка не запускается вовсе",
"fakePlatform": {
"log": "Загрузка конфигурации из файлов.\r\n",
"check": {
"log": "Проба: Список параметров метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
}
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-ConfigDir",
"{workDir}/src",
"-Extension",
"Проба",
"-NoApplyCheck"
],
"expect": {
"stdoutContains": "Load completed successfully",
"stdoutNotContains": "will not apply"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется постусловие",
"preRun": [
{
"writeFile": {
"path": "src/Configuration.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject/>"
}
}
]
}
@@ -0,0 +1,34 @@
{
"name": "То же с -StrictLog: предупреждение поднимает код возврата",
"fakePlatform": {
"log": "Загрузка конфигурации из файлов.\r\n",
"check": {
"log": "Проба: Список параметров метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
}
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-ConfigDir",
"{workDir}/src",
"-Extension",
"Проба",
"-StrictLog"
],
"expectError": true,
"expect": {
"stdoutContains": "will not apply it"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется постусловие",
"preRun": [
{
"writeFile": {
"path": "src/Configuration.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject/>"
}
}
]
}
@@ -0,0 +1,36 @@
{
"name": "Расширение загрузилось, но платформа его не применит — предупреждение, код возврата не меняется",
"fakePlatform": {
"log": "Загрузка конфигурации из файлов.\r\n",
"check": {
"log": "Проба: Список параметров метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
}
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-ConfigDir",
"{workDir}/src",
"-Extension",
"Проба"
],
"expect": {
"stdoutContains": [
"Load completed successfully",
"[warning] the extension is loaded, but the platform will not apply it:",
"не соответствует методу"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется постусловие",
"preRun": [
{
"writeFile": {
"path": "src/Configuration.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject/>"
}
}
]
}
@@ -0,0 +1,25 @@
{
"name": "Обновление расширения прошло, но платформа его не применит — предупреждение",
"fakePlatform": {
"log": "Обновление конфигурации успешно завершено\r\n",
"check": {
"log": "Проба: Список параметров метода \"Проба_Расчёт\" не соответствует методу \"Расчёт\".\r\n",
"exit": 1
}
},
"args_extra": [
"-V8Path",
"{fakePlatform}",
"-InfoBasePath",
"{workDir}/ib",
"-Extension",
"Проба"
],
"expect": {
"stdoutContains": [
"Database configuration updated successfully",
"will not apply it"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется постусловие"
}
+45 -1
View File
@@ -71,7 +71,7 @@ const FAMILIES = [
// вверх. Группа db-* использует её же, чтобы найти запись базы и взять реквизиты
// хранилища — задача одна, поэтому семья общая, а не вторая с тем же телом.
{ id: 'full', authority: 'cf-edit',
consumers: ['cfe-borrow', 'db-cfe-admin', 'db-dump-xml', 'db-load-git', 'db-load-xml', 'db-repo', 'db-update',
consumers: ['cfe-borrow', 'db-cfe-admin', 'db-dump-xml', 'db-load-cf', 'db-load-git', 'db-load-xml', 'db-repo', 'db-update',
'form-add', 'form-compile', 'form-edit', 'help-add', 'interface-edit', 'meta-compile',
'meta-edit', 'meta-remove', 'mxl-compile', 'role-compile', 'skd-compile', 'skd-edit',
'subsystem-compile', 'subsystem-edit', 'template-add', 'xdto-compile', 'xdto-edit'] },
@@ -250,6 +250,50 @@ const FAMILIES = [
{ id: 'base', authority: 'db-load-xml', consumers: ['db-load-git', 'db-update'] },
],
},
// ─── Постусловие применимости расширения ────────────────────────────────
// Проверку ОБЯЗАТЕЛЬНО запускать отдельным процессом: в одной командной строке DESIGNER
// выполняет только последнюю пакетную команду, и дописанная проверка отменяет саму загрузку.
// Разъехавшиеся копии означали бы, что один навык предупреждает о неприменимом расширении,
// а соседний по той же операции — молчит.
{
name: 'apply check: run', py: 'run_apply_check', ps1: 'Invoke-ApplyCheck',
variants: [
{ id: 'base', authority: 'db-load-xml', consumers: ['db-load-cf', 'db-load-git', 'db-update'] },
],
},
{
name: 'apply check: report', py: 'apply_check_report', ps1: 'Invoke-ApplyCheckReport',
variants: [
{ id: 'base', authority: 'db-load-xml', consumers: ['db-load-cf', 'db-load-git', 'db-update'] },
],
},
{
name: 'apply check: enabled', py: 'apply_check_enabled', ps1: 'Get-ApplyCheckEnabled',
variants: [
{ id: 'base', authority: 'db-load-xml', consumers: ['db-load-cf', 'db-load-git', 'db-update'] },
],
},
// Дополнительные аргументы: и владение ключами, и запрет пакетных команд. Разъехавшиеся копии
// означали бы, что один навык отбивает команду, отменяющую его же операцию, а соседний — нет.
{
name: 'platform: assert_extra_args', py: 'assert_extra_args', ps1: 'Assert-ExtraArgs',
variants: [
{ id: 'base', authority: 'db-create',
consumers: ['db-cfe-admin', 'db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt',
'db-load-git', 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump'] },
// NB: stub-db-create.py внутри epf-build держит свою копию с выводом в stderr, но гарду она
// не видна: индекс берёт первый файл навыка по алфавиту (epf-build.py). Правя эту семью,
// не забывать про стаб — автоматически он не проверяется.
],
},
{
name: 'platform: arg_key_match', py: 'arg_key_match', ps1: 'Test-ArgKeyMatch',
variants: [
{ id: 'base', authority: 'db-create',
consumers: ['db-cfe-admin', 'db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt',
'db-load-git', 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump'] },
],
},
{
name: 'platform: resolve_extra_args', py: 'resolve_extra_args', ps1: 'Resolve-ExtraArgs',
variants: [
+11 -5
View File
@@ -373,15 +373,20 @@ function buildArgs(skillConfig, caseData, workDir, inputFilePath, runtime) {
// под текущую ОС, поэтому один кейс проверяется на обеих. Раньше каждый такой сценарий
// приходилось дублировать -posix двойником, и забытый двойник означал дыру: у db-repo на
// маке выполнялся 1 кейс из 12, и заметили это случайно.
const FAKE_PLATFORM_CMD = "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n";
const FAKE_PLATFORM_SH = "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\n# Значение /Out несёт кавычки ВНУТРИ токена (соглашение 1С, см. run_v8) — в batch их\n# снимает %~2, в sh их надо снять руками, иначе cp целится в имя с кавычками.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then\n OUT=\"$2\"\n OUT=\"${OUT#\\\"}\"\n OUT=\"${OUT%\\\"}\"\n fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n";
// Второй ответ — на проверку применимости расширения: навык запускает её ОТДЕЛЬНЫМ процессом
// (в одной командной строке платформа выполнила бы только последнюю команду), поэтому фейк
// отличает проверку по составу аргументов, а не по номеру вызова.
const FAKE_PLATFORM_CMD = "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\nset KIND=main\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nif /i \"%~1\"==\"/CheckCanApplyConfigurationExtensions\" set KIND=check\r\nshift\r\ngoto loop\r\n:done\r\nif \"%KIND%\"==\"check\" if exist \"%SELF%log_check.txt\" (copy /y \"%SELF%log_check.txt\" \"%OUT%\" >nul & exit /b CHECKCODE)\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b EXITCODE\r\n";
const FAKE_PLATFORM_SH = "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\n# Значение /Out несёт кавычки ВНУТРИ токена (соглашение 1С, см. run_v8) — в batch их\n# снимает %~2, в sh их надо снять руками, иначе cp целится в имя с кавычками.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nKIND=main\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/CheckCanApplyConfigurationExtensions\" ]; then\n KIND=check\n fi\n if [ \"$1\" = \"/Out\" ]; then\n OUT=\"$2\"\n OUT=\"${OUT#\\\"}\"\n OUT=\"${OUT%\\\"}\"\n fi\n shift\ndone\nif [ \"$KIND\" = check ] && [ -f \"$SELF/log_check.txt\" ]; then\n cp \"$SELF/log_check.txt\" \"$OUT\"\n exit CHECKCODE\nfi\ncp \"$SELF/log.txt\" \"$OUT\"\nexit EXITCODE\n";
function writeFakePlatform(workDir, spec) {
const isWin = process.platform === 'win32';
const code = Number.isInteger(spec.exit) ? spec.exit : 0;
const body = isWin
? FAKE_PLATFORM_CMD.replace('exit /b 0', `exit /b ${code}`)
: FAKE_PLATFORM_SH.replace('exit 0', `exit ${code}`);
// spec.check — ответ на проверку применимости расширения (отдельный запуск платформы)
const checkCode = spec.check && Number.isInteger(spec.check.exit) ? spec.check.exit : 0;
const body = (isWin ? FAKE_PLATFORM_CMD : FAKE_PLATFORM_SH)
.replaceAll('EXITCODE', String(code))
.replaceAll('CHECKCODE', String(checkCode));
const exe = join(workDir, isWin ? 'fake.cmd' : 'fake.sh');
writeFileSync(exe, body, 'utf8');
// Бит исполнения: на *nix навык запускает платформу через exec, без +x фейк не стартует.
@@ -389,6 +394,7 @@ function writeFakePlatform(workDir, spec) {
// Лог пишется как есть — вместе с BOM и CRLF, если кейс их объявил: /Out платформы
// выглядит именно так, и разбор должен проверяться на настоящей форме.
writeFileSync(join(workDir, 'log.txt'), spec.log ?? '', 'utf8');
if (spec.check) writeFileSync(join(workDir, 'log_check.txt'), spec.check.log ?? '', 'utf8');
// Заглушка базы: навыки отказываются работать, не найдя 1Cv8.1CD, и это правильно.
if (spec.baseStub !== false) {
const ib = join(workDir, 'ib');