mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-06 18:20:52 +03:00
feat(epf-build): исходники проверяются платформой до сборки
Сборка .epf/.erf не компилирует модули, поэтому сломанный BSL уезжал пользователю и падал при открытии обработки. Прямой команды «проверь внешнюю обработку» у платформы нет, но объект конфигурации она проверяет: обработка кладётся в конфигурацию временной базы (stub-db-create -EmbedSourceFile) и спрашивается /CheckConfig. При находках сборка отменяется, в выводе — сообщение платформы и путь к файлу исходника. Ключи -Checks и -Context; выключение -Checks off или externalCheck: false в .v8-project.json. С выключенной проверкой поведение прежнее. У копии объекта перевыпускаются все GUID: с идентификаторами исходника платформа путает копию в базе с загружаемой внешней обработкой и через раз отвечает «Исключение XDTO при чтении файла» на исправном исходнике. Ручная матрица (база есть/нет × ps1/py × исправный/битый × с модулями/без) вскрыла ещё три вещи: отказ платформы принять исходники при подготовке проверки выдавался за сбой базы; py-порт стаба терял причину отказа LoadConfigFromFiles (не было /Out, в PS он есть); копии общих утилит в stub-db-create.py разошлись с эталоном db-create — у run_v8 не было POSIX-ветки, из-за чего на darwin/Linux временная база с пробелом в пути молча не находилась бы. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013MQkqXxErBepYmoUycfkbn
This commit is contained in:
co-authored by
Claude Opus 5
parent
a3ea0b7be6
commit
448f05ed11
@@ -78,6 +78,7 @@ allowed-tools:
|
||||
| `ibcmdargs` | array | Доп. аргументы `ibcmd` (форма `--ключ=значение`) |
|
||||
| `newObjectPosition` | `"end"` / `"byName"` | Куда попадает новый объект в составе конфигурации: в конец своего вида (по умолчанию) или на место по имени. Переопределяется в `databases[]` |
|
||||
| `extensionApplyCheck` | bool | Проверять ли применимость расширения после загрузки в базу (по умолчанию `true`). Разово отключается ключом `-NoApplyCheck` |
|
||||
| `externalCheck` | bool | Проверять ли исходники внешней обработки/отчёта платформой перед сборкой (по умолчанию `true`). Разово отключается ключом `-Checks off` |
|
||||
| `databases` | array | Массив баз данных |
|
||||
| `default` | string | id базы по умолчанию |
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ allowed-tools:
|
||||
|
||||
## Параметры подключения (опционально)
|
||||
|
||||
Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы.
|
||||
Предпочтительно использовать конкретную базу — это надёжнее. Временная база всё равно поднимается
|
||||
под проверку исходников, если она не отключена.
|
||||
|
||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||
@@ -55,11 +56,22 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <п
|
||||
| `-Password <пароль>` | нет | Пароль |
|
||||
| `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников |
|
||||
| `-OutputFile <путь>` | да | Путь к выходному EPF/ERF-файлу |
|
||||
| `-Checks <список>` | нет | Что проверить перед сборкой: `modules`, `handlers`, `unreferenced`, `empty-handlers`, `config`; `off` — не проверять. По умолчанию `modules,handlers` |
|
||||
| `-Context <список>` | нет | Контексты проверки `modules`: `ThinClient`, `Server`, `ExternalConnection`, `ThickClientOrdinaryApplication`. По умолчанию `ThinClient,Server` |
|
||||
| `-AdditionalV8Arguments <список>` | нет | Доп. аргументы запуска `1cv8.exe` через запятую, напр. `/UseHwLicenses+` |
|
||||
| `-AdditionalIbcmdArguments <список>` | нет | Доп. аргументы `ibcmd` через запятую, в форме `--ключ=значение` |
|
||||
|
||||
> `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных
|
||||
|
||||
## Проверка перед сборкой
|
||||
|
||||
Перед сборкой исходники проверяет платформа — синтаксис модулей и наличие обработчиков форм.
|
||||
Если она нашла проблемы, сборка отменяется и файл не создаётся; в выводе — сообщение
|
||||
платформы со строкой и колонкой и путь к файлу исходника. Отключается `-Checks off`
|
||||
или ключом `"externalCheck": false` в `.v8-project.json`.
|
||||
|
||||
Проверка идёт на отдельной временной базе, даже если база указана: чужую конфигурацию навык не трогает.
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# epf-build v1.16 — Build external data processor or report (EPF/ERF) from XML sources
|
||||
# epf-build v1.17 — Build external data processor or report (EPF/ERF) from XML sources
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||
<#
|
||||
@@ -72,6 +72,15 @@ param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$OutputFile,
|
||||
|
||||
# Что проверить в исходниках перед сборкой: modules (синтаксис в контекстах), handlers,
|
||||
# unreferenced, empty-handlers, config; off — не проверять. По умолчанию modules,handlers.
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Checks,
|
||||
|
||||
# Контексты синтаксической проверки. По умолчанию ThinClient,Server.
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Context,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string[]]$AdditionalV8Arguments = @(),
|
||||
|
||||
@@ -388,6 +397,122 @@ function Test-OutputNonEmpty {
|
||||
return (Test-Path $Path -PathType Leaf) -and ((Get-Item $Path -ErrorAction SilentlyContinue).Length -gt 0)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
# --- Проверка исходников платформой ---
|
||||
# Сборка .epf/.erf ничего не проверяет: /LoadExternalDataProcessorOrReportFromFiles упаковывает XML
|
||||
# и модули не компилирует, поэтому сломанный модуль доезжает до пользователя и падает при открытии
|
||||
# обработки. Прямой команды «проверь внешнюю обработку» у платформы нет, но объект КОНФИГУРАЦИИ она
|
||||
# проверяет — поэтому обработка кладётся в конфигурацию временной базы (stub-db-create
|
||||
# -EmbedSourceFile) и спрашивается штатной /CheckConfig.
|
||||
#
|
||||
# Запуск отдельный и только через 1cv8: у ibcmd такой команды нет, а в одной командной строке
|
||||
# DESIGNER выполняет лишь последнюю пакетную команду.
|
||||
function Get-CheckFlags {
|
||||
param([string[]]$Checks, [string[]]$Contexts)
|
||||
$flags = @()
|
||||
if ($Checks -contains 'modules') { foreach ($c in $Contexts) { $flags += "-$c" } }
|
||||
if ($Checks -contains 'handlers') { $flags += '-HandlersExistence' }
|
||||
if ($Checks -contains 'unreferenced') { $flags += '-UnreferenceProcedures' }
|
||||
if ($Checks -contains 'empty-handlers') { $flags += '-EmptyHandlers' }
|
||||
if ($Checks -contains 'config') { $flags += '-ConfigLogIntegrity', '-IncorrectReferences' }
|
||||
return $flags
|
||||
}
|
||||
|
||||
# Платформа называет объект своим именем внутри конфигурации; модели нужен путь к исходнику.
|
||||
# Путь СКЛЕИВАЕТСЯ по конвенции выгрузки, поэтому возвращается только существующий файл:
|
||||
# выдуманный путь хуже отсутствующего — модель пойдёт открывать файл, которого нет.
|
||||
function Resolve-SourcePath {
|
||||
param([string]$Line, [string]$SourceDir)
|
||||
$candidate = $null
|
||||
$m = [regex]::Match($Line, '(?:Обработка|Отчет|DataProcessor|Report)\.([^.]+)\.(?:Форма|Form)\.([^.]+)\.')
|
||||
if ($m.Success) { $candidate = (Join-Path $SourceDir (Join-Path $m.Groups[1].Value (Join-Path "Forms" (Join-Path $m.Groups[2].Value "Ext\Form\Module.bsl")))) }
|
||||
if (-not $candidate) {
|
||||
$m = [regex]::Match($Line, '(?:Обработка|Отчет|DataProcessor|Report)\.([^.]+)\.(МодульОбъекта|ObjectModule)')
|
||||
if ($m.Success) { $candidate = (Join-Path $SourceDir (Join-Path $m.Groups[1].Value "Ext\ObjectModule.bsl")) }
|
||||
}
|
||||
if (-not $candidate) {
|
||||
$m = [regex]::Match($Line, '(?:Обработка|Отчет|DataProcessor|Report)\.([^.]+)\.(МодульМенеджера|ManagerModule)')
|
||||
if ($m.Success) { $candidate = (Join-Path $SourceDir (Join-Path $m.Groups[1].Value "Ext\ManagerModule.bsl")) }
|
||||
}
|
||||
if ($candidate -and (Test-Path $candidate -PathType Leaf)) { return $candidate }
|
||||
return $null
|
||||
}
|
||||
|
||||
# $true, если платформа нашла проблемы — вызывающий не собирает артефакт.
|
||||
function Invoke-SourceCheck {
|
||||
param([string]$Exe, [string]$BasePath, [string[]]$Flags, [string]$SourceDir, [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)) {
|
||||
Write-Host "[note] source check skipped: 1cv8 not found at $v8" -ForegroundColor Yellow
|
||||
return $false
|
||||
}
|
||||
$dir = Join-Path $env:TEMP "epf_check_$(Get-Random)"
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
try {
|
||||
$outFile = Join-Path $dir "check_log.txt"
|
||||
$a = @("DESIGNER", "/F", "`"$BasePath`"", "/CheckConfig") + $Flags + @("/Out", "`"$outFile`"", "/DisableStartupDialogs") + $ExtraArgs
|
||||
Write-Host "Running: 1cv8.exe $($a -join ' ')"
|
||||
$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" | Where-Object { $_.Trim() -ne '' }) }
|
||||
}
|
||||
# Платформа отвечает 101 на найденные проблемы; «Ошибок не обнаружено» приходит с кодом 0.
|
||||
if ($res.ExitCode -eq 0) { return $false }
|
||||
Write-Host "Error: платформа нашла проблемы в исходниках — сборка отменена" -ForegroundColor Red
|
||||
# Пустой лог при ненулевом коде — отказ не по находкам (база занята, нет лицензии); молчать нельзя.
|
||||
if ($lines.Count -eq 0) { Write-Host " платформа вернула код $($res.ExitCode) без сообщений" -ForegroundColor Red }
|
||||
foreach ($l in $lines) {
|
||||
Write-Host " $($l.TrimEnd())" -ForegroundColor Red
|
||||
$srcPath = Resolve-SourcePath $l $SourceDir
|
||||
if ($srcPath) { Write-Host " -> $srcPath" -ForegroundColor Red }
|
||||
}
|
||||
return $true
|
||||
} finally {
|
||||
if (Test-Path $dir) { Remove-Item -Path $dir -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
|
||||
# Проверять ли исходники: -Checks off сильнее настройки проекта externalCheck.
|
||||
function Get-SourceCheckList {
|
||||
param([string]$Checks)
|
||||
$known = @('modules', 'handlers', 'unreferenced', 'empty-handlers', 'config')
|
||||
if ($Checks) {
|
||||
$list = @($Checks -split ',' | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ })
|
||||
if ($list -contains 'off') { return @() }
|
||||
foreach ($c in $list) {
|
||||
if ($known -notcontains $c) {
|
||||
Write-Host "Error: unknown check '$c' (expected: $($known -join ', ') or off)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
return $list
|
||||
}
|
||||
$pf = Find-V8Project (Get-Location).Path
|
||||
if ($pf) {
|
||||
try {
|
||||
$proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
if ($null -ne $proj.externalCheck -and -not [bool]$proj.externalCheck) { return @() }
|
||||
} catch {}
|
||||
}
|
||||
return @('modules', 'handlers')
|
||||
}
|
||||
|
||||
|
||||
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||
|
||||
# --- Resolve additional arguments for the selected engine ---
|
||||
@@ -398,33 +523,69 @@ if ($engine -eq "ibcmd" -and $InfoBaseServer -and $InfoBaseRef) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Auto-create stub database if no connection specified ---
|
||||
$autoCreatedBase = $null
|
||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||
$sourceDir = Split-Path $SourceFile -Parent
|
||||
$autoBasePath = Join-Path $env:TEMP "epf_stub_db_$(Get-Random)"
|
||||
# --- Что проверяем в исходниках перед сборкой ---
|
||||
$checkList = @(Get-SourceCheckList $Checks)
|
||||
$contextList = @()
|
||||
if ($Context) { $contextList = @($Context -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) }
|
||||
if ($contextList.Count -eq 0) { $contextList = @('ThinClient', 'Server') }
|
||||
elseif ($checkList.Count -gt 0 -and $checkList -notcontains 'modules') {
|
||||
Write-Host "Error: -Context задан, но в -Checks нет modules — контексты относятся только к ней" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$sourceDir = Split-Path $SourceFile -Parent
|
||||
|
||||
function New-StubBase {
|
||||
# Стаб запускает свои процессы платформы (CREATEINFOBASE, LoadConfigFromFiles, UpdateDBCfg) —
|
||||
# им нужны те же дополнительные аргументы, что и сборке. Передаются только явные: файл проекта
|
||||
# стаб читает сам. Вызов через -Command, не -File: -File берёт хвост буквально, и массивный
|
||||
# параметр пришёл бы одним склеенным токеном.
|
||||
param([string]$BasePath, [switch]$Embed)
|
||||
$stubScript = Join-Path $PSScriptRoot "stub-db-create.ps1"
|
||||
Write-Host "No database specified. Creating temporary stub database..."
|
||||
# The stub runs its own platform processes (CREATEINFOBASE, LoadConfigFromFiles,
|
||||
# UpdateDBCfg) — they need the same extra arguments as the final build. Only the
|
||||
# explicit ones are forwarded: the stub reads .v8-project.json itself.
|
||||
# Invoked via -Command, not -File: -File takes the tail literally, so an array
|
||||
# parameter would arrive as a single comma-glued token.
|
||||
$q = { param($s) "'" + ($s -replace "'", "''") + "'" }
|
||||
$stubCmd = "& $(& $q $stubScript) -SourceDir $(& $q $sourceDir) -V8Path $(& $q $V8Path) -TempBasePath $(& $q $autoBasePath)"
|
||||
$stubCmd = "& $(& $q $stubScript) -SourceDir $(& $q $sourceDir) -V8Path $(& $q $V8Path) -TempBasePath $(& $q $BasePath)"
|
||||
if ($Embed) { $stubCmd += " -EmbedSourceFile $(& $q $SourceFile)" }
|
||||
if ($AdditionalV8Arguments.Count -gt 0) {
|
||||
$stubCmd += " -AdditionalV8Arguments " + (($AdditionalV8Arguments | ForEach-Object { & $q $_ }) -join ',')
|
||||
}
|
||||
if ($AdditionalIbcmdArguments.Count -gt 0) {
|
||||
$stubCmd += " -AdditionalIbcmdArguments " + (($AdditionalIbcmdArguments | ForEach-Object { & $q $_ }) -join ',')
|
||||
}
|
||||
$stubProc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -Command `"$stubCmd`"" -NoNewWindow -Wait -PassThru
|
||||
if ($stubProc.ExitCode -ne 0) {
|
||||
Write-Host "Error: failed to create stub database" -ForegroundColor Red
|
||||
$p = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -Command `"$stubCmd`"" -NoNewWindow -Wait -PassThru
|
||||
return $p.ExitCode
|
||||
}
|
||||
|
||||
# --- Auto-create stub database if no connection specified ---
|
||||
$autoCreatedBase = $null
|
||||
$checkBase = $null
|
||||
$checkBasePath = $null
|
||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||
$autoBasePath = Join-Path $env:TEMP "epf_stub_db_$(Get-Random)"
|
||||
Write-Host "No database specified. Creating temporary stub database..."
|
||||
if ((New-StubBase $autoBasePath -Embed:($checkList.Count -gt 0)) -ne 0) {
|
||||
# С внедрённой обработкой база падает прежде всего из-за самих исходников
|
||||
# (пример: DefaultForm на несуществующую форму) — говорить про базу значит увести не туда.
|
||||
if ($checkList.Count -gt 0) {
|
||||
Write-Host "Error: платформа не приняла исходники при подготовке проверки — сборка отменена" -ForegroundColor Red
|
||||
Write-Host " сообщение платформы выше; имя объекта в нём конфигурационное: DataProcessor/Report = проверяемая внешняя обработка/отчёт" -ForegroundColor Red
|
||||
} else {
|
||||
Write-Host "Error: failed to create stub database" -ForegroundColor Red
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
$InfoBasePath = $autoBasePath
|
||||
$autoCreatedBase = $autoBasePath
|
||||
if ($checkList.Count -gt 0) { $checkBasePath = $autoBasePath }
|
||||
} elseif ($checkList.Count -gt 0) {
|
||||
# Базу указали снаружи: класть проверяемую обработку в чужую конфигурацию нельзя, поэтому под
|
||||
# проверку поднимается своя временная база, а сборка идёт на указанной.
|
||||
$checkBase = Join-Path $env:TEMP "epf_check_db_$(Get-Random)"
|
||||
Write-Host "Creating temporary database for the source check..."
|
||||
if ((New-StubBase $checkBase -Embed) -ne 0) {
|
||||
Write-Host "Error: платформа не приняла исходники при подготовке проверки — сборка отменена" -ForegroundColor Red
|
||||
Write-Host " сообщение платформы выше; имя объекта в нём конфигурационное: DataProcessor/Report = проверяемая внешняя обработка/отчёт" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$checkBasePath = $checkBase
|
||||
}
|
||||
|
||||
# --- Validate source file ---
|
||||
@@ -433,6 +594,18 @@ if (-not (Test-Path $SourceFile)) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Проверка исходников платформой: сломанный .epf до пользователя доезжать не должен ---
|
||||
if ($checkList.Count -gt 0 -and $checkBasePath) {
|
||||
# Проверку ведёт 1cv8, поэтому ibcmd-шные дополнительные аргументы ей не отдаём.
|
||||
$checkExtra = if ($engine -eq "ibcmd") { @() } else { $extraArgs }
|
||||
$found = Invoke-SourceCheck $V8Path $checkBasePath (Get-CheckFlags $checkList $contextList) $sourceDir $checkExtra
|
||||
if ($found) {
|
||||
if ($autoCreatedBase -and (Test-Path $autoCreatedBase)) { Remove-Item -Path $autoCreatedBase -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
if ($checkBase -and (Test-Path $checkBase)) { Remove-Item -Path $checkBase -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# --- Ensure output directory exists ---
|
||||
$outDir = Split-Path $OutputFile -Parent
|
||||
if ($outDir -and -not (Test-Path $outDir)) {
|
||||
@@ -526,4 +699,7 @@ try {
|
||||
if ($autoCreatedBase -and (Test-Path $autoCreatedBase)) {
|
||||
Remove-Item -Path $autoCreatedBase -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($checkBase -and (Test-Path $checkBase)) {
|
||||
Remove-Item -Path $checkBase -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
# epf-build v1.16 — Build external data processor or report (EPF/ERF) from XML sources
|
||||
# epf-build v1.17 — 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 io
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
@@ -410,6 +411,136 @@ def _redact(text, *secrets):
|
||||
return text
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
# --- Проверка исходников платформой ---
|
||||
# Сборка .epf/.erf ничего не проверяет: /LoadExternalDataProcessorOrReportFromFiles упаковывает XML
|
||||
# и модули не компилирует, поэтому сломанный модуль доезжает до пользователя и падает при открытии
|
||||
# обработки. Прямой команды «проверь внешнюю обработку» у платформы нет, но объект КОНФИГУРАЦИИ она
|
||||
# проверяет — поэтому обработка кладётся в конфигурацию временной базы (stub-db-create
|
||||
# -EmbedSourceFile) и спрашивается штатной /CheckConfig.
|
||||
#
|
||||
# Запуск отдельный и только через 1cv8: у ibcmd такой команды нет, а в одной командной строке
|
||||
# DESIGNER выполняет лишь последнюю пакетную команду.
|
||||
def get_check_flags(checks, contexts):
|
||||
flags = []
|
||||
if 'modules' in checks:
|
||||
for c in contexts:
|
||||
flags.append('-' + c)
|
||||
if 'handlers' in checks:
|
||||
flags.append('-HandlersExistence')
|
||||
if 'unreferenced' in checks:
|
||||
flags.append('-UnreferenceProcedures')
|
||||
if 'empty-handlers' in checks:
|
||||
flags.append('-EmptyHandlers')
|
||||
if 'config' in checks:
|
||||
flags += ['-ConfigLogIntegrity', '-IncorrectReferences']
|
||||
return flags
|
||||
|
||||
|
||||
# Платформа называет объект своим именем внутри конфигурации; модели нужен путь к исходнику.
|
||||
# Путь СКЛЕИВАЕТСЯ по конвенции выгрузки, поэтому возвращается только существующий файл:
|
||||
# выдуманный путь хуже отсутствующего — модель пойдёт открывать файл, которого нет.
|
||||
def resolve_source_path(line, source_dir):
|
||||
candidate = None
|
||||
m = re.search(r'(?:\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430|\u041e\u0442\u0447\u0435\u0442|DataProcessor|Report)\.([^.]+)\.(?:\u0424\u043e\u0440\u043c\u0430|Form)\.([^.]+)\.', line)
|
||||
if m:
|
||||
candidate = os.path.join(source_dir, m.group(1), 'Forms', m.group(2), 'Ext', 'Form', 'Module.bsl')
|
||||
if candidate is None:
|
||||
m = re.search(r'(?:\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430|\u041e\u0442\u0447\u0435\u0442|DataProcessor|Report)\.([^.]+)\.(?:\u041c\u043e\u0434\u0443\u043b\u044c\u041e\u0431\u044a\u0435\u043a\u0442\u0430|ObjectModule)', line)
|
||||
if m:
|
||||
candidate = os.path.join(source_dir, m.group(1), 'Ext', 'ObjectModule.bsl')
|
||||
if candidate is None:
|
||||
m = re.search(r'(?:\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430|\u041e\u0442\u0447\u0435\u0442|DataProcessor|Report)\.([^.]+)\.(?:\u041c\u043e\u0434\u0443\u043b\u044c\u041c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430|ManagerModule)', line)
|
||||
if m:
|
||||
candidate = os.path.join(source_dir, m.group(1), 'Ext', 'ManagerModule.bsl')
|
||||
if candidate and os.path.isfile(candidate):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
# True, если платформа нашла проблемы — вызывающий не собирает артефакт.
|
||||
def invoke_source_check(exe, base_path, flags, source_dir, extra_args):
|
||||
exe_dir = os.path.dirname(exe)
|
||||
exe_leaf = os.path.basename(exe)
|
||||
if exe_leaf.lower().startswith('ibcmd'):
|
||||
v8 = os.path.join(exe_dir, '1cv8' + os.path.splitext(exe)[1])
|
||||
else:
|
||||
v8 = exe
|
||||
if not os.path.exists(v8):
|
||||
print(f'[note] source check skipped: 1cv8 not found at {v8}')
|
||||
return False
|
||||
d = os.path.join(tempfile.gettempdir(), f'epf_check_{random.randint(0, 999999)}')
|
||||
os.makedirs(d, exist_ok=True)
|
||||
try:
|
||||
out_file = os.path.join(d, 'check_log.txt')
|
||||
a = (['DESIGNER', '/F', f'"{base_path}"', '/CheckConfig'] + flags
|
||||
+ ['/Out', f'"{out_file}"', '/DisableStartupDialogs']
|
||||
+ [quote_if_needed(x) for x in extra_args])
|
||||
print(f'Running: 1cv8.exe {" ".join(a)}')
|
||||
result = run_v8(v8, a)
|
||||
lines = []
|
||||
if os.path.isfile(out_file):
|
||||
try:
|
||||
with io.open(out_file, encoding='utf-8-sig', errors='replace') as fh:
|
||||
raw = fh.read()
|
||||
lines = [l for l in raw.splitlines() if l.strip()]
|
||||
except Exception:
|
||||
lines = []
|
||||
# Платформа отвечает 101 на найденные проблемы; «Ошибок не обнаружено» приходит с кодом 0.
|
||||
if result.returncode == 0:
|
||||
return False
|
||||
print('Error: \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u043d\u0430\u0448\u043b\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0430\u0445 \u2014 \u0441\u0431\u043e\u0440\u043a\u0430 \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u0430')
|
||||
# Пустой лог при ненулевом коде — отказ не по находкам (база занята, нет лицензии); молчать нельзя.
|
||||
if not lines:
|
||||
print(f' платформа вернула код {result.returncode} без сообщений')
|
||||
for l in lines:
|
||||
print(f' {l.rstrip()}')
|
||||
src_path = resolve_source_path(l, source_dir)
|
||||
if src_path:
|
||||
print(f' -> {src_path}')
|
||||
return True
|
||||
finally:
|
||||
shutil.rmtree(d, ignore_errors=True)
|
||||
|
||||
|
||||
# Проверять ли исходники: -Checks off сильнее настройки проекта externalCheck.
|
||||
def get_source_check_list(checks):
|
||||
known = ['modules', 'handlers', 'unreferenced', 'empty-handlers', 'config']
|
||||
if checks:
|
||||
lst = [c.strip().lower() for c in checks.split(',') if c.strip()]
|
||||
if 'off' in lst:
|
||||
return []
|
||||
for c in lst:
|
||||
if c not in known:
|
||||
print(f'Error: unknown check \'{c}\' (expected: {", ".join(known)} or off)')
|
||||
sys.exit(1)
|
||||
return lst
|
||||
pf = _sg_find_v8project(os.getcwd())
|
||||
if pf:
|
||||
try:
|
||||
with open(pf, encoding='utf-8-sig') as fh:
|
||||
proj = json.load(fh)
|
||||
if 'externalCheck' in proj and not proj['externalCheck']:
|
||||
return []
|
||||
except Exception:
|
||||
pass
|
||||
return ['modules', 'handlers']
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
@@ -425,6 +556,11 @@ def main():
|
||||
parser.add_argument("-Password", default="", help="1C user password")
|
||||
parser.add_argument("-SourceFile", required=True, help="Path to root XML source file")
|
||||
parser.add_argument("-OutputFile", required=True, help="Path to output EPF/ERF file")
|
||||
# Что проверить в исходниках перед сборкой: modules (синтаксис в контекстах), handlers,
|
||||
# unreferenced, empty-handlers, config; off — не проверять. По умолчанию modules,handlers.
|
||||
parser.add_argument("-Checks", default="")
|
||||
# Контексты синтаксической проверки. По умолчанию ThinClient,Server.
|
||||
parser.add_argument("-Context", default="")
|
||||
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
|
||||
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
|
||||
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
|
||||
@@ -458,34 +594,80 @@ def main():
|
||||
print("Error: ibcmd supports file infobases only (use -InfoBasePath or omit for stub)")
|
||||
sys.exit(1)
|
||||
|
||||
# --- Auto-create stub database if no connection specified ---
|
||||
auto_created_base = None
|
||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||
source_dir = os.path.dirname(os.path.abspath(args.SourceFile))
|
||||
auto_base_path = os.path.join(tempfile.gettempdir(), f"epf_stub_db_{random.randint(0, 999999)}")
|
||||
stub_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stub-db-create.py")
|
||||
print("No database specified. Creating temporary stub database...")
|
||||
stub_cmd = [sys.executable, stub_script, "-SourceDir", source_dir, "-V8Path", v8path,
|
||||
"-TempBasePath", auto_base_path]
|
||||
# --- Что проверяем в исходниках перед сборкой ---
|
||||
check_list = get_source_check_list(args.Checks)
|
||||
context_list = [c.strip() for c in args.Context.split(',') if c.strip()] if args.Context else []
|
||||
if not context_list:
|
||||
context_list = ['ThinClient', 'Server']
|
||||
elif check_list and 'modules' not in check_list:
|
||||
print('Error: -Context задан, но в -Checks нет modules — контексты относятся только к ней')
|
||||
sys.exit(1)
|
||||
source_dir = os.path.dirname(os.path.abspath(args.SourceFile))
|
||||
|
||||
def new_stub_base(base_path, embed):
|
||||
# The stub runs its own platform processes (CREATEINFOBASE, LoadConfigFromFiles,
|
||||
# UpdateDBCfg) — they need the same extra arguments as the final build. Only the
|
||||
# explicit ones are forwarded: the stub reads .v8-project.json itself.
|
||||
stub_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stub-db-create.py")
|
||||
stub_cmd = [sys.executable, stub_script, "-SourceDir", source_dir, "-V8Path", v8path,
|
||||
"-TempBasePath", base_path]
|
||||
if embed:
|
||||
stub_cmd += ["-EmbedSourceFile", args.SourceFile]
|
||||
if v8_extra:
|
||||
stub_cmd += ["-AdditionalV8Arguments"] + list(v8_extra)
|
||||
if ibcmd_extra:
|
||||
stub_cmd += ["-AdditionalIbcmdArguments"] + list(ibcmd_extra)
|
||||
result = subprocess.run(stub_cmd, capture_output=False)
|
||||
if result.returncode != 0:
|
||||
print("Error: failed to create stub database")
|
||||
return subprocess.run(stub_cmd, capture_output=False).returncode
|
||||
|
||||
# --- Auto-create stub database if no connection specified ---
|
||||
auto_created_base = None
|
||||
check_base = None
|
||||
check_base_path = None
|
||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||
auto_base_path = os.path.join(tempfile.gettempdir(), f"epf_stub_db_{random.randint(0, 999999)}")
|
||||
print("No database specified. Creating temporary stub database...")
|
||||
if new_stub_base(auto_base_path, bool(check_list)) != 0:
|
||||
# С внедрённой обработкой база падает прежде всего из-за самих исходников
|
||||
# (пример: DefaultForm на несуществующую форму) — говорить про базу значит увести не туда.
|
||||
if check_list:
|
||||
print('Error: платформа не приняла исходники при подготовке проверки — сборка отменена')
|
||||
print(' сообщение платформы выше; имя объекта в нём конфигурационное: DataProcessor/Report = проверяемая внешняя обработка/отчёт')
|
||||
else:
|
||||
print("Error: failed to create stub database")
|
||||
sys.exit(1)
|
||||
args.InfoBasePath = auto_base_path
|
||||
auto_created_base = auto_base_path
|
||||
if check_list:
|
||||
check_base_path = auto_base_path
|
||||
elif check_list:
|
||||
# Базу указали снаружи: класть проверяемую обработку в чужую конфигурацию нельзя, поэтому под
|
||||
# проверку поднимается своя временная база, а сборка идёт на указанной.
|
||||
check_base = os.path.join(tempfile.gettempdir(), f"epf_check_db_{random.randint(0, 999999)}")
|
||||
print("Creating temporary database for the source check...")
|
||||
if new_stub_base(check_base, True) != 0:
|
||||
print('Error: платформа не приняла исходники при подготовке проверки — сборка отменена')
|
||||
print(' сообщение платформы выше; имя объекта в нём конфигурационное: DataProcessor/Report = проверяемая внешняя обработка/отчёт')
|
||||
sys.exit(1)
|
||||
check_base_path = check_base
|
||||
|
||||
# --- Validate source file ---
|
||||
if not os.path.isfile(args.SourceFile):
|
||||
print(f"Error: source file not found: {args.SourceFile}")
|
||||
sys.exit(1)
|
||||
|
||||
# --- Проверка исходников платформой: сломанный .epf до пользователя доезжать не должен ---
|
||||
if check_list and check_base_path:
|
||||
# Проверку ведёт 1cv8, поэтому ibcmd-шные дополнительные аргументы ей не отдаём.
|
||||
check_extra = [] if engine == "ibcmd" else extra_args
|
||||
found = invoke_source_check(v8path, check_base_path,
|
||||
get_check_flags(check_list, context_list), source_dir, check_extra)
|
||||
if found:
|
||||
if auto_created_base and os.path.exists(auto_created_base):
|
||||
shutil.rmtree(auto_created_base, ignore_errors=True)
|
||||
if check_base and os.path.exists(check_base):
|
||||
shutil.rmtree(check_base, ignore_errors=True)
|
||||
sys.exit(1)
|
||||
|
||||
# --- Ensure output directory exists ---
|
||||
out_dir = os.path.dirname(args.OutputFile)
|
||||
if out_dir and not os.path.exists(out_dir):
|
||||
@@ -579,6 +761,8 @@ def main():
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
if auto_created_base and os.path.exists(auto_created_base):
|
||||
shutil.rmtree(auto_created_base, ignore_errors=True)
|
||||
if check_base and os.path.exists(check_base):
|
||||
shutil.rmtree(check_base, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# stub-db-create v1.8 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||
# stub-db-create v1.9 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -9,6 +9,10 @@ param(
|
||||
|
||||
[string]$TempBasePath,
|
||||
|
||||
# XML проверяемой обработки/отчёта: объект кладётся в конфигурацию-заглушку, чтобы платформа
|
||||
# смогла проверить его штатными проверками. Без параметра стаб работает как раньше.
|
||||
[string]$EmbedSourceFile,
|
||||
|
||||
[string[]]$AdditionalV8Arguments = @(),
|
||||
|
||||
[string[]]$AdditionalIbcmdArguments = @()
|
||||
@@ -350,6 +354,9 @@ foreach ($f in $xmlFiles) {
|
||||
}
|
||||
|
||||
$hasRefTypes = $typeMap.Count -gt 0
|
||||
# Конфигурация нужна и тогда, когда ссылочных типов нет: в неё кладётся сам объект.
|
||||
$embedRequested = -not [string]::IsNullOrWhiteSpace($EmbedSourceFile)
|
||||
$needCfg = $hasRefTypes -or $embedRequested
|
||||
|
||||
# --- 2. Determine TempBasePath ---
|
||||
if (-not $TempBasePath) {
|
||||
@@ -370,13 +377,106 @@ if ($needsRegistrator) {
|
||||
$typeMap["Document"]["ЗаглушкаРегистратора"] = $true
|
||||
}
|
||||
|
||||
# --- Внедрение проверяемого объекта в конфигурацию-заглушку ---
|
||||
# Платформа не умеет проверять внешнюю обработку: /LoadExternalDataProcessorOrReportFromFiles
|
||||
# только упаковывает XML и модули не компилирует. Зато она проверяет объект КОНФИГУРАЦИИ, а
|
||||
# внешняя обработка отличается от него немногим (замер 8.3.24): корневым тегом, именем
|
||||
# порождаемого объектного типа и отсутствием типа менеджера. Правим ровно эти точки и переносим
|
||||
# остальное как есть — под проверку попадает всё, что написал автор, включая реквизиты, формы и
|
||||
# макеты, а формат может расти без правок здесь.
|
||||
#
|
||||
# Подстановка типа делается ТОЛЬКО в .xml (это DefaultForm и основной реквизит формы); в .bsl
|
||||
# такой же текст был бы кодом, и трогать его нельзя.
|
||||
function Add-SourceObjectToConfig {
|
||||
param([string]$SourceXml, [string]$CfgDir)
|
||||
|
||||
# Копия объекта живёт в конфигурации базы, а следом в ту же базу грузится исходник как ВНЕШНЯЯ
|
||||
# обработка. С одинаковыми идентификаторами платформа путает их и через раз отвечает «Исключение
|
||||
# XDTO при чтении файла» на исправном исходнике — поэтому у копии все GUID свои, но согласованные
|
||||
# между её файлами (ссылки внутри объекта идут по идентификатору).
|
||||
$guidMap = @{}
|
||||
$reGuid = [regex]'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
|
||||
$reissue = {
|
||||
param($m)
|
||||
$k = $m.Value.ToLower()
|
||||
if (-not $guidMap.ContainsKey($k)) { $guidMap[$k] = [guid]::NewGuid().ToString() }
|
||||
$guidMap[$k]
|
||||
}
|
||||
|
||||
$text = [IO.File]::ReadAllText($SourceXml, [Text.Encoding]::UTF8)
|
||||
if ($text -match '<ExternalDataProcessor[\s>]') {
|
||||
$extTag = 'ExternalDataProcessor'; $cfgTag = 'DataProcessor'; $folder = 'DataProcessors'
|
||||
} elseif ($text -match '<ExternalReport[\s>]') {
|
||||
$extTag = 'ExternalReport'; $cfgTag = 'Report'; $folder = 'Reports'
|
||||
} else {
|
||||
return $null
|
||||
}
|
||||
|
||||
$name = if ($text -match '<Name>([^<]+)</Name>') { $Matches[1] } else { [IO.Path]::GetFileNameWithoutExtension($SourceXml) }
|
||||
|
||||
$conv = $reGuid.Replace($text, $reissue)
|
||||
$conv = $conv.Replace("<$extTag ", "<$cfgTag ").Replace("<$extTag>", "<$cfgTag>").Replace("</$extTag>", "</$cfgTag>")
|
||||
$conv = $conv.Replace("${extTag}Object.", "${cfgTag}Object.").Replace("$extTag.", "$cfgTag.")
|
||||
|
||||
# Тип менеджера у внешней обработки не объявлен, а объекту конфигурации он обязателен:
|
||||
# без него платформа отвечает «отсутствует один или более типов объекта».
|
||||
$mgr = "`t`t`t<xr:GeneratedType name=`"${cfgTag}Manager.$name`" category=`"Manager`">`r`n" +
|
||||
"`t`t`t`t<xr:TypeId>$([guid]::NewGuid().ToString())</xr:TypeId>`r`n" +
|
||||
"`t`t`t`t<xr:ValueId>$([guid]::NewGuid().ToString())</xr:ValueId>`r`n" +
|
||||
"`t`t`t</xr:GeneratedType>`r`n"
|
||||
if ($conv -match '</InternalInfo>') {
|
||||
$conv = [regex]::Replace($conv, '(\s*)</InternalInfo>', ("`r`n" + $mgr + "`t`t</InternalInfo>"), 1)
|
||||
} else {
|
||||
$objType = "`t`t`t<xr:GeneratedType name=`"${cfgTag}Object.$name`" category=`"Object`">`r`n" +
|
||||
"`t`t`t`t<xr:TypeId>$([guid]::NewGuid().ToString())</xr:TypeId>`r`n" +
|
||||
"`t`t`t`t<xr:ValueId>$([guid]::NewGuid().ToString())</xr:ValueId>`r`n" +
|
||||
"`t`t`t</xr:GeneratedType>`r`n"
|
||||
$conv = [regex]::Replace($conv, "(<$cfgTag[^>]*>)", ("`$1`r`n`t`t<InternalInfo>`r`n" + $objType + $mgr + "`t`t</InternalInfo>"), 1)
|
||||
}
|
||||
|
||||
$objDir = Join-Path $CfgDir $folder
|
||||
New-Item -ItemType Directory -Path $objDir -Force | Out-Null
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
[IO.File]::WriteAllText((Join-Path $objDir "$name.xml"), $conv, $encBom)
|
||||
|
||||
# Содержимое объекта — как есть; в XML та же подстановка типа, .bsl копируются байт в байт.
|
||||
$srcContent = Join-Path (Split-Path $SourceXml -Parent) $name
|
||||
if (Test-Path $srcContent) {
|
||||
$dstContent = Join-Path $objDir $name
|
||||
foreach ($f in (Get-ChildItem -Path $srcContent -Recurse -File)) {
|
||||
$rel = $f.FullName.Substring($srcContent.Length).TrimStart('\', '/')
|
||||
$dst = Join-Path $dstContent $rel
|
||||
New-Item -ItemType Directory -Path (Split-Path $dst -Parent) -Force | Out-Null
|
||||
if ($f.Extension -ieq '.xml') {
|
||||
$t = [IO.File]::ReadAllText($f.FullName, [Text.Encoding]::UTF8)
|
||||
$t = $reGuid.Replace($t, $reissue)
|
||||
$t = $t.Replace("${extTag}Object.", "${cfgTag}Object.").Replace("$extTag.", "$cfgTag.")
|
||||
[IO.File]::WriteAllText($dst, $t, $encBom)
|
||||
} else {
|
||||
Copy-Item -Path $f.FullName -Destination $dst -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @{ Tag = $cfgTag; Name = $name }
|
||||
}
|
||||
|
||||
# --- 4. Generate configuration XML ---
|
||||
|
||||
if ($hasRefTypes) {
|
||||
if ($needCfg) {
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
$cfgDir = Join-Path $TempBasePath "cfg"
|
||||
New-Item -ItemType Directory -Path $cfgDir -Force | Out-Null
|
||||
|
||||
$embedded = $null
|
||||
if ($embedRequested) {
|
||||
$embedded = Add-SourceObjectToConfig $EmbedSourceFile $cfgDir
|
||||
if (-not $embedded) {
|
||||
Write-Host "Error: $EmbedSourceFile is neither ExternalDataProcessor nor ExternalReport" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Заглушке нужна САМАЯ НИЗКАЯ работающая версия, а не версия исходников: ограничение платформы
|
||||
# одностороннее — она читает формат не новее себя. Отсюда min(версия исходников, 2.17): на 2.17+
|
||||
# заглушка остаётся 2.17 (как было), а под исходники 2.13-2.16 опускается до их версии, иначе
|
||||
@@ -566,6 +666,7 @@ if ($hasRefTypes) {
|
||||
$childXml += "`r`n`t`t`t<$tag>$name</$tag>"
|
||||
}
|
||||
}
|
||||
if ($embedded) { $childXml += "`r`n`t`t`t<$($embedded.Tag)>$($embedded.Name)</$($embedded.Tag)>" }
|
||||
|
||||
$cfgXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -1548,7 +1649,7 @@ if ($stubEngine -eq "ibcmd") {
|
||||
$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" }
|
||||
if ($needCfg) { $ibArgs += "--import=$(Join-Path $TempBasePath 'cfg')", "--apply", "--force" }
|
||||
$ibArgs += "--data=$ibData"
|
||||
$ibArgs += $extraArgs
|
||||
$__ib = Invoke-PlatformProcess $V8Path $ibArgs
|
||||
@@ -1560,7 +1661,7 @@ if ($stubEngine -eq "ibcmd") {
|
||||
Write-Error "Failed to create stub infobase (code: $ibRc)"
|
||||
exit 1
|
||||
}
|
||||
if ($hasRefTypes) { Remove-Item -Path (Join-Path $TempBasePath "cfg") -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
if ($needCfg) { Remove-Item -Path (Join-Path $TempBasePath "cfg") -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
Write-Host "[OK] Stub database created: $TempBasePath"
|
||||
Write-Host $TempBasePath
|
||||
exit 0
|
||||
@@ -1576,8 +1677,8 @@ if ($proc.ExitCode -ne 0) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- 6. Load config and update DB if ref types exist ---
|
||||
if ($hasRefTypes) {
|
||||
# --- 6. Load config and update DB if there is one ---
|
||||
if ($needCfg) {
|
||||
$cfgDir = Join-Path $TempBasePath "cfg"
|
||||
# LoadConfigFromFiles
|
||||
Write-Host "Loading configuration from files..."
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
# stub-db-create v1.8 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||
# stub-db-create v1.9 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
@@ -64,11 +66,31 @@ def run_v8(v8path, arguments):
|
||||
The arguments carry their own quotes inside the value (File="C:\\a b") — that is where
|
||||
1C's parser expects them, on Windows and on *nix alike. Windows list2cmdline would
|
||||
escape those quotes, so there the command line is handed over ready-made.
|
||||
|
||||
На POSIX аргументы уходят СПИСКОМ, и кавычки, нужные для склейки на Windows, стали бы
|
||||
частью значения: путь с пробелом платформа не находит («Неопределена информационная
|
||||
база»), многословный -comment теряет молча. Поэтому здесь снимается ОДИН слой
|
||||
обрамляющих кавычек. Склеенные ключи (/N"user", /ConfigurationRepositoryF"путь",
|
||||
File="…") не задеты: у них кавычки внутри токена, а не по краям.
|
||||
"""
|
||||
if os.name == "nt":
|
||||
cmd = '"' + v8path + '" ' + " ".join(arguments)
|
||||
else:
|
||||
cmd = [v8path] + arguments
|
||||
def strip_framing_quotes(a):
|
||||
# Кавычки, которыми мы обрамляем значения ради склейки на Windows, на POSIX
|
||||
# становятся ЧАСТЬЮ значения. Проверено на darwin: путь с пробелом отдельным
|
||||
# токеном даёт «Неопределена информационная база», а склеенный
|
||||
# /ConfigurationRepositoryF"путь с пробелом" — «завершилось с ошибкой»;
|
||||
# без кавычек обе формы работают.
|
||||
if len(a) > 1 and a[0] == '"' and a[-1] == '"':
|
||||
return a[1:-1] # "значение" отдельным токеном
|
||||
if a[0:1] == "/" and a[-1:] == '"' and '"' in a[:-1]:
|
||||
i = a.index('"')
|
||||
return a[:i] + a[i + 1:-1] # /N"имя" -> /Nимя
|
||||
return a # File="…" не трогаем: там кавычки —
|
||||
# часть синтаксиса строки соединения,
|
||||
# и с ними на POSIX всё работает
|
||||
cmd = [v8path] + [strip_framing_quotes(a) for a in arguments]
|
||||
r = subprocess.run(cmd, input=b"", capture_output=True)
|
||||
r.stdout = decode_platform_bytes(r.stdout)
|
||||
r.stderr = decode_platform_bytes(r.stderr)
|
||||
@@ -178,7 +200,6 @@ def assert_extra_args(extra, engine, hints):
|
||||
print(
|
||||
f"Error: '{tok}' is a positional token — pass values as --key=value "
|
||||
f"({param} cannot extend the ibcmd command)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
if engine != "ibcmd":
|
||||
@@ -187,7 +208,6 @@ def assert_extra_args(extra, engine, hints):
|
||||
print(
|
||||
f"Error: {b} is a batch command; passed via {param} it would replace "
|
||||
f"the skill's own operation (a command line runs only its last batch command)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
for k in owned:
|
||||
@@ -195,7 +215,6 @@ def assert_extra_args(extra, engine, hints):
|
||||
hint = f" (use {hints[k]})" if hints and k in hints else ""
|
||||
print(
|
||||
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -263,14 +282,12 @@ def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
|
||||
print(
|
||||
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
|
||||
"(use -AdditionalIbcmdArguments)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
if engine != "ibcmd" and ibcmd_extra:
|
||||
print(
|
||||
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
|
||||
"(use -AdditionalV8Arguments)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
if engine == "ibcmd":
|
||||
@@ -1103,6 +1120,94 @@ def write_bom(path, content):
|
||||
f.write(content)
|
||||
|
||||
|
||||
# --- Внедрение проверяемого объекта в конфигурацию-заглушку ---
|
||||
# Платформа не умеет проверять внешнюю обработку: /LoadExternalDataProcessorOrReportFromFiles
|
||||
# только упаковывает XML и модули не компилирует. Зато она проверяет объект КОНФИГУРАЦИИ, а
|
||||
# внешняя обработка отличается от него немногим (замер 8.3.24): корневым тегом, именем
|
||||
# порождаемого объектного типа и отсутствием типа менеджера. Правим ровно эти точки и переносим
|
||||
# остальное как есть — под проверку попадает всё, что написал автор, включая реквизиты, формы и
|
||||
# макеты, а формат может расти без правок здесь.
|
||||
#
|
||||
# Подстановка типа делается ТОЛЬКО в .xml (это DefaultForm и основной реквизит формы); в .bsl
|
||||
# такой же текст был бы кодом, и трогать его нельзя.
|
||||
GUID_RE = re.compile(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}')
|
||||
|
||||
|
||||
def add_source_object_to_config(source_xml, cfg_dir):
|
||||
# Копия объекта живёт в конфигурации базы, а следом в ту же базу грузится исходник как ВНЕШНЯЯ
|
||||
# обработка. С одинаковыми идентификаторами платформа путает их и через раз отвечает «Исключение
|
||||
# XDTO при чтении файла» на исправном исходнике — поэтому у копии все GUID свои, но согласованные
|
||||
# между её файлами (ссылки внутри объекта идут по идентификатору).
|
||||
guid_map = {}
|
||||
|
||||
def reissue(text):
|
||||
def sub(m):
|
||||
k = m.group(0).lower()
|
||||
if k not in guid_map:
|
||||
guid_map[k] = new_uuid()
|
||||
return guid_map[k]
|
||||
return GUID_RE.sub(sub, text)
|
||||
|
||||
with io.open(source_xml, encoding='utf-8-sig') as fh:
|
||||
text = fh.read()
|
||||
if re.search(r'<ExternalDataProcessor[\s>]', text):
|
||||
ext_tag, cfg_tag, folder = 'ExternalDataProcessor', 'DataProcessor', 'DataProcessors'
|
||||
elif re.search(r'<ExternalReport[\s>]', text):
|
||||
ext_tag, cfg_tag, folder = 'ExternalReport', 'Report', 'Reports'
|
||||
else:
|
||||
return None
|
||||
|
||||
m = re.search(r'<Name>([^<]+)</Name>', text)
|
||||
name = m.group(1) if m else os.path.splitext(os.path.basename(source_xml))[0]
|
||||
|
||||
conv = reissue(text)
|
||||
conv = conv.replace('<%s ' % ext_tag, '<%s ' % cfg_tag).replace('<%s>' % ext_tag, '<%s>' % cfg_tag)
|
||||
conv = conv.replace('</%s>' % ext_tag, '</%s>' % cfg_tag)
|
||||
conv = conv.replace('%sObject.' % ext_tag, '%sObject.' % cfg_tag).replace('%s.' % ext_tag, '%s.' % cfg_tag)
|
||||
|
||||
# Тип менеджера у внешней обработки не объявлен, а объекту конфигурации он обязателен:
|
||||
# без него платформа отвечает «отсутствует один или более типов объекта».
|
||||
mgr = ('\t\t\t<xr:GeneratedType name="%sManager.%s" category="Manager">\r\n' % (cfg_tag, name) +
|
||||
'\t\t\t\t<xr:TypeId>%s</xr:TypeId>\r\n' % new_uuid() +
|
||||
'\t\t\t\t<xr:ValueId>%s</xr:ValueId>\r\n' % new_uuid() +
|
||||
'\t\t\t</xr:GeneratedType>\r\n')
|
||||
if '</InternalInfo>' in conv:
|
||||
conv = re.sub(r'\s*</InternalInfo>', lambda _m: '\r\n' + mgr + '\t\t</InternalInfo>', conv, count=1)
|
||||
else:
|
||||
obj_type = ('\t\t\t<xr:GeneratedType name="%sObject.%s" category="Object">\r\n' % (cfg_tag, name) +
|
||||
'\t\t\t\t<xr:TypeId>%s</xr:TypeId>\r\n' % new_uuid() +
|
||||
'\t\t\t\t<xr:ValueId>%s</xr:ValueId>\r\n' % new_uuid() +
|
||||
'\t\t\t</xr:GeneratedType>\r\n')
|
||||
conv = re.sub('(<%s[^>]*>)' % cfg_tag,
|
||||
lambda m2: m2.group(1) + '\r\n\t\t<InternalInfo>\r\n' + obj_type + mgr + '\t\t</InternalInfo>',
|
||||
conv, count=1)
|
||||
|
||||
obj_dir = os.path.join(cfg_dir, folder)
|
||||
os.makedirs(obj_dir, exist_ok=True)
|
||||
write_bom(os.path.join(obj_dir, '%s.xml' % name), conv)
|
||||
|
||||
# Содержимое объекта — как есть; в XML та же подстановка типа, .bsl копируются байт в байт.
|
||||
src_content = os.path.join(os.path.dirname(source_xml), name)
|
||||
if os.path.isdir(src_content):
|
||||
dst_content = os.path.join(obj_dir, name)
|
||||
for root, _dirs, files in os.walk(src_content):
|
||||
for fname in files:
|
||||
full = os.path.join(root, fname)
|
||||
rel = os.path.relpath(full, src_content)
|
||||
dst = os.path.join(dst_content, rel)
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
if os.path.splitext(fname)[1].lower() == '.xml':
|
||||
with io.open(full, encoding='utf-8-sig') as fh:
|
||||
t = fh.read()
|
||||
t = reissue(t)
|
||||
t = t.replace('%sObject.' % ext_tag, '%sObject.' % cfg_tag).replace('%s.' % ext_tag, '%s.' % cfg_tag)
|
||||
write_bom(dst, t)
|
||||
else:
|
||||
shutil.copyfile(full, dst)
|
||||
|
||||
return {'tag': cfg_tag, 'name': name}
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
sys.stderr.reconfigure(encoding='utf-8')
|
||||
@@ -1111,6 +1216,9 @@ def main():
|
||||
parser.add_argument('-SourceDir', required=True)
|
||||
parser.add_argument('-V8Path', required=True)
|
||||
parser.add_argument('-TempBasePath', default='')
|
||||
# XML проверяемой обработки/отчёта: объект кладётся в конфигурацию-заглушку, чтобы платформа
|
||||
# смогла проверить его штатными проверками. Без параметра стаб работает как раньше.
|
||||
parser.add_argument('-EmbedSourceFile', default='')
|
||||
parser.add_argument('-AdditionalV8Arguments', nargs='*', default=[],
|
||||
help='Extra 1cv8 arguments, e.g. /UseHwLicenses+')
|
||||
parser.add_argument('-AdditionalIbcmdArguments', nargs='*', default=[],
|
||||
@@ -1122,10 +1230,13 @@ def main():
|
||||
args.SourceDir = clean_path(args.SourceDir, "-SourceDir")
|
||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||
args.TempBasePath = clean_path(args.TempBasePath, "-TempBasePath")
|
||||
args.EmbedSourceFile = clean_path(args.EmbedSourceFile, "-EmbedSourceFile")
|
||||
|
||||
type_map = scan_ref_types(args.SourceDir)
|
||||
register_columns = scan_register_columns(args.SourceDir)
|
||||
has_ref_types = len(type_map) > 0
|
||||
embed_requested = bool(args.EmbedSourceFile and args.EmbedSourceFile.strip())
|
||||
need_cfg = has_ref_types or embed_requested
|
||||
stub_format_version = detect_stub_format_version(args.SourceDir)
|
||||
stub_compat = stub_compatibility_mode(stub_format_version)
|
||||
ns_decl = f'{NS} version="{stub_format_version}"'
|
||||
@@ -1138,10 +1249,18 @@ def main():
|
||||
if needs_registrator:
|
||||
type_map.setdefault('Document', {})['\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430'] = True # ЗаглушкаРегистратора
|
||||
|
||||
if has_ref_types:
|
||||
if need_cfg:
|
||||
cfg_dir = os.path.join(temp_base, 'cfg')
|
||||
os.makedirs(cfg_dir, exist_ok=True)
|
||||
|
||||
embedded = None
|
||||
if embed_requested:
|
||||
embedded = add_source_object_to_config(args.EmbedSourceFile, cfg_dir)
|
||||
if not embedded:
|
||||
print('Error: %s is neither ExternalDataProcessor nor ExternalReport' % args.EmbedSourceFile,
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Configuration.xml
|
||||
uuid_cfg = new_uuid()
|
||||
uuid_lang = new_uuid()
|
||||
@@ -1158,6 +1277,8 @@ def main():
|
||||
tag = META_INFO[meta_type][0]
|
||||
for name in names:
|
||||
child_xml += f'\n\t\t\t<{tag}>{name}</{tag}>'
|
||||
if embedded:
|
||||
child_xml += '\n\t\t\t<%s>%s</%s>' % (embedded['tag'], embedded['name'], embedded['tag'])
|
||||
|
||||
cfg_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject {ns_decl}>
|
||||
@@ -1388,7 +1509,7 @@ def main():
|
||||
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:
|
||||
if need_cfg:
|
||||
ib_args += [f'--import={os.path.join(temp_base, "cfg")}', '--apply', '--force']
|
||||
ib_args.append(f'--data={ib_data}')
|
||||
ib_args.extend(extra_args)
|
||||
@@ -1401,7 +1522,7 @@ def main():
|
||||
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:
|
||||
if need_cfg:
|
||||
import shutil
|
||||
shutil.rmtree(os.path.join(temp_base, 'cfg'), ignore_errors=True)
|
||||
print(f'[OK] Stub database created: {temp_base}')
|
||||
@@ -1417,13 +1538,24 @@ def main():
|
||||
print(f'Failed to create infobase (code: {result.returncode})', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if has_ref_types:
|
||||
if need_cfg:
|
||||
cfg_dir = os.path.join(temp_base, 'cfg')
|
||||
# LoadConfigFromFiles
|
||||
print('Loading configuration from files...')
|
||||
load_log = os.path.join(tempfile.gettempdir(), 'stub_load_log.txt')
|
||||
result = run_v8(args.V8Path, ['DESIGNER', f'/F"{temp_base}"', '/LoadConfigFromFiles', f'"{cfg_dir}"',
|
||||
'/Out', f'"{load_log}"',
|
||||
'/DisableStartupDialogs'] + [quote_if_needed(a) for a in extra_args])
|
||||
if result.returncode != 0:
|
||||
# Причина отказа живёт только в /Out: в консоль пакетный 1cv8 не пишет ничего.
|
||||
if os.path.isfile(load_log):
|
||||
try:
|
||||
with io.open(load_log, encoding='utf-8-sig', errors='replace') as fh:
|
||||
text = fh.read().strip()
|
||||
if text:
|
||||
print(text)
|
||||
except Exception:
|
||||
pass
|
||||
print_platform_output(result)
|
||||
print(f'Failed to load config (code: {result.returncode})', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -12,6 +12,8 @@ allowed-tools:
|
||||
|
||||
Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Также работает для внешних отчётов (ERF).
|
||||
|
||||
Проверяется XML. Синтаксис модулей проверяет сборка: `/epf-build`, `/erf-build`.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|
||||
@@ -25,7 +25,8 @@ allowed-tools:
|
||||
|
||||
## Параметры подключения (опционально)
|
||||
|
||||
Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы.
|
||||
Предпочтительно использовать конкретную базу — это надёжнее. Временная база всё равно поднимается
|
||||
под проверку исходников, если она не отключена.
|
||||
|
||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||
@@ -57,11 +58,22 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-bu
|
||||
| `-Password <пароль>` | нет | Пароль |
|
||||
| `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников |
|
||||
| `-OutputFile <путь>` | да | Путь к выходному ERF-файлу |
|
||||
| `-Checks <список>` | нет | Что проверить перед сборкой: `modules`, `handlers`, `unreferenced`, `empty-handlers`, `config`; `off` — не проверять. По умолчанию `modules,handlers` |
|
||||
| `-Context <список>` | нет | Контексты проверки `modules`: `ThinClient`, `Server`, `ExternalConnection`, `ThickClientOrdinaryApplication`. По умолчанию `ThinClient,Server` |
|
||||
| `-AdditionalV8Arguments <список>` | нет | Доп. аргументы запуска `1cv8.exe` через запятую, напр. `/UseHwLicenses+` |
|
||||
| `-AdditionalIbcmdArguments <список>` | нет | Доп. аргументы `ibcmd` через запятую, в форме `--ключ=значение` |
|
||||
|
||||
> `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных
|
||||
|
||||
## Проверка перед сборкой
|
||||
|
||||
Перед сборкой исходники проверяет платформа — синтаксис модулей и наличие обработчиков форм.
|
||||
Если она нашла проблемы, сборка отменяется и файл не создаётся; в выводе — сообщение
|
||||
платформы со строкой и колонкой и путь к файлу исходника. Отключается `-Checks off`
|
||||
или ключом `"externalCheck": false` в `.v8-project.json`.
|
||||
|
||||
Проверка идёт на отдельной временной базе, даже если база указана: чужую конфигурацию навык не трогает.
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
|
||||
@@ -14,6 +14,8 @@ allowed-tools:
|
||||
|
||||
Использует тот же скрипт, что и `/epf-validate` — автоопределение по типу элемента (ExternalReport).
|
||||
|
||||
Проверяется XML. Синтаксис модулей проверяет сборка: `/epf-build`, `/erf-build`.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
| `editingAllowedCheck` | `"deny"`/`"warn"`/`"off"` | нет | `deny` | Глобальная реакция support-guard на правку объектов на замке (см. ниже) | Руками |
|
||||
| `newObjectPosition` | `"end"`/`"byName"` | нет | `end` | Куда навыки ставят новый объект в `<ChildObjects>` (см. ниже) | Руками |
|
||||
| `extensionApplyCheck` | bool | нет | `true` | Проверять ли применимость расширения после загрузки в базу (навыки `db-load-*`, `db-update`); разово отключается ключом `-NoApplyCheck` | Руками |
|
||||
| `externalCheck` | bool | нет | `true` | Проверять ли исходники платформой перед сборкой внешней обработки/отчёта (навыки `epf-build`, `erf-build`); разово отключается ключом `-Checks off` | Руками |
|
||||
| `skillSuggester` | `"on"`/`"off"` | нет | `on` | Подсказки навыков от хука skill-suggester (только если хук включён, см. ниже) | Руками |
|
||||
| `webPath` | string | нет | `tools/apache24` | Каталог Apache HTTP Server | Руками |
|
||||
| `ffmpegPath` | string | нет | `tools/ffmpeg/bin/ffmpeg.exe` | Путь к ffmpeg | Руками |
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "Перед сборкой запускается /CheckConfig (fake platform)",
|
||||
"osOnly": "win32",
|
||||
"fakePlatform": {
|
||||
"exit": 0,
|
||||
"log": "Загрузка завершена.\n",
|
||||
"check": {
|
||||
"exit": 0,
|
||||
"log": ""
|
||||
}
|
||||
},
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\fake.cmd",
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"/CheckConfig -ThinClient -Server -HandlersExistence",
|
||||
"/LoadExternalDataProcessorOrReportFromFiles"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "платформа фейковая, EPF не собирается — проверяется командная строка проверки"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "Находки проверки отменяют сборку (fake platform)",
|
||||
"osOnly": "win32",
|
||||
"fakePlatform": {
|
||||
"exit": 0,
|
||||
"log": "",
|
||||
"check": {
|
||||
"exit": 101,
|
||||
"log": "{Обработка.Тест.МодульОбъекта(5,1)}: Ожидается ключевое слово 'КонецФункции'\r\n"
|
||||
}
|
||||
},
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\fake.cmd",
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"платформа нашла проблемы в исходниках",
|
||||
"МодульОбъекта(5,1)",
|
||||
"src\\Тест\\Ext\\ObjectModule.bsl"
|
||||
],
|
||||
"stdoutNotContains": [
|
||||
"/LoadExternalDataProcessorOrReportFromFiles"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "сборка отменена — артефакта нет по замыслу"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "ibcmd-аргументы не утекают в проверку (fake platform)",
|
||||
"osOnly": "win32",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "ibcmd.cmd",
|
||||
"content": "@echo off\r\necho ARGS: %*\r\nexit /b 0\r\n",
|
||||
"executable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "1cv8.cmd",
|
||||
"content": "@echo off\r\necho ARGS: %*\r\nexit /b 0\r\n",
|
||||
"executable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "ib/1Cv8.1CD",
|
||||
"content": "stub\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\ibcmd.cmd",
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf",
|
||||
"-AdditionalIbcmdArguments",
|
||||
"--nonesuch=1"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"/CheckConfig -ThinClient -Server -HandlersExistence",
|
||||
"Running: ibcmd infobase config import"
|
||||
],
|
||||
"stdoutNotContains": [
|
||||
"/DisableStartupDialogs --nonesuch=1"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "платформа фейковая, EPF не собирается — проверяется состав командных строк"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "-Checks off отключает проверку (fake platform)",
|
||||
"osOnly": "win32",
|
||||
"fakePlatform": {
|
||||
"exit": 0,
|
||||
"log": "",
|
||||
"check": {
|
||||
"exit": 101,
|
||||
"log": "Не должно быть вызвано\n"
|
||||
}
|
||||
},
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\fake.cmd",
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf",
|
||||
"-Checks",
|
||||
"off"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"/LoadExternalDataProcessorOrReportFromFiles"
|
||||
],
|
||||
"stdoutNotContains": [
|
||||
"/CheckConfig"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "платформа фейковая, EPF не собирается — проверяется командная строка"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "Путь не печатается, если файла нет (fake platform)",
|
||||
"osOnly": "win32",
|
||||
"fakePlatform": {
|
||||
"exit": 0,
|
||||
"log": "",
|
||||
"check": {
|
||||
"exit": 101,
|
||||
"log": "{Обработка.Тест.МодульМенеджера(3,1)}: Ошибка\r\n"
|
||||
}
|
||||
},
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\fake.cmd",
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"платформа нашла проблемы в исходниках",
|
||||
"МодульМенеджера(3,1)"
|
||||
],
|
||||
"stdoutNotContains": [
|
||||
"ManagerModule.bsl"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "сборка отменена — артефакта нет по замыслу"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "ibcmd без соседнего 1cv8: проверка пропускается, сборка идёт (fake platform)",
|
||||
"osOnly": "win32",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "ibcmd.cmd",
|
||||
"content": "@echo off\r\necho ARGS: %*\r\nexit /b 0\r\n",
|
||||
"executable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "ib/1Cv8.1CD",
|
||||
"content": "stub\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\ibcmd.cmd",
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"[note] source check skipped",
|
||||
"Running: ibcmd infobase config import"
|
||||
],
|
||||
"stdoutNotContains": [
|
||||
"/CheckConfig"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "платформа фейковая, EPF не собирается — проверяется поведение без 1cv8 рядом"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "-Context без modules отбивается",
|
||||
"osOnly": "win32",
|
||||
"fakePlatform": {
|
||||
"exit": 0,
|
||||
"log": ""
|
||||
},
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\fake.cmd",
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf",
|
||||
"-Checks",
|
||||
"handlers",
|
||||
"-Context",
|
||||
"Server"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"-Context задан, но в -Checks нет modules"
|
||||
],
|
||||
"stdoutNotContains": [
|
||||
"/CheckConfig",
|
||||
"/LoadExternalDataProcessorOrReportFromFiles"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "отказ до запуска платформы"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "Отказ платформы при подготовке проверки не выдаётся за сбой базы (fake platform)",
|
||||
"osOnly": "win32",
|
||||
"fakePlatform": {
|
||||
"exit": 1,
|
||||
"log": ""
|
||||
},
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\fake.cmd",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"платформа не приняла исходники"
|
||||
],
|
||||
"stdoutNotContains": [
|
||||
"failed to create stub database",
|
||||
"/LoadExternalDataProcessorOrReportFromFiles"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "платформа фейковая и падает на первом запуске — проверяется вердикт"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "Неизвестная проверка отбивается",
|
||||
"osOnly": "win32",
|
||||
"fakePlatform": {
|
||||
"exit": 0,
|
||||
"log": ""
|
||||
},
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
"-V8Path",
|
||||
"{workDir}\\fake.cmd",
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Тест.epf",
|
||||
"-Checks",
|
||||
"syntax"
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": [
|
||||
"unknown check 'syntax'"
|
||||
],
|
||||
"stdoutNotContains": [
|
||||
"/CheckConfig",
|
||||
"/LoadExternalDataProcessorOrReportFromFiles"
|
||||
]
|
||||
},
|
||||
"expectError": true,
|
||||
"noSnapshot": "отказ до запуска платформы"
|
||||
}
|
||||
@@ -2,6 +2,13 @@
|
||||
"name": "Доп. аргументы доходят до самой сборки EPF (fake platform)",
|
||||
"osOnly": "win32",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "fake.cmd",
|
||||
@@ -13,12 +20,6 @@
|
||||
"path": "ib/1Cv8.1CD",
|
||||
"content": "fake infobase\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "src/Обработка.xml",
|
||||
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject xmlns=\"http://v8.1c.ru/8.3/MDClasses\"/>\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"args_extra": [
|
||||
@@ -27,9 +28,9 @@
|
||||
"-InfoBasePath",
|
||||
"{workDir}\\ib",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Обработка.xml",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Обработка.epf",
|
||||
"{workDir}\\build\\Тест.epf",
|
||||
"-AdditionalV8Arguments",
|
||||
"/UseHwLicenses+"
|
||||
],
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
"osOnly": "win32",
|
||||
"preRun": [
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "fake.cmd",
|
||||
"content": "@echo off\r\necho ARGS: %*\r\nexit /b 1\r\n"
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-SrcDir": "{workDir}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"writeFile": {
|
||||
"path": "src/Обработка.xml",
|
||||
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject xmlns=\"http://v8.1c.ru/8.3/MDClasses\"/>\n"
|
||||
"path": "fake.cmd",
|
||||
"content": "@echo off\r\necho ARGS: %*\r\nexit /b 1\r\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -19,9 +20,9 @@
|
||||
"-V8Path",
|
||||
"{workDir}\\fake.cmd",
|
||||
"-SourceFile",
|
||||
"{workDir}\\src\\Обработка.xml",
|
||||
"{workDir}\\src\\Тест.xml",
|
||||
"-OutputFile",
|
||||
"{workDir}\\build\\Обработка.epf",
|
||||
"{workDir}\\build\\Тест.epf",
|
||||
"-AdditionalV8Arguments",
|
||||
"/UseHwLicenses+"
|
||||
],
|
||||
|
||||
@@ -72,7 +72,7 @@ const FAMILIES = [
|
||||
// хранилища — задача одна, поэтому семья общая, а не вторая с тем же телом.
|
||||
{ id: 'full', authority: 'cf-edit',
|
||||
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',
|
||||
'epf-build', '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'] },
|
||||
],
|
||||
|
||||
@@ -376,13 +376,14 @@ function buildArgs(skillConfig, caseData, workDir, inputFilePath, runtime) {
|
||||
// Второй ответ — на проверку применимости расширения: навык запускает её ОТДЕЛЬНЫМ процессом
|
||||
// (в одной командной строке платформа выполнила бы только последнюю команду), поэтому фейк
|
||||
// отличает проверку по составу аргументов, а не по номеру вызова.
|
||||
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";
|
||||
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\nif /i \"%~1\"==\"/CheckConfig\" 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\" ] || [ \"$1\" = \"/CheckConfig\" ]; 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;
|
||||
// spec.check — ответ на проверку применимости расширения (отдельный запуск платформы)
|
||||
// spec.check — ответ платформы на отдельный запуск проверки
|
||||
// (/CheckCanApplyConfigurationExtensions у расширений, /CheckConfig у внешних обработок)
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user