diff --git a/cmd/arcrun-app/.version-source.json b/cmd/arcrun-app/.version-source.json new file mode 100644 index 0000000..47f7948 --- /dev/null +++ b/cmd/arcrun-app/.version-source.json @@ -0,0 +1,3 @@ +{ + "v0.18.9": "8be9260eb542f527" +} diff --git a/cmd/arcrun-app/daemon-version.py b/cmd/arcrun-app/daemon-version.py index 16a55d9..bbce66f 100755 --- a/cmd/arcrun-app/daemon-version.py +++ b/cmd/arcrun-app/daemon-version.py @@ -33,6 +33,8 @@ daemon-version.py --stamp # 把「下一版(未發佈)」戳成正式版號+日期後印出 """ import datetime +import hashlib +import json import re import subprocess import sys @@ -45,6 +47,8 @@ CHANGELOG = ROOT / "docs-site/src/content/docs/help/changelog.md" LINE_FILE = ROOT / "DAEMON_LINE" UNRELEASED = "## 下一版(未發佈)" +# 版本 → 當時原始碼指紋。用來擋「同一個版號、不同的執行檔」。 +SOURCE_LOCK = HERE / ".version-source.json" RELEASED_RE = re.compile(r"^## v(\d+)\.(\d+)\.(\d+)(", re.M) @@ -53,6 +57,52 @@ def die(msg): sys.exit(1) +def source_fingerprint(): + """會被編進執行檔的原始碼指紋(已提交的樹 + 未提交的差異)。 + + 🔴 2026-08-06 補這道閘的由來: + 戳完版號後我又改了 code,再打一次包,腳本判定「沒有未發佈段=重打同一版」 + ⇒ **兩個內容不同的執行檔都叫 v0.18.9**(實測 sha256 前 20 碼 + 63ec1077a29383b31379 vs 1ca73a5aef5bf4f90bd3)。 + 這正是 leo 08-05 點名的「版本號手寫=說謊溫床」換個形式復發—— + 機械產生版本號還不夠,還要保證**同一個版號只對應一份原始碼**。 + """ + def sh(*args): + return subprocess.check_output(args, cwd=ROOT, text=True).strip() + try: + tree = sh("git", "rev-parse", "HEAD:collector") + except subprocess.CalledProcessError: + return "" # 不在 git 裡就不擋(例如從 tarball 解出來 build) + diff = subprocess.run(["git", "diff", "HEAD", "--", "collector"], + cwd=ROOT, text=True, capture_output=True).stdout + return hashlib.sha256((tree + diff).encode()).hexdigest()[:16] + + +def check_or_record(version, stamp): + """同一個版號只准對應一份原始碼;不同就擋下並說怎麼辦。""" + fp = source_fingerprint() + if not fp: + return + lock = json.loads(SOURCE_LOCK.read_text()) if SOURCE_LOCK.exists() else {} + known = lock.get(version) + if known and known != fp: + die("""❌ %s 這個版號已經對應過另一份原始碼了(記錄 %s,現在 %s)。 + + 代表你在戳完版號之後又改了 code。再打一次會產生 + **兩個內容不同、卻都叫 %s 的執行檔** —— 那就是「版本號說謊」。 + + 要出新版:在 changelog 最上面加一段 + + ## 下一版(未發佈) + + - (用戶語言的更新內容) + + 然後重跑打包,版號會自動變成下一個。""" % (version, known, fp, version)) + if stamp and not known: + lock[version] = fp + SOURCE_LOCK.write_text(json.dumps(lock, indent=1, ensure_ascii=False) + "\n") + + def main(): stamp = "--stamp" in sys.argv if not CHANGELOG.exists(): @@ -74,6 +124,7 @@ def main(): if not has_unreleased: # 沒有待發佈內容=重打同一版。冪等,不虛增。 version = "v%d.%d.%d" % prev + check_or_record(version, stamp) if stamp: print(" ℹ️ changelog 沒有「下一版(未發佈)」段,版本維持 %s(重打同一版)" % version, file=sys.stderr) @@ -89,6 +140,7 @@ def main(): version = "v%d.%d.%d" % nxt if stamp: + check_or_record(version, True) today = datetime.date.today().isoformat() heading = "## %s(%s)" % (version, today) CHANGELOG.write_text(text.replace(UNRELEASED, heading, 1))