release.sh: переставлять split-ветки принудительно

git subtree split -b отказывается двигать существующую ветку, если новый
коммит ей не потомок — после reset в ядре ветка молча оставалась старой,
и продукт тянул устаревшую половину. Ветки производные, поэтому branch -f.

Поймано doctor'ом: сверка поддерева с ядром показала расхождение серверной
половины при совпадающей браузерной.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LeBkZLiQrvdECf5nfgiMz
This commit is contained in:
2026-08-26 20:45:08 +03:00
co-authored by Claude Opus 5
parent 0c7fee73a6
commit 7977ea6738
3 changed files with 76 additions and 21 deletions
+40 -8
View File
@@ -15,6 +15,7 @@
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const HERE = path.dirname(fileURLToPath(import.meta.url));
@@ -47,6 +48,24 @@ const SERVER_CORE = path.join(BACK, 'core');
// «Demo Panel» в нём норма, а не недоделка. Узнаётся по каталогу core/ рядом.
const IS_TEMPLATE = fs.existsSync(path.join(PANEL, 'core', 'browser'));
// Ядро попадает в панель одним из двух способов, и проверки у них разные:
//
// subtree — панель лежит в git-репозитории продукта, ядро подтянуто
// `git subtree add` и потому отслеживается git. Обновляется
// `git subtree pull`; update.sh здесь не нужен и вреден — он
// сносит каталог целиком и затёр бы поддерево.
//
// copy — ядро раскладывает update.sh из соседнего ma7engine. Так устроен
// сам шаблон, и так живёт панель вне git.
function coreIsTracked(dir) {
try {
const out = execFileSync('git', ['ls-files', '--', dir],
{ cwd: PANEL, stdio: ['ignore', 'pipe', 'ignore'] });
return out.toString().trim() !== '';
} catch { return false; }
}
const MODE = !IS_TEMPLATE && coreIsTracked(path.join(FRONT, 'src/core')) ? 'subtree' : 'copy';
// ─── накопитель результатов ─────────────────────────────────────────────────
const errors = [], warns = [], skips = [], oks = [];
@@ -71,20 +90,33 @@ if (!fs.existsSync(FRONT)) {
}
const updateSh = path.join(FRONT, 'scripts/update.sh');
if (fs.existsSync(updateSh)) ok('update.sh на месте');
if (MODE === 'subtree') {
if (fs.existsSync(updateSh)) {
err('в subtree-панели остался frontend/scripts/update.sh',
'он сносит каталог ядра целиком (rm -rf) и затрёт подтянутое поддерево — ' +
'обновление здесь делается git subtree pull');
} else ok('ядро приходит через git subtree (update.sh не нужен)');
} else if (fs.existsSync(updateSh)) ok('update.sh на месте');
else err('нет frontend/scripts/update.sh', 'без него ядро в панели не обновляется');
const frontPkgText = need(path.join(FRONT, 'package.json'), 'фронт');
const frontPkg = frontPkgText ? JSON.parse(frontPkgText) : null;
if (frontPkg) {
const s = frontPkg.scripts ?? {};
if (/update\.sh/.test(s.update ?? '')) ok('скрипт update ведёт на scripts/update.sh');
else err('frontend/package.json: скрипт "update" не зовёт scripts/update.sh');
if (MODE === 'subtree') {
const stale = ['update', 'predev', 'prebuild'].filter(h => /update/.test(s[h] ?? ''));
if (stale.length) err(`frontend/package.json: остались хуки копирования (${stale.join(', ')})`,
'ядро приходит поддеревом — эти скрипты его затрут');
else ok('в package.json нет хуков копирования ядра');
} else {
if (/update\.sh/.test(s.update ?? '')) ok('скрипт update ведёт на scripts/update.sh');
else err('frontend/package.json: скрипт "update" не зовёт scripts/update.sh');
for (const hook of ['predev', 'prebuild']) {
if (/update/.test(s[hook] ?? '')) ok(`${hook} обновляет ядро`);
else err(`frontend/package.json: нет "${hook}": "npm run update"`,
'без него dev и build идут на устаревшей копии ядра');
for (const hook of ['predev', 'prebuild']) {
if (/update/.test(s[hook] ?? '')) ok(`${hook} обновляет ядро`);
else err(`frontend/package.json: нет "${hook}": "npm run update"`,
'без него dev и build идут на устаревшей копии ядра');
}
}
}
@@ -330,7 +362,7 @@ if (!IS_TEMPLATE) {
// ─── вывод ──────────────────────────────────────────────────────────────────
const rel = path.relative(process.cwd(), PANEL) || '.';
console.log(`doctor: ${rel}${IS_TEMPLATE ? ' (шаблон ma7engine)' : ''}\n`);
console.log(`doctor: ${rel}${IS_TEMPLATE ? ' (шаблон ma7engine)' : ` (ядро: ${MODE})`}\n`);
for (const m of oks) console.log(` ok ${m}`);
for (const m of skips) console.log(` — ${m}`);