From d1cb4d932a65096c4baa0d82462253bfc3d59283 Mon Sep 17 00:00:00 2001 From: vitya Date: Sat, 19 Sep 2026 14:59:13 +0300 Subject: [PATCH] =?UTF-8?q?fix(admin):=20sync-sched-publish=20=E2=80=94=20?= =?UTF-8?q?.mappa=20=D0=B2=D0=BD=D0=B5=20=D0=BF=D0=B0=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D0=BA-=D1=81=D0=B5=D1=82=D0=B0=20+=20=D0=BF=D0=BE=D1=80=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D1=8B=D0=B9=20leak-=D1=81=D0=BA?= =?UTF-8?q?=D0=B0=D0=BD=20(gotcha=2015/16,=20task:3059)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/sync-sched-publish.mjs | 41 +++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/scripts/sync-sched-publish.mjs b/scripts/sync-sched-publish.mjs index ea37f1a..132bfce 100644 --- a/scripts/sync-sched-publish.mjs +++ b/scripts/sync-sched-publish.mjs @@ -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; }