feat(verify-snapshots): реальная платформенная проверка skd-compile

До сих пор для skd-compile (как и других STANDALONE_SKILLS)
verify-snapshots просто запускал скрипт и помечал PASS — без
платформенной нагрузки. Опасный пробел: можно было закоммитить
snapshot, который 1С Designer не примет.

Теперь для skd-compile snapshot оборачивается во внешний отчёт
(erf-init --WithSKD), Template.xml подменяется на сгенерированный
кейсом, и запускается erf-build. Платформа парсит схему — если
принимает, кейс PASS; если отклоняет, в errors попадает её stderr.
Ссылочные типы (CatalogRef.X и т.п.) не требуют реальной базы:
epf-build сам поднимает временную stub-конфигурацию.

Если v8 недоступен — мягкий skip с пометкой "no v8 context".

Замер: 21 кейс x ~5s avg = ~110s на полный verify-snapshots
--skill skd-compile. Все 21 текущих кейса проходят — значит каждый
snapshot гарантированно платформо-валиден.

Аналогичная обёртка для mxl-compile / role-compile — отдельной
задачей по образцу.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-04-25 18:56:13 +03:00
parent 210bde7b2e
commit 204a262746
+49
View File
@@ -320,6 +320,11 @@ const STANDALONE_SKILLS = new Set([
'mxl-compile', 'mxl-decompile', 'mxl-info', 'mxl-validate',
]);
// Standalone skills that CAN be platform-verified by wrapping their output in
// an external report (ERF) and running erf-build — the platform parses the
// schema and we know if it's accepted.
const SKD_PLATFORM_VERIFY = new Set(['skd-compile']);
// EPF/ERF skills — need epf-build to verify, not LoadConfigFromFiles
const EPF_SKILLS = new Set([
'epf-init', 'erf-init', 'template-add', 'help-add',
@@ -511,6 +516,50 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
if (inputFile && existsSync(inputFile)) rmSync(inputFile);
// ── Step 5: Determine verification strategy ──
if (SKD_PLATFORM_VERIFY.has(skillName)) {
// Wrap produced Template.xml in an external report (ERF) and try to build —
// platform either accepts the schema or rejects it with an error.
if (!opts.v8ctx) {
result.passed = true;
log('platform-load', true, 'skipped (no v8 context)');
return result;
}
const tplName = caseData.params?.outputPath || 'Template.xml';
const tplPath = join(workDir, tplName);
if (!existsSync(tplPath)) {
result.errors.push(`Output not produced at ${tplPath}`);
return result;
}
const erfDir = join(workDir, 'erf-src');
const erfOutDir = join(workDir, 'erf-build');
mkdirSync(erfOutDir, { recursive: true });
try {
execSkill(opts.runtime, 'erf-init/scripts/init', ['-Name', 'TestReport', '-SrcDir', erfDir, '-WithSKD']);
log('erf-init', true);
} catch (e) {
const detail = (e.stderr || e.stdout || e.message).trim();
log('erf-init', false, detail);
result.errors.push(`erf-init failed: ${detail.substring(0, 500)}`);
return result;
}
const dcsTpl = join(erfDir, 'TestReport', 'Templates', 'ОсновнаяСхемаКомпоновкиДанных', 'Ext', 'Template.xml');
cpSync(tplPath, dcsTpl, { force: true });
try {
execSkill(opts.runtime, 'epf-build/scripts/epf-build', [
'-V8Path', opts.v8ctx.v8path,
'-SourceFile', join(erfDir, 'TestReport.xml'),
'-OutputFile', join(erfOutDir, 'TestReport.erf'),
], 120_000);
log('erf-build', true, 'platform accepted schema');
result.passed = true;
} catch (e) {
const detail = (e.stderr || e.stdout || e.message).trim();
log('erf-build', false, detail);
result.errors.push(`erf-build rejected schema: ${detail.substring(0, 1000)}`);
}
return result;
}
if (isStandalone) {
result.passed = true;
log('platform-load', true, 'skipped (standalone file, not a config)');