Files
houseplan-card/scripts/classify-changes.mjs
T
Codex 0c85307bf4 ci: перф-смок по диффу — изометрия и взаимодействие в код-ревью
Классификация changes вынесена в scripts/classify-changes.mjs (выходы
perf_iso/perf_interaction, fallback --all). performance_smoke добавляет
large-house-isometric-v1 при правке src/iso-* и large-house-interaction-v1
при правке живого пути, по 3 образца против hardMaxMs полных профилей
(budgets-*-smoke.json). Набор профилей входит в ключ reuse. PROCESS.md §8.

Issue: #473
User-Visible: no
2026-09-06 15:00:29 +03:00

60 lines
3.5 KiB
JavaScript
Executable File

// Классификация изменённых файлов для job `changes` в validate.yml (#473 AC8).
//
// Шаблоны жили inline в shell-шаге `classify` как `has('regex')`. Пока выходов
// было три, это терпимо; с диффозависимыми профилями перф-смока (#473) их
// пять, и вопрос «запустит ли правка src/iso-x.ts изометрический профиль»
// стал вопросом к workflow, на который нельзя ответить тестом — shell в YAML
// не исполняется локально. Теперь ответ даёт функция, а shell только
// переписывает её вывод в `$GITHUB_OUTPUT`.
//
// Контракт неизменен: каждый выход — строка 'true'/'false', как её и читают
// условия `if: needs.changes.outputs.X == 'true'`. Fallback «полный прогон
// без классификации» (--all) выставляет все выходы в 'true' — в том числе те,
// что появятся позже: пропущенный ключ в fallback-е означал бы, что job
// с этим условием молча не запускается ровно в тех прогонах, где база
// недоказуема и проверять надо всё.
import { readFileSync } from 'node:fs';
export const CLASSIFIERS = {
frontend: /^(src\/|demo\/|test\/|dist\/|custom_components\/houseplan\/frontend\/|package(-lock)?\.json$|rollup\.config\.mjs$|tsconfig)/,
backend: /^(custom_components\/.*\.py$|tests_backend\/|scripts\/support-relay\/|pytest\.ini$)/,
integration: /^(custom_components\/houseplan\/manifest\.json$|hacs\.json$|custom_components\/.*\.py$|custom_components\/.*\/translations\/)/,
// Перф-смок (#473 §5): изометрический профиль — при правке изометрии,
// профиль взаимодействия — при правке живого пути и оркестраторов кадра.
// Только `src/**`: тесты и демо кадр не замедляют.
perf_iso: /^src\/iso-[^/]+\.ts$/,
perf_interaction: /^src\/(live-[^/]+|render-[^/]+|houseplan-render-lifecycle|houseplan-card)\.ts$/,
};
export const OUTPUTS = Object.keys(CLASSIFIERS);
/** Список файлов → выходы job `changes` ('true'/'false' по каждому ключу). */
export function classifyChanges(files) {
const list = (Array.isArray(files) ? files : String(files).split('\n'))
.map((file) => file.trim()).filter(Boolean);
const result = {};
for (const [name, pattern] of Object.entries(CLASSIFIERS)) {
result[name] = list.some((file) => pattern.test(file)) ? 'true' : 'false';
}
return result;
}
/** Fallback без классификации: всё прогоняется. */
export function classifyAll() {
return Object.fromEntries(OUTPUTS.map((name) => [name, 'true']));
}
/** Формат `$GITHUB_OUTPUT`. */
export function formatOutputs(outputs) {
return OUTPUTS.map((name) => `${name}=${outputs[name]}`).join('\n') + '\n';
}
const invokedDirectly = process.argv[1]
&& import.meta.url === new URL(`file://${process.argv[1]}`).href;
if (invokedDirectly) {
const all = process.argv.includes('--all');
const outputs = all ? classifyAll() : classifyChanges(readFileSync(0, 'utf8'));
process.stdout.write(formatOutputs(outputs));
}