fix(admin): sync-sched-publish — .mappa вне паблик-сета + портативный leak-скан (gotcha 15/16, task:3059)

This commit is contained in:
2026-09-19 14:59:13 +03:00
parent 9d1ca60ddd
commit d1cb4d932a

View File

@@ -60,13 +60,25 @@ function copyTree(srcFile, dstFile) {
function collect() {
const out = shSilent(`git -C "${DEV}" ls-files`);
if (out === null) { fail("dev не git-репо"); return null; }
const EXCL = /^\.wiki\/|^\.tasks\/|^\.agents\/|^AGENTS\.md$|^CLAUDE\.md$|^branding\/pora-|^pngtree-|^327274997_|^cat-clock-|^MIGRATION\.md$/;
const EXCL = /^\.wiki\/|^\.tasks\/|^\.agents\/|^\.mappa\/|^AGENTS\.md$|^CLAUDE\.md$|^branding\/pora-|^pngtree-|^327274997_|^cat-clock-|^MIGRATION\.md$/;
return out.split("\n").filter((f) => f && !EXCL.test(f));
}
// dev-only артефакты, которые не должны жить в pub НИКОГДА (gotcha 15: .mappa/config.yaml
// несёт внутренний реестр — tenant/mappa.vds.kzntsv.site/git.kzntsv.site; в волне 2026-09-19
// утёк в паблик-коммит, потому что exclusion-list его не знал)
const NEVER = [".mappa"];
function purge() {
for (const p of NEVER) {
const t = `${PUB}/${p}`;
if (fs.existsSync(t)) { fs.rmSync(t, { recursive: true, force: true }); log(`purge: удалён ${p} (dev-only)`); }
}
}
function copy(files) {
for (const f of files) copyTree(`${DEV}/${f}`, `${PUB}/${f}`);
log(`copy: ${files.length} файлов`);
purge();
}
// выбеливание — точечные замены, идемпотентно (эталон: верифицированный b0afd14)
@@ -268,13 +280,32 @@ function gate() {
return true;
}
// Портативный скан (gotcha 16): прежний `grep -rnE '…'` через cmd.exe на Windows НЕ работал:
// cmd не снимает одинарные кавычки, а `|` для него — оператор pipe → команда падала,
// shSilent возвращал null, проверка молча считалась зелёной (0 хитов). Теперь скан — на Node.
function grepCheck() {
const hits = shSilent(`cd "${PUB}" && grep -rnE 'git\\.kzntsv|verdaccio|vds\\.kzntsv|registry\\.kzntsv|VERDACCIO' --exclude-dir=node_modules --exclude-dir=.git .`);
if (hits && hits.length > 0) {
fail(`grep: остались ${hits.split("\n").length} вхождений kzntsv/verdaccio`);
const re = /git\.kzntsv|verdaccio|vds\.kzntsv|registry\.kzntsv|VERDACCIO/i;
const SKIP = new Set(["node_modules", ".git", ".yarn"]);
const BIN = /\.(svg|png|webp|jpg|jpeg|gif|ico|woff2?|ttf|zip|gz)$/i;
const hits = [];
const walk = (dir) => {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = `${dir}/${e.name}`;
if (e.isDirectory()) { if (!SKIP.has(e.name)) walk(p); continue; }
if (BIN.test(p)) continue;
let data;
try { data = fs.readFileSync(p, "utf8"); } catch { continue; }
data.split("\n").forEach((line, i) => {
if (re.test(line)) hits.push(`${path.relative(PUB, p)}:${i + 1}: ${line.trim().slice(0, 140)}`);
});
}
};
walk(PUB);
if (hits.length > 0) {
fail(`grep: остались ${hits.length} вхождений kzntsv/verdaccio\n` + hits.slice(0, 10).join("\n"));
return false;
}
log("grep: 0 вхождений kzntsv/verdaccio");
log("grep: 0 вхождений kzntsv/verdaccio (портативный скан)");
return true;
}