feat(skd-validate): проверка схемы компоновки по XSD платформы (#101)

Если в проекте есть схемы /v8-xsd-fetch, skd-validate дополнительно
проверяет порядок элементов, типы и значения по XSD версии формата
выгрузки (штамп Configuration.xml или корня внешнего отчёта). Схемы
точной версии — ошибки, только более новой — предупреждения, иначе
проверка пропускается. Места, которые платформа пишет иначе, чем
описывает XSD, отсекаются по месту нарушения: на 2606 схемах компоновки
выгрузок БП, УНФ и ERP (форматы 2.17–2.21) — ни одного срабатывания в
обоих портах.

Runner: поле кейса requires (путь от корня репозитория, нет — skipped)
и плейсхолдер {repoRoot} — кейсы на настоящих схемах, которые в
репозиторий не входят.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dc2yLQfw6wppbopM1Y4Ps
This commit is contained in:
Nick Shirokov
2026-09-29 15:24:26 +03:00
co-authored by Claude Opus 5.5
parent 9293305a12
commit 5465a7803c
25 changed files with 1594 additions and 12 deletions
+8 -1
View File
@@ -282,7 +282,7 @@ function cleanupWorkspace(ws) {
// выбор платформы пришлось бы писать двумя копиями под osOnly.
function substPlaceholders(s, workDir) {
const fakePath = join(workDir, process.platform === 'win32' ? 'fake.cmd' : 'fake.sh');
return s.replaceAll('{workDir}', workDir).replaceAll('{fakePlatform}', fakePath);
return s.replaceAll('{workDir}', workDir).replaceAll('{fakePlatform}', fakePath).replaceAll('{repoRoot}', REPO_ROOT);
}
function substDeep(value, workDir) {
@@ -835,6 +835,13 @@ async function runCaseAsync(testCase, opts) {
return { id: testCase.id, skill: testCase.skillDir, name: testCase.name, passed: true, skipped: true, errors: [], elapsed: '0.0s' };
}
// requires: кейс опирается на локальные данные, которые в репозиторий не входят (XSD-схемы 1С
// в .v8-xsd — их правообладатель 1С). Пути от корня репозитория; нет хотя бы одного — skipped.
// Не external: тот отдаёт каталог на месте только для чтения, а кейсу нужно разложить свои файлы.
if (caseData.requires && ![].concat(caseData.requires).every(p => existsSync(resolve(REPO_ROOT, p)))) {
return { id: testCase.id, skill: testCase.skillDir, name: testCase.name, passed: true, skipped: true, errors: [], elapsed: '0.0s' };
}
try {
const skillCasesDir = join(CASES, testCase.skillDir);
const fixturePath = ensureSetup(setupName, opts.runtime, skillCasesDir);