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

Готовый Template.xml оборачивается в исходники EPF: epf-init создаёт
скелет, template-add регистрирует SpreadsheetDocument-макет, MXL копируется
поверх дефолтного, далее epf-build реально проверяет, что платформа
принимает разметку. mxl-compile убран из STANDALONE_SKILLS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-04-26 16:59:06 +03:00
parent cd74afa5e0
commit 704fbc9f28
+56 -1
View File
@@ -317,7 +317,7 @@ function runPreSteps(preRun, workDir, runtime, log) {
// Standalone file skills — produce files (not configs), platform load = just run script
const STANDALONE_SKILLS = new Set([
'skd-compile', 'skd-edit', 'skd-info', 'skd-validate',
'mxl-compile', 'mxl-decompile', 'mxl-info', 'mxl-validate',
'mxl-decompile', 'mxl-info', 'mxl-validate',
]);
// Standalone skills that CAN be platform-verified by wrapping their output in
@@ -325,6 +325,10 @@ const STANDALONE_SKILLS = new Set([
// schema and we know if it's accepted.
const SKD_PLATFORM_VERIFY = new Set(['skd-compile', 'skd-edit']);
// MXL: wrap produced Template.xml as a SpreadsheetDocument template inside
// an EPF source and run epf-build — platform parses the macro layout.
const MXL_PLATFORM_VERIFY = new Set(['mxl-compile']);
// EPF/ERF skills — verified by epf-build on the produced source.
// Map skill -> output extension (.epf/.erf).
const EPF_SKILLS = new Map([
@@ -567,6 +571,57 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
return result;
}
if (MXL_PLATFORM_VERIFY.has(skillName)) {
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 epfDir = join(workDir, 'epf-src');
const epfOutDir = join(workDir, 'epf-build');
mkdirSync(epfOutDir, { recursive: true });
try {
execSkill(opts.runtime, 'epf-init/scripts/init', ['-Name', 'TestProc', '-SrcDir', epfDir]);
log('epf-init', true);
} catch (e) {
const detail = (e.stderr || e.stdout || e.message).trim();
log('epf-init', false, detail);
result.errors.push(`epf-init failed: ${detail.substring(0, 500)}`);
return result;
}
try {
execSkill(opts.runtime, 'template-add/scripts/add-template', [
'-ObjectName', 'TestProc',
'-TemplateName', 'Макет',
'-TemplateType', 'SpreadsheetDocument',
'-SrcDir', epfDir,
]);
log('template-add', true);
} catch (e) {
const detail = (e.stderr || e.stdout || e.message).trim();
log('template-add', false, detail);
result.errors.push(`template-add failed: ${detail.substring(0, 500)}`);
return result;
}
const tplDest = join(epfDir, 'TestProc', 'Templates', 'Макет', 'Ext', 'Template.xml');
cpSync(tplPath, tplDest, { force: true });
try {
execSkill(opts.runtime, 'epf-build/scripts/epf-build', [
'-V8Path', opts.v8ctx.v8path,
'-SourceFile', join(epfDir, 'TestProc.xml'),
'-OutputFile', join(epfOutDir, 'TestProc.epf'),
], 180_000);
log('epf-build', true, 'platform accepted MXL');
result.passed = true;
} catch (e) {
const detail = (e.stderr || e.stdout || e.message).trim();
log('epf-build', false, detail);
result.errors.push(`epf-build rejected MXL: ${detail.substring(0, 1000)}`);
}
return result;
}
if (isStandalone) {
result.passed = true;
log('platform-load', true, 'skipped (standalone file, not a config)');