bd25efaae5
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>
143 lines
6.6 KiB
Bash
Executable File
143 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# check-standalone.sh — 證明 `collector/` 沒有任何「往上伸」的相依(D95 第一輪②的驗收閘)
|
||
#
|
||
# 🔴 leo 2026-08-17:「身為管理者,你要從頭到尾**不要有很多扭曲**,因為你根本不記得
|
||
# 你做的這些扭曲,**每次都要查**,很直接,源碼、產出物,從 stage 到 prod。」
|
||
#
|
||
# ── 為什麼主證明不是 grep ──────────────────────────────────────────────────
|
||
# grep 只證明「我想得到的那幾種寫法沒出現」,證明不了「真的自足」。
|
||
# 少想到一種寫法(`$(dirname $0)/../../..`、環境變數繞路、執行期才拼出來的路徑…)
|
||
# 就會拿到一個**假綠**——而假綠比紅還糟,因為它讓人停止檢查。
|
||
#
|
||
# ⇒ **② 才是判準**:把 collector/ 的檔案單獨複製到一個臨時目錄
|
||
# (那裡**沒有** docs-site/installer/landing/repo 根的任何東西,
|
||
# 也不是原 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"
|