feat(db-*,epf-*,erf-*,web-publish): v8path на уровне записи базы

В одном проекте базы могут жить на разных версиях платформы, а версию формата
XML-выгрузки задаёт та платформа, которая выгружает: без выбора платформы по базе
срабатывает автопоиск и берёт самую старшую установленную — то есть самый новый
формат, который коллега на младшей платформе уже не загрузит.

Теперь порядок такой: явный -V8Path -> v8path записи базы -> корневой v8path ->
автопоиск. Запись базы ищет сам скрипт тем же матчем, которым добывал реквизиты
хранилища (Find-ProjectDatabase): файловую по path, серверную по server + ref.

- в 9 навыков, где матча не было, скопирован блок из авторитета db-repo;
- списки потребителей трёх семей в check-inline-drift расширены; заведена семья
  platform: resolve_v8path — обёртка меняла сигнатуру, а под гардом не была;
- раннер подставляет {workDir}/{fakePlatform} и в содержимое preRun.writeFile,
  иначе кейс про выбор платформы нельзя написать кроссплатформенно;
- в SKILL.md платформа теперь берётся ПОСЛЕ разрешения базы, строка про
  автоопределение через Program Files убрана как деталь реализации.

Живой прогон: две базы в одном проекте при корневом v8path = 8.5 дали формат
2.17 (8.3.24) и 2.20 (8.3.27), оба порта.

Closes #93

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-09-19 20:35:20 +03:00
co-authored by Claude Opus 5
parent 303db4e9f9
commit 874ae650ca
56 changed files with 1249 additions and 193 deletions
+24 -8
View File
@@ -274,6 +274,25 @@ function cleanupWorkspace(ws) {
// ─── Arg building ───────────────────────────────────────────────────────────
// Плейсхолдеры кейса: {workDir} — абсолютный путь рабочего каталога, {fakePlatform} — путь к
// фейковой платформе, которую раскладывает writeFakePlatform (имя зависит от ОС, поэтому кейс
// адресует её плейсхолдером и остаётся кроссплатформенным). Подстановка нужна не только в
// аргументах: путь к фейку кладут и ВНУТРЬ подложенного .v8-project.json — иначе кейс про
// выбор платформы пришлось бы писать двумя копиями под osOnly.
function substPlaceholders(s, workDir) {
const fakePath = join(workDir, process.platform === 'win32' ? 'fake.cmd' : 'fake.sh');
return s.replaceAll('{workDir}', workDir).replaceAll('{fakePlatform}', fakePath);
}
function substDeep(value, workDir) {
if (typeof value === 'string') return substPlaceholders(value, workDir);
if (Array.isArray(value)) return value.map(v => substDeep(v, workDir));
if (value && typeof value === 'object') {
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, substDeep(v, workDir)]));
}
return value;
}
// Байты входного файла в заданной кодировке. Нужно для кейсов про кодировку: writeFileSync
// пишет только UTF-8, а навык обязан одинаково вести себя на UTF-16 с BOM (принять) и на
// cp1251 (отвергнуть, а не молча подменить кириллицу на U+FFFD). cp1251 в Node нет — кодируем
@@ -355,13 +374,8 @@ function buildArgs(skillConfig, caseData, workDir, inputFilePath, runtime) {
}
// Append extra args from case (for optional params like -Vendor, -Version).
// Supports {workDir} substitution for tests that need absolute paths inside the workspace,
// and {fakePlatform} — путь к фейковой платформе, которую раскладывает writeFakePlatform.
if (caseData.args_extra) {
const fakePath = join(workDir, process.platform === 'win32' ? 'fake.cmd' : 'fake.sh');
args.push(...caseData.args_extra.map(a => typeof a === 'string'
? a.replace('{workDir}', workDir).replace('{fakePlatform}', fakePath)
: a));
args.push(...caseData.args_extra.map(a => typeof a === 'string' ? substPlaceholders(a, workDir) : a));
}
return { scriptPath, args };
@@ -837,9 +851,11 @@ async function runCaseAsync(testCase, opts) {
// writeFile step — записать произвольный файл в workDir перед запуском скрипта
if (step.writeFile) {
const wfPath = join(workDir, step.writeFile.path);
// Плейсхолдеры подставляются и здесь. В объектной форме — ДО JSON.stringify: иначе
// обратные слэши windows-пути попали бы в готовый JSON неэкранированными.
const wfContent = typeof step.writeFile.content === 'string'
? step.writeFile.content
: JSON.stringify(step.writeFile.content, null, 2);
? substPlaceholders(step.writeFile.content, workDir)
: JSON.stringify(substDeep(step.writeFile.content, workDir), null, 2);
mkdirSync(dirname(wfPath), { recursive: true });
writeFileSync(wfPath, wfContent, 'utf8');
// Бит исполнения: на *nix навык запускает платформу через exec, и фейк без +x