feat(epf-build): общие модули конфигурации в проверке исходников через configSrc

Проверка исходников идёт в конфигурации-заглушке, где общих модулей не было:
обработка с кодом БСП не собиралась без -Checks off.

- stub-db-create -ConfigSrc: общие модули, к которым обращается код, получают
  пустых двойников с флагами контекста из выгрузки; неизвестные имена не
  угадываются
- epf-build -ConfigSrc, по умолчанию configSrc базы из .v8-project.json
- подсказки к «Переменная не определена (X)»: модуль не в том контексте /
  модуля нет в configSrc / выгрузка не передана (с -ConfigSrc и -Checks off)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-09-25 21:21:28 +03:00
co-authored by Claude Opus 5.5
parent b43d4050a6
commit 636344666d
9 changed files with 575 additions and 10 deletions
+7
View File
@@ -58,6 +58,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <п
| `-OutputFile <путь>` | да | Путь к выходному EPF/ERF-файлу |
| `-Checks <список>` | нет | Что проверить перед сборкой: `modules`, `handlers`, `unreferenced`, `empty-handlers`, `config`; `off` — не проверять. По умолчанию `modules,handlers` |
| `-Context <список>` | нет | Контексты проверки `modules`: `ThinClient`, `Server`, `ExternalConnection`, `ThickClientOrdinaryApplication`. По умолчанию `ThinClient,Server` |
| `-ConfigSrc <путь>` | нет | Каталог XML-выгрузки конфигурации, в которой будет работать обработка. По умолчанию — `configSrc` указанной базы из `.v8-project.json` |
| `-AdditionalV8Arguments <список>` | нет | Доп. аргументы запуска `1cv8.exe` через запятую, напр. `/UseHwLicenses+` |
| `-AdditionalIbcmdArguments <список>` | нет | Доп. аргументы `ibcmd` через запятую, в форме `--ключ=значение` |
@@ -72,6 +73,12 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <п
Проверка идёт на отдельной временной базе, даже если база указана: чужую конфигурацию навык не трогает.
Общие модули конфигурации (`ОбщегоНазначения`, `УправлениеПечатью` и т.п.) проверка видит только
при известной выгрузке конфигурации — `-ConfigSrc` или `configSrc` базы в `.v8-project.json`.
Без неё обращения к ним — ошибка «Переменная не определена»; тогда укажи выгрузку или собери
с `-Checks off`. Методы глобальных общих модулей, вызванные без имени модуля, проверка не видит
и с выгрузкой — для такого кода остаётся `-Checks off`.
## Примеры
```powershell
+63 -2
View File
@@ -1,4 +1,4 @@
# epf-build v1.19 — Build external data processor or report (EPF/ERF) from XML sources
# epf-build v1.20 — 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 не исполняется.
<#
@@ -33,6 +33,9 @@
.PARAMETER OutputFile
Путь к выходному EPF/ERF-файлу
.PARAMETER ConfigSrc
Каталог XML-выгрузки целевой конфигурации (общие модули для проверки исходников)
.PARAMETER AdditionalV8Arguments
Дополнительные аргументы запуска 1cv8.exe (например /UseHwLicenses+)
@@ -80,6 +83,10 @@ param(
# Контексты синтаксической проверки. По умолчанию ThinClient,Server.
[Parameter(Mandatory=$false)]
[string]$Context,
# Выгрузка конфигурации, в которой будет работать обработка: её общие модули видны проверке.
# Без параметра берётся configSrc базы из .v8-project.json.
[Parameter(Mandatory=$false)]
[string]$ConfigSrc,
[Parameter(Mandatory=$false)]
[string[]]$AdditionalV8Arguments = @(),
@@ -262,6 +269,7 @@ $V8Path = ConvertTo-CleanPath $V8Path '-V8Path'
$InfoBasePath = ConvertTo-CleanPath $InfoBasePath '-InfoBasePath'
$SourceFile = ConvertTo-CleanPath $SourceFile '-SourceFile'
$OutputFile = ConvertTo-CleanPath $OutputFile '-OutputFile'
$ConfigSrc = ConvertTo-CleanPath $ConfigSrc '-ConfigSrc'
function Assert-InfoBaseExists {
# These skills work on a ready infobase. Saying so up front beats the platform's
@@ -481,6 +489,24 @@ function Resolve-SourcePath {
return $null
}
# «Переменная не определена (X)» при обращении X.… — чаще всего общий модуль конфигурации, которого
# проверке не показали. Платформа печатает строку кода следующей строкой лога с меткой <<?>> перед X.
# Подсказка одна на имя; сама ошибка остаётся ошибкой.
function Get-UndefinedModuleHint {
param([string]$Line, [string]$CodeLine, $Hinted)
$m = [regex]::Match($Line, 'Переменная не определена \(([^)]+)\)')
if (-not $m.Success) { return $null }
$name = $m.Groups[1].Value
if (-not [regex]::IsMatch($CodeLine, '<<\?>>\s*' + [regex]::Escape($name) + '\s*\.')) { return $null }
if (-not $Hinted.Add($name)) { return $null }
if (-not $checkConfigSrc) { return "похоже на общий модуль конфигурации — выгрузка конфигурации проверке не передана" }
$cmDir = Join-Path $checkConfigSrc "CommonModules"
$inCfg = (Test-Path -LiteralPath $cmDir -PathType Container) -and
@(Get-ChildItem -LiteralPath $cmDir -Filter "*.xml" -File | Where-Object { $_.BaseName.Equals($name, [StringComparison]::OrdinalIgnoreCase) }).Count -gt 0
if ($inCfg) { return "общий модуль $name есть в configSrc, но недоступен в контексте проверки (см. «Проверка: …» в строке выше)" }
return "общего модуля $name нет в configSrc ($checkConfigSrc)"
}
# $true, если платформа нашла проблемы — вызывающий не собирает артефакт.
function Invoke-SourceCheck {
param([string]$Exe, [string]$BasePath, [string[]]$Flags, [string]$SourceDir, [string[]]$ExtraArgs)
@@ -508,10 +534,19 @@ function Invoke-SourceCheck {
Write-Host "Error: платформа нашла проблемы в исходниках — сборка отменена" -ForegroundColor Red
# Пустой лог при ненулевом коде — отказ не по находкам (база занята, нет лицензии); молчать нельзя.
if ($lines.Count -eq 0) { Write-Host " платформа вернула код $($res.ExitCode) без сообщений" -ForegroundColor Red }
foreach ($l in $lines) {
$hinted = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
for ($i = 0; $i -lt $lines.Count; $i++) {
$l = $lines[$i]
Write-Host " $($l.TrimEnd())" -ForegroundColor Red
$srcPath = Resolve-SourcePath $l $SourceDir
if ($srcPath) { Write-Host " -> $srcPath" -ForegroundColor Red }
# Подсказка — под строкой кода, которую платформа печатает следом за ошибкой.
$hint = if ($i -gt 0) { Get-UndefinedModuleHint $lines[$i - 1] $l $hinted } else { $null }
if ($hint) { Write-Host " [hint] $hint" -ForegroundColor Yellow }
}
if ($hinted.Count -gt 0 -and -not $checkConfigSrc) {
Write-Host "[hint] Общие модули конфигурации проверке не видны: $(@($hinted) -join ', ')." -ForegroundColor Yellow
Write-Host " Укажите -ConfigSrc (каталог выгрузки конфигурации) или базу с configSrc в .v8-project.json; либо соберите без проверки: -Checks off" -ForegroundColor Yellow
}
return $true
} finally {
@@ -566,6 +601,31 @@ elseif ($checkList.Count -gt 0 -and $checkList -notcontains 'modules') {
}
$sourceDir = Split-Path $SourceFile -Parent
# Выгрузка целевой конфигурации для проверки: явный -ConfigSrc, иначе configSrc базы из реестра.
# Без неё обращения к общим модулям конфигурации проверка считает неопределёнными переменными.
function Resolve-ConfigSrc {
if ($ConfigSrc) {
if (-not (Test-Path -LiteralPath $ConfigSrc -PathType Container)) {
Write-Host "Error: -ConfigSrc not found: $ConfigSrc" -ForegroundColor Red
exit 1
}
return (Resolve-Path -LiteralPath $ConfigSrc).Path
}
$db = Find-ProjectDatabase
if (-not $db -or -not $db.configSrc) { return $null }
$p = [string]$db.configSrc
if (-not [System.IO.Path]::IsPathRooted($p)) {
$pf = Find-V8Project (Get-Location).Path
$p = Join-Path (Split-Path $pf -Parent) $p
}
if (-not (Test-Path -LiteralPath $p -PathType Container)) {
Write-Host "WARNING: configSrc базы не найден: $p — общие модули конфигурации проверке недоступны" -ForegroundColor Yellow
return $null
}
return $p
}
$checkConfigSrc = if ($checkList.Count -gt 0) { Resolve-ConfigSrc } else { $null }
function New-StubBase {
# Стаб запускает свои процессы платформы (CREATEINFOBASE, LoadConfigFromFiles, UpdateDBCfg) —
# им нужны те же дополнительные аргументы, что и сборке. Передаются только явные: файл проекта
@@ -576,6 +636,7 @@ function New-StubBase {
$q = { param($s) "'" + ($s -replace "'", "''") + "'" }
$stubCmd = "& $(& $q $stubScript) -SourceDir $(& $q $sourceDir) -V8Path $(& $q $V8Path) -TempBasePath $(& $q $BasePath)"
if ($Embed) { $stubCmd += " -EmbedSourceFile $(& $q $SourceFile)" }
if ($Embed -and $checkConfigSrc) { $stubCmd += " -ConfigSrc $(& $q $checkConfigSrc)" }
if ($AdditionalV8Arguments.Count -gt 0) {
$stubCmd += " -AdditionalV8Arguments " + (($AdditionalV8Arguments | ForEach-Object { & $q $_ }) -join ',')
}
+69 -4
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# epf-build v1.19 — Build external data processor or report (EPF/ERF) from XML sources
# epf-build v1.20 — Build external data processor or report (EPF/ERF) from XML sources
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -513,7 +513,7 @@ def resolve_source_path(line, source_dir):
# True, если платформа нашла проблемы — вызывающий не собирает артефакт.
def invoke_source_check(exe, base_path, flags, source_dir, extra_args):
def invoke_source_check(exe, base_path, flags, source_dir, extra_args, config_src=None):
exe_dir = os.path.dirname(exe)
exe_leaf = os.path.basename(exe)
if exe_leaf.lower().startswith('ibcmd'):
@@ -547,16 +547,73 @@ def invoke_source_check(exe, base_path, flags, source_dir, extra_args):
# Пустой лог при ненулевом коде — отказ не по находкам (база занята, нет лицензии); молчать нельзя.
if not lines:
print(f' платформа вернула код {result.returncode} без сообщений')
for l in lines:
hinted = []
for i, l in enumerate(lines):
print(f' {l.rstrip()}')
src_path = resolve_source_path(l, source_dir)
if src_path:
print(f' -> {src_path}')
# Подсказка — под строкой кода, которую платформа печатает следом за ошибкой.
hint = get_undefined_module_hint(lines[i - 1], l, hinted, config_src) if i > 0 else None
if hint:
print(f' [hint] {hint}')
if hinted and not config_src:
print('[hint] \u041e\u0431\u0449\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u043d\u0435 \u0432\u0438\u0434\u043d\u044b: %s.' % ', '.join(hinted))
print(' \u0423\u043a\u0430\u0436\u0438\u0442\u0435 -ConfigSrc (\u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438) \u0438\u043b\u0438 \u0431\u0430\u0437\u0443 \u0441 configSrc \u0432 .v8-project.json; \u043b\u0438\u0431\u043e \u0441\u043e\u0431\u0435\u0440\u0438\u0442\u0435 \u0431\u0435\u0437 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438: -Checks off')
return True
finally:
shutil.rmtree(d, ignore_errors=True)
# Выгрузка целевой конфигурации для проверки: явный -ConfigSrc, иначе configSrc базы из реестра.
# Без неё обращения к общим модулям конфигурации проверка считает неопределёнными переменными.
def resolve_config_src(args):
if args.ConfigSrc:
if not os.path.isdir(args.ConfigSrc):
print('Error: -ConfigSrc not found: %s' % args.ConfigSrc)
sys.exit(1)
return os.path.abspath(args.ConfigSrc)
db = find_project_database(args)
if not db or not db.get('configSrc'):
return None
p = str(db['configSrc'])
# Реестр пишут и на Windows: относительный путь может прийти с обратными слешами.
if os.sep == '/':
p = p.replace('\\', '/')
if not os.path.isabs(p):
pf = _sg_find_v8project(os.getcwd())
p = os.path.join(os.path.dirname(pf), p)
if not os.path.isdir(p):
print('WARNING: configSrc \u0431\u0430\u0437\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d: %s \u2014 \u043e\u0431\u0449\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b' % p)
return None
return p
# «Переменная не определена (X)» при обращении X.… — чаще всего общий модуль конфигурации, которого
# проверке не показали. Платформа печатает строку кода следующей строкой лога с меткой <<?>> перед X.
# Подсказка одна на имя; сама ошибка остаётся ошибкой.
def get_undefined_module_hint(line, code_line, hinted, config_src):
m = re.search(r'\u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u043d\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \(([^)]+)\)', line)
if not m:
return None
name = m.group(1)
if not re.search(r'<<\?>>\s*' + re.escape(name) + r'\s*\.', code_line):
return None
if any(h.casefold() == name.casefold() for h in hinted):
return None
hinted.append(name)
if not config_src:
return '\u043f\u043e\u0445\u043e\u0436\u0435 \u043d\u0430 \u043e\u0431\u0449\u0438\u0439 \u043c\u043e\u0434\u0443\u043b\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u2014 \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u043d\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0430'
cm_dir = os.path.join(config_src, 'CommonModules')
in_cfg = os.path.isdir(cm_dir) and any(
f.lower().endswith('.xml') and os.path.splitext(f)[0].casefold() == name.casefold()
and os.path.isfile(os.path.join(cm_dir, f))
for f in os.listdir(cm_dir))
if in_cfg:
return '\u043e\u0431\u0449\u0438\u0439 \u043c\u043e\u0434\u0443\u043b\u044c %s \u0435\u0441\u0442\u044c \u0432 configSrc, \u043d\u043e \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 (\u0441\u043c. \u00ab\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430: \u2026\u00bb \u0432 \u0441\u0442\u0440\u043e\u043a\u0435 \u0432\u044b\u0448\u0435)' % name
return '\u043e\u0431\u0449\u0435\u0433\u043e \u043c\u043e\u0434\u0443\u043b\u044f %s \u043d\u0435\u0442 \u0432 configSrc (%s)' % (name, config_src)
# Проверять ли исходники: -Checks off сильнее настройки проекта externalCheck.
def get_source_check_list(checks):
known = ['modules', 'handlers', 'unreferenced', 'empty-handlers', 'config']
@@ -601,6 +658,9 @@ def main():
parser.add_argument("-Checks", default="")
# Контексты синтаксической проверки. По умолчанию ThinClient,Server.
parser.add_argument("-Context", default="")
# Выгрузка конфигурации, в которой будет работать обработка: её общие модули видны проверке.
# Без параметра берётся configSrc базы из .v8-project.json.
parser.add_argument("-ConfigSrc", default="")
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
@@ -614,6 +674,7 @@ def main():
assert_infobase_exists(args.InfoBasePath)
args.SourceFile = clean_path(args.SourceFile, "-SourceFile")
args.OutputFile = clean_path(args.OutputFile, "-OutputFile")
args.ConfigSrc = clean_path(args.ConfigSrc, "-ConfigSrc")
# --- Resolve V8Path ---
v8path = resolve_v8path(args.V8Path, args)
@@ -643,6 +704,7 @@ def main():
print('Error: -Context задан, но в -Checks нет modules — контексты относятся только к ней')
sys.exit(1)
source_dir = os.path.dirname(os.path.abspath(args.SourceFile))
check_config_src = resolve_config_src(args) if check_list else None
def new_stub_base(base_path, embed):
# The stub runs its own platform processes (CREATEINFOBASE, LoadConfigFromFiles,
@@ -653,6 +715,8 @@ def main():
"-TempBasePath", base_path]
if embed:
stub_cmd += ["-EmbedSourceFile", args.SourceFile]
if embed and check_config_src:
stub_cmd += ["-ConfigSrc", check_config_src]
if v8_extra:
stub_cmd += ["-AdditionalV8Arguments"] + list(v8_extra)
if ibcmd_extra:
@@ -700,7 +764,8 @@ def main():
# Проверку ведёт 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)
get_check_flags(check_list, context_list), source_dir, check_extra,
check_config_src)
if found:
if auto_created_base and os.path.exists(auto_created_base):
shutil.rmtree(auto_created_base, ignore_errors=True)
@@ -1,4 +1,4 @@
# stub-db-create v1.11 — Create temp 1C infobase with metadata stubs for EPF/ERF build
# stub-db-create v1.12 — Create temp 1C infobase with metadata stubs for EPF/ERF build
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -13,6 +13,10 @@ param(
# смогла проверить его штатными проверками. Без параметра стаб работает как раньше.
[string]$EmbedSourceFile,
# Выгрузка целевой конфигурации: общие модули, к которым обращается код, получают в заглушке
# пустых двойников с настоящими флагами контекста.
[string]$ConfigSrc,
[string[]]$AdditionalV8Arguments = @(),
[string[]]$AdditionalIbcmdArguments = @()
@@ -43,6 +47,7 @@ function ConvertTo-CleanPath {
$SourceDir = ConvertTo-CleanPath $SourceDir '-SourceDir'
$V8Path = ConvertTo-CleanPath $V8Path '-V8Path'
$TempBasePath = ConvertTo-CleanPath $TempBasePath '-TempBasePath'
$ConfigSrc = ConvertTo-CleanPath $ConfigSrc '-ConfigSrc'
# --- Additional platform arguments ---
$script:V8OwnedKeys = @(
@@ -353,10 +358,67 @@ foreach ($f in $xmlFiles) {
}
}
# --- 1c. Общие модули целевой конфигурации, к которым обращается код ---
# Проверка модулей (замер 8.3.24, 8.3.27) требует от общего модуля только двух вещей: чтобы он
# существовал и был доступен в контексте вызова. Методы, их экспорт и число параметров она не
# сверяет. Поэтому двойнику хватает имени и флагов контекста из настоящей выгрузки, тело пустое,
# и зависимости модуля за ним не тянутся. Двойник получает только имя, которое есть в выгрузке:
# неизвестное имя остаётся ошибкой проверки, а не угадывается.
# Код без строковых литералов и комментариев: слова в них — не обращения к модулям.
function Remove-BslNoise {
param([string]$Code)
$out = New-Object System.Text.StringBuilder
foreach ($line in ($Code -split "`r?`n")) {
# Продолжение многострочной строки («|ВЫБРАТЬ …»): литерал до закрывающей кавычки, после
# неё может идти код («|ГДЕ …"; Х = Модуль.Метод();»).
$l = [regex]::Replace($line, '^\s*\|(?:[^"]|"")*("|$)', '""')
# Литерал до закрывающей кавычки или, если строка продолжится ниже, до конца строки.
$l = [regex]::Replace($l, '"(?:[^"]|"")*("|$)', '""')
$ci = $l.IndexOf('//')
if ($ci -ge 0) { $l = $l.Substring(0, $ci) }
[void]$out.AppendLine($l)
}
return $out.ToString()
}
$commonModuleFlags = @('Global', 'ClientManagedApplication', 'Server', 'ExternalConnection', 'ClientOrdinaryApplication', 'ServerCall', 'Privileged')
$commonModules = [ordered]@{} # имя из выгрузки -> @{ флаг = 'true'|'false' }
if ($ConfigSrc) {
$cmDir = Join-Path $ConfigSrc "CommonModules"
if (-not (Test-Path -LiteralPath $cmDir -PathType Container)) {
Write-Host "WARNING: в -ConfigSrc нет каталога CommonModules: $ConfigSrc" -ForegroundColor Yellow
} else {
# Идентификаторы 1С регистронезависимы — и имена модулей в коде тоже.
$known = New-Object 'System.Collections.Generic.Dictionary[string,string]' ([StringComparer]::OrdinalIgnoreCase)
foreach ($f in (Get-ChildItem -LiteralPath $cmDir -Filter "*.xml" -File)) { $known[$f.BaseName] = $f.FullName }
$used = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
foreach ($f in (Get-ChildItem -LiteralPath $SourceDir -Filter "*.bsl" -Recurse -File)) {
$code = Remove-BslNoise ([System.IO.File]::ReadAllText($f.FullName, [System.Text.Encoding]::UTF8))
foreach ($m in [regex]::Matches($code, '(?<![\w.])([^\W\d]\w*)\s*\.')) { [void]$used.Add($m.Groups[1].Value) }
}
$names = @($used | Where-Object { $known.ContainsKey($_) } | ForEach-Object { [IO.Path]::GetFileNameWithoutExtension($known[$_]) })
[Array]::Sort($names, [StringComparer]::Ordinal)
foreach ($n in $names) {
$t = [System.IO.File]::ReadAllText($known[$n], [System.Text.Encoding]::UTF8)
$flags = @{}
foreach ($fl in $commonModuleFlags) {
$flags[$fl] = if ($t -match "<$fl>(true|false)</$fl>") { $Matches[1].ToLower() } else { 'false' }
}
$commonModules[$n] = $flags
}
if ($commonModules.Count -gt 0) {
Write-Host "Общие модули из configSrc: $(@($commonModules.Keys) -join ', ')"
} else {
Write-Host "Общие модули из configSrc: не понадобились"
}
}
}
$hasRefTypes = $typeMap.Count -gt 0
# Конфигурация нужна и тогда, когда ссылочных типов нет: в неё кладётся сам объект.
$embedRequested = -not [string]::IsNullOrWhiteSpace($EmbedSourceFile)
$needCfg = $hasRefTypes -or $embedRequested
$needCfg = $hasRefTypes -or $embedRequested -or $commonModules.Count -gt 0
# --- 2. Determine TempBasePath ---
if (-not $TempBasePath) {
@@ -673,6 +735,7 @@ if ($needCfg) {
# ChildObjects entries
$childXml = "`r`n`t`t`t<Language>Русский</Language>"
foreach ($cmName in $commonModules.Keys) { $childXml += "`r`n`t`t`t<CommonModule>$cmName</CommonModule>" }
foreach ($metaType in $typeMap.Keys) {
if (-not $metaInfo.ContainsKey($metaType)) { continue }
$tag = $metaInfo[$metaType].tag
@@ -779,6 +842,34 @@ if ($needCfg) {
"@
[System.IO.File]::WriteAllText((Join-Path $langDir "Русский.xml"), $langXml, $enc)
# --- 4b'. Common modules: пустые двойники с флагами контекста из выгрузки ---
if ($commonModules.Count -gt 0) {
$cmOutDir = Join-Path $cfgDir "CommonModules"
New-Item -ItemType Directory -Path $cmOutDir -Force | Out-Null
foreach ($cmName in $commonModules.Keys) {
$flags = $commonModules[$cmName]
$flagXml = ""
foreach ($fl in $commonModuleFlags) { $flagXml += "`r`n`t`t`t<$fl>$($flags[$fl])</$fl>" }
$cmXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject $ns>
<CommonModule uuid="$([guid]::NewGuid().ToString())">
<Properties>
<Name>$cmName</Name>
<Synonym/>
<Comment/>$flagXml
<ReturnValuesReuse>DontUse</ReturnValuesReuse>
</Properties>
</CommonModule>
</MetaDataObject>
"@
[System.IO.File]::WriteAllText((Join-Path $cmOutDir "$cmName.xml"), $cmXml, $enc)
$cmExt = Join-Path $cmOutDir (Join-Path $cmName "Ext")
New-Item -ItemType Directory -Path $cmExt -Force | Out-Null
[System.IO.File]::WriteAllText((Join-Path $cmExt "Module.bsl"), "", $enc)
}
}
# --- 4c. Metadata object stubs ---
foreach ($metaType in $typeMap.Keys) {
if (-not $metaInfo.ContainsKey($metaType)) { continue }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# stub-db-create v1.11 — Create temp 1C infobase with metadata stubs for EPF/ERF build
# stub-db-create v1.12 — Create temp 1C infobase with metadata stubs for EPF/ERF build
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1216,6 +1216,100 @@ def add_source_object_to_config(source_xml, cfg_dir):
return {'tag': cfg_tag, 'name': name}
# --- Общие модули целевой конфигурации, к которым обращается код ---
# Проверка модулей (замер 8.3.24, 8.3.27) требует от общего модуля только двух вещей: чтобы он
# существовал и был доступен в контексте вызова. Методы, их экспорт и число параметров она не
# сверяет. Поэтому двойнику хватает имени и флагов контекста из настоящей выгрузки, тело пустое,
# и зависимости модуля за ним не тянутся. Двойник получает только имя, которое есть в выгрузке:
# неизвестное имя остаётся ошибкой проверки, а не угадывается.
COMMON_MODULE_FLAGS = ['Global', 'ClientManagedApplication', 'Server', 'ExternalConnection',
'ClientOrdinaryApplication', 'ServerCall', 'Privileged']
def remove_bsl_noise(code):
# Код без строковых литералов и комментариев: слова в них — не обращения к модулям.
out = []
for line in re.split(r'\r?\n', code):
# Продолжение многострочной строки («|ВЫБРАТЬ …»): литерал до закрывающей кавычки, после
# неё может идти код («|ГДЕ …"; Х = Модуль.Метод();»).
line = re.sub(r'^\s*\|(?:[^"]|"")*("|$)', '""', line)
# Литерал до закрывающей кавычки или, если строка продолжится ниже, до конца строки.
line = re.sub(r'"(?:[^"]|"")*("|$)', '""', line)
ci = line.find('//')
if ci >= 0:
line = line[:ci]
out.append(line)
return '\n'.join(out) + '\n'
def scan_common_modules(source_dir, config_src):
"""\u0418\u043c\u044f \u0438\u0437 \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0438 -> {\u0444\u043b\u0430\u0433: 'true'|'false'} \u0434\u043b\u044f \u043e\u0431\u0449\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439, \u043a \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043e\u0431\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u0434."""
result = {}
if not config_src:
return result
cm_dir = os.path.join(config_src, 'CommonModules')
if not os.path.isdir(cm_dir):
print('WARNING: \u0432 -ConfigSrc \u043d\u0435\u0442 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 CommonModules: %s' % config_src)
return result
# Идентификаторы 1С регистронезависимы — и имена модулей в коде тоже.
known = {}
for fn in os.listdir(cm_dir):
full = os.path.join(cm_dir, fn)
if fn.lower().endswith('.xml') and os.path.isfile(full):
known[os.path.splitext(fn)[0].casefold()] = full
used = set()
for root, _dirs, files in os.walk(source_dir):
for fn in files:
if not fn.lower().endswith('.bsl'):
continue
# Битые байты (файл не в UTF-8) заменяются, как в PS-мастере, а не валят заглушку.
with open(os.path.join(root, fn), 'r', encoding='utf-8-sig', errors='replace') as fh:
code = remove_bsl_noise(fh.read())
for m in re.finditer(r'(?<![\w.])([^\W\d]\w*)\s*\.', code):
used.add(m.group(1).casefold())
names = sorted(os.path.splitext(os.path.basename(known[u]))[0] for u in used if u in known)
for n in names:
with open(known[n.casefold()], 'r', encoding='utf-8-sig') as fh:
t = fh.read()
flags = {}
for fl in COMMON_MODULE_FLAGS:
m = re.search(r'<%s>(true|false)</%s>' % (fl, fl), t, re.IGNORECASE)
flags[fl] = m.group(1).lower() if m else 'false'
result[n] = flags
if result:
print('\u041e\u0431\u0449\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u0438\u0437 configSrc: %s' % ', '.join(result.keys()))
else:
print('\u041e\u0431\u0449\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u0438\u0437 configSrc: \u043d\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u0438\u0441\u044c')
return result
def write_common_module_stubs(cfg_dir, ns_decl, common_modules):
# Пустые двойники с флагами контекста из выгрузки.
if not common_modules:
return
cm_out = os.path.join(cfg_dir, 'CommonModules')
os.makedirs(cm_out, exist_ok=True)
for name, flags in common_modules.items():
flag_xml = ''.join('\n\t\t\t<%s>%s</%s>' % (fl, flags[fl], fl) for fl in COMMON_MODULE_FLAGS)
cm_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject {ns_decl}>
\t<CommonModule uuid="{new_uuid()}">
\t\t<Properties>
\t\t\t<Name>{name}</Name>
\t\t\t<Synonym/>
\t\t\t<Comment/>{flag_xml}
\t\t\t<ReturnValuesReuse>DontUse</ReturnValuesReuse>
\t\t</Properties>
\t</CommonModule>
</MetaDataObject>
"""
write_bom(os.path.join(cm_out, name + '.xml'), cm_xml)
ext_dir = os.path.join(cm_out, name, 'Ext')
os.makedirs(ext_dir, exist_ok=True)
write_bom(os.path.join(ext_dir, 'Module.bsl'), '')
def main():
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
@@ -1227,6 +1321,9 @@ def main():
# XML проверяемой обработки/отчёта: объект кладётся в конфигурацию-заглушку, чтобы платформа
# смогла проверить его штатными проверками. Без параметра стаб работает как раньше.
parser.add_argument('-EmbedSourceFile', default='')
# Выгрузка целевой конфигурации: общие модули, к которым обращается код, получают в заглушке
# пустых двойников с настоящими флагами контекста.
parser.add_argument('-ConfigSrc', default='')
parser.add_argument('-AdditionalV8Arguments', nargs='*', default=[],
help='Extra 1cv8 arguments, e.g. /UseHwLicenses+')
parser.add_argument('-AdditionalIbcmdArguments', nargs='*', default=[],
@@ -1239,12 +1336,14 @@ def main():
args.V8Path = clean_path(args.V8Path, "-V8Path")
args.TempBasePath = clean_path(args.TempBasePath, "-TempBasePath")
args.EmbedSourceFile = clean_path(args.EmbedSourceFile, "-EmbedSourceFile")
args.ConfigSrc = clean_path(args.ConfigSrc, "-ConfigSrc")
type_map = scan_ref_types(args.SourceDir)
register_columns = scan_register_columns(args.SourceDir)
common_modules = scan_common_modules(args.SourceDir, args.ConfigSrc)
has_ref_types = len(type_map) > 0
embed_requested = bool(args.EmbedSourceFile and args.EmbedSourceFile.strip())
need_cfg = has_ref_types or embed_requested
need_cfg = has_ref_types or embed_requested or len(common_modules) > 0
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}"'
@@ -1279,6 +1378,8 @@ def main():
co_xml += f'\n\t\t\t<xr:ContainedObject>\n\t\t\t\t<xr:ClassId>{CLASS_IDS[i]}</xr:ClassId>\n\t\t\t\t<xr:ObjectId>{co_ids[i]}</xr:ObjectId>\n\t\t\t</xr:ContainedObject>'
child_xml = '\n\t\t\t<Language>\u0420\u0443\u0441\u0441\u043a\u0438\u0439</Language>' # Русский
for cm_name in common_modules:
child_xml += '\n\t\t\t<CommonModule>%s</CommonModule>' % cm_name
for meta_type, names in type_map.items():
if meta_type not in META_INFO:
continue
@@ -1381,6 +1482,8 @@ def main():
"""
write_bom(os.path.join(lang_dir, '\u0420\u0443\u0441\u0441\u043a\u0438\u0439.xml'), lang_xml)
write_common_module_stubs(cfg_dir, ns_decl, common_modules)
# Metadata stubs
for meta_type, names in type_map.items():
if meta_type not in META_INFO:
+7
View File
@@ -60,6 +60,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-bu
| `-OutputFile <путь>` | да | Путь к выходному ERF-файлу |
| `-Checks <список>` | нет | Что проверить перед сборкой: `modules`, `handlers`, `unreferenced`, `empty-handlers`, `config`; `off` — не проверять. По умолчанию `modules,handlers` |
| `-Context <список>` | нет | Контексты проверки `modules`: `ThinClient`, `Server`, `ExternalConnection`, `ThickClientOrdinaryApplication`. По умолчанию `ThinClient,Server` |
| `-ConfigSrc <путь>` | нет | Каталог XML-выгрузки конфигурации, в которой будет работать отчёт. По умолчанию — `configSrc` указанной базы из `.v8-project.json` |
| `-AdditionalV8Arguments <список>` | нет | Доп. аргументы запуска `1cv8.exe` через запятую, напр. `/UseHwLicenses+` |
| `-AdditionalIbcmdArguments <список>` | нет | Доп. аргументы `ibcmd` через запятую, в форме `--ключ=значение` |
@@ -74,6 +75,12 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-bu
Проверка идёт на отдельной временной базе, даже если база указана: чужую конфигурацию навык не трогает.
Общие модули конфигурации (`ОбщегоНазначения`, `УправлениеПечатью` и т.п.) проверка видит только
при известной выгрузке конфигурации — `-ConfigSrc` или `configSrc` базы в `.v8-project.json`.
Без неё обращения к ним — ошибка «Переменная не определена»; тогда укажи выгрузку или собери
с `-Checks off`. Методы глобальных общих модулей, вызванные без имени модуля, проверка не видит
и с выгрузкой — для такого кода остаётся `-Checks off`.
## Примеры
```powershell
@@ -0,0 +1,58 @@
{
"name": "С -ConfigSrc: модуль есть, но не в контексте / модуля нет — разные подсказки, без -Checks off (fake platform)",
"osOnly": "win32",
"fakePlatform": {
"exit": 0,
"log": "",
"check": {
"exit": 101,
"log": "{Обработка.Тест.МодульОбъекта(2,2)}: Переменная не определена (ОбщегоНазначения)\r\n\t<<?>>ОбщегоНазначения.СообщитьПользователю(\"x\"); (Проверка: Сервер)\r\n{Обработка.Тест.МодульОбъекта(3,2)}: Переменная не определена (ОбщегоНазначениеее)\r\n\t<<?>>ОбщегоНазначениеее.Метод(); (Проверка: Сервер)\r\n"
}
},
"preRun": [
{
"script": "epf-init/scripts/init",
"args": {
"-Name": "Тест",
"-SrcDir": "{workDir}/src"
}
},
{
"writeFile": {
"path": "src/Тест/Ext/ObjectModule.bsl",
"content": "Процедура Проба()\r\n\tОбщегоНазначения.СообщитьПользователю(\"x\");\r\nКонецПроцедуры\r\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/ОбщегоНазначения.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject xmlns=\"http://v8.1c.ru/8.3/MDClasses\" version=\"2.17\">\n\t<CommonModule uuid=\"10000000-0000-0000-0000-000000000001\">\n\t\t<Properties>\n\t\t\t<Name>ОбщегоНазначения</Name>\n\t\t\t<Server>true</Server>\n\t\t</Properties>\n\t</CommonModule>\n</MetaDataObject>\n"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-SourceFile",
"{workDir}\\src\\Тест.xml",
"-OutputFile",
"{workDir}\\build\\Тест.epf",
"-ConfigSrc",
"{workDir}\\cfgsrc"
],
"expect": {
"stdoutContains": [
"Общие модули из configSrc: ОбщегоНазначения",
"[hint] общий модуль ОбщегоНазначения есть в configSrc, но недоступен в контексте проверки",
"[hint] общего модуля ОбщегоНазначениеее нет в configSrc"
],
"stdoutNotContains": [
"/LoadExternalDataProcessorOrReportFromFiles",
"-Checks off"
]
},
"expectError": true,
"noSnapshot": "сборка отменена — артефакта нет по замыслу"
}
@@ -0,0 +1,44 @@
{
"name": "Неопределённый общий модуль без configSrc: подсказка про -ConfigSrc и -Checks off (fake platform)",
"osOnly": "win32",
"fakePlatform": {
"exit": 0,
"log": "",
"check": {
"exit": 101,
"log": "{Обработка.Тест.МодульОбъекта(2,2)}: Переменная не определена (ОбщегоНазначения)\r\n\t<<?>>ОбщегоНазначения.СообщитьПользователю(\"x\"); (Проверка: Сервер)\r\n{Обработка.Тест.МодульОбъекта(3,2)}: Переменная не определена (ОбщегоНазначения)\r\n\t<<?>>ОбщегоНазначения.Прочее(); (Проверка: Сервер)\r\n{Обработка.Тест.МодульОбъекта(4,2)}: Переменная не определена (Сумма)\r\n\tХ = <<?>>Сумма + 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": [
"[hint] похоже на общий модуль конфигурации",
"[hint] Общие модули конфигурации проверке не видны: ОбщегоНазначения.",
"-Checks off"
],
"stdoutNotContains": [
"/LoadExternalDataProcessorOrReportFromFiles",
"проверке не видны: ОбщегоНазначения, Сумма"
]
},
"expectError": true,
"noSnapshot": "сборка отменена — артефакта нет по замыслу"
}
@@ -0,0 +1,129 @@
{
"name": "Общие модули из -ConfigSrc: только те, к которым обращается код, с флагами из выгрузки",
"fakePlatform": {
"exit": 1,
"log": ""
},
"preRun": [
{
"writeFile": {
"path": "src/Проба.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject xmlns=\"http://v8.1c.ru/8.3/MDClasses\" xmlns:v8=\"http://v8.1c.ru/8.1/data/core\" xmlns:xr=\"http://v8.1c.ru/8.3/xcf/readable\" version=\"2.17\">\n\t<ExternalDataProcessor uuid=\"aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb\">\n\t\t<InternalInfo>\n\t\t\t<xr:GeneratedType name=\"ExternalDataProcessorObject.Проба\" category=\"Object\">\n\t\t\t\t<xr:TypeId>cccccccc-4444-5555-6666-dddddddddddd</xr:TypeId>\n\t\t\t\t<xr:ValueId>eeeeeeee-7777-8888-9999-ffffffffffff</xr:ValueId>\n\t\t\t</xr:GeneratedType>\n\t\t</InternalInfo>\n\t\t<Properties>\n\t\t\t<Name>Проба</Name>\n\t\t</Properties>\n\t\t<ChildObjects/>\n\t</ExternalDataProcessor>\n</MetaDataObject>\n"
}
},
{
"writeFile": {
"path": "src/Проба/Ext/ObjectModule.bsl",
"content": "Процедура Проба()\r\n\tОбщМод.Метод();\r\n\tобщмод2.Метод(\"ЛишнийМодуль.Х\"); // КомментМодуль.Х\r\n\tТекст = \"ВЫБРАТЬ\r\n\t|ТекстМодуль.Поле\";\r\n\tНетВКонфиге.Метод();\r\n\tЗапрос.Текст = Модуль3 .Поле;\r\nТ = \"ВЫБРАТЬ\r\n\t|ГДЕ Ложь\"; Модуль4.Метод();\r\nКонецПроцедуры\r\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/ОбщМод.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject xmlns=\"http://v8.1c.ru/8.3/MDClasses\" version=\"2.20\">\n\t<CommonModule uuid=\"10000000-0000-0000-0000-000000000001\">\n\t\t<Properties>\n\t\t\t<Name>ОбщМод</Name>\n\t\t\t<Global>false</Global>\n\t\t\t<ClientManagedApplication>false</ClientManagedApplication>\n\t\t\t<Server>true</Server>\n\t\t\t<ExternalConnection>true</ExternalConnection>\n\t\t\t<ClientOrdinaryApplication>false</ClientOrdinaryApplication>\n\t\t\t<ServerCall>true</ServerCall>\n\t\t\t<Privileged>false</Privileged>\n\t\t\t<ReturnValuesReuse>DuringRequest</ReturnValuesReuse>\n\t\t</Properties>\n\t</CommonModule>\n</MetaDataObject>\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/ОбщМод2.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject xmlns=\"http://v8.1c.ru/8.3/MDClasses\" version=\"2.20\">\n\t<CommonModule uuid=\"10000000-0000-0000-0000-000000000002\">\n\t\t<Properties>\n\t\t\t<Name>ОбщМод2</Name>\n\t\t\t<ClientManagedApplication>true</ClientManagedApplication>\n\t\t\t<Server>true</Server>\n\t\t</Properties>\n\t</CommonModule>\n</MetaDataObject>\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/Модуль3.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject xmlns=\"http://v8.1c.ru/8.3/MDClasses\" version=\"2.20\">\n\t<CommonModule uuid=\"10000000-0000-0000-0000-000000000003\">\n\t\t<Properties>\n\t\t\t<Name>Модуль3</Name>\n\t\t\t<Server>true</Server>\n\t\t</Properties>\n\t</CommonModule>\n</MetaDataObject>\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/ЛишнийМодуль.xml",
"content": "<MetaDataObject/>\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/КомментМодуль.xml",
"content": "<MetaDataObject/>\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/ТекстМодуль.xml",
"content": "<MetaDataObject/>\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/НеИспользуется.xml",
"content": "<MetaDataObject/>\n"
}
},
{
"writeFile": {
"path": "cfgsrc/CommonModules/Модуль4.xml",
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaDataObject xmlns=\"http://v8.1c.ru/8.3/MDClasses\" version=\"2.20\">\n\t<CommonModule uuid=\"10000000-0000-0000-0000-000000000004\">\n\t\t<Properties>\n\t\t\t<Name>Модуль4</Name>\n\t\t\t<Server>true</Server>\n\t\t</Properties>\n\t</CommonModule>\n</MetaDataObject>\n"
}
}
],
"args_extra": [
"-SourceDir",
"{workDir}/src",
"-V8Path",
"{fakePlatform}",
"-TempBasePath",
"{workDir}/base",
"-EmbedSourceFile",
"{workDir}/src/Проба.xml",
"-ConfigSrc",
"{workDir}/cfgsrc"
],
"expect": {
"stdoutContains": [
"Общие модули из configSrc: Модуль3, Модуль4, ОбщМод, ОбщМод2"
],
"files": [
"base/cfg/CommonModules/ОбщМод/Ext/Module.bsl",
"base/cfg/CommonModules/ОбщМод2/Ext/Module.bsl",
"base/cfg/CommonModules/Модуль3.xml"
],
"filesAbsent": [
"base/cfg/CommonModules/ЛишнийМодуль.xml",
"base/cfg/CommonModules/КомментМодуль.xml",
"base/cfg/CommonModules/ТекстМодуль.xml",
"base/cfg/CommonModules/НеИспользуется.xml",
"base/cfg/CommonModules/НетВКонфиге.xml"
],
"fileContains": [
{
"file": "base/cfg/CommonModules/ОбщМод.xml",
"text": [
"<Name>ОбщМод</Name>",
"<ClientManagedApplication>false</ClientManagedApplication>",
"<Server>true</Server>",
"<ExternalConnection>true</ExternalConnection>",
"<ServerCall>true</ServerCall>",
"version=\"2.17\""
]
},
{
"file": "base/cfg/CommonModules/ОбщМод2.xml",
"text": [
"<Name>ОбщМод2</Name>",
"<ClientManagedApplication>true</ClientManagedApplication>",
"<ServerCall>false</ServerCall>"
]
},
{
"file": "base/cfg/Configuration.xml",
"text": [
"<CommonModule>ОбщМод</CommonModule>",
"<CommonModule>ОбщМод2</CommonModule>",
"<CommonModule>Модуль3</CommonModule>"
]
}
]
},
"expectError": true,
"noSnapshot": "платформа фейковая: проверяется сгенерированная конфигурация, а не база"
}