mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 11:18:48 +00:00
Решения владельца: 1б — индекс ревью не пересобирается в ветке задачи, только коммитами, идущими в dev; 2б — бандл меняет только кандидат беты/релиза, стенд dev берёт его из артефакта Validate. - scripts/bundle-policy.mjs: коммит, трогающий dist/** или custom_components/houseplan/frontend/**, обязан нести Release: (хук commit-msg и история в CI через validate-commit-provenance; коммиты с датой автора до 2026-09-27 не судятся); --verify судит целостность свежей сборки всегда, побайтовую сверку с закоммиченной копией — только на коммите, меняющем бандл, или кандидате; --clean. - release-prerelease: публикация отказывает, если отпечаток исходников в закоммиченном манифесте не равен отпечатку дерева (хотфикс поверх кандидата без пересборки). - bundle-sync: по умолчанию только demo/srv/assets; --release (npm run bundle:release) — ещё и custom_components. - rebase-on-dev: конфликт в бандле берёт копию dev, без пересборки и amend. - validate.yml: job dev_build публикует card-bundle головы dev в сиротскую ветку dev-build (scripts/dev-build.mjs); стенд накладывает её demo/stand/update-dev-bundle.sh. - _process.yml: индекс ревью больше не пересобирается при приведении к dev и при публикации документа в ветку задачи. - golden-wsl-artifact/golden-container: сборка перед съёмкой не считается правкой источника, после — bundle:clean. - test/bundle-tree-committed: судит закоммиченный снимок, не диск. - 11 мутантов в реестре; PROCESS/AGENTS/DEVELOPMENT/AUTHOR/REVIEWER. Issue: #657 User-Visible: no
88 lines
4.9 KiB
JavaScript
88 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Съёмка golden-кандидатов в пиновом образе Playwright (#334).
|
||
*
|
||
* node scripts/golden-container.mjs # capture
|
||
* node scripts/golden-container.mjs --mode=verify
|
||
* node scripts/golden-container.mjs --image=mcr.microsoft.com/playwright:v1.62.0-jammy
|
||
* node scripts/golden-container.mjs --dry-run # только напечатать команду
|
||
*
|
||
* Зачем. Прежде эталон принимался только из артефакта CI, и каждый визуальный
|
||
* фикс стоил двух полных прогонов: пуш, ожидание, скачивание артефакта,
|
||
* приёмка, второй пуш. Здесь картинки снимаются локально в том же образе, что
|
||
* стоит на раннере, — одинаковый Chromium и одинаковые шрифты.
|
||
*
|
||
* Гарантия при этом не на слове. `accept.mjs` принимает съёмку только если
|
||
* разошлись РОВНО объявленные сцены (`--expect-change`), а всё остальное
|
||
* совпало с принятыми эталонами. Если растеризация в образе всё-таки другая,
|
||
* разойдутся посторонние сцены с текстом — и приёмка будет запрещена с их
|
||
* перечислением. Поэтому даже неверно угаданный тег образа не может испортить
|
||
* эталоны: он может только не сработать.
|
||
*
|
||
* Почему node_modules прячется анонимным томом: в репозитории владельца он
|
||
* собран под Windows, а `npm ci` внутри контейнера поставил бы туда
|
||
* linux-бинарники и сломал бы хозяйское дерево. Том перекрывает каталог, и
|
||
* установка живёт только внутри контейнера.
|
||
*/
|
||
import { spawnSync } from 'node:child_process';
|
||
import { readFileSync } from 'node:fs';
|
||
import { dirname, resolve } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||
const arg = (name, fallback = '') => {
|
||
const found = process.argv.find((item) => item.startsWith(`--${name}=`));
|
||
return found ? found.slice(name.length + 3) : fallback;
|
||
};
|
||
|
||
const lock = JSON.parse(readFileSync(resolve(ROOT, 'package-lock.json'), 'utf8'));
|
||
const pinned = lock.packages?.['node_modules/playwright']?.version;
|
||
if (!pinned) throw new Error('в package-lock.json не найдена залоченная версия playwright');
|
||
|
||
const image = arg('image', `mcr.microsoft.com/playwright:v${pinned}`);
|
||
const mode = arg('mode', 'capture');
|
||
if (!['capture', 'verify'].includes(mode)) throw new Error(`неизвестный режим: ${mode}`);
|
||
const dryRun = process.argv.includes('--dry-run');
|
||
|
||
// `CI=` пустой намеренно: отчёт должен честно говорить, что съёмка локальная.
|
||
const inner = [
|
||
'set -eu',
|
||
'npm ci --no-audit --no-fund',
|
||
'npm run bundle:sync',
|
||
`npm run golden:${mode}`,
|
||
// Сборка переписала отслеживаемый dist/ хозяйского дерева: с #657 его
|
||
// меняет только кандидат, поэтому после съёмки он возвращается.
|
||
'npm run bundle:clean',
|
||
].join(' && ');
|
||
|
||
const args = [
|
||
'run', '--rm', '--ipc=host',
|
||
'-v', `${ROOT}:/work`,
|
||
// Анонимный том перекрывает хозяйский node_modules — см. комментарий выше.
|
||
'-v', '/work/node_modules',
|
||
'-w', '/work',
|
||
'-e', 'CI=',
|
||
image, 'bash', '-lc', inner,
|
||
];
|
||
|
||
console.log(`Залоченный playwright: ${pinned}`);
|
||
console.log(`Образ: ${image}`);
|
||
console.log(`docker ${args.slice(0, -1).join(' ')} '${inner}'`);
|
||
if (dryRun) process.exit(0);
|
||
|
||
const docker = spawnSync('docker', ['--version'], { encoding: 'utf8' });
|
||
if (docker.status !== 0) {
|
||
console.error('docker недоступен. Съёмка возможна и без контейнера — в любой Linux-среде,'
|
||
+ ' включая WSL: правило приёмки одинаково и само отвергнет съёмку, если растеризация'
|
||
+ ' разойдётся с раннером. См. demo/golden/README.md.');
|
||
process.exit(2);
|
||
}
|
||
|
||
const run = spawnSync('docker', args, { stdio: 'inherit' });
|
||
if (run.status !== 0) {
|
||
console.error(`\nСъёмка в контейнере не удалась (код ${run.status}).`
|
||
+ ' Если docker не смог получить образ — укажите тег дистрибутива:'
|
||
+ ` --image=mcr.microsoft.com/playwright:v${pinned}-jammy`);
|
||
}
|
||
process.exit(run.status ?? 1);
|