Files
arcrun-collector/check-standalone.sh
T
Leo bd25efaae5 refactor(collector): 剪掉臍帶——collector/ 自己就算得出自己的版本號(D95 第一輪②)
leo 2026-08-17:「身為管理者,你要從頭到尾**不要有很多扭曲**,因為你根本不記得
你做的這些扭曲,**每次都要查**,很直接,源碼、產出物,從 stage 到 prod。」

`collector/` 原本要伸手到 repo 根才算得出自己的版本,共四條:

  ① `daemon-version.py` 讀 `docs-site/src/content/docs/help/changelog.md`
  ② `daemon-version.py` 讀 `ROOT/DAEMON_LINE`
  ③ `daemon-version.py` 的原始碼指紋從 repo 根 `git ls-files collector`
  ④ `changelog-section.sh` 讀 `$REPO_ROOT/docs-site/...` 並 exec
     `$REPO_ROOT/installer/scripts/daemon-notes.mjs`
  ⑤(複驗才發現的第五條)`build-msix.sh` 用 `git rev-parse --show-toplevel`/../../.env
     推 InkStoneCo 頂層拿 MS Store Identity

現在全部落在 `collector/` 內部:

  · `collector/CHANGELOG.md`  ← 新家。**原檔同時裝著兩條版本線**
    (桌面版 `v0.18.x` + 雲端引擎 `1.4.x`),已按版號格式拆開;
    雲端那 17 段原地不動留在 docs-site,桌面版這 30 段搬過來。
  · `collector/DAEMON_LINE`   ← 從 repo 根搬進來
  · 指紋改以 `collector/` 為根算(`cwd=COLLECTOR`、pathspec `.`)
    ⇒ 相對路徑前綴變了、而路徑有進雜湊 ⇒ `FINGERPRINT_ALGO` 3→4,
      照既有設計讓帳本自動整本作廢重記(不要手改 JSON)
  · `daemon-notes.mjs` 實作搬進 `collector/cmd/arcrun-app/`,
    `installer/scripts/daemon-notes.mjs` 變薄殼轉呼叫
    ⇒ **根可以往內伸手,collector 不可以往外伸手**,方向單向
  · `build-msix.sh` 改成往上找「帶著那把鍵的 .env」,
    leo 08-06「腳本自己去讀不要再問人」原樣保留,但不再綁目錄結構

驗收閘:`collector/check-standalone.sh`——不是 grep,是**行為證明**:
把 collector/ 的檔案單獨複製到 repo 之外的臨時目錄、在那裡 git init,
再跑版本計算與打包前置閘。跑得起來=真的自足。

自己發現並修掉的三件:
  · `arcrun-tray/assets/store/` 與 `--setup` 其實還活著(見上一顆 commit)
  · 兩支同名的 `daemon-notes.mjs` CLI 守衛比的是**檔名尾綴**
    ⇒ 兩支一起開火,問雲端版號時 collector 那支先 exit(1),
      薄殼根本沒機會查 docs-site。改成比絕對路徑。
  · `collector/CHANGELOG.md` 的「怎麼出新版」說明若照抄那行標題,
    會被 `daemon-version.py` 的純字串比對當成「有待發佈內容」而誤升版
    ⇒ 說明裡刻意不寫成真的標題,並把這個邊角寫在檔案裡

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:05:33 +08:00

143 lines
6.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# check-standalone.sh — 證明 `collector/` 沒有任何「往上伸」的相依(D95 第一輪②的驗收閘)
#
# 🔴 leo 2026-08-17:「身為管理者,你要從頭到尾**不要有很多扭曲**,因為你根本不記得
# 你做的這些扭曲,**每次都要查**,很直接,源碼、產出物,從 stage 到 prod。」
#
# ── 為什麼主證明不是 grep ──────────────────────────────────────────────────
# grep 只證明「我想得到的那幾種寫法沒出現」,證明不了「真的自足」。
# 少想到一種寫法(`$(dirname $0)/../../..`、環境變數繞路、執行期才拼出來的路徑…)
# 就會拿到一個**假綠**——而假綠比紅還糟,因為它讓人停止檢查。
#
# ⇒ **② 才是判準**:把 collector/ 的檔案單獨複製到一個臨時目錄
# (那裡**沒有** docs-siteinstallerlandingrepo 根的任何東西,
# 也不是原 repo 的子目錄,`git rev-parse --show-toplevel` 只會指到它自己),
# 在那裡跑真正的版本計算與打包前置閘。
# **跑得起來=真的自足**;有任何一條偷偷伸手到 repo 根,在那裡就會當場斷掉。
#
# ① 只是**快篩**,抓兩個「出現在可執行位置就一定是錯」的字樣,讓回歸早一步紅。
#
# ⚠️ 快篩必須避開一個假警報:collector 是**處理使用者知識庫**的程式,
# 它的原始碼與測試裡到處是 `system-dev/wiki/...` 這種字串——那是**使用者 vault 裡的路徑**,
# 不是本 repo 的目錄。拿目錄名當樣式掃會掃出上百筆雜訊,然後沒有人再看這份輸出。
# ⇒ 快篩只認語法構造(`git rev-parse --show-toplevel``REPO_ROOT`),且**先剝掉註解與說明字串**。
#
# 用法:
# bash collector/check-standalone.sh # 全部檢查
# KEEP=1 bash collector/check-standalone.sh # 保留臨時目錄以便查看
set -uo pipefail
cd "$(dirname "$0")"
COLLECTOR="$(pwd)"
FAIL=0
ok(){ printf " ✅ %s\n" "$1"; }
ng(){ printf " ❌ %s\n" "$1"; FAIL=1; }
# 受控檔案清單(含未提交、排除 gitignore)——與 daemon-version.py 算指紋的口徑一致。
files_list() { git -C "$COLLECTOR" ls-files -co --exclude-standard . ; }
echo "━━━ ① 快篩:可執行位置不得出現往 repo 根定位的構造 ━━━"
HITS="$(files_list | python3 -c '
import io, sys, tokenize, re
from pathlib import Path
BAD = [
(re.compile(r"rev-parse\b[^|;)]*--show-toplevel"), "git rev-parse --show-toplevel"),
(re.compile(r"\bREPO_ROOT\b"), "REPO_ROOT"),
]
SELF = {"check-standalone.sh", "CHANGELOG.md"}
def strip_py(src):
"""用 tokenize 剝掉 Python 的註解與獨立字串(docstring)——說明文字裡提到不算違規。"""
out = {}
try:
toks = list(tokenize.generate_tokens(io.StringIO(src).readline))
except Exception:
return {i + 1: l for i, l in enumerate(src.split("\n"))}
for t in toks:
if t.type in (tokenize.COMMENT, tokenize.STRING, tokenize.NL, tokenize.NEWLINE):
continue
out.setdefault(t.start[0], "")
out[t.start[0]] += t.string + " "
return out
def strip_hash(src):
"""`#` / `//` / ` * ` 行註解剝掉(sh、js、mjs、go)。"""
out = {}
for i, l in enumerate(src.split("\n"), 1):
s = l.split("#", 1)[0] if l.lstrip().startswith("#") else l
if s.lstrip().startswith(("//", "*", "/*")):
s = ""
s = re.sub(r"#.*$", "", s) if l.lstrip().startswith("#") else s
out[i] = s
return out
for rel in (x.strip() for x in sys.stdin):
if not rel or Path(rel).name in SELF:
continue
p = Path(rel)
if not p.is_file() or p.suffix in (".png", ".ico", ".sum", ".md", ".json"):
continue
try:
src = p.read_text(errors="ignore")
except Exception:
continue
lines = strip_py(src) if p.suffix == ".py" else strip_hash(src)
for n, code in lines.items():
for rx, why in BAD:
if rx.search(code):
print(f"{rel}:{n}: [{why}] {code.strip()[:100]}")
')"
if [ -n "$HITS" ]; then
ng "有往 repo 根定位的構造(下列都在可執行位置,不是註解)"
printf '%s\n' "$HITS" | sed 's/^/ /'
else
ok "沒有 git rev-parse --show-toplevel、沒有 \$REPO_ROOT"
fi
echo
echo "━━━ ② 行為證明:把 collector/ 單獨搬出去,看它還算不算得出自己的版本 ━━━"
TMP="$(mktemp -d)"
trap '[ "${KEEP:-}" = "1" ] || rm -rf "$TMP"' EXIT
DEST="$TMP/collector-standalone"
mkdir -p "$DEST"
# 只複製受控檔案——build 產物、node_modules、dist 一律不帶(它們本來就不該影響自足性)
N=0
while IFS= read -r f; do
[ -f "$COLLECTOR/$f" ] || continue
mkdir -p "$DEST/$(dirname "$f")"
cp "$COLLECTOR/$f" "$DEST/$f"
N=$((N+1))
done < <(files_list)
echo " ️ 臨時樹:$DEST$N 個檔,不在原 repo 底下)"
# 這裡是全新的 git repo——`git rev-parse --show-toplevel` 只會指到 $DEST 自己,
# 任何原本靠 repo 根才拿得到的東西都會在這裡消失。
git -C "$DEST" init -q
git -C "$DEST" add -A
git -C "$DEST" -c user.email=x@y -c user.name=z commit -qm standalone
APP="$DEST/cmd/arcrun-app"
VER="$("$APP/daemon-version.py" 2>/dev/null)"
if [ -n "$VER" ]; then ok "daemon-version.py 在獨立樹裡算得出版本:$VER"
else ng "daemon-version.py 在獨立樹裡算不出版本(還有東西往上伸):$("$APP/daemon-version.py" 2>&1 | head -2)"; fi
FP="$(cd "$APP" && python3 -c "
import importlib.util
s=importlib.util.spec_from_file_location('dv','daemon-version.py')
m=importlib.util.module_from_spec(s); s.loader.exec_module(m)
print(m.source_fingerprint())" 2>/dev/null)"
[ -n "$FP" ] && ok "原始碼指紋算得出來:$FP" || ng "原始碼指紋算不出來"
if (cd "$APP" && ./changelog-section.sh "$VER" --check >/dev/null 2>&1); then
ok "changelog-section.sh --check 通過(三支 build 腳本靠它擋『忘了寫更新內容』)"
else
ng "changelog-section.sh --check 失敗:$(cd "$APP" && ./changelog-section.sh "$VER" --check 2>&1 | head -2)"
fi
NOTES="$(cd "$APP" && ./changelog-section.sh "$VER" 2>/dev/null | head -1)"
[ -n "$NOTES" ] && ok "更新說明投影得出來:${NOTES:0:36}…" || ng "更新說明投影不出來(daemon-notes.mjs 還在往上找)"
echo
[ "$FAIL" = 0 ] && echo "✅ collector/ 自足:搬出去照樣算得出自己的版本號" \
|| echo "❌ collector/ 還有往上伸的相依(見上)"
exit "$FAIL"