mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-27 20:45:54 +03:00
Временный каталог брался из $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>
68 lines
3.8 KiB
JavaScript
68 lines
3.8 KiB
JavaScript
#!/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);
|