From 60c7141f888fbf9af23d5569b0214bd6dd9f5464 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Mon, 10 Aug 2026 09:27:07 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=87=D0=B8=D0=BD=D0=B8=D1=82=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=B1=D0=BE=D1=80=20workflow=20=E2=80=94=20=D0=BE?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B5=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=B2=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Многострочный python3 -c внутри блока run начинался с нулевого отступа и обрывал YAML-скаляр: Gitea отказывалась читать файл целиком ("line 137: could not find expected ':'"). Генерация markdown уехала в tools/release_notes.py — в CI остался один вызов без экранирования, а сам рендер покрыт тестами. Co-Authored-By: Claude Opus 5 --- .gitea/workflows/build.yml | 20 +-------------- tools/release_notes.py | 52 ++++++++++++++++++++++++++++++++++++++ tools/test_build.py | 29 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 tools/release_notes.py diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 65433e9..793d7e2 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -124,25 +124,7 @@ jobs: set -eu tag=$(date -u '+%Y.%m.%d-%H%M') echo "tag=$tag" >> "$GITHUB_OUTPUT" - { - echo "Собрано из \`$(git rev-parse --short HEAD)\`." - echo - echo '| Категория | Доменов |' - echo '| --- | ---: |' - python3 -c "import json; [print(f'| {k} | {v} |') for k, v in json.load(open('dist/metadata.json'))['categories'].items()]" - echo - echo '| IP-категория | Сетей | Ограничения |' - echo '| --- | ---: | --- |' - python3 -c " -import json -for k, v in json.load(open('dist/metadata.json'))['ip_categories'].items(): - limits = ' '.join(filter(None, ['/'.join(v.get('network', [])), v.get('ports', '')])) or '—' - print(f\"| {k} | {v['count']} | {limits} |\") -" - echo - echo "sha256(discord-geosite.dat) = \`$(cut -d' ' -f1 dist/discord-geosite.dat.sha256sum)\`" - echo "sha256(discord-geoip.dat) = \`$(cut -d' ' -f1 dist/discord-geoip.dat.sha256sum)\`" - } > /tmp/notes.md + python3 tools/release_notes.py "$(git rev-parse --short HEAD)" > /tmp/notes.md cat /tmp/notes.md - name: Публикация в ветку release diff --git a/tools/release_notes.py b/tools/release_notes.py new file mode 100644 index 0000000..72de249 --- /dev/null +++ b/tools/release_notes.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Печатает описание релиза в markdown по dist/metadata.json. + + python3 tools/release_notes.py <короткий-sha> [каталог-сборки] + +Отдельным скриптом, а не строчкой в CI: внутри YAML такой markdown +пришлось бы экранировать до нечитаемости. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + + +def render(meta: dict, revision: str) -> str: + lines = [f"Собрано из `{revision}`.", ""] + + lines += ["| Категория | Доменов |", "| --- | ---: |"] + for name, count in meta["categories"].items(): + lines.append(f"| {name} | {count} |") + lines.append("") + + lines += ["| IP-категория | Сетей | Ограничения |", "| --- | ---: | --- |"] + for name, info in meta["ip_categories"].items(): + limits = " ".join(filter(None, ["/".join(info.get("network", [])), info.get("ports", "")])) + lines.append(f"| {name} | {info['count']} | {limits or '—'} |") + lines.append("") + + lines += [ + f"sha256(discord-geosite.dat) = `{meta['geosite_dat_sha256']}`", + "", + f"sha256(discord-geoip.dat) = `{meta['geoip_dat_sha256']}`", + ] + return "\n".join(lines) + "\n" + + +def main(argv: list[str]) -> int: + sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr] + if not 2 <= len(argv) <= 3: + print(f"использование: {argv[0]} <короткий-sha> [каталог-сборки]", file=sys.stderr) + return 1 + + out_dir = Path(argv[2]) if len(argv) == 3 else Path("dist") + meta = json.loads((out_dir / "metadata.json").read_text(encoding="utf-8")) + sys.stdout.write(render(meta, argv[1])) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/tools/test_build.py b/tools/test_build.py index a727dbb..f88871c 100644 --- a/tools/test_build.py +++ b/tools/test_build.py @@ -11,6 +11,7 @@ import unittest from pathlib import Path import build +import release_notes import sync from build import BuildError, Net, Options, Rule @@ -336,6 +337,34 @@ class SyncTest(unittest.TestCase): self.assertTrue(sync.is_generated(Path(tmp) / "new.txt")) +class ReleaseNotesTest(unittest.TestCase): + META = { + "geosite_dat_sha256": "aa", + "geoip_dat_sha256": "bb", + "categories": {"discord": 34}, + "ip_categories": { + "discord": {"count": 140}, + "discord-voice": {"count": 13, "network": ["udp"], "ports": "50000-50099"}, + }, + } + + def test_renders_both_tables(self): + notes = release_notes.render(self.META, "abc1234") + self.assertIn("Собрано из `abc1234`.", notes) + self.assertIn("| discord | 34 |", notes) + self.assertIn("| discord | 140 | — |", notes) + self.assertIn("| discord-voice | 13 | udp 50000-50099 |", notes) + self.assertIn("sha256(discord-geoip.dat) = `bb`", notes) + + def test_reads_real_metadata(self): + with tempfile.TemporaryDirectory() as out: + out_dir = Path(out) / "dist" + with data(a="a.com\n", ip={"n": "1.2.3.0/24\n"}): + build.build(out_dir, None, None, "proxy") + meta = json.loads((out_dir / "metadata.json").read_text(encoding="utf-8")) + self.assertIn("| n | 1 | — |", release_notes.render(meta, "abc1234")) + + class CheckTest(unittest.TestCase): def test_clean_sources_pass(self): with data(a="a.com\nb.com\n", ip={"a": "1.2.3.0/24\n"}):