mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-27 12:35:54 +03:00
fix(db-*, epf-*): ошибка до запуска платформы не выдаётся за успех
Временный каталог брался из $env:TEMP, которой вне Windows нет. Join-Path падал на привязке параметра внутри try/finally без catch: try прерывался, finally отрабатывал, и скрипт выходил с кодом 0 — платформа не запускалась, постусловие не проверялось (#106). - временный каталог — [IO.Path]::GetTempPath() (на Windows тот же путь); - верхнеуровневый trap { … exit 1 } в 15 скриптах db-*/epf-*, запускающих платформу: любая необработанная ошибка даёт код 1 и печатает место; - уборка временного каталога в finally не падает на пустом пути; - гард check-ps-portability.mjs держит оба правила. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
317dce6d9f
commit
eb1796ef94
@@ -115,6 +115,7 @@ node tests/skills/check-inline-drift.mjs --list # реестр: семья →
|
||||
| `check-agent-portability.mjs` | исходники навыков не привязаны к конкретному AI-агенту: единственная разрешённая форма — плейсхолдер `${CLAUDE_SKILL_DIR}/`, который разворачивает `scripts/switch.py` |
|
||||
| `check-error-streams.mjs` | сообщения об ошибках идут в один и тот же поток в обоих портах навыка (соответствие из `docs/python-porting-guide.md`) |
|
||||
| `check-nonascii-fs.mjs` | `fsutil`: удаление и копирование держат не-ASCII пути (кириллический `%TEMP%`, кириллические имена объектов 1С), обе копии модуля не разошлись |
|
||||
| `check-ps-portability.mjs` | `.ps1` навыков не читают `$env:TEMP`/`$env:TMP`; скрипты `db-*`/`epf-*`, запускающие платформу, держат верхнеуровневый `trap { … exit 1 }` |
|
||||
|
||||
`check-inline-drift.mjs` держит реестр семей внутри себя: у каждой семьи перечислены варианты, у
|
||||
варианта — навык-эталон и список копий. Отклоняющийся вариант обязан иметь обоснование (`why`),
|
||||
@@ -159,6 +160,16 @@ node tests/skills/check-inline-drift.mjs --list # реестр: семья →
|
||||
|
||||
[nafs]: https://github.com/nodejs/node/issues/61067
|
||||
|
||||
`check-ps-portability.mjs` держит два следствия issue #106. Вне Windows `$env:TEMP` равна `$null`,
|
||||
и `Join-Path $env:TEMP …` падает на привязке параметра. Внутри `try { } finally { }` без `catch`
|
||||
такая ошибка прерывает `try`, отрабатывает `finally` — и скрипт выходит с **кодом 0**: платформа
|
||||
не запускалась, постусловие не проверялось, навык «успешен». Поэтому временный каталог берётся
|
||||
через `[IO.Path]::GetTempPath()`, а скрипты `db-*`/`epf-*`, запускающие платформу, ставят после
|
||||
`param(...)` верхнеуровневый `trap { … exit 1 }` — он ловит и ошибки вне `try`, внутренние `catch`
|
||||
сохраняют приоритет, `finally` отрабатывает. Кейсом это не проверить: libuv на Windows возвращает
|
||||
`TEMP` в окружение дочернего процесса, даже если раннер его убрал, а пустое значение ломает
|
||||
`GetTempPath()` иначе, чем отсутствие переменной.
|
||||
|
||||
`check-format-versions.mjs` держит границы проверенного диапазона версий формата выгрузки. Раньше
|
||||
допустимый список версий был независимым литералом в каждом валидаторе, и сверять его было не с
|
||||
чем: волна 2.21 прошла по четырём валидаторам и молча обошла пятый — `form-validate` остался на
|
||||
|
||||
@@ -23,6 +23,7 @@ const GUARDS = [
|
||||
['check-agent-portability.mjs', 'исходники навыков: без привязки к конкретному AI-агенту'],
|
||||
['check-error-streams.mjs', 'сообщения об ошибках: один и тот же поток в обоих портах'],
|
||||
['check-nonascii-fs.mjs', 'fsutil: удаление и копирование держат не-ASCII пути, копии не разошлись'],
|
||||
['check-ps-portability.mjs', '.ps1: без $env:TEMP, необработанная ошибка у db-*/epf-* даёт код 1'],
|
||||
];
|
||||
|
||||
let failed = 0;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env node
|
||||
// Инвариант: PS-скрипты навыков не опираются на окружение, которое есть только в Windows, а
|
||||
// скрипты, запускающие платформу, не могут завершиться с кодом 0 из-за необработанной ошибки.
|
||||
//
|
||||
// Issue #106: вне Windows `$env:TEMP` равна $null. `Join-Path $env:TEMP …` внутри
|
||||
// `try { } finally { }` без catch — ошибка привязки параметра прерывает try, отрабатывает
|
||||
// finally, и скрипт выходит с кодом 0: платформа не запускалась, постусловие не проверялось.
|
||||
// db-create/db-dump-*/db-load-xml рапортовали «успех», ничего не сделав.
|
||||
//
|
||||
// Два правила:
|
||||
// 1. `$env:TEMP` / `$env:TMP` в .ps1 навыков запрещены — временный каталог берётся через
|
||||
// [IO.Path]::GetTempPath() (на Windows тот же путь, вне Windows — TMPDIR или /tmp).
|
||||
// 2. .ps1 в db-*/epf-*, запускающий платформу (Invoke-PlatformProcess / Start-Process),
|
||||
// держит верхнеуровневый `trap { … exit 1 }`: любая необработанная ошибка — код 1.
|
||||
// Рантайм-кейсом это не поймать: после правила 1 штатного способа уронить скрипт нет.
|
||||
//
|
||||
// Почему гард, а не кейс с урезанным окружением: libuv на Windows возвращает TEMP в окружение
|
||||
// дочернего процесса, даже если его убрали (обязательная переменная), — «TEMP нет» из Node на
|
||||
// Windows не выразить.
|
||||
//
|
||||
// Запуск: node tests/skills/check-ps-portability.mjs
|
||||
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
const SKILLS = join(ROOT, '.claude', 'skills');
|
||||
|
||||
const errors = [];
|
||||
let checked = 0;
|
||||
let trapped = 0;
|
||||
|
||||
for (const skill of readdirSync(SKILLS)) {
|
||||
const dir = join(SKILLS, skill, 'scripts');
|
||||
if (!existsSync(dir)) continue;
|
||||
for (const file of readdirSync(dir)) {
|
||||
if (!file.endsWith('.ps1')) continue;
|
||||
checked++;
|
||||
const lines = readFileSync(join(dir, file), 'utf8').replace(/^/, '').split(/\r?\n/);
|
||||
const code = lines.map((l, i) => ({ l, n: i + 1 })).filter(({ l }) => !l.trimStart().startsWith('#'));
|
||||
|
||||
for (const { l, n } of code) {
|
||||
if (/\$env:(TEMP|TMP)\b/i.test(l)) {
|
||||
errors.push(`${skill}/${file}:${n}: $env:TEMP/$env:TMP есть только в Windows — `
|
||||
+ `используйте [IO.Path]::GetTempPath()`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^(db|epf)-/.test(skill)) continue;
|
||||
const runsPlatform = code.some(({ l }) => /Invoke-PlatformProcess|Start-Process/.test(l));
|
||||
if (!runsPlatform) continue;
|
||||
trapped++;
|
||||
if (!code.some(({ l }) => /^trap \{.*\bexit 1\b/.test(l))) {
|
||||
errors.push(`${skill}/${file}: запускает платформу, но нет верхнеуровневого `
|
||||
+ '`trap { … exit 1 }` — необработанная ошибка внутри try/finally даст код 0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Проверено .ps1: ${checked}; из них запускают платформу (db-*/epf-*): ${trapped}`);
|
||||
if (errors.length === 0) {
|
||||
console.log('OK — без Windows-only окружения, необработанная ошибка даёт код 1.');
|
||||
process.exit(0);
|
||||
}
|
||||
console.log(`\n${errors.length} НАРУШЕНИЙ:`);
|
||||
for (const e of errors) console.log(` [ERROR] ${e}`);
|
||||
process.exit(1);
|
||||
Reference in New Issue
Block a user