mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 19:58:50 +00:00
CODE-REVIEW-699-r1 M1: `node scripts/ratchets.mjs tighten` was named only by the warning `release:prerelease` prints at publication, when the candidate commit is already made. The candidate checklist in docs/DEVELOPMENT.md now runs it after `npm run bundle:release`, so the caps come from the fresh dist/ and land in the candidate commit. - test/ratchets.test.mjs pins the step and its order. - Mutant release-runbook-forgets-tighten. Issue: #699 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
76 lines
4.9 KiB
JavaScript
76 lines
4.9 KiB
JavaScript
// #699, PROCESS.md §8: полоса над потолком беты вместо точки; потолки
|
|
// опускает до факта релиз-менеджер одной командой на кандидате.
|
|
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { readFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import {
|
|
BUNDLE_CEILINGS, CORE_BAND, CORE_BUDGET_FILE, BUNDLE_BUDGET_FILE, formatRow, ratchetRows, ratchetState,
|
|
readConst, readCoreCaps, rewriteConst, rewriteCoreCaps,
|
|
} from '../scripts/ratchets.mjs';
|
|
import { METRIC_NAMES } from '../scripts/monolith-metrics.mjs';
|
|
|
|
const read = (path) => readFileSync(fileURLToPath(new URL(`../${path}`, import.meta.url)), 'utf8');
|
|
|
|
test('#699 состояние храповика: равен, рыхлый, в полосе, выше полосы, не измерен', () => {
|
|
assert.equal(ratchetState(100, 100, 50), 'tight');
|
|
assert.equal(ratchetState(90, 100, 50), 'loose');
|
|
assert.equal(ratchetState(150, 100, 50), 'over', 'граница полосы включительная — как у гейтов ветки');
|
|
assert.equal(ratchetState(151, 100, 50), 'grew');
|
|
assert.equal(ratchetState(undefined, 100, 50), 'missing');
|
|
assert.equal(ratchetState(100, null, 50), 'missing');
|
|
});
|
|
|
|
test('#699 потолки ядер читаются и переписываются в исходнике теста, комментарии на месте', () => {
|
|
const source = read(CORE_BUDGET_FILE);
|
|
const caps = readCoreCaps(source);
|
|
// Имена ядер здесь не пишутся: тесты, читающие монолит как текст, заморожены (#624).
|
|
const [first, second] = Object.keys(caps);
|
|
assert.equal(Object.keys(caps).length, 2, 'два ядра');
|
|
const next = rewriteCoreCaps(source, { [first]: 12000 });
|
|
assert.equal(readCoreCaps(next)[first], 12000);
|
|
assert.equal(readCoreCaps(next)[second], caps[second]);
|
|
assert.equal(next.split('\n').length, source.split('\n').length, 'меняется только число');
|
|
assert.match(next, /2026-09-27, #676/, 'история решений остаётся');
|
|
assert.match(source, new RegExp(`^export const CORE_BAND = ${CORE_BAND};$`, 'm'), 'полоса теста и инструмента — одно число');
|
|
});
|
|
|
|
test('#699 потолки графов читаются и переписываются с разрядами', () => {
|
|
const source = read(BUNDLE_BUDGET_FILE);
|
|
for (const entry of BUNDLE_CEILINGS) assert.ok(readConst(source, entry.name) > 0, entry.name);
|
|
const next = rewriteConst(source, 'INITIAL_VIEW_GZIP_CEILING', 300323);
|
|
assert.equal(readConst(next, 'INITIAL_VIEW_GZIP_CEILING'), 300323);
|
|
assert.match(next, /^export const INITIAL_VIEW_GZIP_CEILING = 300_323;$/m);
|
|
assert.throws(() => readConst(source, 'NO_SUCH_CEILING'), /нет export const NO_SUCH_CEILING/);
|
|
});
|
|
|
|
test('#699 отчёт видит все три вида храповиков и называет, что делать', () => {
|
|
const rows = ratchetRows({
|
|
coreCaps: { 'a.ts': 100 }, coreFacts: { 'a.ts': 90 },
|
|
bundleCeilings: Object.fromEntries(BUNDLE_CEILINGS.map((e) => [e.name, 1000])),
|
|
bundleFacts: Object.fromEntries(BUNDLE_CEILINGS.map((e) => [e.metric, 1000])),
|
|
metrics: Object.fromEntries(METRIC_NAMES.map((n) => [n, 10])),
|
|
baseline: Object.fromEntries(METRIC_NAMES.map((n) => [n, n === 'hostRefs' ? 5 : 10])),
|
|
});
|
|
assert.equal(rows.length, 1 + BUNDLE_CEILINGS.length + METRIC_NAMES.length);
|
|
assert.equal(rows.find((r) => r.kind === 'core').state, 'loose');
|
|
assert.ok(rows.filter((r) => r.kind === 'bundle').every((r) => r.state === 'tight'));
|
|
assert.equal(rows.find((r) => r.name === 'hostRefs').state, 'over');
|
|
assert.match(formatRow(rows.find((r) => r.kind === 'core')), /факт 90 · потолок 100 \(-10, полоса \+50\) — рыхлый/);
|
|
});
|
|
|
|
test('#699 публикация беты напоминает о храповиках, но не останавливается на них', () => {
|
|
const local = read('scripts/release-prerelease.mjs');
|
|
const main = local.slice(local.indexOf('const main = async'));
|
|
assert.match(main, /'scripts\/ratchets\.mjs', 'report', '--warn'/);
|
|
assert.match(main, /'scripts\/ratchets\.mjs', 'report', '--warn'\], \{ allowFailure: true, inherit: true \}\)/);
|
|
});
|
|
|
|
test('#699 r1 M1: runbook беты опускает храповики при подготовке кандидата, до публикации', () => {
|
|
const runbook = read('docs/DEVELOPMENT.md');
|
|
const prepare = runbook.slice(runbook.indexOf('Prepare the candidate as usual'), runbook.indexOf('npm run release:prerelease --'));
|
|
assert.ok(prepare.length > 0, 'раздел подготовки кандидата найден');
|
|
assert.match(prepare, /`npm run bundle:release`[\s\S]*`node scripts\/ratchets\.mjs tighten`/, 'tighten — после свежего dist/');
|
|
assert.match(prepare, /commit them with the candidate/);
|
|
});
|