6698f9fbda
leo 08-06 Windows 回報:「一直在『看守中』和『沒有在跑』中間閃,要我重啟但重啟無效, 我覺得它在跑個迴圈不停重複」+「加一個資料夾明顯沒產生看守資料夾」。 朋友的一般 PC(非 ARM)也一樣,停在等待中 ⇒ **不是 ARM 模擬的問題,是 Windows 通用**。 ## 已定位的機制(不是根因,是「為什麼查不出根因」) 閃爍=子行程一啟動就死 → supervisor 退避重拉 → 狀態在 Starting(alive=true)與 Error(false)之間彈跳 ⇒ 畫面跟著閃。而**死因一直被記在 Status().LastError 裡, 從來沒有上過畫面、也沒寫進 app.log** ⇒ 使用者只看到閃爍與「請重新開啟」, 重開當然無效(死因沒變)。這是「安靜地略過」的同一種病換地方發作。 ## 這版做的 - 死因上畫面:重試 >= 3 次改顯示「同步引擎一直啟動失敗,已自動重試 N 次|原因:…」, 且**黏住不閃**(不受重起過程中的 Starting 影響) - 每次異常結束寫進 app.log(同錯誤 10 次內只記一次,避免洗爆) ## 排除掉的嫌疑(查過,不是) - PDF 引擎(pdfium/wasm):**延遲初始化**,第一次讀 PDF 才啟動 ⇒ 不會在啟動時炸 - ARM 模擬:leo 朋友的一般 x64 PC 同樣症狀 - 待查嫌疑:Wails 的 Windows 版是 `-H windowsgui`(GUI subsystem,無主控台), 而 v0.18.8 的 collector.exe 是 `go build` 的 console subsystem—— v0.18.9 合併成單一 binary 後,子行程換成 GUI subsystem 的自己。**尚未證實。** ## 順帶修好自己的閘(它擋對了我) 指紋閘擋下「v0.18.10 已對應另一份原始碼」——因為第一次打包失敗、版號卻已被戳。 ⇒ 加判準:**磁碟上沒有該版號產物=從未出貨,允許重戳**(不用去手改 JSON, 那種爛示範遲早被改成「都放行」)。 ## 安裝程式(leo ③)尚未兌現,誠實記錄 `wails build -nsis` 需要 makensis;Homebrew 的 3.12 在這台 macOS **連兩行的最小腳本 都在寫檔階段丟 std::bad_alloc**,zlib/bzip2/lzma 三壓縮器全崩,brew extract 舊版 被 homebrew/core 擋。且帶 -nsis 會讓 wails 整個中止、連 exe 都不產 ⇒ build-win.sh 改成**先煙霧測試 makensis**,壞的就只出裸 exe 並大聲警告。
172 lines
7.8 KiB
Python
Executable File
172 lines
7.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""daemon-version.py — daemon 版本號的唯一產生器(2026-08-06 二版)
|
||
|
||
🔴 leo 08-06:「給版本號、本版更新內容、打包產品、顯示在前端,
|
||
這一整串都應該是機械化,**不應該每次手工做**」
|
||
|
||
── 病根(實測)────────────────────────────────────────────────────
|
||
manifest 宣告 daemon v0.18.5,線上產物拆開卻是 v0.18.4。
|
||
原因:本 repo **一個 git tag 都沒有**,build 腳本寫的是 `git describe --tags`
|
||
⇒ 拿不到 v0.18.x ⇒ 版本只能靠打包時人工 `VERSION=v0.18.4 ./build-win.sh` 敲。
|
||
人工敲 ⇒ 兩條打包線(win/mac)各敲各的 ⇒ manifest 說謊。
|
||
|
||
── 為什麼不是「數 commit 數」(一版的做法,已否決)─────────────────
|
||
第一版用「動過 collector/ 的 commit 數」當 patch。確實不用手打,
|
||
但**每個 commit 都變成一版**(一天 5 個 commit 就跳 5 版),
|
||
而且每跳一版就要補一段 changelog——把機械化變成新的手工活。
|
||
|
||
── 現在的做法:版本由 changelog 決定,且不用手打數字 ──────────────
|
||
單一真相源= docs-site/src/content/docs/help/changelog.md(用戶語言那份)。
|
||
|
||
· 要出新版 ⇒ 在檔案最上面加一段標題 `## 下一版(未發佈)`,底下寫白話更新內容。
|
||
· 打包時本腳本把它**戳成正式版號**(上一版 patch + 1)並補上今天日期。
|
||
· 沒有「未發佈」段 ⇒ 版本=最上面那一版(重打同一版,不會虛增)。
|
||
|
||
⇒ **版本號從頭到尾沒有人打過**,而且「更新內容」與「版本」天生綁在一起——
|
||
不可能出現「版本升了但沒人知道改了什麼」,也不可能「寫了內容卻忘了升版」。
|
||
⇒ 不需要 git tag/Actions/輪詢(守 D20 紅線)。
|
||
|
||
換線(0.18 → 0.19):改 `DAEMON_LINE`,下一版就從 0.19.0 開始。
|
||
|
||
用法:
|
||
daemon-version.py # 印出版本(唯讀,不改檔)
|
||
daemon-version.py --stamp # 把「下一版(未發佈)」戳成正式版號+日期後印出
|
||
"""
|
||
import datetime
|
||
import hashlib
|
||
import json
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
HERE = Path(__file__).resolve().parent
|
||
ROOT = Path(subprocess.check_output(
|
||
["git", "rev-parse", "--show-toplevel"], cwd=HERE, text=True).strip())
|
||
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)
|
||
|
||
|
||
def die(msg):
|
||
print(msg, file=sys.stderr)
|
||
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 artifacts_for(version):
|
||
"""磁碟上有沒有這個版號的產物(exe/dmg/msix)。有=真的出過東西,不准重戳。"""
|
||
dist = HERE / "dist"
|
||
if not dist.is_dir():
|
||
return []
|
||
return [f.name for f in dist.iterdir()
|
||
if version in f.name and f.suffix in (".exe", ".dmg", ".msix", ".zip")]
|
||
|
||
|
||
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)
|
||
# 🔴 2026-08-06:打包**失敗**時版號已被戳、產物卻沒生出來 ⇒ 那個版號被白白燒掉,
|
||
# 修完再打就被自己的閘擋下,只能去手改 JSON(爛示範,遲早有人順手改成「都放行」)。
|
||
# ⇒ 沒有任何產物的版號=**從未出貨**,本來就該可以重戳。
|
||
# 判準是「磁碟上有沒有這個版號的產物」,不是「我覺得它沒出貨」。
|
||
if known and known != fp and not artifacts_for(version):
|
||
print("ℹ️ %s 之前戳過但**沒有產出任何檔案**(打包失敗),視為未出貨 → 重新對應這次的原始碼"
|
||
% version, file=sys.stderr)
|
||
known = None
|
||
lock.pop(version, None)
|
||
if known and known != fp:
|
||
die("""❌ %s 這個版號已經對應過另一份原始碼了(記錄 %s,現在 %s)。
|
||
|
||
代表你在戳完版號之後又改了 code。再打一次會產生
|
||
**兩個內容不同、卻都叫 %s 的執行檔** —— 那就是「版本號說謊」。
|
||
|
||
要出新版:在 changelog 最上面加一段
|
||
|
||
## 下一版(未發佈)
|
||
|
||
- (用戶語言的更新內容)
|
||
|
||
然後重跑打包,版號會自動變成下一個。""" % (version, known, fp, version))
|
||
if stamp and lock.get(version) != fp:
|
||
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():
|
||
die("❌ 找不到 changelog:%s" % CHANGELOG)
|
||
line = LINE_FILE.read_text().strip() if LINE_FILE.exists() else "0.18"
|
||
if not re.fullmatch(r"\d+\.\d+", line):
|
||
die("❌ DAEMON_LINE 必須是 MAJOR.MINOR(例 0.18),現在是 %r" % line)
|
||
|
||
text = CHANGELOG.read_text()
|
||
top = RELEASED_RE.search(text)
|
||
if not top:
|
||
die("❌ changelog 裡找不到任何 `## vX.Y.Z(…)` 的版本段落")
|
||
prev = (int(top.group(1)), int(top.group(2)), int(top.group(3)))
|
||
|
||
has_unreleased = UNRELEASED in text
|
||
if has_unreleased and text.index(UNRELEASED) > top.start():
|
||
die("❌ `%s` 必須放在所有已發佈版本的**上面**(它代表還沒出的那一版)" % UNRELEASED)
|
||
|
||
if not has_unreleased:
|
||
# 沒有待發佈內容=重打同一版。冪等,不虛增。
|
||
version = "v%d.%d.%d" % prev
|
||
check_or_record(version, stamp)
|
||
if stamp:
|
||
print(" ℹ️ changelog 沒有「下一版(未發佈)」段,版本維持 %s(重打同一版)"
|
||
% version, file=sys.stderr)
|
||
print(version)
|
||
return
|
||
|
||
# 有待發佈內容 ⇒ 升版。換線時從該線的 .0 開始。
|
||
major, minor = (int(x) for x in line.split("."))
|
||
if (major, minor) != (prev[0], prev[1]):
|
||
nxt = (major, minor, 0)
|
||
else:
|
||
nxt = (prev[0], prev[1], prev[2] + 1)
|
||
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))
|
||
print(" ✅ changelog:「下一版(未發佈)」→ %s" % heading, file=sys.stderr)
|
||
|
||
print(version)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|